import * as net from 'net';
import * as tls from 'tls';
import * as util from 'util';
import { EventEmitter } from 'events';

import { HybridSocket } from './hybridsocket';
import { HybridSwitcher as Switcher, HybridSwitcherOptions } from './hybrid/switcher';
import { HandshakeCommand } from './commands/handshake';
import { HeartbeatCommand } from './commands/heartbeat';
import * as Kick from './commands/kick';
import * as coder from './common/coder';
import { ConnectorComponent } from '../components/connector';
import { DictionaryComponent } from '../components/dictionary';
import { ProtobufComponent } from '../components/protobuf';
import { IComponent } from '../interfaces/IComponent';
import { pinus } from '../pinus';
import { IConnector } from '../interfaces/IConnector';
import { TlsOptions } from 'tls';
import * as WebSocket from 'ws';
import { TcpSocket } from './hybrid/tcpsocket';
import { IHybridSocket } from './hybrid/IHybridSocket';

let curId = 1;

export interface HybridConnectorOptions extends HybridSwitcherOptions {
    useDict?: boolean;
    useProtobuf?: boolean;
    distinctHost?: boolean;
    realIPKey?: string;   // 代理过后真实客户端ip获取字段
    realPortKey?: string; // 代理过后真实客户端port获取字段
}

/**
 * Connector that manager low level connection and protocol bewteen server and client.
 * Develper can provide their own connector to switch the low level prototol, such as tcp or probuf.
 */
export class HybridConnector extends EventEmitter implements IConnector {
    opts: HybridConnectorOptions;
    port: number;
    host: string;
    useDict: boolean;
    useProtobuf: boolean;
    handshake: HandshakeCommand;
    heartbeat: HeartbeatCommand;
    distinctHost: boolean;
    ssl: tls.TlsOptions;
    switcher: Switcher;

    connector: IConnector;
    dictionary: DictionaryComponent;
    protobuf: ProtobufComponent;
    decodeIO_protobuf: IComponent;

    listeningServer: net.Server | tls.Server;

    constructor(port: number, host: string, opts?: HybridConnectorOptions) {
        super();

        this.opts = opts || {};
        if (this.opts.realPortKey) {
            this.opts.realPortKey = opts.realPortKey.toLowerCase();
        }
        if (this.opts.realIPKey) {
            this.opts.realIPKey = opts.realIPKey.toLowerCase();
        }
        this.port = port;
        this.host = host;
        this.useDict = opts.useDict;
        this.useProtobuf = opts.useProtobuf;
        this.handshake = new HandshakeCommand(opts);
        this.heartbeat = new HeartbeatCommand(opts);
        this.distinctHost = opts.distinctHost;
        this.ssl = opts.ssl;

        this.switcher = null;
    }

    /**
     * Start connector to listen the specified port
     */
    start(cb: () => void) {
        let app = pinus.app;
        let self = this;

        let gensocket = function (socket: IHybridSocket, request: any) {
            let hybridsocket = new HybridSocket(curId++, socket, request, self.opts);
            hybridsocket.on('handshake', self.handshake.handle.bind(self.handshake, hybridsocket));
            hybridsocket.on('heartbeat', self.heartbeat.handle.bind(self.heartbeat, hybridsocket));
            hybridsocket.on('disconnect', self.heartbeat.clear.bind(self.heartbeat, hybridsocket.id));
            hybridsocket.on('closing', Kick.handle.bind(null, hybridsocket));
            self.emit('connection', hybridsocket);
        };

        this.connector = app.components.__connector__.connector;
        this.dictionary = app.components.__dictionary__;
        this.protobuf = app.components.__protobuf__;
        this.decodeIO_protobuf = app.components.__decodeIO__protobuf__;

        if (!this.ssl) {
            this.listeningServer = net.createServer();
        } else {
            this.listeningServer = tls.createServer(this.ssl);
            if (this.opts.sslWatcher) {
                this.opts.sslWatcher((opts) => {
                    (this.listeningServer as tls.Server).setSecureContext(opts);
                });
            }
        }
        this.switcher = new Switcher(this.listeningServer, self.opts);

        this.switcher.on('connection', function (socket, request) {
            gensocket(socket, request);
        });

        if (!!this.distinctHost) {
            this.listeningServer.listen(this.port, this.host);
        } else {
            this.listeningServer.listen(this.port);
        }

        process.nextTick(cb);
    }

    stop(force: boolean, cb: () => void) {
        this.switcher.close();
        this.listeningServer.close();

        process.nextTick(cb);
    }
    decode = coder.decode;

    encode = coder.encode;

}
