from pyjsparser import PyJsParser

REGEXP_CONVERTER = PyJsParser()

def indent(lines, ind=4):
    return ind*' '+lines.replace('\n', '\n'+ind*' ').rstrip(' ')

def inject_before_lval(source, lval, code):
    if source.count(lval)>1:
        print
        print lval
        raise RuntimeError('To many lvals (%s)' % lval)
    elif not source.count(lval):
        print
        print lval
        assert lval not in source
        raise RuntimeError('No lval found "%s"' % lval)
    end = source.index(lval)
    inj = source.rfind('\n', 0, end)
    ind = inj
    while source[ind+1]==' ':
        ind+=1
    ind -= inj
    return source[:inj+1]+ indent(code, ind) + source[inj+1:]


def get_continue_label(label):
    return CONTINUE_LABEL%label.encode('hex')

def get_break_label(label):
    return BREAK_LABEL%label.encode('hex')


def is_valid_py_name(name):
    try:
        compile(name, 'a','exec')
    except:
        return False
    return True

def indent(lines, ind=4):
    return ind*' '+lines.replace('\n', '\n'+ind*' ').rstrip(' ')

def compose_regex(val):
    reg, flags = val
    reg = REGEXP_CONVERTER._unescape_string(reg)
    return u'/%s/%s' % (reg, flags)

def float_repr(f):
    if int(f)==f:
        return repr(int(f))
    return repr(f)

def argsplit(args, sep=','):
    """used to split JS args (it is not that simple as it seems because
       sep can be inside brackets).

       pass args *without* brackets!

       Used also to parse array and object elements, and more"""
    parsed_len  = 0
    last = 0
    splits = []
    for e in bracket_split(args, brackets=['()', '[]', '{}']):
        if e[0] not in ['(', '[', '{']:
            for i, char in enumerate(e):
                if char==sep:
                    splits.append(args[last:parsed_len+i])
                    last = parsed_len + i + 1
        parsed_len += len(e)
    splits.append(args[last:])
    return splits


def bracket_split(source, brackets=('()','{}','[]'), strip=False):
    """DOES NOT RETURN EMPTY STRINGS (can only return empty bracket content if strip=True)"""
    starts = [e[0] for e in brackets]
    in_bracket = 0
    n = 0
    last = 0
    while n<len(source):
        e = source[n]
        if not in_bracket and e in starts:
            in_bracket = 1
            start = n
            b_start, b_end = brackets[starts.index(e)]
        elif in_bracket:
            if e==b_start:
                in_bracket += 1
            elif e==b_end:
                in_bracket -= 1
                if not in_bracket:
                    if source[last:start]:
                        yield source[last:start]
                    last = n+1
                    yield source[start+strip:n+1-strip]
        n+=1
    if source[last:]:
        yield source[last:]


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):
    # eval and return undefined
    return 'PyJsComma(%s, Js(None))' % 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':
        print a
        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 js_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 js_postfix(a, True, False)

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

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

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



CONTINUE_LABEL = 'JS_CONTINUE_LABEL_%s'
BREAK_LABEL = 'JS_BREAK_LABEL_%s'
PREPARE = '''HOLDER = var.own.get(NAME)\nvar.force_own_put(NAME, PyExceptionToJs(PyJsTempException))\n'''
RESTORE = '''if HOLDER is not None:\n    var.own[NAME] = HOLDER\nelse:\n    del var.own[NAME]\ndel HOLDER\n'''
TRY_CATCH = '''%stry:\nBLOCKfinally:\n%s''' % (PREPARE, indent(RESTORE))



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}
BINARY = {}
BINARY.update(ADDS)
BINARY.update(MULTS)
BINARY.update(BSHIFTS)
BINARY.update(COMPS)
BINARY.update(EQS)
BINARY.update(BAND)
BINARY.update(BXOR)
BINARY.update(BOR)
BINARY.update(AND)
BINARY.update(OR)
#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
         }