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')

