#!/usr/bin/env python

##\file
# This file defined the base Function class.
# This class is used to provide a piece-wise
# linear function for use in the model. The
# primary use of this these classes is presenting
# the information from the species establishment
# and senescenes tables.
#

# Standard Python Modules
import exceptions
import numpy
import random
import scipy
import scipy.interpolate as interp
import xlrd

# Third Party Modules
import matplotlib as mpl
import matplotlib.pylab as plt
import mpl_toolkits.mplot3d.axes3d as Axes3D
import pandas

class Function(object):
    xValue = list()
    yValue = list()

    def __init__(self, xValue, yValue):
        if len(xValue) != len(yValue):
            msg  = 'Function: Error: Number of elements in xValue an yValue must match'
            msg += ' len(xValue) = ' + str(len(xValue)) + ' len(yValue) = ' + str(len(yValue))
            raise exceptions.RuntimeError(msg)

        self.xValue = xValue
        self.yValue = yValue

    def toIndex(self, x, vec):
        if ( x < vec[0] or vec[-1] < x ):
            msg  = 'Function: Error: x out of range\n'
            msg += 'x = ' + str(x)             + '\n'
            msg += 'vec[0]  = ' + str(vec[0])  + '\n'
            msg += 'vec[-1] = ' + str(vec[-1]) + '\n'
            raise exceptions.RuntimeError(msg)

        b = 0                          # b = the "bottom" index
        t = len(vec)-1                 # t = the "top" index
        m = int(scipy.ceil((b+t)/2.0)) # m = the middle index

        if ( x == vec[t] ):
            b     = t-1
            scale = 1.0
            return b,scale

        while ( vec[b+1] <= x ):
            if ( vec[b] <= x and x < vec[m] ):
                t = m
            else:
                b = m
            m = int(scipy.ceil((b+t)/2.0))

        scale = (x - vec[b])/(vec[b+1] - vec[b])

        return b,scale

    def __getitem__(self, x):
        b,scale = self.toIndex(x, self.xValue)
        return self.yValue[b]*(1.0 - scale) + self.yValue[b+1]*scale


class ReadVegTable1D(object):
    def __init__(self):
        pass

    def read(self, filename, modelType, species):
        try:
            data = pandas.read_excel(filename, modelType, index_col=None )
            xValue = list( data.ix[:,0] )
            yValue = list( data[species] )
            return {'elvValue':xValue, 'rate':yValue}
        except exceptions.IOError as error:
            errorMessage  = 'ReadVegTable1D: Error: Could not open file for reading : ' + filename + '\n'
            errorMessage += 'ReadVegTable1D: Error: Addition error info : ' + str(error) + '\n'
            raise exceptions.RuntimeError(errorMessage)
        except xlrd.biffh.XLRDError as error:
            errorMessage  = 'ReadVegTable1D: Error: Could not find sheet named ' + modelType + ' in file ' + filename + '\n'
            errorMessage += 'ReadVegTable1D: Error: Additional error info : ' + str(error) + '\n'
            raise exceptions.RuntimeError(errorMessage)
        except exceptions.KeyError as error:
            errorMessage  = 'ReadVegTable1D: Error: Could not find species named ' + species + ' in sheet ' + modelType + ' in file ' + filename +'\n'
            errorMessage += 'ReadVegTable1D: Error: Additional error info : ' + str(error) + '\n'
            raise exceptions.RuntimeError(errorMessage)

class Function2DFast(object):

    def __init__(self, xValue, yValue, data):
        # xValue = wave amplitudes
        # yValue = mean salinity
        # data   = senescence and growth rates
        if ( len(yValue) != numpy.size(data, 0) ):
            msg  = 'Function2DFast: Error: number of elements in yValue must match number of rows in data'
            msg += ' len(yValue) = ' + str(len(yValue)) + ' data.shape = ' + str(data.shape)
            raise exceptions.RuntimeError(msg)

        if ( len(xValue) != numpy.size(data, 1) ):
            msg  = 'Function2DFast: Error: number of elements in xValue must match number of columns in data'
            msg += ' len(xValue) = ' + str(len(Value)) + ' size(data) = ' + str(data.shape)
            raise exceptions.RuntimeError(msg)

        self.xValue = xValue
        self.yValue = yValue
        self.data   = data

    def toIndex(self, x, vec):
        if ( x < vec[0] or vec[-1] < x ):
            msg  = 'Function2DFast: Error: x out of range\n'
            msg += 'x = ' + str(x)             + '\n'
            msg += 'vec[0]  = ' + str(vec[0])  + '\n'
            msg += 'vec[-1] = ' + str(vec[-1]) + '\n'
            raise exceptions.RuntimeError(msg)

        b = 0                          # b = the "bottom" index
        t = len(vec)-1                 # t = the "top" index
        m = int(scipy.ceil((b+t)/2.0)) # m = the middle index

        if ( x == vec[t] ):
            b     = t-1
            scale = 1.0
            return b,scale

        while ( vec[b+1] <= x ):
            if ( vec[b] <= x and x < vec[m] ):
                t = m
            else:
                b = m
            m = int(scipy.ceil((b+t)/2.0))

        scale = (x - vec[b])/(vec[b+1] - vec[b])

        return b,scale

    def __getitem__(self, item):
        i, i_scale = self.toIndex(item[0], self.xValue)
        j, j_scale = self.toIndex(item[1], self.yValue)

        x0 = self.data[j  ,i] * (1.0 - i_scale) + self.data[j  ,i+1]*i_scale
        x1 = self.data[j+1,i] * (1.0 - i_scale) + self.data[j+1,i+1]*i_scale

        return x0*(1.0 - j_scale) + x1*j_scale

class Function2D(object):

    def __init__(self, xValue, yValue, data = 0):
        # xValue = wave amplitudes
        # yValue = mean salinity
        # data   = senescence and growth rates
        if len(yValue) != numpy.size(data, 0):
            msg  = 'Function2D: Error: number of elements in yValue must match number of rows in data'
            msg += ' len(yValue) = ' + str(len(yValue)) + ' data.shape = ' + str(data.shape)
            raise exceptions.RuntimeError(msg)

        if len(xValue) != numpy.size(data, 1):
            msg  = 'Function2D: Error: number of elements in xValue must match number of columns in data'
            msg += ' len(xValue) = ' + str(len(Value)) + ' size(data) = ' + str(data.shape)
            raise exceptions.RuntimeError(msg)

        self.point = numpy.array( [ [x,y] for y in yValue for x in xValue ] )
        self.value = numpy.array( [elt for row in data for elt in row] )
        self.minX  = min( xValue )
        self.maxX  = max( xValue )
        self.minY  = min( yValue )
        self.maxY  = max( yValue )

    def __getitem__(self, item):
        if ( item[0] < self.minX or self.maxX < item[0] ):
            raise exceptions.RuntimeError('Function2D: Error: x coordinate out of range. ' + str(item[0]) + ' not in ' + '[ ' + str(self.minX) + ', ' + str(self.maxX) + ' ]'    )

        if ( item[1] < self.minY or self.maxY < item[1] ):
            raise exceptions.RuntimeError('Function2D: Error: y coordinate out of range. ' + str(item[1]) + ' not in ' + '[ ' + str(self.minY) + ', ' + str(self.maxY) + ' ]'    )

        return interp.griddata(self.point, self.value, ([item[0]],[item[1]] ), method='linear')[0]

class ReadVegTable2D(object):
    def __init__(self):
        pass

    def read(self, filename, species):
        try:
            data       = pandas.read_excel(filename, species, index_col=None, parse_cols=range(1,23), skiprows=1)
            waValue    = data.columns.values.tolist()[1:len(data.columns)]
            spcode     = data.columns.values.tolist()[0]
            salValue   = data[spcode].tolist()
            rate       = numpy.asarray(data)[:,1:22]
            return {'spcode':spcode, 'waValue':waValue, 'salValue':salValue, 'rate':rate }
        except exceptions.IOError as error:
            errorMessage  = 'ReadVegTable2D: Error: Could not open file for reading : ' + filename + '\n'
            errorMessage += 'ReadVegTable2D: Error: Addition error info : ' + str(error) + '\n'
            raise exceptions.RuntimeError(errorMessage)
        except xlrd.biffh.XLRDError as error:
            errorMessage  = 'ReadVegTable2D: Error: Could not find sheet named ' + species +  ' in file : ' + filename + '\n'
            errorMessage += 'ReadVegTable2D: Error: Addition error info : ' + str(error) + '\n'
            raise exceptions.RuntimeError(errorMessage)


