#!/usr/bin/env python3

import sys
import ast

class Box(object):
    pass

# TODO check whether a function is recursive or not (also mutual recursion)
# TODO use node transformer
# TODO symbol name mappings? (at least True and False ?)
# TODO ctx = Load()|Store()
# TODO auto-degrate to subroutine for tuple and list returns
# TODO determine types by type inference
# TODO emit forward declarations for functions (not subroutines)
# TODO emit CALL statements for subroutines (not functions) - and be sure to disconnect them from the rest of the expression
def g(n):
    binaryOps = [ast.Gt, ast.Mod, ast.Add, ast.Sub, ast.Mult, ast.Div]
    indentation = 0
    bLineEmpty = True
    bindings = {}
    def nl():
        nonlocal indentation
        nonlocal bLineEmpty
        if not bLineEmpty:
            sys.stdout.write("\n%*s" % (indentation * 2, "", ))
            bLineEmpty = True
    def write(*args):
        nonlocal indentation
        nonlocal bLineEmpty
        sys.stdout.write(" ".join(map(str, args)))
        bLineEmpty = False
    def indent():
        nonlocal indentation
        nonlocal bLineEmpty
        indentation += 1
        if bLineEmpty:
            sys.stdout.write("%*s" % (1 * 2, "", ))
    def dedent():
        nonlocal indentation
        nonlocal bLineEmpty
        assert(indentation > 0)
        indentation -= 1
        bLineEmpty = False
    def nlindent():
        indent()
        nl()
    def nldedent():
        dedent()
        nl()
    def writeop(n):
        opNames = {
            ast.Add: " + ",
            ast.Mod: " % ",
            ast.Mult: "*",
            ast.Sub: " - ",
            ast.Gt: " .GT. ",
            ast.Lt: " .LT. ",
            ast.LtE: " .LE. ",
            ast.GtE: " .GE. ",
            ast.Eq: " .EQ. ",
            ast.Is: " ??? ", # XXX
            ast.IsNot: " ??? ", # XXX
            ast.Not: " .NOT. ", # unary
            ast.And: " .AND. ", # unary
            ast.Or: " .OR. ", # unary
            ast.USub: "-",
            ast.NotIn: " .NOTIN. ", # XXX
            ast.In: " .IN. ", # XXX
            ast.LShift: " .SHL. ",
            ast.BitOr: " .BITOR. ",
        }
        write(opNames[n.__class__])
    def introduce(name, value = None):
        if name not in bindings:
            bindings[name] = value or Box()
        return(name)
    def printFrame():
        nonlocal bindings
        for name, box in bindings.items():
            write("???*16, intent(in) :: %s" % (name, ))
            nl()
    def enterFrame():
        nonlocal bindings
        bindings = {}
    def leaveFrame():
        nonlocal bindings
        bindings = {}
    def findVariableReferences(n, d, visited):
        if id(n) in visited:
            return
        visited.add(id(n))
        if isinstance(n, list):
            for item in n:
                findVariableReferences(item, d, visited)
        elif isinstance(n, str) or isinstance(n, int) or n is None or isinstance(n, bytes):
            pass
        else:
            # id, value, attr, ctx
            for name, value in ast.iter_fields(n):
                findVariableReferences(value, d, visited)
            #    #visit(value)
            for c in ast.iter_child_nodes(n):
                findVariableReferences(c, d, visited)
    def visit(n):
        if isinstance(n, list):
            bFirst = True
            for i in n:
                if bFirst:
                    bFirst = False
                else:
                    write(", ")
                visit(i)
        elif isinstance(n, str):
            write("\"%s\"" % (n.replace('\\', '\\\\').replace("\n", "\\n").replace('"', '\\"'), ))
            # TODO ast.Assign targets
            # TODO ast.Num n
            # TODO ast.Store
            # TODO int
        elif isinstance(n, bytes):
            write("???") # \"%s\"" % (n.replace('\\', '\\\\').replace("\n", "\\n").replace('"', '\\"'), ))
            # TODO ast.Assign targets
            # TODO ast.Num n
            # TODO ast.Store
            # TODO int
        elif isinstance(n, ast.Assign):
            # TODO decomposition
            assert(len(n.targets) == 1)
            t = n.targets[0]
            visit(t)
            write(" = ")
            visit(n.value)
        elif isinstance(n, ast.AugAssign):
            return visit(ast.Assign([n.target], ast.BinOp(n.target, n.op, n.value)))
        elif isinstance(n, ast.Num):
            return visit(n.n)
        elif isinstance(n, ast.Str):
            return visit(n.s)
        elif isinstance(n, ast.Bytes):
            return visit(n.s)
        elif isinstance(n, ast.Name):
            write(n.id)
        elif isinstance(n, ast.List):
            # TODO non-inline these
            write("DATA /")
            visit(n.elts)
            write("/")
            nl()
            # FIXME n.ctx
        elif isinstance(n, tuple):
            # FIXME handle tuples
            return [visit(i) for i in n]
            #write("str", n.str)
        elif isinstance(n, ast.Tuple):
            return visit(n.elts)
            # TODO n.ctx
        elif isinstance(n, int):
            write(n)
        elif n is None:
            write(n) # FIXME
        elif isinstance(n, ast.Import):
            n.names # FIXME
        elif isinstance(n, ast.Continue):
            write("CYCLE")
            nl()
        elif isinstance(n, ast.Break):
            write("EXIT")
            nl()
        elif isinstance(n, ast.While):
            write("DO WHILE (")
            visit(n.test)
            write(")")
            indent()
            for item in n.body:
                nl()
                visit(item)
            nldedent()
            write("END DO")
            nl()
            # FIXME n.orelse
        elif isinstance(n, ast.Yield):
            write("??? ")
            write(n.value)
        elif isinstance(n, ast.Call):
            visit(n.func)
            write("(")
            if not isinstance(n.args, ast.arguments): # ???
                visit(ast.arguments(args = n.args))
            else:
                visit(n.args)
            write(")")
        elif isinstance(n, ast.Compare):
            visit(n.left) #<_ast.Name object at 0x7fc6872d2490>
            comparators = n.ops # huh?
            ops = n.comparators
            assert(len(comparators) == 1)
            comparator = comparators[0]
            for op in ops:
                writeop(comparator)
                visit(op)
            #comparators [<_ast.Name object at 0x7fc6872d24d0>]
            #visit(n.right)
        elif isinstance(n, ast.Return):
            write("BEGIN")
            nlindent()
            write("result = ")
            visit(n.value)
            nl()
            write("EXIT") # FIXME exit the function!
            nldedent()
            write("END")
            nl()
        elif isinstance(n, ast.Attribute):
            visit(n.value)
            write(".")
            visit(ast.Name(n.attr, n.ctx))
            #visit(n.ctx)
            #elif isinstance(n, ast.Load): # ctx
            #write("WTF")
        elif isinstance(n, ast.Module):
            for child in n.body:
                nl()
                visit(child)
        elif isinstance(n, ast.FunctionDef):
            write("FUNCTION ")
            write(n.name)
            write("(")
            visit(n.args)
            write(") RESULT (result)")
            nlindent()
            enterFrame()
            for arg in n.args.args:
                introduce(arg.arg)
            introduce("result")
            d = []
            findVariableReferences(n.body, d, set())
            for item in d:
                introduce(item)
            printFrame()
            for item in n.body:
                nl()
                visit(item)
            nldedent()
            leaveFrame()
            write("END")
            nl()
            # ignore decorator_list []
            # FIXME visit(n.returns)
        elif isinstance(n, ast.arguments):
            bFirst = True
            for i in n.args:
                if bFirst:
                    bFirst = False
                else:
                    write(", ")
                visit(i)
            #FIXME n.vararg None
            #FIXME n.varargannotation None
            #FIXME n.kwonlyargs []
            #FIXME n.kwarg None
            #FIXME n.kwargannotation None
            #FIXME n.defaults []
            #FIXME n.kw_defaults []
        elif isinstance(n, ast.arg):
            write(n.arg)
        elif isinstance(n, ast.Nonlocal):
            #FIXME n.names
            pass
        elif isinstance(n, ast.Assert):
            write("!assert(")
            visit(n.test)
            write(", ")
            visit(n.msg or "")
            write(")")
            nl()
        elif isinstance(n, ast.BinOp):
            #any([isinstance(n, i) for i in binaryOps]):
            visit(n.left)
            writeop(n.op) # add etc
            visit(n.right)
        elif isinstance(n, ast.Dict):
            #visit(n.keys)
            #visit(n.values)
            write("???")
        elif isinstance(n, ast.Expr):
            return visit(n.value)
        elif isinstance(n, ast.If):
            write("IF (")
            visit(n.test)
            write(") THEN")
            indent()
            for item in n.body:
                nl()
                visit(item)
            nldedent()
            write("ELSE ")
            indent()
            for item in n.orelse:
                nl()
                visit(item)
            #visit(n.orelse)
            nldedent()
            write("END IF")
            nl()
        elif isinstance(n, ast.Subscript):
            visit(n.value)
            write("(")
            if isinstance(n.slice, ast.Index):
                visit(n.slice.value)
            else:
                write("???") # FIXME
            write(")")
            # FIXME n.ctx
        elif isinstance(n, ast.ListComp):
            write("???")
        elif isinstance(n, ast.UnaryOp):
            write("(")
            writeop(n.op)
            visit(n.operand)
            write(")")
        elif isinstance(n, ast.For):
            write("DO ")
            visit(introduce(n.target))
            write(" = ")
            visit(n.iter)
            indent()
            for item in n.body:
                nl()
                visit(item)
            nldedent()
            write("END DO")
            nl()
            if n.orelse: # guess this isn't really supported by Fortran.
                write("ELSE ")
                nlindent()
                for item in n.orelse:
                    nl()
                    visit(item)
                nldedent()
        elif isinstance(n, ast.Pass):
            pass # TODO empty statement/expr
        elif isinstance(n, ast.ClassDef):
            pass # haha no. 
        elif isinstance(n, ast.BoolOp):
            bFirst = True
            for op in n.values:
                if bFirst:
                    bFirst = False
                else:
                    writeop(n.op)
                visit(op)
        elif isinstance(n, ast.IfExp):
            write("(IF (")
            visit(n.test)
            write(") THEN ")
            visit(n.body)
            write(" ELSE ")
            visit(n.orelse)
            write(")")
        elif isinstance(n, ast.Try):
            for item in n.body:
                nl()
                visit(item)
            # FIXME handlers orelse finalbody
            for item in n.finalbody:
                nl()
                visit(item)
            pass
        else:
            write(n)
            # id, value, attr, ctx
            for name, value in ast.iter_fields(n):
                print(name, value)
                #visit(value)
            sys.exit(1)
            for c in ast.iter_child_nodes(n):
                return visit(c)
            write("END", n)
            sys.exit(1)
    result = visit(n)
    nl()
    return(result)
print("! useful types: COMPLEX*16, DOUBLE PRECISION, INTEGER")
print("! useful conversion functions: DCMPLX(a, b), DBLE(z), CONJ(z), ABS(z)")
print("! arrays: v(0:999)")
print("! open arrays: v(0:*)")
m = ast.parse(open(sys.argv[-1], "r").read())
g(m)
# TODO FunctionDef ClassDef Delete Print For While If With TryExcept TryFinally Assert Import ImportFrom Exec Expr Pass Return Raise Break Continue
# TODO Yield Lambda(args,body)
