/*global Module*/
/*global HEAP32*/
/*global readLatin1String, writeStringToMemory*/
/*global requireRegisteredType, throwBindingError*/

var _emval_handle_array = [{}]; // reserve zero
var _emval_free_list = [];

// Public JS API

/** @expose */
Module.count_emval_handles = function() {
    var count = 0;
    for (var i = 1; i < _emval_handle_array.length; ++i) {
        if (_emval_handle_array[i] !== undefined) {
            ++count;
        }
    }
    return count;
};

/** @expose */
Module.get_first_emval = function() {
    for (var i = 1; i < _emval_handle_array.length; ++i) {
        if (_emval_handle_array[i] !== undefined) {
            return _emval_handle_array[i];
        }
    }
    return null;
};

// Private C++ API

var _emval_symbols = {}; // address -> string

function __emval_register_symbol(address) {
    _emval_symbols[address] = readLatin1String(address);
}

function getStringOrSymbol(address) {
    var symbol = _emval_symbols[address];
    if (symbol === undefined) {
        return readLatin1String(address);
    } else {
        return symbol;
    }
}

function requireHandle(handle) {
    if (!handle) {
        throwBindingError('Cannot use deleted val. handle = ' + handle);
    }
}

function __emval_register(value) {
    var handle = _emval_free_list.length ?
        _emval_free_list.pop() :
        _emval_handle_array.length;

    _emval_handle_array[handle] = {refcount: 1, value: value};
    return handle;
}

function __emval_incref(handle) {
    if (handle) {
        _emval_handle_array[handle].refcount += 1;
    }
}

function __emval_decref(handle) {
    if (handle && 0 === --_emval_handle_array[handle].refcount) {
        delete _emval_handle_array[handle];
        _emval_free_list.push(handle);

        var actual_length = _emval_handle_array.length;
        while (actual_length > 0 && _emval_handle_array[actual_length - 1] === undefined) {
            --actual_length;
        }
        _emval_handle_array.length = actual_length;
    }
}

function __emval_new_array() {
    return __emval_register([]);
}

function __emval_new_object() {
    return __emval_register({});
}

function __emval_undefined() {
    return __emval_register(undefined);
}

function __emval_null() {
    return __emval_register(null);
}

function __emval_new_cstring(v) {
    return __emval_register(getStringOrSymbol(v));
}

function __emval_take_value(type, v) {
    type = requireRegisteredType(type, '_emval_take_value');
    v = type.fromWireType(v);
    return __emval_register(v);
}

var __newers = {}; // arity -> function

function __emval_new(handle, argCount, argTypes) {
    requireHandle(handle);

    var args = parseParameters(
        argCount,
        argTypes,
        Array.prototype.slice.call(arguments, 3));

    // Alas, we are forced to use operator new until WebKit enables
    // constructing typed arrays without new.
    // In WebKit, Uint8Array(10) throws an error.
    // In every other browser, it's identical to new Uint8Array(10).

    var newer = __newers[argCount];
    if (!newer) {
        var parameters = new Array(argCount);
        for (var i = 0; i < argCount; ++i) {
            parameters[i] = 'a' + i;
        }
        /*jshint evil:true*/
        newer = __newers[argCount] = new Function(
            ['c'].concat(parameters),
            "return new c(" + parameters.join(',') + ");");
    }
    
    var constructor = _emval_handle_array[handle].value;
    var obj = newer.apply(undefined, [constructor].concat(args));
/*
    // implement what amounts to operator new
    function dummy(){}
    dummy.prototype = constructor.prototype;
    var obj = new constructor;
    var rv = constructor.apply(obj, args);
    if (typeof rv === 'object') {
        obj = rv;
    }
*/
    return __emval_register(obj);
}

// appease jshint (technically this code uses eval)
var global = (function(){return Function;})()('return this')();

function __emval_get_global(name) {
    name = getStringOrSymbol(name);
    return __emval_register(global[name]);
}

function __emval_get_module_property(name) {
    name = getStringOrSymbol(name);
    return __emval_register(Module[name]);
}

function __emval_get_property(handle, key) {
    requireHandle(handle);
    return __emval_register(_emval_handle_array[handle].value[_emval_handle_array[key].value]);
}

function __emval_set_property(handle, key, value) {
    requireHandle(handle);
    _emval_handle_array[handle].value[_emval_handle_array[key].value] = _emval_handle_array[value].value;
}

function __emval_as(handle, returnType) {
    requireHandle(handle);
    returnType = requireRegisteredType(returnType, 'emval::as');
    var destructors = [];
    // caller owns destructing
    return returnType.toWireType(destructors, _emval_handle_array[handle].value);
}

function parseParameters(argCount, argTypes, argWireTypes) {
    var a = new Array(argCount);
    for (var i = 0; i < argCount; ++i) {
        var argType = requireRegisteredType(
            HEAP32[(argTypes >> 2) + i],
            "parameter " + i);
        a[i] = argType.fromWireType(argWireTypes[i]);
    }
    return a;
}

function __emval_call(handle, argCount, argTypes) {
    requireHandle(handle);
    var fn = _emval_handle_array[handle].value;
    var args = parseParameters(
        argCount,
        argTypes,
        Array.prototype.slice.call(arguments, 3));
    var rv = fn.apply(undefined, args);
    return __emval_register(rv);
}

function __emval_call_method(handle, name, argCount, argTypes) {
    requireHandle(handle);
    name = getStringOrSymbol(name);

    var args = parseParameters(
        argCount,
        argTypes,
        Array.prototype.slice.call(arguments, 4));
    var obj = _emval_handle_array[handle].value;
    var rv = obj[name].apply(obj, args);
    return __emval_register(rv);
}

function __emval_call_void_method(handle, name, argCount, argTypes) {
    requireHandle(handle);
    name = getStringOrSymbol(name);

    var args = parseParameters(
        argCount,
        argTypes,
        Array.prototype.slice.call(arguments, 4));
    var obj = _emval_handle_array[handle].value;
    obj[name].apply(obj, args);
}

function __emval_has_function(handle, name) {
    name = getStringOrSymbol(name);
    return _emval_handle_array[handle].value[name] instanceof Function;
}
