#!/usr/bin/env /usr/bin/ecell3-python

import sys
import getopt
import code
import os

import ecell.config
import ecell.ecs
import ecell.emc
from ecell.Session import Session

class Prompt:
    def __init__( self, aSession ):
        self.theSession = aSession

    def __str__( self ):
        if self.theSession.theModelName == '':
            return 'ecell3-session>>> '
        else:
            return '<%s, t=%g>>> ' %\
                   ( self.theSession.theModelName, \
                     self.theSession.getCurrentTime() )

class Console:
    def __init__( self, session, banner ):
        self.session = session
        self.banner = banner

    def __createScriptContext( self, parameters ):
        # theSession == self in the script
        aContext = { 'theSession': self.session, 'self': self.session }

        # flatten class methods and object properties so that
        # 'self.' isn't needed for each method calls in the script
        aKeyList = list( self.session.__dict__.keys() +\
                         self.session.__class__.__dict__.keys() )
        aDict = {}
        for aKey in aKeyList:
            if not aKey.startswith('__'):
                aDict[ aKey ] = getattr( self.session, aKey )

        aContext.update( aDict )
            
        # add parameters to the context
        aContext.update( parameters )

        return aContext

    # Session methods
    def loadScript( self, ecs, parameters={} ):
        aContext = self.__createScriptContext( parameters )
        execfile( ecs, aContext )

    def interact( self, parameters = {} ):
        aContext = self.__createScriptContext( parameters )
        try:
            import readline # to provide convenient commandline editing :)
        except:
            pass
        aContext[ '__prompt__' ] = Prompt( self.session )
        anInterpreter = code.InteractiveConsole( aContext )
        anInterpreter.runsource( 'import sys; sys.ps1 = __prompt__; del sys, __prompt__' )
        anInterpreter.interact( self.banner )

#FIXME: commandline processing needed

def usage():
    aProgramName = os.path.basename( sys.argv[0] )
    print '''
%(appname)s -- invoke ecell3 python interactive mode or run ess file

Usage:

    %(appname)s [-f] [-f EMLFILE]          : Load EMLFILE and then
                                             switch to intractive mode
    %(appname)s [ESSFILE] [-e ESSFILE]     : Run an ess file
    %(appname)s                            : Run in intractive mode
    %(appname)s [-h]                       : Print this message

Options:

    -C                                 :  change to the directory where
                                          the script (.ess) file is placed.
    -e or --exec=[.ess file]           :  load script (.ess) file
    -f or --file=[.eml file]           :  load model (.eml) file
    
    Either -e or -f option can be specified at once. 

    -DNAME=VALUE                       :  Set session parameter
    --parameters="[python dictionary]" :  Set session parameters
        
    -h or --help              :  Print this message.

    Example: 
       %(appname)s -DNAME1=VALUE1 -DNAME2=VALUE2
       %(appname)s --parameters="{NAME1:VALUE1,NAME2:VALUE2}"

    Note that spaces are not allowed in names and values.

Configurations:
    If ECELL3_DM_PATH environment variable is set to a colon (%(pathsep)s)
    separated directory path, it tries to find dynamic modules within the
    locations referred to by it.

    Example: 
      ECELL3_DM_PATH=/home/user/dm%(pathsep)s/home/user/dm_other %(appname)s

'''% { 'appname': aProgramName, 'pathsep': os.pathsep }

def main():
    # -------------------------------------
    # initialize file names
    # -------------------------------------
    anEmlFile = None
    anEssFile = None
    chdirToEssFile = False
    anEmlFlag = 0

    aParameters = {}

    # -------------------------------------
    # when ecell3-session [ essfile ] check file exist
    # and set anESSfile 
    # -------------------------------------

    for i in range( len( sys.argv ) - 1 ):
        i = i + 1
        if not sys.argv[ i ][0] == "-":
            if os.path.isfile( sys.argv[ i ] ):
                if sys.argv[ i - 1 ] != "-e" and sys.argv[ i - 1 ] != "-f":
                    anEssFile = sys.argv[i]
            else:
                sys.stderr.write( "Error: %s does not exist.\n" % sys.argv[ i ] )
                sys.exit( 1 ) 

    # -------------------------------------
    # gets options
    # -------------------------------------
    try:
        opts, args = getopt.gnu_getopt(
            sys.argv[ 1: ] , 'he:f:D:C',
            [ "parameters=", "help", "exec=", "file=" ] )
    except:
        usage()
        sys.exit( -1 ) 
        
    # -------------------------------------
    # checks argument
    # -------------------------------------
    for anOption, anArg in opts:

        # prints help message
        if anOption in ( "-h", '--help' ):
            usage()
            sys.exit( 0 )
            
        # executes script file (.ess)
        if anOption in ( "-e", '--exec'):
            if not anArg:
                sys.stderr.write( "Error: ess file not specified.\n" )
                usage()
                sys.exit( -1 )
            anEssFile = anArg
            
        # load model file (.eml)
        if anOption in ( "-f", '--file' ):
            if not anArg:
                sys.stderr.write( "Error: eml file not specified.\n" )
                usage()
                sys.exit( -1 )
            anEmlFile = anArg            

        # change directory where ESS file is placed.
        if anOption == "-C":
            chdirToEssFile = True

        # set session parameters            
        if anOption == "-D":
            aSplitArgList = anArg.split( '=' )

            if not aSplitArgList[1]:
                aSplitArgList[1] = 1
            
            try:
                anEvaluatedString = eval(aSplitArgList[1])
                aParameters[aSplitArgList[0]] = anEvaluatedString
            except:
                aParameters[aSplitArgList[0]] = aSplitArgList[1]

        # set session parameters            
        if anOption == "--parameters":
            try:
                anEvaluatedArg = eval(anArg)

            except:
                import traceback 
                aErrorMessageList = traceback.format_exception(
                    sys.exc_type,sys.exc_value,sys.exc_traceback )
                for aLine in aErrorMessageList: 
                    sys.stderr.write( aLine )
                sys.stderr.write( "Error: %s is not a valid python expression.\n" % anArg );
                sys.exit( -1 )

            # check anEvaluatedArg type
            if not type(anEvaluatedArg) == dict:
                sys.stderr.write( "Error: %s does not result in a python dictionary.\n" % aParameters )
                sys.exit( -1 )

            # add parameters to aParameters 
            for aKeyString in anEvaluatedArg.keys():
                aParameters[ aKeyString ] = anEvaluatedArg[ aKeyString ]
                
            
    aSimulator = ecell.emc.Simulator()
    aSimulator.setDMSearchPath( aSimulator.DM_SEARCH_PATH_SEPARATOR.join( ecell.config.dm_path ) )

    aSession = Session( aSimulator )

    aConsole = Console( aSession, 
        '''ecell3-session [ E-Cell SE Version %s, on Python Version %d.%d.%d ]
Copyright (C) 1996-2010 Keio University.
Copyright (C) 2005-2009 The Molecular Sciences Institute.
More info: http://www.e-cell.org/software''' % (
        ecell.ecs.getLibECSVersion(),
        sys.version_info[0], sys.version_info[1], sys.version_info[2] )
        )

    if anEmlFile:
        aSession.loadModel( anEmlFile )

    if anEssFile:
        if chdirToEssFile:
            anEssDirectory, anEssFile = os.path.split( anEssFile )
            os.chdir( anEssDirectory )
        aConsole.loadScript( anEssFile, aParameters )
    else:
        aConsole.interact( aParameters )
         
if __name__ == '__main__':
    main()
    sys.exit( 0 )
