#!/usr/bin/python2
# Copyright (c) 2012-2013, Itzik Kotler
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#
#     * Neither the name of the author nor the names of its contributors may
#       be used to endorse or promote products derived from this software without
#       specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import sys

try:

    import readline

except ImportError:

    # Windows ...

    pass

import code
import codeop
import argparse
import logging
import os
import atexit
import runpy
import multiprocessing
import pprint


try:

    import _preamble

except ImportError:

    sys.exc_clear()


import pythonect
import pythonect.internal.parsers


# Pythonect Console

class PythonectCompile(codeop.Compile):

    def __init__(self):

        codeop.Compile.__init__(self)

    def __call__(self, source, filename, symbol):

        if source[-1] == '\\':
            return None

        return source.replace('\\\n', '')


class PythonectCommandCompiler(codeop.CommandCompiler):

    def __init__(self):

        codeop.CommandCompiler.__init__(self)

        self.compiler = PythonectCompile()


class PythonectInteractiveConsole(code.InteractiveConsole):

    def __init__(self, locals=None, histfile=os.path.expanduser("~/.pythonect_history")):

        code.InteractiveConsole.__init__(self, locals)

        self.compile = PythonectCommandCompiler()

        self.init_history(histfile)

    def init_history(self, histfile):

        try:

            readline.read_history_file(histfile)

        except Exception:

            pass

        atexit.register(self.save_history, histfile)

    def save_history(self, histfile):

        try:

            readline.write_history_file(histfile)

        # i.e. NameError: name 'readline' is not defined ...

        except NameError:

            pass

    # This is a cut & paste from /usr/lib/python2.7/code.py
    # Except we're not calling `exec` statement

    def runcode(self, code_):

        try:

            return_value = pythonect.eval(code_, {}, self.locals)

            # Meaningful Return Value?

            if return_value is not None:

                # String?

                if isinstance(return_value, basestring):

                    # Enclose in single quotes

                    return_value = "'" + return_value + "'"

                self.write(str(return_value) + '\n')

                # Keep return_value for further reference or reset to None?

                if return_value is False or return_value is True:

                    # Reset locals to None

                    self.locals['_'] = None

        except SystemExit:

            raise

        except:

            self.showtraceback()

        else:

            if code.softspace(sys.stdout, 0):

                print


def main(argv=sys.argv):

    locals_ = {}
    globals_ = {}

    verbose_levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]

    # Pythonect's Banner (for -V, --version purposes)

    banner = "Pythonect %s" % pythonect.__version__

    # Parse command-line arguments

    parser = argparse.ArgumentParser(prog='pythonect')

    parser.add_argument('script', metavar='file', nargs='?', type=argparse.FileType('rt'), help='program read from script file')
    parser.add_argument('arg', metavar='arg', nargs='*', help='arguments passed to program in sys.argv[1:]')
    parser.add_argument('--verbose', '-v', action='count', default=0)
    parser.add_argument('--version', '-V', action='version', version=banner)
    parser.add_argument('--interactive', '-i', action='store_true', default=False, help='inspect interactively after running script')
    parser.add_argument('--command', '-c', metavar='cmd', nargs='*', help='program passed in as string')
    parser.add_argument('--module', '-m', metavar='mod', action='store', help='run library module as a script')
    parser.add_argument('--max-threads-per-flow', '-mt', metavar='N', default=multiprocessing.cpu_count() + 1, type=int, action='store', help='max threads per flow')

    args = parser.parse_args(args=argv[1:])

    # Setup logging level

    logging.basicConfig(level=verbose_levels[args.verbose % 4], format="%(levelname)s:%(message)s")

    logging.info('Interpreter Command line arguments:\n%s' % pprint.pformat(vars(args)))

    # Add current working directory to sys.path

    sys.path.insert(0, os.getcwd())

    logging.info('Interpreter Initial sys.argv = %s' % sys.argv)

    # Argument Passing

    if args.arg:

        sys.argv = [sys.argv[0]] + args.arg

    logging.info('Interpreter Final sys.argv = %s' % sys.argv)

    # Module as script mode (i.e. python -m 'os')

    if args.module:

        logging.info('*** Entering Module Mode ***')

        locals_ = runpy.run_module(args.module)

    # Max threads per pipe

    locals_.update(__MAX_THREADS_PER_FLOW__=args.max_threads_per_flow)

    # Command line mode (i.e. ./pythonect -c "'Hello world' -> print")

    if args.command:

        logging.info('*** Entering Command Mode ***')

        pythonect.eval(args.command[0], globals_, locals_)

    # Script-mode (i.e. ./pythonect script or #!/usr/bin/env pythonect)

    if args.script:

        logging.info('*** Entering Script Mode ***')

        content = args.script.read()

        scriptname, scriptextension = os.path.splitext(args.script.name)

        logging.info('Script Filename = %s' % scriptname)

        if scriptextension:

            logging.info('Script Filename Extension = %s' % scriptextension)

            parsers = pythonect.internal.parsers.get_parsers(os.path.abspath(os.path.join(os.path.dirname(pythonect.internal.parsers.__file__), '..', 'parsers')))

            logging.debug('Compiling a List of Parsers:\n%s' % pprint.pformat(parsers))

            content = parsers[scriptextension[1:]].parse(content)

            logging.info('Using Parser %s to Parse `%s`' % (parsers[scriptextension[1:]], args.script.name))

            if content is None:

                raise Exception("Unable to parse %s with %s" % (scriptname, parsers[scriptextension[1:]].__repr__()))

        else:

            logging.warning("Couldn't find %s Filename Extension! Guesstimating Parser..." % args.script.name)

        pythonect.eval(content, globals_, locals_)

        args.script.close()

    if not args.module and not args.command and not args.script:

        args.interactive = True

    else:

        banner = ''

    # Interactive-mode (i.e. ./pythonect)

    if args.interactive:

        logging.info('*** Entering Interactive Mode! ***\n\n')

        # Pythonect's Banner (for -V, --version purposes)

        banner = """Python %s\n[Pythonect %s] on %s\nType "help", "copyright", "credits" or "license" for more information.""" % (sys.version.split('\n')[0], pythonect.__version__, sys.platform)

        PythonectInteractiveConsole(locals_).interact(banner)

    return 0


# Entry Point

if __name__ == "__main__":

    try:

        sys.exit(main())

    except ValueError as e:

        pass
