#!/usr/bin/env python

##\file
##\brief Top level file for the ecological modeling.
##\detail This file describes the model proper and the major
# model-specific coding elements used to build this model.
##\section Includes Included Libraries
# The file makes use of the following libraries
# - Standard Python Libraries
#   - copy
#   - exceptions
#   - itertools
#   - re
#   - StringIO
#   - sys
#   - time
# - Third Party Libraries
#   - pandas version 0.13.1
#   - xlrd
# - Model defined libraries
#   - config
#   - event
#   - function
#   - landscape

# STD Python modules
import copy
import csv
import exceptions
import itertools
import math
import numpy
from collections import OrderedDict
import re
import StringIO
import sys
import time

# Third party modules
import pandas
import xlrd

# This model's modules
import config
import event
import landscape
import function


##\class SpeciesModel
##\brief Base class for all species models.
##\detail This is a generic base class defining the functionality
# of a single species model for a single location in the landscape.
# All species models should inherit
# from this class to make sure they get the basic elements.
##\role{Ecology/Machinery}
class SpeciesModel(object):
    ##\brief A reference to the model Params object
    params   = None
    ##\brief A reference to the model DispersalModel object
    dspModel = None

    ## Class constructor.
    #\param [in] self      The object's reference to itself.
    #\param [in] index     The species index.
    #\param [in] abr       The species USDA alpha-numeric vegetation code.
    #\param [in] name      The full species name.
    #\param [in] modelType The type of a model.
    #\param [in] cover     The fraction of model cell that is covered by a species.
    #\param [in] loc       The x,y location of a cell. Given as the row,column index with in the model grid.
    def __init__(self, index=0, name='', abr='', modelType='', cover=0, cellID=0):
        ##\brief Species index
        ##\details An integer that is a unique numerical species index.
        ## - Type: integer
        ## - Range: [0, inf)
        self.index     = index

        ##\brief The long name for a species
        ##\details A string used to make output pretty
        ## - Type: string
        self.name      = name

        ##\brief Species code
        ##\details A string abbreviation for the species. The value is the species' USDA alpha-numeric vegetation code.
        # This is an important member value in the model. It is used as the key in the dictionary that stores the list
        # of species in a patch.
        ## - Type: string
        self.abr       = abr

        ##\brief The model type
        ##\details A string containing the model type.
        ##\n
        ## This member should one of the following values (as a string):\n
        # - BarrierIslandSpeciesModel
        # - EmergentWetlandModel
        # - UplandForestModel
        # - SAVModel
        # - NullModel
        ##\n
        self.modelType = modelType

        ##\brief The area occupied by a species
        ##\details This is the fraction of a patch covered by a species.
        ## - Type: float
        ## - Range: [0, inf)
        self.cover     = cover

        ##\brief Species Location
        ##\details This is the location of a species. It is stored as a the index of a cell.
        ## - Type: int
        self.cellID       = cellID

    ##\brief Object configuration.
    #\param self The object's reference to itself.
    #\param index The species index.
    #\param abr The species USDA alpha-numeric vegetation code.
    #\param name The full species name.
    #\param modelType The type of a model.
    #\param cover The fraction of model cell that is covered by a species.
    #\param loc The x,y location of a cell. Given as the row,column index with in the model grid.
    ##\detail This function is used to configure an object after it has been created.
    # this is used SpeciesModelList when it is creating the list of species from
    # the species growth/senescence tables and by PatchModel when creating the
    # individual species model for each location.
    def config(self, index=0, name='', abr='', modelType='', cover=0, cellID=0):
        self.index     = index
        self.name      = name
        self.abr       = abr
        self.modelType = modelType
        self.cover     = cover
        self.cellID    = cellID

    ##\brief Computes the probability of senescence.
    ##\detail This is a generic function that returns the probability of plants senesencing.
    # This function should be redefined by each class that inherits form SpeciesModel.
    def senescence(self):
        return 0.0

    ##\brief Computes the probability of establishment
    ##\details This is a generic function that returns the probability of plats growing.
    # This function should be redefined by each class that inherits from SpeciesModel.
    def growth(self):
        return 0.0

    def __float__(self):
        return self.cover

    def __str__(self):
        ret =  str(self.index) + ', '
        ret += self.name            + ', '
        ret += self.abr             + ', '
        ret += self.modelType       + ', '
        ret += str(self.cover)      + ', '
        ret += str(self.cellID)
        return ret

##\class NullModel
##\brief A place holder.
##\role{Machinery}
##\detail A place holder for habitat and vegetation types that
# do not really do anything. Having this class makes
# the rest of the model code simpler.
class NullModel(SpeciesModel):
    def __init__(self, index=0, name='', abr='', cover=0, cellID=0):
        SpeciesModel.__init__(self, index, name, abr, 'NullModel', cover, cellID)

    ##\brief
    ##\detail
    def senescence(self):
        return 0.0

    ##\brief
    ##\detail
    def growth(self):
        return 0.0

##\class UplandForestModel

##\brief This class handles the ecology for the upland forest species.
##\role{Ecology}
##\detail Upland forest types change state based on the
# height of the habitat above mean water surface.
class UplandForestModel(SpeciesModel):
    def __init__(self, index=0, name='', abr='', cover=0, cellID=0, Pdata=None, Ddata=None):
        SpeciesModel.__init__(self, index, name, abr, 'UplandForestModel', cover, cellID)
        self.P = function.Function(Pdata['elvValue'], Pdata['rate'])
        self.D = function.Function(Ddata['elvValue'], Ddata['rate'])

    ## Senescence function
    # This function determines the probability that an upland forest species
    # will experience senescence. The probability of senescence is a function
    # of the elevation of the local habitat above the mean water surface. The
    # exact functional relationship between height above water and the probability
    # of senescence is determined by the senescence table for each species.
    # The senescence tables are defined in a model input file.
    def senescence(self):
        try:
            #elv = SpeciesModel.params.heighAboveWater[self.cellID]
            #sal = SpeciesModel.params.meanSal[self.cellID]
            #bi  = SpeciesModel.params.biEstCond[self.cellID]

            #if bi or sal > 1.0:
                #return 1.0

            #return self.D[elv]
            return 0.0

        except exceptions.RuntimeError as error:
            msg = 'UplandForestModel.senescence(): Error: location out of range. Additional info follows\n'
            msg += str(error)
            raise exceptions.RuntimeError(msg)

    ## Growth function
    # This function determines the probability that an upland forest species will become
    # established at the current location. The probability of an upland forest species becoming
    # established is depended on three major factors. First, the salinity must be below 1.0 ppt.
    # Second, there must be a period of 14 days with no flooding followed by a period of 14 with
    # water depths below 14 cm. Finally, the height of the habitat above mean water level determines
    # the final probability.


    # I'm not entirely happy with the current state of this function because the computation
    # of the basic establishment conditions (14 day no flood, 14 days water depth < 14 cm) is
    # handled external to the model. The problem is that the current approach divides
    # the responsibilities for representing the ecology of upland forest species. An external program
    # determines the establishment conditions while the model proper handles the final computation of the
    # probability of establishment. I would like to fix this.
    def growth(self):
        try:
            #elv = SpeciesModel.params.heighAboveWater[self.cellID]
            #sal = SpeciesModel.params.meanSal[self.cellID]
            #est = SpeciesModel.params.treeEstCond[self.cellID]
            #bi  = SpeciesModel.params.biEstCond[self.cellID]
            #dsp = SpeciesModel.dspModel[self.cellID][self.abr]
            #dsp = 1.0

            #if bi or est == 0 or sal > 1.0:
                #return 0

            #return self.P[elv] * dsp
            return 0.0
        except exceptions.RuntimeError as error:
            msg = 'UplandForestModel.growth(): Error: location out of range. Additional info follows\n'
            msg += str(error)
            raise exceptions.RuntimeError(msg)

##\class EmergentWetlandModel
##\brief This class handles the ecology for the emergent wetland species.
##\role{Ecology}
##\detail
#
class EmergentWetlandModel(SpeciesModel):
    def __init__(self, index=0, name='', abr='', cover=0, cellID=0, Pdata=None, Ddata=None):
        SpeciesModel.__init__(self, index, name, abr, 'EmergentWetlandModel', cover, cellID)
        self.P     = function.Function2DFast(Pdata['waValue'], Pdata['salValue'], Pdata['rate'])
        self.D     = function.Function2DFast(Ddata['waValue'], Ddata['salValue'], Ddata['rate'])

    ## Senescence function
    # This function determines the probability that an emergent wetland species
    # will experience senescence. The probability of senescence is a function
    # of variation in water stage height (computed as the standard deviation of stage)
    # and salinity. The exact relationship between the probability of senescence and
    # the environmental factors is describe the by scenescence table for each species.
    # The senescence tables are defined in a model input file.
    def senescence(self):
        waveAmp = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.WAVEAMP]
        meanSal = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SAL]

        return self.D[waveAmp,meanSal]

   ## Growth function
    # This function determines the probability that an emergent wetland species will become
    # established at the current location. The probability of an wetland species becoming
    # established is depended on
    # three major factors. First, the salinity must be below 1.0 ppt.
    # Second, there must be a period of 14 days with no flooding followed by a period of 14 with
    # water depths below 14 cm. Finally, the height of the habitat above mean water level determines
    # the final probability.
    def growth(self):
        waveAmp = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.WAVEAMP]
        meanSal = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SAL]

        return self.P[waveAmp, meanSal]

##\class SAVModel
##\brief This class handles the ecology for the SAV species.
##\role{Ecology}
##\detail
#
class SAVModel(SpeciesModel):
    def __init__(self, index=0, name='', abr='', cover=0, cellID=0, SAVData=None):
        SpeciesModel.__init__(self, index, name, abr, 'SAVModel', cover, cellID)

        if SAVData == None:
            errorMessage = 'SAVModel: Error: No SAVData object passed to constructor (i.e. __init__() )\n'
            raise exceptions.RuntimeError(errorMessage)

        self.betaIntercept = SAVData['Intercept']
        self.betaTemp      = SAVData['Temp']
        self.betaSal       = SAVData['Sal']
        self.betaDepth     = SAVData['Depth']

    def senescence(self):
        return 0;

    def growth(self):
        d = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SUMMERWD]
        s = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SUMMERSAL]
        t = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SUMMERTEMP]

        return min( 1.0, max(0.0, self.betaIntercept + self.betaTemp*t + self.betaSal*s + self.betaDepth*d   )  )

##\class BarrierIslandSpeciesModel
##\brief This class handles the ecology for the barrier island species.
##\role{Ecology}
##\detail
#
class BarrierIslandSpeciesModel(SpeciesModel):
    def __init__(self, index=0, name='', abr='', cover=0, cellID=0, Pdata=None, Ddata=None):
        SpeciesModel.__init__(self, index, name, abr, 'BarrierIslandSpeciesModel', cover, cellID)
        self.P = function.Function(Pdata['elvValue'], Pdata['rate'])
        self.D = function.Function(Ddata['elvValue'], Ddata['rate'])

    def senescence(self):
        #elv = SpeciesModel.params.heighAboveWater[self.cellID]
        #bi  = SpeciesModel.params.biEstCond[self.cellID]

        #if not bi:
            #return 1.0

        #return self.D[elv]
        return 0.0

    def growth(self):
        #elv = SpeciesModel.params.heighAboveWater[self.cellID]
        #bi  = SpeciesModel.params.biEstCond[self.cellID]
        #dsp = SpeciesModel.dspModel[self.cellID][self.abr]
        #dsp = 1.0

        #if not bi:
            #return 0.0

        #return self.P[elv] * dsp
        return 0.0

##\brief Growth/Senescence table storage
##\detail This class stores the growth and senescence tables
# for each species. The class is structured as a dictionary.
# Each species is identified by a unique abbreviation. For
# example, _Phragmities australis_ (Roseau cane) is represented
# by the abbreviation PAHU7. The abbreviations are based on the
# USDA species abbreviations. The abbreviations for each of the species
# used in this model are defined in the establishment table file.
#
# Each unique abbreviation is associated with the growth and senescence
# tables. These tables are defined in the establishment and senescence file.
#
# This class' job is to make the data from the establishment and senescence
# tables available to the other model components.
#
class SpeciesModelList(dict):
    ##\brief Class constructor
    def __init__(self):
        dict.__init__(self)

    ##\brief Load data from establishment and senescence files.
    ##@param [in] estFilename The name of the file containing the establishment tables.
    ##@param [in] mortFilename The name of the file containing the senescence tables.
    ##\details This member function loads the establishment and senescence tables
    # from the filenames passed in as arguments. The files are expected to be
    # MSExcel spreadsheets stored in the XLSX format. The data in each file
    # should be divided into a set of separate tabbed sheets. One sheet should
    # be named "VegTypeNames". This sheet should list all the species to include
    # in the model. There should also be one table for each of the species included in the
    # model. There are three exceptions to this: SAV, the Upland forest species and the
    # barrier island species. SAV parameters are read from the model configuration file.
    # The barrier island species and the upland forest species are each represented by a single
    # sheet.
    #
    # The function performs the following steps:\n
    def config(self, estFilename, mortFilename):
        errorMessage = ''

        ###################################################
        ##
        # - __Step 1__: Read the VegTypeNames sheet from the
        #   establishment and senescence files.
        #
        #   The VegTypeNames file is where we get the
        #   names of the species in the model. If this
        #   sheet does not exist in the Excel files,
        #   or if the files do not exist, raise and
        #   exception.
        ###################################################

        try:
            estData = pandas.read_excel(estFilename, 'VegTypeNames', index_col=None)
        except exceptions.IOError as error:
            errorMessage += 'SpeciesModelList: Error: Could not open file for reading: ' + str(estFilename) + '\n'
            errorMessage += 'SpeciesModelList: Error: Additional error info: ' + str(error) + '\n';
        except xlrd.biffh.XLRDError as error:
            errorMessage += 'SpeciesModelList: Error: While opening : ' + str(estFilename) + '\n'
            errorMessage += 'SpeciesModelList: Error: Additional error info: ' + str(error) + '\n';

        try:
            mortData = pandas.read_excel(mortFilename, 'VegTypeNames', index_col=None)
        except exceptions.IOError as error:
            errorMessage += 'SpeciesModelList: Error: Could not open file for reading: ' + str(estFilename) + '\n'
            errorMessage += 'SpeciesModelList: Error: Additional error info: ' + str(error) + '\n';
        except xlrd.biffh.XLRDError as error:
            errorMessage += 'SpeciesModelList: Error: While opening : ' + str(estFilename) + '\n'
            errorMessage += 'SpeciesModelList: Error: Additional error info: ' + str(error) + '\n';

        if len(errorMessage):
            raise exceptions.RuntimeError(errorMessage);

        ###################################################
        ##
        # - __Step 2__: Get the list of species from the 'Symbol'
        #   column. Raise exceptions as needed.
        #
        ###################################################
        try:
            speciesList = estData['Symbol']
        except exceptions.KeyError as error:
            errorMessage += 'SpeciesModelList: Error: Symbol column not defined in establishment tables \n'
            raise exceptions.RuntimeError(errorMessage);

        ###################################################
        ##
        # - __Step 3__: Read the establishment and senescence tables.
        #
        #   For each species, configure the P & D
        #   Functions for each species and push everything
        #   into a dictionary (dict).
        #
        ###################################################

        for spSymbol in speciesList:
            print 'SpeciesModelList: Msg: Configuring model for species ' + str(spSymbol)

            spInfo      = estData[ estData['Symbol'] == spSymbol ].to_dict('record')[0]
            spID        = spInfo['ID']
            spName      = spInfo['Common Name']
            spModelType = spInfo['ModelType']

            ###########################################
            # Upland Forest Model
            #
            ###########################################
            if ( spModelType == 'UplandForestModel'):
                reader = function.ReadVegTable1D()
                Pdata = None
                Ddata = None

                try:
                    Pdata = reader.read(estFilename, 'UplandForest', spSymbol)
                except exceptions.RuntimeError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'
                except exceptions.ValueError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'


                try:
                    Ddata = reader.read(mortFilename, 'UplandForest', spSymbol)
                except exceptions.RuntimeError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'
                except exceptions.ValueError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'

                spModel = UplandForestModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1, Pdata=Pdata, Ddata=Ddata)
                self.__setitem__(spSymbol, spModel)

            ###########################################
            # Emergent Wetland Model
            #
            ###########################################
            elif (spModelType == 'EmergentWetlandModel'):
                reader = function.ReadVegTable2D()
                Pdata = None
                Ddata = None
                try:
                    Pdata = reader.read(estFilename, spSymbol)
                except exceptions.RuntimeError as error:
                    errorMessage += str(error) + '\n';
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'
                except exceptions.ValueError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'

                try:
                    Ddata = reader.read(mortFilename, spSymbol)
                except exceptions.RuntimeError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(species) + '\n'
                except exceptions.ValueError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(species) + '\n'

                spModel = EmergentWetlandModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1, Pdata=Pdata, Ddata=Ddata)
                self.__setitem__(spSymbol, spModel)



            ###########################################
            # Submerged Aquatic Vegetation Model
            #
            ###########################################
            elif ( spModelType == 'SAVModel'):
                savData = None
                try:
                    savData = pandas.read_excel(estFilename, spSymbol, index_col=0 ).to_dict('dict')['Value']
                except exceptions.IOError as error:
                    errorMessage += 'SpeciesModelList: Error : Could not open file for reading : ' + estFilename + '\n'
                    errorMessage += 'SpeciesModelList: Extra error info: ' + str(error) + '\n'
                except xlrd.biffh.XLRDError as error:
                    errorMessage += 'SpeciesModelList: Error : Could not find sheet name ' + species + ' in file ' + estFilename + '\n'
                    errorMessage += 'SpeciesModelList: Extra error info: ' + str(error) + '\n'
                except exceptions.KeyError as error:
                    errorMessage += 'SpeciesModelList: Error : Dictionary key error. SAV table probably has wrong column headings\n'
                    errorMessage += 'SpeciesModelList: Error : '

                spModel = SAVModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1, SAVData=savData)
                self.__setitem__(spSymbol, spModel)

            ###########################################
            # Barrier Island Model
            #
            ###########################################
            elif ( spModelType == 'BarrierIslandSpeciesModel'):
                reader = function.ReadVegTable1D()
                Pdata = None
                Ddata = None

                try:
                    Pdata = reader.read(estFilename, 'BarrierIsland', spSymbol)
                except exceptions.RuntimeError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'
                except exceptions.ValueError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'


                try:
                    Ddata = reader.read(mortFilename, 'BarrierIsland', spSymbol)
                except exceptions.RuntimeError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'
                except exceptions.ValueError as error:
                    errorMessage += str(error) + '\n'
                    errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n'

                spModel = BarrierIslandSpeciesModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1, Pdata=Pdata, Ddata=Ddata)
                self.__setitem__(spSymbol, spModel)

            ###########################################
            # Null Model
            #
            ###########################################
            elif ( spModelType == 'NullModel'):
                spModel = NullModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1)
                self.__setitem__(spSymbol, spModel)

            ###########################################
            # Error State
            #
            ###########################################
            else:
                errorMessage += 'SpeciesModelList: Error: Unknown model type specified : ' + spModelType + '\n'

        if len(errorMessage):
            raise exceptions.RuntimeError(errorMessage)

##\class Params
##\brief This class is the interface between the outside world and the model.
##\role{Machinery}
##\detail This class is an interface between the internal components of the model
# and the outside world. That is, everything that is read in from outside the
# model is stored in an object of class Params. Params then makes all of this
# information available to the other model components.
#
# It is important to note that
# there is only meant to be ONE object derived from this class, and that object
# is owned by the object of class Model.
#
# Params handles reading values from the model configuration file. Params
# also manages all of the model IO streams, as well as the landscape.Landscape
# objects that hold the spatial data files used by the model, including the
# hydrology, salinity, elevation, initial conditions and other spatial data sets. Params also
# holds a special object of type model.SpeciesModelList. This class is
# used to store the the growth and senescence tables for each species along with
# any other species specific information.
class Params(object):
    ##\brief Class constructor
    def __init__(self):
        ##\brief Holds the configuration information
        self.configDict     = config.Config()
        ##\brief Model start year
        self.startYear      = 0
        ##\brief Model end year
        self.endYear        = 0
        ##\brief List of model input filenames.
        #
        self.inputStrm      = {'HydrologyFile'       :None}

        ##\brief List of model output filenames.
        #
        self.outputStrm      = { 'OutputFile'            :None}

        ##\brief The annual hydrology data
        self.hydrology         = landscape.Landscape()

        ##\brief The model initial conditions.
        #
        self.initCond        = landscape.Landscape() # InitialConditionFile

        ##\brief The species model list (SpeciesModelList)
        #
        self.spModelList     = SpeciesModelList()

        ##\brief The elevation threshold for tree establishment (soon to be deprecated).
        #
        self.elevationThreshold = 0.1525

        self.numLocs            = 0

        ##\brief These are column names used in the hydrology file. The values
        # assigned here are the default values. However, if they change, you can
        # reset them in the configuration file. There is error checking associated
        # with these names at the end of the  params.config()
        self.YEAR       = 'year'
        self.CELLID     = 'CellID'
        self.SAL        = 'mean(sal_year)'
        self.WAVEAMP    = 'std(dep_year)'
        self.SUMMERWD   = 'mean(dep_summer)'
        self.SUMMERSAL  = 'mean(sal_summer)'
        self.SUMMERTEMP = 'mean(temp_summer)'
        self.LAND       = 'land/water'

    ##\brief Open input/output files
    ##\param key The keyword name for the file as it appears in the configuration file.
    ##\param mode Should the file be opened for reading or writing.
    ##\details This is a convenience function. It checks to make sure the
    # file actually exists and opens if it does. If any problems are encountered,
    # such as file not existing or cannot be opened, an exception is raised.
    def open_file(self, key, mode):
        try:
            filename = self.configDict[key];
        except exceptions.KeyError as error:
            errorMessage  = 'Params: Error: ' + str(key) + ' is not defined in the configuration file.\n'
            errorMessage += 'Params: Error: Additional error info : ' + str(error) + '\n'
            raise exceptions.RuntimeError(errorMessage);

        try:
            strm = open(filename, mode)
        except exceptions.IOError as error:
            errorMessage  = 'Params: Error: Could not open the file ' + filename + (' for reading' if mode == 'r' else ' for writing') +'\n'
            errorMessage += 'Params: Error: Additional error info : ' + str(error) + '\n'
            raise exceptions.RuntimeError(errorMessage);

        return strm

    ##\brief Read the model configuration information.
    ##\param argv A object containing the name of the configuration file.
    ##\details This member function reads in the information
    # contained in the model configuration file and sets up
    # a number of program elements based on this information
    #
    # The function performs the follwing steps:
    #
    def config(self, argv):

        errorMessage = ''


        ###################################################
        ##
        # - __Step 1__: Read in the configuration data from the
        #   configuration file.
        #
        ###################################################
        try:
            self.configDict.config(argv);
        except exceptions.RuntimeError as error:
            errorMessage = 'Params: Error: An error occurred reading the configuration file.\n' + str(error)
            raise exceptions.RuntimeError(errorMessage)

        ###################################################
        ##
        # - __Step 2__: Get the start end end year of the simulation
        #
        ###################################################
        try:
            self.startYear = int( self.configDict['StartYear'] )
            print 'Params: Msg: StartYear = ' + str(self.startYear)
        except exceptions.KeyError as error:
            errorMessage += 'Params: Error: StartYear is not defined in the configuration file\n'

        try:
            self.endYear = int( self.configDict['EndYear'])
            print 'Params: Msg: EndYear = ' + str(self.endYear)
        except exceptions.KeyError as error:
            errorMessage += 'Params: Error: EndYear is not defined in the configuration file\n'

        ###################################################
        ##
        # - __Step 3__: Read the initial conditions file
        #
        ###################################################
        try:
            print 'Params: Msg: Reading initial conditions from ' + self.configDict['InitialConditionFile'] + '\n'
            initCondStrm = self.open_file('InitialConditionFile','r')
            reader = landscape.ReadLandscape()
            reader.read(initCondStrm, self.initCond)
            initCondStrm.close();
        except exceptions.RuntimeError as error:
            print(error)
            errorMessage += str(error)



        ###################################################
        ##
        # - __Step 4__: Configure the SpeciesModelList object
        #
        ###################################################
        try:
            estFilename = self.configDict['EstFilename']
        except exceptions.KeyError as error:
            errorMessage += 'Params: Error: ' + str(error) + '\n'
        except exceptions.RuntimeError as error:
            errorMessage += str(error);

        try:
            mortFilename = self.configDict['MortFilename']
        except exceptions.KeyError as error:
            errorMessage += 'Params: Error: ' + str(error) + '\n'
        except exceptions.RuntimeError as error:
            errorMessage += str(error);

        try:
            self.spModelList.config(estFilename, mortFilename)
        except exceptions.KeyError as error:
            errorMessage += 'Params: Error: ' + str(error) + '\n'
        except exceptions.RuntimeError as error:
            errorMessage += str(error);

        ###################################################
        ##
        # - __Step 5__: Open all the input files for reading.
        #
        ###################################################
        for key in self.inputStrm.iterkeys():
            try:
                self.inputStrm[key] = self.open_file(key, 'r')
                print 'Params: Msg: Opened file for reading: ' + str(key) + ' = ' + self.configDict[key]
            except exceptions.RuntimeError as error:
                errorMessage += str(error)

        ###################################################
        ##
        # - __Step 6__: Open all the output files for writing.
        #
        ###################################################

        # Steps 6.1 to 6.3: Set up the single year files.
        # Step 6.1: Get the filename template for the single year files.
        try:
            outputTemplate = None
            outputTemplate = self.configDict['OutputTemplate']
            print 'Params: Msg: OutputTemplate = ' + self.configDict['OutputTemplate']
        except exceptions.KeyError as error:
            errorMessage += 'Params: Error: OutputTemplate is not defined in the configuration file\n'
            errorMessage += 'Params: Error: Additional error info: ' + str(error)

        # Step 6.2: Get the years we want output stored in separate files
        outputYear = range(self.startYear, self.endYear+1)
        #try:
        #    outputYear = None
        #    outputYear = self.configDict['OutputYears']
        #    outputYear = [ int(x) for x in outputYear.split(',')]
        #    print 'Params: Msg: OutputYears = ' + str(outputYear)
        #except exceptions.KeyError as error:
        #    errorMessage += 'Params: Error: OutputYears is not defined in the configuration file\n'
        #    errorMessage += 'Params: Error: Additional error info: ' + str(error)

        # Step 6.3: Build the file names for the separate files.
        if outputTemplate != None and outputYear != None:
            for year in outputYear:
                filename = re.sub(r'<YEAR>', str(year), outputTemplate);
                self.configDict['__Single_'+str(year)] = filename
                self.outputStrm['__Single_'+str(year)] = None


        # Step 6.7: Open all the output files.
        for key in self.outputStrm.iterkeys():
            try:
                self.outputStrm[key] = self.open_file(key, 'w+')
                print 'Params: Msg: Output file opened ' + str(self.configDict[key])
            except exceptions.RuntimeError as error:
                errorMessage += str(error)
                errorMessage += 'Params: Error: This is very odd\n'

        ###################################################
        ##
        # - __Step 7__: If any errors have occurred, raise an
        # exception
        #
        ###################################################
        if len(errorMessage):
            raise exceptions.RuntimeError(errorMessage)

        ###################################################
        ##
        # - __Step X__: Find the number of mesh cells
        #
        ###################################################
        self.numLocs = len(self.initCond.data)

        ###################################################
        ##
        # - __Step 8__: Give all of the landscape objects their
        #  correct size
        ###################################################
        print 'Params: Msg: Giving all data layers their proper size'
        reader = landscape.ReadLandscape()
        reader.read(self.inputStrm['HydrologyFile'], self.hydrology, self.numLocs)

        print 'Params: Msg: Rewinding all the input streams'
        for strm in self.inputStrm.itervalues():
            strm.seek(0,0)



        ###################################################
        ##
        # - __Step 9__: Read the column names for the
        # hydrology file. They the column names have not
        # been redefined in the configuration file, they
        # just use the defaults (already set in __init___().
        #
        # Check that the column names actually appear in the
        # hydrology file. If they do not, throw and exception
        #
        ###################################################

        if 'YEAR'       in self.configDict.keys(): self.YEAR       = config['YEAR']
        if 'CELLID'     in self.configDict.keys(): self.CELLID     = config['CELLID']
        if 'SAL'        in self.configDict.keys(): self.SAL        = config['SAL']
        if 'WAVEAMP'    in self.configDict.keys(): self.WAVEAMP    = config['WAVEAMP']
        if 'SUMMERWD'   in self.configDict.keys(): self.SUMMERWD   = config['SUMMERWD']
        if 'SUMMERSAL'  in self.configDict.keys(): self.SUMMERSAL  = config['SUMMERSAL']
        if 'SUMMERTEMP' in self.configDict.keys(): self.SUMMERTEMP = config['SUMMERTEMP']

        for name in [self.YEAR, self.SAL, self.WAVEAMP, self.SUMMERWD, self.SUMMERSAL, self.SUMMERTEMP]:
            if not name in self.hydrology.data.columns:
                errorMessage += 'Params: Error: Column name ' + name + ' is not defined in the hydrology file.\n'

        if len(errorMessage):
            raise exceptions.RuntimeError(errorMessage)

    ##\brief Actions take at the end of the simulation.
    ##\details The primary responsibility of this function
    # is to close all of the open file streams. It is
    # called at the end of the simulation as the program
    # is getting ready to exit.
    def done(self):
        for iter in self.inputStrm.itervalues():
            iter.close();

        print 'Params: Msg: Fixing output file format'
        columnOrder = self.initCond.columns
        index       = pandas.DataFrame( self.initCond.data['index'] )

        for strm in self.outputStrm.itervalues():
            filename = strm.name
            strm.close()
            data = pandas.read_csv(filename, delimiter='\s*,\s*')
            data = pandas.merge(index, data, left_index=True, right_on='CellID')
            data = data[columnOrder]
            data.to_csv(filename, index=False, quoting=csv.QUOTE_NONNUMERIC)
            strm.close()



    def __del__(self):
        pass

##\class PatchModel
##\brief This class handles the plant community dynamics at a single location
##\role {Ecology/Machinery}
##\detail This class handles the ecology of a single location in the landscape.
# The information for each location is stored as a Python dictionary. They
# keys for the dictionary are the abbreviated names, stored as a string, for
# each of the species. The value for each entry in the dictionary is an object
# instantiated from a class that inherits from SpeciesModel. That is, each value
# is an object of one of the following classes: UplandForestModel, EmergentWetlandModel,
# SAVModel, BarierIslandModel, or NullModel.
class PatchModel(dict):
    params = None
    def __init__(self, cellID, initCond, spModelList):
        dict.__init__(self)
        self.cellID = cellID

        for spName,spCover in initCond.iteritems():
            spModel        = copy.copy(spModelList[spName])
            spModel.cover  = spCover
            spModel.cellID = cellID
            dict.__setitem__(self, spName, spModel)

    def update(self):
        occupied         = 0.0
        lost             = 0.0
        growthLikelihood = 0.0

        #######################################################
        # WARNING: The order of steps here is important. Additional
        #          care is required here because you can change
        #          the order without actually causing the model to break.
        #          Changing the order will change the way the model simulates
        #          the ecology the plants. Unless you really know what
        #          you are doing, do not change the order. Even if
        #          you think you know what you are doing, think about
        #          what you are going to do carefully. Perhaps you should
        #          go take and nap and think things over before you make
        #          changes here. If you break things, it will be on your head.
        #
        #######################################################

        #######################################################
        # Step 1: Work out the increase and decrease in cover
        #         for all types except, SAV, WATER and
        #         Those types have to be
        #         handled in a special way.
        #
        #######################################################

        # Step 1.0: The first thing we need to do is workout the effects of added or lost land
        # There are four cases.
        # Case 1: Land/Land: The cell is currently classified as land and the next state (obtained from the input file)
        # is also land. In this case, we do not need to do anything.
        #
        # Case 2: Water/Water: The cell is currently classified as water and the next state is also water.
        # In this case we do not need to do anything.
        #
        # Case 3: Land/Water: The cell is currently classified as land and the next state is water.
        # In this case we need to set the cover of all plant species to zero and set the cover of water to 1.0
        # the proceed as normal
        #
        # Case 4: Water/Land: The cell is currently classified as water and the next state is land.
        # In this case we need to set the cover of water to zero (?) and set BAREGRND to 1.0. The proceed as
        # normal.
        #
        # A cell that has some vegetation present will be classified as land.
        nextLandState = PatchModel.params.hydrology[self.cellID][PatchModel.params.LAND]

        # Step 1.0 a: Figure out if the cell is currently classified as land or water.
        isLand     = 1
        #SAVModel   = dict.__getitem__(self, 'SAV')
        #waterModel = dict.__getitem__(self, 'WATER')
        #totalWater = SAVModel.cover + waterModel.cover
        totalWater = self['WATER'].cover + self['SAV'].cover
        if totalWater == 1.0:
            isLand = 0

        if isLand == 1 and nextLandState == 0: # Case 3
            for spName, spModel in self.iteritems():
                spModel.cover = 0.0
            self['WATER'].cover = 1.0
            #waterModel = dict.__getitem__(self, 'WATER')
            #waterModel.cover = 1.0

        elif isLand == 0 and nextLandState == 1: # Case 4
            self['BAREGRND'].cover = 1.0
            self['WATER'].cover    = 0.0
            self['SAV'].cover      = 0.0

        else: # Case 1 and Case 2
            pass

        # Step 1.1: Work out the loss of cover for all species
        unoccupied = self['BAREGRND'].cover

        for spName, spModel in itertools.ifilterfalse(lambda (k,v): k == 'BAREGRND' or k =='SAV' or k == 'WATER', self.iteritems()):
            cover             = spModel.cover
            death             = spModel.senescence()
            #occupied         += cover
            lost             += death * cover
            spModel.cover    -= death * cover

            growthLikelihood += spModel.growth()

        #unoccupied = max(     0, min( (1.0-occupied)+lost, 1.0)     )
        unoccupied += lost
        available   = copy.copy(unoccupied)

        # Step 1.2: Work out the gain in cover for all species
        # The rounding of growth is complicated because what I really want to happen is for the
        # digits below the thousandths place to be dropped. This isn't really rounding. The reason
        # for this approach is to make growth slightly smaller than it might be. this should
        # prevent unoccupied from becoming negative. This is a rather crude way to achieve the
        # desired result, but it is simple and requires a minimum of recoding and new coding.
        if growthLikelihood:
            for spName, spModel in itertools.ifilterfalse(lambda (k,v): k == 'BAREGRND' or k=='SAV' or k=='WATER', self.iteritems()):
                #spModel.cover += spModel.growth()/growthLikelihood * unoccupied
                growth         = math.floor( spModel.growth()/growthLikelihood * 1000.0 )/1000.0
                #spModel.cover += growth * unoccupied
                #unoccupied    -= growth * unoccupied
                spModel.cover += growth * available
                unoccupied    -= growth * available

        self['BAREGRND'].cover = unoccupied


        #######################################################
        # Step 3: Work out the change in cover for SAV and WATER
        #
        #######################################################
        SAVModel         = dict.__getitem__(self, 'SAV')
        waterModel       = dict.__getitem__(self, 'WATER')
        totalWater       = SAVModel.cover + waterModel.cover
        SAVModel.cover   = SAVModel.growth() * totalWater
        waterModel.cover = totalWater - SAVModel.cover

        #######################################################
        # Step 4: Check sum
        #
        #######################################################
        errorMsg = str()
        checkSum = 0.0
        for spName, spModel in self.iteritems():
            checkSum += spModel.cover
            if spModel.cover < 0.0:
                errorMsg += 'PatchModel: Error: Species cover < 0.0. This should not happen.\n'
                errorMsg += 'PatchModel: Error: Species = ' + spName + '\n'

        tol = 0.005
        if not( (1.0 - tol) < checkSum and checkSum < (1.0 + tol) ):
            errorMsg += 'PatchMod: Error: checkSum is out of range. checkSum = ' + str(checkSum) + ' should be 1.0\n'

        if len(errorMsg) != 0:
            raise exceptions.RuntimeError(errorMsg)

    def write_to_str(self, dataFormat='{0}'):
        ret = ''
        sep = ''
        for value in self.itervalues():
            ret += sep + dataFormat.format(value.cover)
            sep = ', '
        return(ret)

    def write_to_stream(self, stream = sys.stdout, dataFormat='{0}'):
        sep=''
        for key, value in self.iteritems():
            print >> stream, sep + dataFormat.format(value)
            sep = ', '

##\class DynamicsModel
##\brief This class coordinates the updating of plant community dynamics
##\role{Ecology}
##\detail This class handles updating the spatial distribution of species
# in response to environmental conditions and plant dispersal ability.
# This class defines the spatial structure of the model using a
# raster grid. The grid structure is inherited from landscape.LandscapePlus.
# Each element of the raster contains an object of type PatchModel.
# Local dynamics, those taking place within each raster cell, are
# computed by objects of class PatchModel.
class DynamicsModel(dict):
    def __init__(self):
        dict.__init__(self)
        self.locInfo = None

    def config(self, params):
        # Get the species codes (e.g. 'SPPA', 'NYAQ2', etc.. ) from the
        # from the species model list object.
        speciesList = params.spModelList.keys()

        # Pull off just the location information from the
        # initial conditions files. This should be the
        # the row and column indicies (m and n), the
        # actual geographic coordinates (x and y) and the
        # location index (CellID).
        try:
            self.locInfo = params.initCond.data[ ['m','n','x','y'] ]
        except exceptions.KeyError:
            errMsg  = 'DynamicsModel: Error: One or more spatial location columns are not defined in the initialization\n'
            errMsg += 'DynamicsModel: Error: file. Columns should be CellID, m, n, x and y (order does not matter)\n'
            raise exceptions.RuntimeError(errMsg)

        # Cycle over the locations in the initial conditions.
        # Each row in the initial conditions csv file represents
        # a single location/patch.
        for cellID, patch in params.initCond.iterlocs():
            # Get just the cover values for the current patch.
            patchInitCond = patch[speciesList]
            # Make a PatchModel object for the patch and initialize it
            newPatch      = PatchModel(cellID, patchInitCond, params.spModelList)
            # Place the patch model in a dictionary referenced by the cellID.
            dict.__setitem__(self, cellID, newPatch)

    def __getitem__(self, cellID):
        dict.__getitem__(self, cellID)

    def update(self):
        for index,patch in self.iteritems():
            patch.update()

    def write_to_stream(self, stream, header=True, time=None):
        #if time is not None:
            #cols = self.locInfo.columns.insert(0, 'year')
            #self.locInfo['year'] = numpy.full( len(self.locInfo), time)
            #self.locInfo = self.locInfo[ cols ]

        if header:
            line = 'CellID'
            for name in self.locInfo.columns:
                line += ', ' + str(name)
            for name in self.itervalues().next().iterkeys():
                line += ', ' + str(name)
            print >> stream, line

        for cellID,patch in self.iteritems():
            try:
                pandas.DataFrame( self.locInfo.ix[ cellID ] ).T.to_csv(stream, header=False, line_terminator='')
                print >> stream, ', ' + patch.write_to_str()
            except exceptions.IndexError as error:
                print cellID
                raise error

        #if time is not None:
            #del self.locInfo['year']

##\class DispersalModel
##\brief Computes the dispersal of each species over space
##\role{Ecology}
##\detail This class computes the dispersal kernel for each species.
class DispersalModel(object):
    dynModel = None

    def __init__(self):
        self.patchIndexLandscape = landscape.Landscape()
        self.patchDict           = dict()

    def config(self, params):
        self.patchIndexLandscape.copy(params.initCond)

        #for row in range(0, int(params.initCond.nrow) ):
           #for col in range(0, int(params.initCond.ncol) ):
                # if params.initCond.has_data_at(row,col):
        for row,col in itertools.ifilter( lambda (r,c): params.initCond.has_data_at(r,c), itertools.product(   range(0,int(params.initCond.nrow)), range(0,int(params.initCond.ncol))   ) ):
               patchIndex                 = params.initCond.data[row,col]
               patchInitCond              = params.initCond[(row,col)]
               neighborList               = [ (nRow,nCol) for nRow,nCol in itertools.ifilter( lambda (r,c): params.initCond.has_data_at(r,c), itertools.product(range(row-1,row+2), range(col-1,col+2)) ) ] # This is slick as hell. I like python.
               newPatch                   = { 'loc':(row,col), 'spFreq':patchInitCond, 'neighborList':neighborList }
               self.patchDict[patchIndex] = newPatch

        patchInitCond = params.initCond.table.itervalues().next()
        for key in patchInitCond.iterkeys():
            patchInitCond[key] = 0.0
        newPatch = {'loc':(-1,-1), 'spFreq':patchInitCond, 'neighborList':[]}
        self.patchDict[params.initCond.nodata_value] = newPatch

    def summary(self):
        print 'DispersalModel: Msg: Summary info start'
        print self.patchIndexLandscape.header()
        print 'len(patchDict) = ' + str(len(self.patchDict))
        print next( self.patchDict.itervalues() )
        patchAddress = self.patchIndexLandscape.data[241,44]
        print 'DispersalModel: patchAddress = ' + str(patchAddress)
        print 'DispersalModel: Stuff at patchAddress = ' + str( self.patchDict[patchAddress] )
        print 'DispersalModel: Stuff at patchAddress[\'spFreq\'] = ' + str( self.patchDict[patchAddress]['spFreq'])
        print 'DispersalModel: Msg: Summary info end'

    def __getitem__(self, item):
        patchAddress = self.patchIndexLandscape.data[item]
        return self.patchDict[patchAddress]['spFreq']

    def has_data_at(self,row,col):
        return self.patchIndexLandscape.has_data_at(row,col)

    def _compute_local_dsp(self, ret):
        #row,col      = ret['loc']
        ptr          = ret['spFreq']
        neighborList = ret['neighborList']

        total = 0.0

        for sp in ptr.iterkeys():
            ptr[sp] = 0

        for neighbor in neighborList:
            patchModel = DispersalModel.dynModel[neighbor]
            for sp,spModel in patchModel.iteritems():
                ptr[sp] += spModel.cover
                total   += spModel.cover

        #for offsetRow in range(-1,2):
        #    for offsetCol in range(-1,2):
        #        neighborRow = row + offsetRow
        #        neighborCol = col + offsetCol
        #        if DispersalModel.dynModel.has_data_at(neighborRow, neighborCol):
        #            patchModel = DispersalModel.dynModel[neighborRow,neighborCol]
        #            for sp,spModel in patchModel.iteritems():
        #                try:
        #                    ptr[sp] += spModel.cover
        #                    total   += spModel.cover
        #                except exceptions.TypeError as error:
        #                    print 'DispersalModel::_compute_local_dsp(): Error: ret         = ' + str(ret)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: row         = ' + str(row)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: col         = ' + str(col)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: ptr         = ' + str(ptr)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: total       = ' + str(total)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: offsetRow   = ' + str(offsetRow)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: offsetCol   = ' + str(offsetCol)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: neighborRow = ' + str(neighborRow)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: neighborCol = ' + str(neighborCol)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: patch       = ' + str(patch)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: sp          = ' + str(sp)
        #                    print 'DispersalModel::_compute_local_dsp(): Error: cover       = ' + str(cover)
        #                    raise error

        if total:
            for sp in ptr.iterkeys():
                ptr[sp] /= total

    def update(self):
        for loc in itertools.ifilter( lambda key: key != self.patchIndexLandscape.nodata_value, self.patchDict.iterkeys()):
            self._compute_local_dsp(self.patchDict[loc])


##\class ModelUpdateEvent
##\brief An event class to update the dynamics model and the dispersal model.
##\role{Machinery}
##\detail This class will likely be removed in an upcoming version of the model.
#
class ModelUpdateEvent(event.Event):
    def __init__(self, time, name, model):
        event.Event.__init__(self, time, name)
        self.model = model

    def act(self):
        print self.name
        self.model.update()

##\class StopWatch
##\brief A class to measure elapsed wall clock time.
##\role{Machinery}
##\detail This class is used to measure how much time
# (wall clock time) is used for different aspects of the model.
# This provides some rough profiling information so we can estimate
# how long future runs of the model might take.
class StopWatch:
    def __init__(self):
        self.running  = 0
        self._start   = 0
        self._end     = 0

    def start(self):
        self.running = 1
        self._start = time.time()
        self._stop  = 0

    def stop(self):
        self._end   = time.time()
        self.running = 0
        print 'StopWatch: Msg: Delta t = ' + str(self._end - self._start)

    def act(self):
        if self.running:
            self.stop()
        else:
            self.start()

    def __str__(self):
        ret  = str(id(self))     + ' '
        ret += str(self.running) + ' '
        ret += str(self._start)  + ' '
        ret += str(self._end)    + ' '
        ret += str(self._end - self._start)
        return ret

#class StopWatchEvent(event.Event):
    #def __init__(self, time, name='StopWatchEvent', stopwatch=None):
        event.Event.__init__(self, time, name)
        self.stopwatch = stopwatch
#
    #def act(self):
        #if self.stopwatch.running:
            #self.stopwatch.stop()
        #else:
            #self.stopwatch.start()


# This is the top level class for the LAVegMod. To get the model going you need
# to do four things.
#  1) Include the model module in the Python file that will be used
#  to coordinate the running of the various models.
#  The include statement should look something like:
#  import model
#  Note that this assumes that the LAVegMod code is in the same
#  directory as the toplevel script.
#
#  2) Instantiate an object of class Model
#  This will create a copy of the model and all of its subcomponents.
#  Instantiating model should look something like:
#
#  laVegMod = model.Model()
#
#
#  3) Call Model.config(self, argv) for the instantated object
#  Model.config takes a single (non-self) argument that contains the
#  name of the configuration file for the model. The call to Model.config()
#  should look something like:
#
#  laVegMod.config( <configFilename> )
#
#  where <configFilename> is a Python string containing the full path
#  and filename of the configuration file for the LAVegMod.
#
#  The call to Model.config() will bring the model up to a state where it
#  is ready to run. It is possible that more or more errors may occur while
#  configuring the model. You can capture these and processes them yourself
#  or you can just let them go and they will halt the code. To capture the
#  errors from the LAVegMod you need code that looks something like:
#
#  try:
#      laVegMod.config( <configFilename> )
#  except exception.RunTimeError as error:
#      <do something with the error>
#
#  4) Call Model.step() repeatedly for the instantated object.
#  Each time you call model.step() the model will advance by one year.
#
#  The call to Model.step() should look something like:
#
#  laVegMod.step()
#
#  Again, this code may throw exceptions if something goes wrong. You can
#  capture these errors using code that looks like this:
#
#  try:
#      laVegMod.step()
#  except exception.RunTimeError as error:
#      <do something with the error>
#
#  Note that the LAVegMod only throws, (or should only throw)
#  exception.RunTimeError()
#
#  The thrown error contains one or error messages stored in a string that describe what
#  went wrong. You can print these with something like:
#
#  print error
#
#  If the model has thrown an exception, then something is broken and
#  there is no way to fix it during run time. You or I, or someone, will
#  have to chase down the error and fix it. Often the error is going to be
#  in the input files. So I would check those first.
#
#  I have tried to make the error checking and reporting as comprehensive
#  and as detailed as possible.
#
#

class Model(object):
    def __init__(self):
        self.currentTime        = event.Time(0,0)
        self.params             = Params()
        self.eventQueue         = event.EventQueue()
        self.dynModel           = DynamicsModel()
        #self.dspModel           = DispersalModel()

        SpeciesModel.params     = self.params    # I'm not sure I like this.
        PatchModel.params       = self.params
        #SpeciesModel.dspModel   = self.dspModel  #
        DispersalModel.dynModel = self.dynModel  #

    def config(self, argv):
        print 'Model: Msg: Reading configuration information'
        self.params.config(argv)

        print 'Model: Msg: Configuring the dynamics model'
        self.dynModel.config(self.params)

        print 'Model: Msg: Configuring the dispersal model'
        #self.dspModel.config(self.params)
        # self.dspModel.summary()

        print 'Model: Msg: Configuring the update queue'
        self.eventQueue.clear()
        perYearSW = StopWatch()
        totalSW   = StopWatch()

        self.eventQueue.add_event(       event.GenericEvent(event.Time(self.params.startYear,    0), name='StopWatch', callable=totalSW) )
        self.eventQueue.add_event( landscape.WriteLandscape(event.Time(self.params.startYear,  200), name='WriteASCIIGrid: Msg: Writing model output', stream=self.params.outputStrm['OutputFile'], landscape=self.dynModel, header=True))

        for year in range(self.params.startYear+1, self.params.endYear+1):
            self.eventQueue.add_event(       event.GenericEvent(event.Time(year,  100), name='StopWatch', callable=perYearSW ) )
            self.eventQueue.add_event(           event.MsgEvent(event.Time(year,  200), name='MsgEvent', msg='Year = ' + str(year)) )
            self.eventQueue.add_event(  landscape.ReadLandscape(event.Time(year,  300), name='ReadASCIIGrid: Msg: Reading wave amp data',    stream=self.params.inputStrm['HydrologyFile'], landscape=self.params.hydrology, numLocs=self.params.numLocs))
            self.eventQueue.add_event(         ModelUpdateEvent(event.Time(year, 1100), name='ModelUpdateEvent: Msg: Updating veg dynamics', model=self.dynModel  )  )
            self.eventQueue.add_event( landscape.WriteLandscape(event.Time(year, 1400), name='WriteLandscape: Msg: Writing model output',    stream=self.params.outputStrm['OutputFile'],   landscape=self.dynModel, header=False))
            self.eventQueue.add_event(       event.GenericEvent(event.Time(year, 1600), name='StopWatch', callable=perYearSW ) )


        for yearKey,yearStream in itertools.ifilter( lambda (k,v): re.match(r'^__Single_',k) != None, self.params.outputStrm.iteritems() ):
            year = int( yearKey.replace('__Single_','') )
            self.eventQueue.add_event(landscape.WriteLandscape(event.Time(year, 1500), name='WriteLandscape: Msg: Writing model output for year  ' + str(year), stream=yearStream, landscape=self.dynModel))

        self.eventQueue.add_event(             event.GenericEvent(event.Time(self.params.endYear,3000), name='StopWatch', callable=totalSW) )

    def step(self):
        self.eventQueue.run(while_condition=(lambda arg: arg.name != 'PauseEvent'))
        return 0

    def run(self):
        try:
            self.config(sys.argv)
        except exceptions.RuntimeError as error:
            print error
            return 1
        #except:
            #print 'Model: Error: Caught an unknown error : ' , sys.exc_info()[0]
            #return 1

        self.eventQueue.run()

        self.params.done()

        return 0;

