import { createDefaultAcceptor, IAcceptor, IAcceptorFactory } from './acceptor';
import { EventEmitter } from 'events';
import { Dispatcher, MsgPkg, Services } from './dispatcher';
import * as Loader from 'omelox-loader';
import * as utils from '../util/utils';
import { Tracer } from '../util/tracer';
import * as util from 'util';
import * as fs from 'fs';
import { AcceptorOpts } from './acceptor';
import { LoaderPathType } from 'omelox-loader';

export interface RpcServerOpts extends AcceptorOpts {
    port?: number | string;
    paths?: Array<RemoteServerCode>;
    context?: object;
    services?: Services;
    acceptorFactory?: IAcceptorFactory;
}

export interface RemoteServerCode {
    namespace: string;
    serverType: string;
    path: string;
}

export class Gateway extends EventEmitter {
    opts: any;
    port: number;
    started = false;
    stoped = false;
    services: Services;
    acceptor: IAcceptor;
    dispatcher: Dispatcher;

    constructor(opts: RpcServerOpts) {
        super();
        this.opts = opts || {};
        this.port = <number>opts.port || 3050;
        this.started = false;
        this.stoped = false;
        let acceptorFactory = opts.acceptorFactory || createDefaultAcceptor;
        this.services = opts.services;
        const dispatcher = this.dispatcher = new Dispatcher(this.services);
        if (!!this.opts.reloadRemotes) {
            this.watchServices();
        }
        this.acceptor = acceptorFactory(opts as AcceptorOpts, (tracer, msg, cb) => {
            dispatcher.route(tracer, msg, cb);
        });
    }


    stop(force: boolean) {
        if (!this.started || this.stoped) {
            return;
        }
        this.stoped = true;
        try {
            this.acceptor.close();
        } catch (err) {
        }
    }

    start() {
        if (this.started) {
            throw new Error('gateway already start.');
        }
        this.started = true;

        let self = this;
        this.acceptor.on('error', self.emit.bind(self, 'error'));
        this.acceptor.on('closed', self.emit.bind(self, 'closed'));
        this.acceptor.listen(this.port);
    }


    watchServices() {
        let paths = this.opts.paths;
        let app = this.opts.context;
        for (let i = 0; i < paths.length; i++) {
            ((index) => {
                fs.watch(paths[index].path, (event: string, name: string) => {
                    if (event === 'change') {
                        this.reloadRemoter(app, paths[index]);
                    }
                });
            })(i);
        }
    }

    manualReloadRemoters() {
        let paths = this.opts.paths;
        let app = this.opts.context;
        for (let i = 0; i < paths.length; i++) {
            this.reloadRemoter(app, paths[i]);
        }
    }

    private reloadRemoter(app: any, item: any) {
        let res: { [key: string]: any } = {};
        let m: { [key: string]: any } = Loader.load(item.path, app, true, true, LoaderPathType.OMELOX_REMOTER);
        if (m) {
            createNamespace(item.namespace, res);
            for (let s in m) {
                res[item.namespace][s] = m[s];
            }
        }
        this.dispatcher.emit('reload', res);
    }
}

/**
 * create and init gateway
 *
 * @param opts {services: {rpcServices}, connector:conFactory(optional), router:routeFunction(optional)}
 */
export function createGateway(opts: RpcServerOpts) {
    if (!opts || !opts.services) {
        throw new Error('opts and opts.services should not be empty.');
    }

    return new Gateway(opts);
}


let createNamespace = function (namespace: string, proxies: { [key: string]: object }) {
    proxies[namespace] = proxies[namespace] || {};
};