#!/usr/bin/env python
'''
This is the main PyLearn executable.

Matt Adams
Michael Domingues
Alex Voorhees
Tung Phan

Last Modified: 24 February 2013

Be sure to update logger_id when doing any updates!
Pre-Release Format: pylearn-ReleaseName-RevisionNumber
Release Format: pylearn-FullVersionNumber.PartialVersionNumber
'''
logger_id = 'pylearn-beta-0' # Update this on ANY file change (no other place to update)
successful_run = True
logger_on = False


# PyLearn module imports
import errorTree
import preprocessor
if logger_on:
    import Logger.sendError
import errorTree.tbTools
import colorama

# Initialize colorama for windows
colorama.init()

# imports
import os, sys, traceback, platform


# set subProgName global to easily get it inside the except handler 
# (which is called by python with specific args)
subProgName = ''


def exceptHandler(etype, value, tb):
    '''This overrides sys.excepthook to display our custom errors.'''
    global successful_run
    successful_run = False

    source_code = get_source_code()

    try:
        err_msg = errorTree.getErrorMsg(etype, value, tb)
        
        # get the error value from error tree's error string to send to the database
        try:
            pl_err_val = err_msg.split(etype.__name__)[-1].strip()
            if pl_err_val[0:6] == ':\033[0m ': pl_err_val = pl_err_val[6:]
        except:
            try:
                pl_err_val = err_msg.split(etype.__name__)[-1].strip()
            except:
                pl_err_val = ''
        
        if logger_on:
            Logger.sendError.sendError(etype, pl_err_val, err_msg, source_code, logger_id, silent=True) # questions? help(Logger.sendError)

       # actual error print statement
        sys.stderr.write(err_msg+'\n')
    except:
        # if here, errorTree.getErrorMsg raised an exception.
        error_in_pylearn_handler(tb, etype, value)
        

def error_in_pylearn_handler(tb, etype, value):
    '''This function should get called whenever there is an error in pylearn itself.'''
    try:
        pyLearnFormattedErrorMessage = errorTree.tbTools.extract_tb_str(tb) + errorTree.tbTools.formatErrMsg(etype, value)
        sys.stderr.write(pyLearnFormattedErrorMessage+'\n')
    except:
        sys.stderr.write('Sorry, but there seems to have been an error in pylearn itself.\nPlease run your program using regular python.\nAn error report is being sent to the PyLearn comps group.\n')
    # log the exception that caused pylearn to break to the database
    try:
        logger_traceback = traceback.format_exc()
        if logger_on:
            Logger.sendError.sendInnerPyLearnError(logger_traceback, source_code, logger_id)
    except:
        pass


def get_source_code():
    ''' attempt to read in the user program text to then send to the error database '''
    source_code = ''
    try:
        with open(subProgName, 'r') as f:
            source_code = f.read()
    except IOError, e:
        pass
    return source_code

def runSubProg():
    '''Executes the program specified by the user in its own sandbox.'''
    global subProgName

    subProgName = sys.argv[1]
    sys.argv = sys.argv[1:]

    if os.path.exists(subProgName): 
        if os.path.isdir(subProgName):
            sys.stderr.write('pylearn couldn\'t open "{}" because it is a directory.\n'.format(subProgName))
            sys.exit(1)

        # whitespace preprocessing
        wpParser = preprocessor.spaceTabCheck.WhitespaceParser(subProgName)
        if wpParser.hasError():
            tb_entry = errorTree.tbTools.get_one_file_tb_entry_str(wpParser.getErrorLineNum(), wpParser.errorLineString.rstrip())
            err_msg = errorTree.tbTools.formatErrMsg(wpParser.get_error_type(), wpParser.get_error_value())
            sys.stderr.write(tb_entry+err_msg+'\n')
            if logger_on:
                Logger.sendError.sendError(wpParser.get_error_type(), wpParser.get_error_value(), err_msg, get_source_code(), logger_id, silent=True) # questions? help(Logger.sendError)
            sys.exit(1)
            
        runHelper(subProgName)
    else:
        sys.stderr.write('pylearn couldn\'t open "{}", because that file doesn\'t exist.\n'.format(subProgName))


def runHelper(subProgName):
    '''Runs the file and catch exceptions by overriding the system excepthook. This isolates the call one extra level.'''
    # add the directory of the user's file to PYTHONPATH to make sure imports work
    # note that it is added at the front so it is checked first
    sys.path = [os.path.split(os.path.abspath(subProgName))[0]]+sys.path[1:]

    sys.excepthook = exceptHandler
    # Sandbox user code exeuction to its own namespace to prevent malicious code injection.
    execfile(subProgName, {'__builtins__': __builtins__, '__name__': '__main__', '__file__': subProgName, '__doc__': None, '__package__': None})
    # Reset the system excepthook
    sys.excepthook = sys.__excepthook__


"""Interpreter Section!"""

def interpreterErrHandler(filename=None):
    '''This is the error handler called when pylearn is run as an interactive interperter.'''
    etype, value, tb = sys.exc_info()
    try:
        err_msg = errorTree.getErrorMsg(etype, value, tb)
        sys.stderr.write(err_msg+'\n')
    except:
        error_in_pylearn_handler(tb, etype, value)


def runInterpreter():
    '''Run the interactive interpreter version of pylearn.'''
    import code
    if platform.system() != 'Windows': 
        import readline

    # Initiate and run the code module's interactive console with custom error handlers
    # and set the console's locals dict to be the same as that of the normal interpreter
    interp = code.InteractiveConsole({'__builtins__': __builtins__, '__name__': '__main__', '__doc__': None, '__package__': None}) 
    interp.showtraceback = interpreterErrHandler
    interp.showsyntaxerror = interpreterErrHandler

    # run the console (note that this loops until exit() or ctrl-d is called in the console)
    interp.interact(banner='PyLearn Interpreter') 

"""End of Interpreter Section(!)"""


def main():
    #path the current path to os for tbTools to use later
    os.currentPyLearnPath = __file__ 
    colorama.init()

    if len(sys.argv) > 1:
        sys.isConsole = False
        runSubProg()
        if successful_run and logger_on:
            Logger.sendError.sendSuccessfulRun(get_source_code(), logger_id, silent=True)
    else:
        sys.isConsole = True
        try:
            runInterpreter()
        except (SystemExit, KeyboardInterrupt): 
            # added SystemExit b/c exit() wasn't working right
            # added KeyboardInterrupt for good measure (code module docs implied we should do this)
            pass

if __name__ == '__main__':
    main()


