# Copyright (c) 2010-2014 Bo Lin
# Copyright (c) 2010-2014 Yanhong Annie Liu
# Copyright (c) 2010-2014 Stony Brook University
# Copyright (c) 2010-2014 The Research Foundation of SUNY
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import ast
import sys
import argparse

from .parser import Parser
from .pygen import PythonGenerator
from .pypr import to_source
from .pseudo import to_pseudo
from .incgen import gen_inc_module
from .utils import is_valid_debug_level, set_debug_level

# DistAlgo filename suffix
DA_SUFFIX = "da"

stdout = sys.stdout
stderr = sys.stderr


def daast_from_file(filename, args=None):
    """Generates a DistAlgo AST representation from the specified DistAlgo source
       file.

    """
    dt = Parser(filename, args)
    try:
        with open(filename, 'r') as infd:
            pytree = ast.parse(infd.read(), filename)
            dt.visit(pytree)
            stderr.write("%s compiled with %d errors and %d warnings.\n" %
                         (filename, dt.errcnt, dt.warncnt))
            if dt.errcnt == 0:
                return dt.program
    except SyntaxError as e:
        stderr.write("%s:%d:%d: SyntaxError: %s" % (e.filename, e.lineno,
                                                    e.offset, e.text))
    return None

def dafile_to_pyast(filename, args=None):
    """Translates the given DistAlgo source file into Python code. Returns an AST
       representation of the result.

    """
    daast = daast_from_file(filename, args)
    if daast is None:
        stderr.write("Error: unable to generate DistAlgo AST for file %s\n" % filename)
        return None

    pyast = PythonGenerator(filename).visit(daast)
    if pyast is None:
        stderr.write("Error: unable to generate Python AST for file %s\n" % filename)
        return None
    else:
        return pyast

def dafile_to_pseudofile(filename, outname=None):
    """Compiles a DistAlgo source file to Python file.

    'filename' is the input DistAlgo source file. Optional parameter 'outname'
    specifies the file to write the result to. If 'outname' is None the
    filename is inferred by replacing the suffix of 'filename' with '.py'.

    """
    purename, _, suffix = filename.rpartition(".")
    if len(purename) == 0:
        purename = suffix
        suffix = ""
    if suffix == "py":
        stderr.write("Warning: skipping '.py' file %s\n" % filename)
        return
    elif suffix != DA_SUFFIX:
        stderr.write("Warning: unknown suffix '%s' in filename '%s'\n" %
                      (suffix, filename))

    daast = daast_from_file(filename)
    psdstr = to_pseudo(daast)
    if outname is None:
        outname = purename + ".da"
    with open(outname, "w") as outfd:
        outfd.write(psdstr)
        stderr.write("Written pseudo code file %s.\n"% outname)

def dafile_to_pyfile(args):
    """Compiles a DistAlgo source file to Python file.

    'args' is the object generated by argparse from the command line
    arguments, and should contain the following properties:

    'filename' is the input DistAlgo source file. Optional property 'outname'
    specifies the file to write the result to. If 'outname' is None the
    filename is inferred by replacing the suffix of 'filename' with '.py'.

    """
    filename = args.infile
    outname = args.outfile
    purename, _, suffix = filename.rpartition(".")
    if len(purename) == 0:
        purename = suffix
        suffix = ""
    if suffix == "py":
        stderr.write("Warning: skipping '.py' file %s\n" % filename)
        return
    elif suffix != DA_SUFFIX:
        stderr.write("Warning: unknown suffix '%s' in filename '%s'\n" %
                      (suffix, filename))

    pyast = dafile_to_pyast(filename, args)
    if pyast is not None:
        pystr = to_source(pyast)
        if outname is None:
            outname = purename + ".py"
        with open(outname, "w") as outfd:
            outfd.write(pystr)
            stderr.write("Written compiled file %s.\n"% outname)
        return 0
    else:
        return 1

def check_python_version():
    if sys.version_info < (3, 4):
        stderr.write("DistAlgo requires Python version 3.4 or newer.\n")
        return False
    elif sys.version_info > (3, 5):
        stderr.write("Python 3.5 not yet supported.\n")
        return False
    else:
        return True

def dafile_to_incfiles(args):
    """Compiles a DistAlgo source file to Python file and generate an interface
    file for incrementalization.

    'args' is the object generated by argparse from the command line
    arguments, and should contain the following properties:

    'filename' is the input DistAlgo source file. Optional property 'outname'
    specifies the file to write the result to. If 'outname' is None the
    filename is inferred by replacing the suffix of 'filename' with '.py'.

    """

    filename = args.infile
    outname = args.outfile
    purename, _, suffix = filename.rpartition(".")
    if len(purename) == 0:
        purename = suffix
        suffix = ""
    if suffix == "py":
        stderr.write("Warning: skipping '.py' file %s\n" % filename)
        return
    elif suffix != DA_SUFFIX:
        stderr.write("Warning: unknown suffix '%s' in filename '%s'\n" %
                      (suffix, filename))
    daast = daast_from_file(filename, args)
    module_name = purename + "_inc"
    module_filename = module_name + ".py"
    if daast is not None:
        inc, ast = gen_inc_module(daast, args.__dict__,
                                  filename=module_filename)
        incstr = to_source(inc)
        aststr = to_source(ast)
        if outname is None:
            outname = purename + ".py"
        with open(outname, "w") as outfd:
            outfd.write(aststr)
            stderr.write("Written compiled file %s.\n"% outname)
        with open(module_filename, "w") as outfd:
            outfd.write(incstr)
            stderr.write("Written interface file %s.\n" % module_filename)
        return 0
    else:
        return 1

def main(argv=None):
    """Main entry point when invoking compiler module from command line.
    """
    if not check_python_version():
        return 2

    ap = argparse.ArgumentParser(description="DistAlgo compiler.")
    ap.add_argument('-o', help="Output file name.", dest="outfile")
    ap.add_argument('-L', help="Logging output level.",
                    dest="debug", default=None)
    # ap.add_argument('-p', help="Generate pseudo code instead of Python code.",
    #                 action='store_true', dest="genpsd")
    ap.add_argument('--full-event-pattern',
                    help="If set, use the 'full' format "
                    "(TYPE, (CLK, DST, SRC), MSG) for event patterns;"
                    "otherwise, use 'short' format (MSG, SRC)",
                    action='store_true')
    ap.add_argument('--enable-object-pattern',
                    help="Enable the use of object-style tuple pattern syntax:"
                    " Object(ARGS...); which is equivalent to "
                    "('Object', ARGS...)",
                    action='store_true')
    ap.add_argument('--enable-membertest-pattern',
                    help="Overloads the Python 'in' operator to support using "
                    "tuple patterns, e.g.: '(_a, 1, _) in S', which is "
                    "equivalent to 'some((_a, 1, _) in S)'",
                    action='store_true')
    ap.add_argument('--enable-iterator-pattern',
                    help="Overloads the Python 'for ... in ...' keywords to "
                    "support using tuple patterns in the target, "
                    "e.g.: '[b for (_a, 1, b) in S]', which is equivalent to "
                    "'[b for (var1, var2, b) in S if var1 == a if var2 == b]'",
                    action='store_true')
    ap.add_argument('-i',
                    help="Generate interface code for plugging"
                    " into incrementalizer.",
                    action='store_true', dest="geninc")
    ap.add_argument('--no-table1',
                    help="Disable table 1 quantification transformations. "
                    "Only used when '-i' is enabled.",
                    action='store_true', dest="notable1")
    ap.add_argument('--no-table2',
                    help="Disable table 2 quantification transformations. "
                    "Only used when '-i' is enabled.",
                    action='store_true', dest="notable2")
    ap.add_argument('--no-table3',
                    help="Disable table 3 quantification transformations. "
                    "Only used when '-i' is enabled.",
                    action='store_true', dest="notable3")
    ap.add_argument('--no-table4',
                    help="Disable table 4 quantification transformations. "
                    "Only used when '-i' is enabled.",
                    action='store_true', dest="notable4")
    ap.add_argument('--jb-style',
                    help="Generate Jon-friendly quantification transformations. "
                    "Only useful with '-i'.",
                    action='store_true', dest="jbstyle")
    ap.add_argument('--no-all-tables',
                    help="Disable all quantification transformations. "
                    "Only useful with '-i'.",
                    action='store_true', dest="noalltables")
    # ap.add_argument('--psdfile', help="Name of output pseudo code file.",
    #                 dest="psdfile", default=None)
    ap.add_argument('infile', metavar='SOURCEFILE', type=str,
                    help="DistAlgo input source file.")

    if argv is None:
        argv = sys.argv[1:]
    args = ap.parse_args(argv)
    if args.debug is not None:
        try:
            level = int(args.debug)
            if is_valid_debug_level(level):
                set_debug_level(level)
            else:
                raise ValueError()
        except ValueError:
            stderr.write("Invalid debugging level %s.\n" % str(args.debug))

    # if args.genpsd:
    #     res =dafile_to_pseudofile(args.infile, args.psdfile)
    if args.geninc:
        res = dafile_to_incfiles(args)
    else:
        res = dafile_to_pyfile(args)

    return res
