from __future__ import print_function

from jsparser import *
from utils import *
import re
from utils import *

#Note all white space sent to this module must be ' ' so no '\n'
REPL = {}

#PROBLEMS
# <<=, >>=, >>>=
# they are unusual so I will not fix that now. a++ +b works fine and a+++++b  (a++ + ++b) does not work even in V8
ASSIGNMENT_MATCH = '(?<!=|!|<|>)=(?!=)'


def unary_validitator(keyword, before, after):
    if keyword[-1] in IDENTIFIER_PART:
        if not after or after[0] in IDENTIFIER_PART:
            return False
        if before and before[-1] in IDENTIFIER_PART:  # I am not sure here...
            return False
    return True


def comb_validitator(keyword, before, after):
    if keyword == 'instanceof' or keyword == 'in':
        if before and before[-1] in IDENTIFIER_PART:
            return False
        elif after and after[0] in IDENTIFIER_PART:
            return False
    return True


def bracket_replace(code):
    new = ''
    for e in bracket_split(code, ['()', '[]'], False):
        if e[0] == '[':
            name = '#PYJSREPL' + str(len(REPL)) + '{'
            new += name
            REPL[name] = e
        elif e[0] == '(':  # can be a function call
            name = '@PYJSREPL' + str(len(REPL)) + '}'
            new += name
            REPL[name] = e
        else:
            new += e
    return new


class NodeVisitor:
    def __init__(self, code):
        self.code = code

    def rl(self, lis, op):
        """performs this operation on a list from *right to left*
        op must take 2 args
        a,b,c  => op(a, op(b, c))"""
        it = reversed(lis)
        res = trans(it.next())
        for e in it:
            e = trans(e)
            res = op(e, res)
        return res

    def lr(self, lis, op):
        """performs this operation on a list from *left to right*
        op must take 2 args
        a,b,c  => op(op(a, b), c)"""
        it = iter(lis)
        res = trans(it.next())
        for e in it:
            e = trans(e)
            res = op(res, e)
        return res

    def translate(self):
        """Translates outer operation and calls translate on inner operation.
           Returns fully translated code."""
        if not self.code:
            return ''
        new = bracket_replace(self.code)
        #Check comma operator:
        cand = new.split(',')  #every comma in new must be an operator
        if len(cand) > 1:  #LR
            return self.lr(cand, js_comma)
        #Check = operator:
        # dont split at != or !== or == or === or <= or >=
        #note <<=, >>= or this >>> will NOT be supported
        # maybe I will change my mind later
        # Find this crappy ?:
        if '?' in new:
            cond_ind = new.find('?')
            tenary_start = 0
            for ass in re.finditer(ASSIGNMENT_MATCH, new):
                cand = ass.span()[1]
                if cand < cond_ind:
                    tenary_start = cand
                else:
                    break
            actual_tenary = new[tenary_start:]
            spl = ''.join(split_at_any(new, [':', '?'], translate=trans))
            tenary_translation = transform_crap(spl)
            assignment = new[:tenary_start] + ' PyJsConstantTENARY'
            return trans(assignment).replace('PyJsConstantTENARY',
                                             tenary_translation)
        cand = list(split_at_single(new, '=', ['!', '=', '<', '>'], ['=']))
        if len(cand) > 1:  # RL
            it = reversed(cand)
            res = trans(it.next())
            for e in it:
                e = e.strip()
                if not e:
                    raise SyntaxError('Missing left-hand in assignment!')
                op = ''
                if e[-2:] in OP_METHODS:
                    op = ',' + e[-2:].__repr__()
                    e = e[:-2]
                elif e[-1:] in OP_METHODS:
                    op = ',' + e[-1].__repr__()
                    e = e[:-1]
                e = trans(e)
                #Now replace last get method with put and change args
                c = list(bracket_split(e, ['()']))
                beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip(
                )  #strips just to make sure... I will remove it later
                if beg[-4:] != '.get':
                    raise SyntaxError('Invalid left-hand side in assignment')
                beg = beg[0:-3] + 'put'
                arglist = arglist[0:-1] + ', ' + res + op + ')'
                res = beg + arglist
            return res
        #Now check remaining 2 arg operators that are not handled by python
        #They all have Left to Right (LR) associativity
        order = [OR, AND, BOR, BXOR, BAND, EQS, COMPS, BSHIFTS, ADDS, MULTS]
        # actually we dont need OR and AND because they can be handled easier. But just for fun
        dangerous = ['<', '>']
        for typ in order:
            #we have to use special method for ADDS since they can be also unary operation +/++ or -/-- FUCK
            if '+' in typ:
                cand = list(split_add_ops(new))
            else:
                #dont translate. cant start or end on dangerous op.
                cand = list(
                    split_at_any(
                        new,
                        typ.keys(),
                        False,
                        dangerous,
                        dangerous,
                        validitate=comb_validitator))
            if not len(cand) > 1:
                continue
            n = 1
            res = trans(cand[0])
            if not res:
                raise SyntaxError("Missing operand!")
            while n < len(cand):
                e = cand[n]
                if not e:
                    raise SyntaxError("Missing operand!")
                if n % 2:
                    op = typ[e]
                else:
                    res = op(res, trans(e))
                n += 1
            return res
        #Now replace unary operators - only they are left
        cand = list(
            split_at_any(
                new, UNARY.keys(), False, validitate=unary_validitator))
        if len(cand) > 1:  #contains unary operators
            if '++' in cand or '--' in cand:  #it cant contain both ++ and --
                if '--' in cand:
                    op = '--'
                    meths = js_post_dec, js_pre_dec
                else:
                    op = '++'
                    meths = js_post_inc, js_pre_inc
                pos = cand.index(op)
                if cand[pos - 1].strip():  # post increment
                    a = cand[pos - 1]
                    meth = meths[0]
                elif cand[pos + 1].strip():  #pre increment
                    a = cand[pos + 1]
                    meth = meths[1]
                else:
                    raise SyntaxError('Invalid use of ++ operator')
                if cand[pos + 2:]:
                    raise SyntaxError('Too many operands')
                operand = meth(trans(a))
                cand = cand[:pos - 1]
            # now last cand should be operand and every other odd element should be empty
            else:
                operand = trans(cand[-1])
                del cand[-1]
            for i, e in enumerate(reversed(cand)):
                if i % 2:
                    if e.strip():
                        raise SyntaxError('Too many operands')
                else:
                    operand = UNARY[e](operand)
            return operand
        #Replace brackets
        if new[0] == '@' or new[0] == '#':
            if len(
                    list(bracket_split(new, ('#{', '@}')))
            ) == 1:  # we have only one bracket, otherwise pseudobracket like @@....
                assert new in REPL
                if new[0] == '#':
                    raise SyntaxError(
                        '[] cant be used as brackets! Use () instead.')
                return '(' + trans(REPL[new][1:-1]) + ')'
        #Replace function calls and prop getters
        # 'now' must be a reference like: a or b.c.d but it can have also calls or getters ( for example a["b"](3))
        #From here @@ means a function call and ## means get operation (note they dont have to present)
        it = bracket_split(new, ('#{', '@}'))
        res = []
        for e in it:
            if e[0] != '#' and e[0] != '@':
                res += [x.strip() for x in e.split('.')]
            else:
                res += [e.strip()]
        # res[0] can be inside @@ (name)...
        res = filter(lambda x: x, res)
        if is_internal(res[0]):
            out = res[0]
        elif res[0][0] in {'#', '@'}:
            out = '(' + trans(REPL[res[0]][1:-1]) + ')'
        elif is_valid_lval(
                res[0]) or res[0] in {'this', 'false', 'true', 'null'}:
            out = 'var.get(' + res[0].__repr__() + ')'
        else:
            if is_reserved(res[0]):
                raise SyntaxError('Unexpected reserved word: "%s"' % res[0])
            raise SyntaxError('Invalid identifier: "%s"' % res[0])
        if len(res) == 1:
            return out
        n = 1
        while n < len(res):  #now every func call is a prop call
            e = res[n]
            if e[0] == '@':  # direct call
                out += trans_args(REPL[e])
                n += 1
                continue
            args = False  #assume not prop call
            if n + 1 < len(res) and res[n + 1][0] == '@':  #prop call
                args = trans_args(REPL[res[n + 1]])[1:]
                if args != ')':
                    args = ',' + args
            if e[0] == '#':
                prop = trans(REPL[e][1:-1])
            else:
                if not is_lval(e):
                    raise SyntaxError('Invalid identifier: "%s"' % e)
                prop = e.__repr__()
            if args:  # prop call
                n += 1
                out += '.callprop(' + prop + args
            else:  #prop get
                out += '.get(' + prop + ')'
            n += 1
        return out


def js_comma(a, b):
    return 'PyJsComma(' + a + ',' + b + ')'


def js_or(a, b):
    return '(' + a + ' or ' + b + ')'


def js_bor(a, b):
    return '(' + a + '|' + b + ')'


def js_bxor(a, b):
    return '(' + a + '^' + b + ')'


def js_band(a, b):
    return '(' + a + '&' + b + ')'


def js_and(a, b):
    return '(' + a + ' and ' + b + ')'


def js_strict_eq(a, b):

    return 'PyJsStrictEq(' + a + ',' + b + ')'


def js_strict_neq(a, b):
    return 'PyJsStrictNeq(' + a + ',' + b + ')'


#Not handled by python in the same way like JS. For example 2==2==True returns false.
# In JS above would return true so we need brackets.
def js_abstract_eq(a, b):
    return '(' + a + '==' + b + ')'


#just like ==
def js_abstract_neq(a, b):
    return '(' + a + '!=' + b + ')'


def js_lt(a, b):
    return '(' + a + '<' + b + ')'


def js_le(a, b):
    return '(' + a + '<=' + b + ')'


def js_ge(a, b):
    return '(' + a + '>=' + b + ')'


def js_gt(a, b):
    return '(' + a + '>' + b + ')'


def js_in(a, b):
    return b + '.contains(' + a + ')'


def js_instanceof(a, b):
    return a + '.instanceof(' + b + ')'


def js_lshift(a, b):
    return '(' + a + '<<' + b + ')'


def js_rshift(a, b):
    return '(' + a + '>>' + b + ')'


def js_shit(a, b):
    return 'PyJsBshift(' + a + ',' + b + ')'


def js_add(
        a,
        b):  # To simplify later process of converting unary operators + and ++
    return '(%s+%s)' % (a, b)


def js_sub(a, b):  # To simplify
    return '(%s-%s)' % (a, b)


def js_mul(a, b):
    return '(' + a + '*' + b + ')'


def js_div(a, b):
    return '(' + a + '/' + b + ')'


def js_mod(a, b):
    return '(' + a + '%' + b + ')'


def js_typeof(a):
    cand = list(bracket_split(a, ('()', )))
    if len(cand) == 2 and cand[0] == 'var.get':
        return cand[0] + cand[1][:-1] + ',throw=False).typeof()'
    return a + '.typeof()'


def js_void(a):
    return '(' + a + ')'


def js_new(a):
    cands = list(bracket_split(a, ('()', )))
    lim = len(cands)
    if lim < 2:
        return a + '.create()'
    n = 0
    while n < lim:
        c = cands[n]
        if c[0] == '(':
            if cands[n - 1].endswith(
                    '.get') and n + 1 >= lim:  # last get operation.
                return a + '.create()'
            elif cands[n - 1][0] == '(':
                return ''.join(cands[:n]) + '.create' + c + ''.join(
                    cands[n + 1:])
            elif cands[n - 1] == '.callprop':
                beg = ''.join(cands[:n - 1])
                args = argsplit(c[1:-1], ',')
                prop = args[0]
                new_args = ','.join(args[1:])
                create = '.get(%s).create(%s)' % (prop, new_args)
                return beg + create + ''.join(cands[n + 1:])
        n += 1
    return a + '.create()'


def js_delete(a):
    #replace last get with delete.
    c = list(bracket_split(a, ['()']))
    beg, arglist = ''.join(c[:-1]).strip(), c[-1].strip(
    )  #strips just to make sure... I will remove it later
    if beg[-4:] != '.get':
        raise SyntaxError('Invalid delete operation')
    return beg[:-3] + 'delete' + arglist


def js_neg(a):
    return '(-' + a + ')'


def js_pos(a):
    return '(+' + a + ')'


def js_inv(a):
    return '(~' + a + ')'


def js_not(a):
    return a + '.neg()'


def postfix(a, inc, post):
    bra = list(bracket_split(a, ('()', )))
    meth = bra[-2]
    if not meth.endswith('get'):
        raise SyntaxError('Invalid ++ or -- operation.')
    bra[-2] = bra[-2][:-3] + 'put'
    bra[-1] = '(%s,%s%sJs(1))' % (bra[-1][1:-1], a, '+' if inc else '-')
    res = ''.join(bra)
    return res if not post else '(%s%sJs(1))' % (res, '-' if inc else '+')


def js_pre_inc(a):
    return postfix(a, True, False)


def js_post_inc(a):
    return postfix(a, True, True)


def js_pre_dec(a):
    return postfix(a, False, False)


def js_post_dec(a):
    return postfix(a, False, True)


OR = {'||': js_or}
AND = {'&&': js_and}
BOR = {'|': js_bor}
BXOR = {'^': js_bxor}
BAND = {'&': js_band}

EQS = {
    '===': js_strict_eq,
    '!==': js_strict_neq,
    '==': js_abstract_eq,  # we need == and != too. Read a note above method
    '!=': js_abstract_neq
}

#Since JS does not have chained comparisons we need to implement all cmp methods.
COMPS = {
    '<': js_lt,
    '<=': js_le,
    '>=': js_ge,
    '>': js_gt,
    'instanceof': js_instanceof,  #todo change to validitate
    'in': js_in
}

BSHIFTS = {'<<': js_lshift, '>>': js_rshift, '>>>': js_shit}

ADDS = {'+': js_add, '-': js_sub}

MULTS = {'*': js_mul, '/': js_div, '%': js_mod}

#Note they dont contain ++ and -- methods because they both have 2 different methods
# correct method will be found automatically in translate function
UNARY = {
    'typeof': js_typeof,
    'void': js_void,
    'new': js_new,
    'delete': js_delete,
    '!': js_not,
    '-': js_neg,
    '+': js_pos,
    '~': js_inv,
    '++': None,
    '--': None
}


def transform_crap(code):  #needs some more tests
    """Transforms this ?: crap into if else python syntax"""
    ind = code.rfind('?')
    if ind == -1:
        return code
    sep = code.find(':', ind)
    if sep == -1:
        raise SyntaxError('Invalid ?: syntax (probably missing ":" )')
    beg = max(code.rfind(':', 0, ind), code.find('?', 0, ind)) + 1
    end = code.find(':', sep + 1)
    end = len(code) if end == -1 else end
    formula = '(' + code[ind + 1:sep] + ' if ' + code[
        beg:ind] + ' else ' + code[sep + 1:end] + ')'
    return transform_crap(code[:beg] + formula + code[end:])


from code import InteractiveConsole

#e = InteractiveConsole(globals()).interact()
import traceback


def trans(code):
    return NodeVisitor(code.strip()).translate().strip()


#todo finish this trans args
def trans_args(code):
    new = bracket_replace(code.strip()[1:-1])
    args = ','.join(trans(e) for e in new.split(','))
    return '(%s)' % args


EXP = 0


def exp_translator(code):
    global REPL, EXP
    EXP += 1
    REPL = {}
    #print EXP, code
    code = code.replace('\n', ' ')
    assert '@' not in code
    assert ';' not in code
    assert '#' not in code
    #if not code.strip(): #?
    #    return 'var.get("undefined")'
    try:
        return trans(code)
    except:
        #print '\n\ntrans failed on \n\n' + code
        #raw_input('\n\npress enter')
        raise


if __name__ == '__main__':
    #print 'Here',  trans('(eee   )  .   ii  [  PyJsMarker   ]  [   jkj  ]  (  j  ,   j  )  .
    #    jiji   (h  ,  ji  ,  i)(non  )(  )()()()')
    for e in xrange(3):
        print(exp_translator('jk = kk.ik++'))
    #First line translated with PyJs:  PyJsStrictEq(PyJsAdd((Js(100)*Js(50)),Js(30)), Js("5030")), yay!
    print(exp_translator('delete a.f'))
