from __future__ import unicode_literals

from base import Scope
from func_utils import *
from conversions import *
import six
from prototypes.jsboolean import BooleanPrototype
from prototypes.jserror import ErrorPrototype
from prototypes.jsfunction import FunctionPrototype
from prototypes.jsnumber import NumberPrototype
from prototypes.jsobject import ObjectPrototype
from prototypes.jsregexp import RegExpPrototype
from prototypes.jsstring import StringPrototype
from prototypes.jsarray import ArrayPrototype
import prototypes.jsjson as jsjson
import prototypes.jsutils as jsutils

from constructors import jsnumber
from constructors import jsstring
from constructors import jsarray
from constructors import jsboolean
from constructors import jsregexp
from constructors import jsmath
from constructors import jsobject
from constructors import jsfunction
from constructors import jsconsole


def fill_proto(proto, proto_class, space):
    for i in dir(proto_class):
        e = getattr(proto_class, i)
        if six.PY2:
            if hasattr(e, '__func__'):
                meth = e.__func__
            else:
                continue
        else:
            if hasattr(e, '__call__') and not i.startswith('__'):
                meth = e
            else:
                continue
        meth_name = meth.__name__.strip('_')  # RexExp._exec -> RegExp.exec
        js_meth = space.NewFunction(meth, space.ctx, (), meth_name, False, ())
        set_non_enumerable(proto, meth_name, js_meth)
    return proto


def easy_func(f, space):
    return space.NewFunction(f, space.ctx, (), f.__name__, False, ())


def Empty(this, args):
    return undefined


def set_non_enumerable(obj, name, prop):
    obj.define_own_property(
        unicode(name), {
            'value': prop,
            'writable': True,
            'enumerable': False,
            'configurable': True
        }, True)


def set_protected(obj, name, prop):
    obj.define_own_property(
        unicode(name), {
            'value': prop,
            'writable': False,
            'enumerable': False,
            'configurable': False
        }, True)


def fill_space(space, byte_generator):
    # set global scope
    global_scope = Scope({}, space, parent=None)
    global_scope.THIS_BINDING = global_scope
    global_scope.registers(byte_generator.declared_vars)
    space.GlobalObj = global_scope

    space.byte_generator = byte_generator

    # first init all protos, later take care of constructors and details

    # Function must be first obviously, we have to use a small trick to do that...
    function_proto = space.NewFunction(Empty, space.ctx, (), 'Empty', False,
                                       ())
    space.FunctionPrototype = function_proto  # this will fill the prototypes of the methods!
    fill_proto(function_proto, FunctionPrototype, space)

    # Object next
    object_proto = space.NewObject()  # no proto
    fill_proto(object_proto, ObjectPrototype, space)
    space.ObjectPrototype = object_proto
    function_proto.prototype = object_proto

    # Number
    number_proto = space.NewObject()
    number_proto.prototype = object_proto
    fill_proto(number_proto, NumberPrototype, space)
    number_proto.value = 0.
    number_proto.Class = 'Number'
    space.NumberPrototype = number_proto

    # String
    string_proto = space.NewObject()
    string_proto.prototype = object_proto
    fill_proto(string_proto, StringPrototype, space)
    string_proto.value = u''
    string_proto.Class = 'String'
    space.StringPrototype = string_proto

    # Boolean
    boolean_proto = space.NewObject()
    boolean_proto.prototype = object_proto
    fill_proto(boolean_proto, BooleanPrototype, space)
    boolean_proto.value = False
    boolean_proto.Class = 'Boolean'
    space.BooleanPrototype = boolean_proto

    # Array
    array_proto = space.NewArray(0)
    array_proto.prototype = object_proto
    fill_proto(array_proto, ArrayPrototype, space)
    space.ArrayPrototype = array_proto

    # JSON
    json = space.NewObject()
    json.put(u'stringify', easy_func(jsjson.stringify, space))
    json.put(u'parse', easy_func(jsjson.parse, space))

    # Utils
    parseFloat = easy_func(jsutils.parseFloat, space)
    parseInt = easy_func(jsutils.parseInt, space)
    isNaN = easy_func(jsutils.isNaN, space)
    isFinite = easy_func(jsutils.isFinite, space)

    # Error
    error_proto = space.NewError(u'Error', u'')
    error_proto.prototype = object_proto
    error_proto.put(u'name', u'Error')
    fill_proto(error_proto, ErrorPrototype, space)
    space.ErrorPrototype = error_proto

    def construct_constructor(typ):
        def creator(this, args):
            message = get_arg(args, 0)
            if not is_undefined(message):
                msg = to_string(message)
            else:
                msg = u''
            return space.NewError(typ, msg)

        j = easy_func(creator, space)
        j.name = unicode(typ)
        j.prototype = space.ERROR_TYPES[typ]

        def new_create(args, space):
            message = get_arg(args, 0)
            if not is_undefined(message):
                msg = to_string(message)
            else:
                msg = u''
            return space.NewError(typ, msg)

        j.create = new_create
        return j

    # fill remaining error types
    error_constructors = {}
    for err_type_name in (u'Error', u'EvalError', u'RangeError',
                          u'ReferenceError', u'SyntaxError', u'TypeError',
                          u'URIError'):
        extra_err = space.NewError(u'Error', u'')
        extra_err.put(u'name', err_type_name)
        setattr(space, err_type_name + u'Prototype', extra_err)
        error_constructors[err_type_name] = construct_constructor(
            err_type_name)
    assert space.TypeErrorPrototype is not None

    # RegExp
    regexp_proto = space.NewRegExp(u'(?:)', u'')
    regexp_proto.prototype = object_proto
    fill_proto(regexp_proto, RegExpPrototype, space)
    space.RegExpPrototype = regexp_proto

    # Json

    # now all these boring constructors...

    # Number
    number = easy_func(jsnumber.Number, space)
    space.Number = number
    number.create = jsnumber.NumberConstructor
    set_non_enumerable(number_proto, 'constructor', number)
    set_protected(number, 'prototype', number_proto)
    # number has some extra constants
    for k, v in jsnumber.CONSTS.items():
        set_protected(number, k, v)

    # String
    string = easy_func(jsstring.String, space)
    space.String = string
    string.create = jsstring.StringConstructor
    set_non_enumerable(string_proto, 'constructor', string)
    set_protected(string, 'prototype', string_proto)
    # string has an extra function
    set_non_enumerable(string, 'fromCharCode',
                       easy_func(jsstring.fromCharCode, space))

    # Boolean
    boolean = easy_func(jsboolean.Boolean, space)
    space.Boolean = boolean
    boolean.create = jsboolean.BooleanConstructor
    set_non_enumerable(boolean_proto, 'constructor', boolean)
    set_protected(boolean, 'prototype', boolean_proto)

    # Array
    array = easy_func(jsarray.Array, space)
    space.Array = array
    array.create = jsarray.ArrayConstructor
    set_non_enumerable(array_proto, 'constructor', array)
    set_protected(array, 'prototype', array_proto)
    array.put(u'isArray', easy_func(jsarray.isArray, space))

    # RegExp
    regexp = easy_func(jsregexp.RegExp, space)
    space.RegExp = regexp
    regexp.create = jsregexp.RegExpCreate
    set_non_enumerable(regexp_proto, 'constructor', regexp)
    set_protected(regexp, 'prototype', regexp_proto)

    # Object
    _object = easy_func(jsobject.Object, space)
    space.Object = _object
    _object.create = jsobject.ObjectCreate
    set_non_enumerable(object_proto, 'constructor', _object)
    set_protected(_object, 'prototype', object_proto)
    fill_proto(_object, jsobject.ObjectMethods, space)

    # Function
    function = easy_func(jsfunction.Function, space)
    space.Function = function

    # Math
    math = space.NewObject()
    math.Class = 'Math'
    fill_proto(math, jsmath.MathFunctions, space)
    for k, v in jsmath.CONSTANTS.items():
        set_protected(math, k, v)

    console = space.NewObject()
    fill_proto(console, jsconsole.ConsoleMethods, space)

    # set global object
    builtins = {
        'String': string,
        'Number': number,
        'Boolean': boolean,
        'RegExp': regexp,
        'exports': convert_to_js_type({}, space),
        'Math': math,
        #'Date',
        'Object': _object,
        'Function': function,
        'JSON': json,
        'Array': array,
        'parseFloat': parseFloat,
        'parseInt': parseInt,
        'isFinite': isFinite,
        'isNaN': isNaN,
        'eval': easy_func(jsfunction._eval, space),
        'console': console,
        'log': console.get(u'log'),
    }

    builtins.update(error_constructors)

    set_protected(global_scope, 'NaN', NaN)
    set_protected(global_scope, 'Infinity', Infinity)
    for k, v in builtins.items():
        set_non_enumerable(global_scope, k, v)
