/**
 * Copyright 2016 the WHATAP project authors. All rights reserved.
 * Use of this source code is governed by a license that
 * can be found in the LICENSE file.
 */

var WHATAP_CONF = process.env.WHATAP_CONF || 'whatap.conf';

var fs = require('fs'),
    path = require('path'),
    EventEmitter = require('events').EventEmitter;

var DataInputX = require('./../io/data-inputx'),
    secu = require('./../net/security-master'),
    HashUtil = require('./../util/hashutil'),
    Cypher = require('./../util/cypher'),
    ConfigDefault = require("./config-default"),
    LogConfigDefault = require("./log-config-default"),
    MapValue = require('./../value/map-value'),
    IntKeyLinkedMap = require('./../util/intkey-linkedmap'),
    lastModifiedTime = 0;

function toNumber(value) {
    if(typeof value == 'number') {
        return value;
    }

    var number = Number(value);
    if(isNaN(number)) {
        return null;
    } else {
        return number;
    }
}

function toBoolean(value) {
    if(typeof value == 'boolean') {
        return value;
    }

    if(typeof value == 'string') {
        if(value == 'true') {
            return true;
        } else if(value == 'false') {
            return false;
        }
    }
    return null;
}

function replaceCarriageReturns(str) {
    let regxp = /\r/g;
    str = str.replace(regxp, "");
    return str;
}

function cutOut(val, delim) {
    if (val === undefined)
        return val;
    try {
        var x = val.lastIndexOf(delim);
        if (x <= 0)
            return;
        return val.substring(0, x);
    } catch (e) {
        return val;
    }
}

function updatePrivate(self){
    if(self.trace_user_using_ip) {
        self.trace_user_using_type = 1;
    } else {
        self.trace_user_using_type = 3;
    }

    self._log_ignore_set = self.getStringSet('log_ignore_set', null, ',');
}

/**
 * Configuration 클래스 - 싱글톤 패턴으로 구현
 */
var Configuration = (function() {
    // 싱글톤 인스턴스
    var instance;
    var applyEmptyConf = false;

    // 싱글톤 인스턴스 초기화 함수
    function init() {
        // Configuration 기본 객체 생성
        var configInstance = {};

        // EventEmitter 상속
        EventEmitter.call(configInstance);
        Object.setPrototypeOf(configInstance, EventEmitter.prototype);

        // 기본 속성 설정
        configInstance.PCODE = 0;
        configInstance.OID = 0;
        configInstance.ONAME = null;
        configInstance.OKIND = 0;
        configInstance.OKIND_NAME = null;
        configInstance.ONODE = 0;
        configInstance.ONODE_NAME = null;
        configInstance.inx = 0;
        configInstance._customProps = {};
        configInstance._propertyFilePath = undefined;
        configInstance._log_ignore_set = new Set();
        configInstance.socketChk = new IntKeyLinkedMap(500,1).setMax(500);

        // 메서드 정의
        configInstance.getProperty = function(key, defaultValue) {
            if(this[key] === undefined){
                return defaultValue;
            } else{
                return this[key];
            }
        };

        configInstance.setProperty = function(key, value) {
            if(this[key] == value) { return; }
            this[key] = value;
            this.emit(key, value);
        };

        configInstance.getStringSet = function (key, defaultValue, deli) {
            var set = new Set();
            var value = this[key] || defaultValue;
            if(value == null) { return set; }

            var arr = value.split(deli);
            for(var i=0; i<arr.length; i++) {
                var x = arr[i].trim();
                if(x) set.add(replaceCarriageReturns(x));
            }
            return set;
        };

        configInstance.apply = function(properties) {
            for(var k in properties) {
                this.setProperty(k, properties[k]);
            }

        };

        configInstance.init = function(userOpt, cb) {
            this.reload(cb);
        };

        configInstance.reload = function(cb) {
            var self = this;

            this.getPropertyFilePath(function(err, propertyFile) {
                if(err) {
                    if(applyEmptyConf==false){
                        applyEmptyConf=true;
                        require('../logger').print("WHATAP-301", "No Configure file '"+propertyFile+"'", false);

                        var defKeys = Object.keys(ConfigDefault);
                        var p = {};
                        defKeys.forEach(function (key) {
                            if(ConfigDefault[key]) {
                                p[key] = ConfigDefault[key];
                            }
                        });
                        var logDefKeys = Object.keys(LogConfigDefault);
                        logDefKeys.forEach(function (key) {
                            if(LogConfigDefault[key]) {
                                p[key] = LogConfigDefault[key];
                            }
                        });

                        if(process.env.WHATAP_SERVER_HOST){
                            p['whatap.server.host']=process.env.WHATAP_SERVER_HOST;
                        }
                        if(process.env.WHATAP_LICENSE){
                            p['license']=process.env.WHATAP_LICENSE;
                        }
                        self.apply(p);
                        updatePrivate(self);
                        if(cb) cb();
                    }
                } else {
                    if(propertyFile == null) { return; }
                    applyEmptyConf=false;
                    var propStats = fs.lstatSync(propertyFile);
                    if(lastModifiedTime >= propStats.mtime) { return; }
                    lastModifiedTime = propStats.mtime;

                    var props = fs.readFileSync(propertyFile, 'utf8');
                    if(props.constructor == String){
                        var rowProps = props.split('\n');
                        var oldCustom = self._customProps || {};
                        self._customProps = {};
                        for(var i = 0 ; i < rowProps.length; i++) {
                            var p = rowProps[i].split('=');
                            if(p.length > 1) {

                                var boolValue = toBoolean(p[1]);
                                if(boolValue != null) {
                                    self._customProps[p[0]] = toBoolean(p[1]);
                                    continue;
                                }

                                var numberValue = toNumber(p[1]);
                                if(numberValue != null) {
                                    self._customProps[p[0]] = numberValue;
                                    continue;
                                }
                                self._customProps[p[0]] = p[1];
                            }
                        }

                        var oldKeys = Object.keys(oldCustom);
                        var newKeys = Object.keys(self._customProps);

                        oldKeys.forEach(function (key) {
                            if(newKeys.indexOf(key) < 0) {
                                if(ConfigDefault[key] !== null || LogConfigDefault[key] !== null) {
                                    self[key] = ConfigDefault[key] !== null ? ConfigDefault[key] : LogConfigDefault[key];
                                } else {
                                    delete self[key];
                                }
                            }
                        });
                        require('../logger').print("WHATAP-302", "Config file reloaded", false);
                    }

                    if(!self._customProps['license'] && process.env.WHATAP_LICENSE)
                        self._customProps['license'] = process.env.WHATAP_LICENSE;
                    if(!self._customProps['whatap.server.host'] && process.env.WHATAP_SERVER_HOST)
                        self._customProps['whatap.server.host'] = process.env.WHATAP_SERVER_HOST;
                    self.apply(self._customProps);
                    updatePrivate(self);
                    if(cb) cb();
                }
            });
        };

        configInstance.getPropertyFilePath = function(cb) {
            var self = this;
            if(self._propertyFilePath) {
                if(cb) {
                    cb(null, self._propertyFilePath);
                }
                return;
            }

            var confDir= '.';
            if(process.env.WHATAP_CONF_DIR || process.env.WHATAP_HOME){
                confDir = process.env.WHATAP_CONF_DIR || process.env.WHATAP_HOME;
            }else{
                confDir = this.getProperty('app.root', process.cwd());
            }

            var default_conf = WHATAP_CONF;
            var configFile = this.getProperty('whatap.config', default_conf);

            var confFullPathFile = path.join(confDir, configFile);
            fs.access(confFullPathFile, function (err) {
                if (err) {
                    self._propertyFilePath = null;
                    if (cb) cb(err, confFullPathFile);
                } else {
                    self._propertyFilePath = confFullPathFile;
                    if (cb) cb(null, confFullPathFile);
                }
            });
        };

        configInstance.getCustomProps = function(){
            var props = {};
            for(var k in this._customProps){
                props[k] = this._customProps[k];
            }
            return props;
        };

        configInstance.saveProperty = function(keyValues){
            var self = this;
            this.getPropertyFilePath(function(err, propertyFile){
                for(var k in keyValues){
                    var v = keyValues[k];
                    if(!v || v.length < 1){
                        delete self._customProps[k];
                    }else{
                        self._customProps[k] = v;
                    }
                }

                var writeContents = '';
                for(var k in self._customProps){
                    var v = self._customProps[k];
                    writeContents += k +'=' + v +'\n';
                }
                fs.writeFile(propertyFile, writeContents, 'utf-8', function(err, data){
                    if(err){
                        return console.error(err);
                    }
                });
                for(var k in self._customProps){
                    var v = self._customProps[k];
                    self.setProperty(k, v);
                }
            });
        };

        configInstance.updateNetCypherKey = function(data /* byte[] */) {
            try{
                var data = secu.cypher.decrypt(data);
                var inD = new DataInputX(data);
                this.TRANSFER_KEY = inD.readInt();
                this.SECURE_KEY = inD.readBlob();
                this.HIDE_KEY = inD.readInt();
                this.cypher = new Cypher(this.SECURE_KEY, this.HIDE_KEY);
            } catch(e) {
                // 에러 처리
            }
        };

        configInstance.readCustomConfig = function(){
            var props = this.getCustomProps();
            var mv = new MapValue();
            for(var k in props){
                mv.putString(k, props[k]);
            }
            return mv;
        };

        configInstance.isIgnoreLog = function (id) {
            return this._log_ignore_set.has(id);
        };

        configInstance.setSocketChk = function (socket) {
            if(socket.whatapId == undefined) {
                socket.whatapId = (new Date()).getTime().toString(36) + Math.random().toString(36).slice(2);
            }
            if(this.socketChk.get(socket.whatapId) == null) {
                this.socketChk.put(socket.whatapId, socket.whatapId);
                return false;
            } else return true;
        };

        configInstance.getLogSinkProperty = function (id, key) {
            return this.getProperty("pii_mask_logsink_"+id + "_" + key);
        };

        configInstance.getSqlProperty = function (id, key) {
            return this.getProperty("pii_mask_sql_"+id + "_" + key);
        };

        // 기본 설정 적용
        configInstance.apply(ConfigDefault);
        configInstance.apply(LogConfigDefault);

        // 정기적인 설정 리로드
        setInterval(function() {
            configInstance.reload();
        }, 10000);

        // 이벤트 리스너 설정
        configInstance.on('trace_ignore_url_set', function(props){
            if(!props || props.constructor !== String){
                return;
            }

            var ignoreUrls = props.split(',');
            configInstance._trace_ignore_url_set = {};
            for(var i = 0; i < ignoreUrls.length; i++){
                var ignoreUrl = ignoreUrls[i];
                var serviceHash = HashUtil.hashFromString(ignoreUrl);
                configInstance._trace_ignore_url_set[serviceHash] = true;
            }
        });

        configInstance.on('trace_ignore_url_prefix', function(props){
            if(props && props.constructor === String && props.length > 0){
                configInstance._is_trace_ignore_url_prefix = true;
                configInstance._trace_ignore_url_prefix_list = props.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
            }else{
                configInstance._is_trace_ignore_url_prefix = false;
                configInstance._trace_ignore_url_prefix_list = [];
            }
        });

        configInstance.on('trace_ignore_err_cls_contains', function(props){
            if(props && props.constructor === String && props.length > 0){
                configInstance._is_trace_ignore_err_cls_contains = true;
            }else{
                configInstance._is_trace_ignore_err_cls_contains = false;
            }
        });

        configInstance.on('trace_ignore_err_msg_contains', function(props){
            if(props && props.constructor === String && props.length > 0){
                configInstance._is_trace_ignore_err_msg_contains = true;
            }else{
                configInstance._is_trace_ignore_err_msg_contains = false;
            }
        });

        configInstance.on('mtrace_spec', function(props){
            if (!props || props.length == 0) {
                configInstance.mtrace_spec_hash = 0;
            } else {
                props = props.replace(',', '_');
                configInstance.mtrace_spec_hash = HashUtil.hash(props);
            }
        });

        return configInstance;
    }

    // 싱글톤 객체를 반환하는 공개 메서드
    return {
        // 싱글톤 인스턴스 가져오기
        getInstance: function() {
            if(!instance) {
                instance = init();
            }
            return instance;
        }
    };
})();

// 모듈로 내보내기
module.exports = Configuration.getInstance();