/**
 * @author xiangshouding
 * @date 2017-07-06 12:02:41
 */

import {URL} from 'url';
import {consul} from './consul';
import {Map} from './types/lang';
import {Address} from './protocol/protocol';

/** 
 * The Config Class
 */
export class Config {
    env: string = 'prod';
    encoding: string;

    /**
     * protocol: (THRIFT | HTTP | HTTPS)
     */
    protocol: string;
    /**
     * transport ( Framed | Buffered )
     */
    transport: string;
    
    searchHostType: string = 'LOCAL'; // or 'CONSUL'
    /**
     * the connect server timeout settings.
     */
    timeout: number = 2000;

    /**
     * request balance.
     */
    balance: string;
    retry: number = 1;

    consul: { host: string, port: number, action ?: string, timeout ?: number};

    /**
     * {
     *  "bj": [
     *    {
     *      'host': '127.0.0.1',
     *      'port': 80888
     *    },
     *    {
     *      ...
     *    }
     *  ]
     * }
     * 
     * @var Map<string, Map<string, string[]>[]>
     */
    hosts: any = {};
    host: string;
    port: number;
    address: string;
    options: Map<any>;

    log: {logFile: string, logLevel: string, logId: number};

    constructor (public service: string | Map<any>, public idc?: string, public ctx?: any) {
        if (typeof service != 'string') {
            this.create(service);
        } else {
            this.loadFile(<string> this.service);    
        }
    }

    public getHosts(cb: (err: any, hosts: Array<Address>) => void): void {
        if (this.searchHostType == 'LOCAL') {
            let hosts = this.hosts[this.idc];

            //if idc hosts not exist, return `hosts` or `[{host:, port:}]`
            if (Array.isArray(hosts)) {
                return cb(null, hosts);
            }

            if (Array.isArray(this.hosts)) {
                hosts = this.hosts;
            } else if (this.host && this.port) {
                hosts = [new Address(this.host, this.port)];
            } else {
                cb(new Error('please given `hosts` or `host` and `port`'), []);
            }

            return cb(null, hosts);
        } else if (this.searchHostType == 'CONSUL') {
            this.getHostsWithConsul(cb);
        } else {
            return cb(new Error(`unsupport '${this.searchHostType}' searchHostType`), []);
        }
    }
    
    /**
     * git the host list with `idc`
     */
    public getHostsWithIDC() {
        return [];
    }

    public getProtocol() {
        if (!this.protocol) {
            if (this.searchHostType === 'CONSUL') {
                this.protocol = 'THRIFT'; 
            } else {
                this.protocol = 'HTTP';
            }
        }

        return this.protocol;
    }

    public getConsulAddress() {
        if (!this.consul) {
            this.consul = {
                host: 'consul.service.byted.org',
                port: 2280
            };
        }

        return new Address(
            this.consul.host,
            this.consul.port
        );
    }

    public getLogFile() {
        return this.log.logFile;
    }

    public getLogLevel() {
        return this.log.logLevel;
    }
    
    /**
     * config information load from a config file
     * @param {string} file
     * @return void
     */
    protected loadFile(file: string) {
        let config = {};

        /**
         * if set ctx, read from ctx.
         * it's config is preload to context.
         */
        if (this.ctx && this.ctx[file]) {
            config = this.ctx[file];
        } else {
            config = require(file);
        }

        this.create(config);    
    }

    /**
     * create a instance of the Config from a Map.
     * 
     * @param config Map<any>
     */
    private create(config: Map<any>) {
        if (config['timeout']) {
            config['timeout'] = this.timeFromString(config['timeout']);
        } else {
            // default 2s
            config['timeout'] = this.timeout;
        }

        this.options = config;
        
        if (config.hosts) {
            for (var idc in config.hosts) {
                if (config.hosts.hasOwnProperty(idc)) {
                    let hosts = config.hosts[idc];
                    
                    this.hosts[idc] = hosts.map(function (address) {
                        return new Address(address.host, address.port);
                    });
                }
            }
        }

        for (var key in config) {
            if (config.hasOwnProperty(key) && ['hosts'].indexOf(key) == -1) {
                this[key] = config[key];
            }
        }
    }

    /**
     * the time 1s\1ms to \d+(ms)
     * 
     * @param s string | any
     */
    private timeFromString(s: string) {
        if (/([\d.]+)ms/i.test(s)) {
            return parseFloat(RegExp.$1);
        } else if (/([\d.]+)s/i.test(s)) {
            return parseFloat(RegExp.$1) * 1000;
        } else {
            return parseFloat(s) * 1000;
        }
    }

    /**
     * 
     */
    private getHostsWithConsul(cb) {
        // require consul get hosts.
        return consul.get(
            this.getConsulAddress(),
            <string>this.service,
            cb,
            this.env,
            null, // idc
            this.consul.action || 'lookup',
            {timeout: this.consul.timeout}
        );
    }

    /**
     * {
     *   'address': '127.0.0.1:8099'
     * }
     * 
     * host = 127.0.0.1
     * port = 8099
     * 
     * if not set consul address, use it.
     */
    private getHostsWithAddress() : boolean {
        if (typeof this.address != 'string') {
            return false;
        }

        if (this.address.length == 0) {
            return false;
        }

        let url_ = new URL(this.address);

        if (!url_) {
            return false;
        }

        this.host = url_.hostname;
        this.port = parseInt(url_.port);

        return true;
    }

};
