"""
**********************************************************************

  A RapydScript to JavaScript compiler.
  https://github.com/atsepkov/RapydScript

  -------------------------------- (C) ---------------------------------

                       Author: Alexander Tsepkov
                         <atsepkov@pyjeon.com>
                         http://www.pyjeon.com

  Distributed under BSD license:
    Copyright 2013 (c) Alexander Tsepkov <atsepkov@pyjeon.com>

 **********************************************************************
"""

RAPYD_PREFIX = "ՐՏ"     # previously _$rapyd$_

def slice(a, start):
    return Array.prototype.slice.call(a, start or 0)

def member(name, array):
    for i in range(array.length-1, -1, -1):
        if array[i] is name:
            return True
    return False

def find_if(func, array):
    for i in range(len(array)):
        if func(array[i]):
            return array[i]

def repeat_string(str_, i):
    if i <= 0:
        return ""
    if i is 1:
        return str_
    d = repeat_string(str_, i >> 1)
    d += d
    if i & 1:
        d += str_
    return d

def DefaultsError(msg, defs):
    this.msg = msg
    this.defs = defs

class ImportError(Error):
    def __init__(self, message, filename, readfile_error):
        # readfile_error - optional container for underlying error while reading module file
        # it is may be useful when custom `readfile` is used
        self.message = message
        self.filename = filename
        self.readfile_error = readfile_error

class ParseError(Error):
    def __init__(self, message, line, col, pos, is_eof, filename):
        self.message = message
        self.line = line
        self.col = col
        self.pos = pos
        self.stack = Error().stack
        self.is_eof = is_eof
        self.filename = filename

    def toString(self):
        return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack

def defaults(args, defs, croak):
    # Takes args as a user-supplied set of options, applies defaults from defs for any missing options, optionally raises an
    # error if property has been passed in args that doesn't exist in defaults.
    if args is True:
        args = {}
    ret = args or {}
    if croak:
        for key in ret:
            if key not in defs:
                raise new DefaultsError("`" + key + "` is not a supported option", defs)

    for key in defs:
        ret[key] = args and key in args ? args[key] : defs[key]
    return ret

def merge(obj, ext):
    # merge ext into obj, clobbering baselib if values overlap
    for key in ext:
        obj[key] = ext[key]
    return obj

def noop(): pass

MAP = (def():
    def MAP(a, f, backwards):
        ret = []
        top = []

        def doit():
            val = f(a[i], i)
            is_last = isinstance(val, Last)
            if is_last:
                val = val.v
            if isinstance(val, AtTop):
                val = val.v
                if isinstance(val, Splice):
                    top.push.apply(top, (backwards ? val.v.slice().reverse() : val.v))
                else:
                    top.push(val)
            elif val is not skip:
                if isinstance(val, Splice):
                    ret.push.apply(ret, (backwards ? val.v.slice().reverse() : val.v))
                else:
                    ret.push(val)
            return is_last

        if Array.isArray(a):
            if backwards:
                for i in range(a.length-1, -1, -1):
                    if doit():
                        break
                ret.reverse()
                top.reverse()
            else:
                for i in range(len(a)):
                    if doit():
                        break
        else:
            for i in dir(a):
                if a.hasOwnProperty(i):
                    if doit():
                        break
        return top.concat(ret)

    MAP.at_top = def(val):
        return new AtTop(val)
    MAP.splice = def(val):
        return new Splice(val)
    MAP.last = def(val):
        return new Last(val)

    skip = MAP.skip = {}
    def AtTop(val):
        this.v = val

    def Splice(val):
        this.v = val

    def Last(val):
        this.v = val

    return MAP
)()

def push_uniq(array, el):
    # push el into array if it doesn't already exist there
    if el not in array:
        array.push(el)

def string_template(text, props):
    return text.replace(/\{(.+?)\}/g, def(str_, p):
        return props[p]
    )

def remove(array, el):
    # remove all occurences of el from array
    for idx in range(array.length-1, -1, -1):
        if array[idx] is el:
            array.splice(i, 1)

def mergeSort(array, cmp):
    if array.length < 2:
        return array.slice()

    def merge(a, b):
        r = []
        ai = 0
        bi = 0
        i = 0
        while ai < a.length and bi < b.length:
            if cmp(a[ai], b[bi]) <= 0:
                r[i] = a[ai]
                ai += 1
            else:
                r[i] = b[bi]
                bi += 1
            i += 1
        if ai < a.length:
            r.push.apply(r, a.slice(ai))
        if bi < b.length:
            r.push.apply(r, b.slice(bi))
        return r

    def _ms(a):
        if a.length <= 1:
            return a
        m = Math.floor(a.length / 2)
        left = a.slice(0, m)
        right = a.slice(m)
        left = _ms(left)
        right = _ms(right)
        return merge(left, right)
    return _ms(array)

def set_difference(a, b):
    return a.filter(def(el):
        return el not in b
    )

def set_intersection(a, b):
    return a.filter(def(el):
        return el in b
    )

# this function is taken from Acorn [1], written by Marijn Haverbeke
# [1] https://github.com/marijnh/acorn
def makePredicate(words):
    if not Array.isArray(words):
        words = words.split(" ")

    f = ""
    cats = []
    for i in range(len(words)):
        skip = False
        for j in range(len(cats)):
            if cats[j][0].length is words[i].length:
                cats[j].push(words[i])
                skip = True
                break
        if not skip:
            cats.push([ words[i] ])

    def compareTo(arr):
        nonlocal f
        if arr.length is 1:
            return f += "return str === " + JSON.stringify(arr[0]) + ";"

        f += "switch(str){"
        for i in range(len(arr)):
            f += "case " + JSON.stringify(arr[i]) + ":"
        f += "return true}return false;"

    # When there are more than three length categories, an outer
    # switch first dispatches on the lengths, to save on comparisons.
    if cats.length > 3:
        cats.sort(def(a, b):
            return b.length - a.length
        )
        f += "switch(str.length){"
        for i in range(len(cats)):
            cat = cats[i]
            f += "case " + cat[0].length + ":"
            compareTo(cat)
        f += "}"
    else:
        compareTo(words)
    return Function("str", f)

def Dictionary():
    this._values = Object.create(None)
    this._size = 0

Dictionary.prototype = {
    set: def(key, val):
        if not this.has(key):
            this._size += 1
        this._values["$" + key] = val
        return this
    ,
    add: def(key, val):
        if this.has(key):
            this.get(key).push(val)
        else:
            this.set(key, [ val ])
        return this
    ,
    get: def(key):
        return this._values["$" + key]
    ,
    del_: def(key):
        if this.has(key):
            this._size -= 1
            del this._values["$" + key]
        return this
    ,
    has: def(key):
        return "$" + key in this._values
    ,
    each: def(f):
        for i in dir(this._values):
            f(this._values[i], i.substr(1))
    ,
    size: def():
        return this._size
    ,
    map: def(f):
        ret = []
        for i in dir(this._values):
            ret.push(f(this._values[i], i.substr(1)))
        return ret
}


# color codes used by several modules of the compiler

colors = ['red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']
def ansi(code):
    code = code or 0
    return '\u001b[' + code + 'm'

def colored(string, color, bold):
    prefix = []
    if (bold): prefix.push(ansi(1))
    if (color): prefix.push(ansi(colors.indexOf(color) + 31))
    return prefix.join('') + string + ansi(0)

