"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function LOG(...argArr) {
    console.log.call(console, ...argArr);
}
exports.LOG = LOG;
function ERROR_LOG(...argArr) {
    console.error.call(console, ...argArr);
}
exports.ERROR_LOG = ERROR_LOG;
let POLL_TYPE = {
    progress: true
};
// Response class
class IOResponse {
    constructor(data, port, options) {
        this.id = data.id;
        this.port = port;
        this.options = options;
        this.queue = [];
        this.fresh = true;
    }
    progress(data, callback) {
        this.queue.push({
            status: 'progress',
            data: data,
            callback: callback
        });
        this._handleQueue();
    }
    success(data) {
        this.queue.push({
            status: 'success',
            data: data
        });
        this._handleQueue();
    }
    fail(data) {
        this.queue.push({
            status: 'fail',
            data: data
        });
        this._handleQueue();
    }
    setOptions(options) {
        this.options = options;
        this.fresh = true;
        this._handleQueue();
    }
    _multipleResponse() {
        let responseList = [];
        let callbacks = [];
        for (let i = 0; i < this.queue.length; ++i) {
            let d = this.queue[i];
            responseList.push({
                status: d.status,
                data: d.data
            });
            if (d.callback != null) {
                callbacks.push(d.callback);
            }
        }
        function done() {
            for (let j = 0; j < callbacks.length; ++j) {
                callbacks[j]();
            }
        }
        if (this.port.isStreaming) {
            this.port.respondMultiple(this, responseList, this.options, done);
        }
        else if (this.fresh) {
            this.port.respondMultiple(this, responseList, this.options, done);
            this.fresh = false;
        }
        this.queue = [];
    }
    _handleQueue() {
        if (!(this.queue.length > 0)) {
            return;
        }
        if (!this.port.isStreaming && !this.fresh) {
            return;
        }
        if (this.queue.length > 1) {
            this._multipleResponse();
            return;
        }
        let d = this.queue[0];
        if (this.port.isStreaming) {
            this.port.respond(this, d.status, d.data, this.options, d.callback);
        }
        else if (this.fresh) {
            this.port.respond(this, d.status, d.data, this.options, d.callback);
            this.fresh = false;
        }
        this.queue = [];
    }
}
exports.IOResponse = IOResponse;
// Call class
class Call {
    constructor(id, method, data, callbacks, port) {
        this.id = id;
        this.method = method;
        this.data = data;
        this.callbacks = callbacks;
        this.port = port;
        null;
    }
    handle(data, packet) {
        if (this.callbacks[packet.status] == null) {
            if (packet.status === 'progress') {
                return;
            }
            //Handled by caller
            throw new Error(`No callback registered ${this.method} ${packet.status}`);
        }
        let self = this;
        setTimeout(function () {
            try {
                return self.callbacks[packet.status](data, packet);
            }
            catch (error) {
                self.port.onError(error);
            }
        }, 0);
        return POLL_TYPE[packet.status] == null;
    }
}
class Port {
    constructor() {
        this.isStreaming = true;
        this.handlers = {};
        this.callsCache = {};
        this.callsCounter = 0;
        this.id = Math.floor(Math.random() * 1000);
        this.responses = {};
    }
    onError(err) {
        LOG(err);
    }
    onCallError(msg, options = null) {
        let callsCache = this.callsCache;
        this.callsCache = {};
        for (let id in callsCache) {
            let call = callsCache[id];
            if (call.callbacks.fail == null) {
                ERROR_LOG('fail callback not registered', call.method, call.data);
            }
            else {
                call.callbacks.fail({
                    error: 'connectionError',
                    msg: msg,
                    options: options
                });
            }
        }
        return this.errorCallback(msg, options);
    }
    onHandleError(msg, data, options) {
        this.errorCallback(msg, data);
        let response = new IOResponse(data, this, options);
        return response.fail(msg);
    }
    errorCallback(msg, options) {
        return ERROR_LOG(msg, options);
    }
    send(method, data, f) {
        let callbacks;
        if (typeof f === 'function') {
            callbacks = {
                success: f
            };
        }
        else {
            callbacks = f;
        }
        this.sendTyped(method, data, callbacks);
    }
    sendTyped(method, data, callbacks) {
        if (!this.shouldSend(method, data, callbacks)) {
            return;
        }
        let params = this._createCall(method, data, callbacks);
        this._send(params, callbacks);
    }
    shouldSend(method, data, callbacks) {
        return true;
    }
    // Respond to a RPC call
    respond(response, status, data, portOptions, callback) {
        if (POLL_TYPE[status] == null) {
            delete this.responses[response.id];
        }
        if (!this.shouldRespond(response, status, data, portOptions)) {
            return;
        }
        this._respond(this._createResponse(response, status, data), portOptions, callback);
    }
    shouldRespond(response, status, data, portOptions) {
        return true;
    }
    respondMultiple(response, list, portOptions, callback) {
        for (let d of list) {
            if (POLL_TYPE[d.status] == null) {
                delete this.responses[response.id];
                break;
            }
        }
        let data = [];
        for (let d of list) {
            if (!this.shouldRespond(response, d.status, d.data, portOptions)) {
                continue;
            }
            data.push(this._createResponse(response, d.status, d.data));
        }
        if (data.length === 0) {
            return;
        }
        this._respond({
            type: 'list',
            list: data
        }, portOptions, callback);
    }
    // Create Call object
    _createCall(method, data, callbacks) {
        let call = new Call(`${this.id}-${this.callsCounter}`, method, data, callbacks, this);
        this.callsCounter++;
        this.callsCache[call.id] = call;
        let params = {
            type: 'call',
            id: call.id,
            method: call.method,
            data: call.data
        };
        return params;
    }
    // Create Response object
    _createResponse(response, status, data) {
        let params = {
            type: 'response',
            id: response.id,
            status: status,
            data: data
        };
        return params;
    }
    // Add handler
    on(method, callback) {
        return (this.handlers[method] = callback);
    }
    // Handle incoming message
    _handleMessage(packet, options = {}, last = true) {
        switch (packet.type) {
            case 'list':
                packet = packet;
                for (let i = 0; i < packet.list.length; ++i) {
                    this._handleMessage(packet.list[i], options, last && i + 1 === packet.list.length);
                }
                break;
            case 'response':
                try {
                    this._handleResponse(packet, options, last);
                }
                catch (error) {
                    this.onError(error);
                }
                break;
            case 'call':
                try {
                    this._handleCall(packet, options, last);
                }
                catch (error) {
                    this.onError(error);
                }
                break;
            case 'poll':
                try {
                    this._handlePoll(packet, options, last);
                }
                catch (error) {
                    this.onError(error);
                }
        }
    }
    _handleCall(packet, options, last) {
        if (!this.shouldCall(packet, options)) {
            return;
        }
        if (this.handlers[packet.method] == null) {
            this.onHandleError(`Unknown method: ${packet.method}`, packet, options);
            return;
        }
        this.responses[packet.id] = new IOResponse(packet, this, options);
        this.handlers[packet.method](packet.data, packet, this.responses[packet.id]);
    }
    shouldCall(packet, options) {
        return true;
    }
    shouldPoll(packet, options) {
        return true;
    }
    _handleResponse(data, options, last) {
        if (!this.shouldAcceptResponse(data, options)) {
            return;
        }
        if (this.callsCache[data.id] == null) {
            //Cannot reply
            this.errorCallback(`Response without call: ${data.id}`, data);
            return;
        }
        try {
            if (this.callsCache[data.id].handle(data.data, data)) {
                delete this.callsCache[data.id];
            }
        }
        catch (error) {
            this.errorCallback(error.message, data);
            delete this.callsCache[data.id];
        }
    }
    shouldAcceptResponse(packet, options) {
        return true;
    }
    _handlePoll(data, options, last) {
        return this.onHandleError('Poll not implemented', data, options);
    }
}
exports.Port = Port;
