(function () {
    "use strict";

    var util = require('util');
    var events = require('events');
    var net = require('net');
    var iconv = require('iconv-lite');
    var doubleConnect = require('../../../common/js/doubleConnect');
    var debug = 0;
    
    iconv.extendNodeEncodings();

    //define a constructor (object) and inherit EventEmitter functions
    function Telnet() {
        events.EventEmitter.call(this);
        if (false === (this instanceof Telnet)) {
            return new Telnet();
        }
        
        this.doubleConnectInstance = null;
        this.collectState = false;
        this.respTimer = null;
        this.callbackFlag = false;
        this.isSendYesOrNo = false;
        this.waitFlag = false;
        this.telnetSocket = null;
    }

    util.inherits(Telnet, events.EventEmitter);

    //* state: init -> connect -> login -> exec -> disconnect -> init
    //* login state: user/pass(opt) -> exec(opt) -> enable(opt) -> shell
    function changeState(self, next) {
        if (debug) console.log('[Telnet] change from', self.telnetState, 'to', next);
        self.telnetState = next;
    }

    Telnet.prototype.connect = function (opts) {
        var self = this;
        var host = (typeof opts.host !== 'undefined' ? opts.host : '127.0.0.1');
        var port = (typeof opts.port !== 'undefined' ? opts.port : 23);
        this.timeout = (typeof opts.timeout !== 'undefined' ? opts.timeout : 500);
        this.shellPrompt = (typeof opts.shellPrompt !== 'undefined' ? opts.shellPrompt : /(?:\/ )?#\s/);
        this.loginPrompt = (typeof opts.loginPrompt !== 'undefined' ? opts.loginPrompt : /login[: ]*$/i);
        this.passwordPrompt = (typeof opts.passwordPrompt !== 'undefined' ? opts.passwordPrompt : /Password: /i);
        this.username = (typeof opts.username !== 'undefined' ? opts.username : 'root');
        this.password = (typeof opts.password !== 'undefined' ? opts.password : 'guest');
        this.enable = (typeof opts.enable !== 'undefined' ? opts.enable : true);
        this.containShell = (typeof opts.containShell !== 'undefined' ? opts.containShell : false);
        this.enablePrompt = (typeof opts.enablePrompt !== 'undefined' ? opts.enablePrompt : /Password: /i);
        this.enablePassword = (typeof opts.enablePassword !== 'undefined' ? opts.enablePassword : 'enablepass');
        this.moreParser = (typeof opts.moreParser !== 'undefined' ? opts.moreParser : ' --More--           ');
        this.irs = (typeof opts.irs !== 'undefined' ? opts.irs : '\r\n');
        this.ors = (typeof opts.ors !== 'undefined' ? opts.ors : '\r\n');
        this.echoLines = (typeof opts.echoLines !== 'undefined' ? opts.echoLines : 1);
        this.pageSeparator = (typeof opts.pageSeparator !== 'undefined' ? opts.pageSeparator : '--More--');
        this.ignoreOutput = (typeof opts.ignoreOutput !== 'undefined' ? opts.ignoreOutput : false);
        this.ignoreOutputTimeout = (typeof opts.ignoreOutputTimeout !== 'undefined' ? opts.ignoreOutputTimeout : 1000);
        this.response = '';
        this.telnetState = null;
        this.enableShellPrompt = opts.enableShellPrompt;
        this.encoding = (typeof opts.encoding !== 'undefined' ? opts.encoding : 'utf8');
        this.respTimeout =  opts.respTimeout != undefined ? opts.respTimeout:180000;
        this.quickRspTimeOut = opts.quickRspTimeOut != undefined ? opts.quickRspTimeOut:5000;
        this.commonRspTimeOut = this.respTimeout;
        this.enableRespCheck = opts.enableRespCheck != undefined ? opts.enableRespCheck:true;
        this.endChar = '#';
    
        try{
            this.telnetSocket = net.createConnection({
                port: port,
                host: host
            }, function () {
                changeState(self, 'start');
                self.stringData = '';
                self.emit('connect');
            });
        } catch(e) {
            
        }

        //this.telnetSocket.setEncoding(this.encoding);
        
        this.telnetSocket.setTimeout(this.timeout, function () {
            if (self.telnetSocket._connecting === true) {
                // info: cannot connect; emit error and destroy
                self.emit('error', 'Cannot connect');
                self.telnetSocket.destroy();
            }
            else {
                self.emit('timeout');
            }
        });
        
        if (self.respTimer != null) {
            clearTimeout(self.respTimer);
            self.respTimer = null; 
        }

        this.telnetSocket.on('data', function (data) {
            if (self.doubleConnectInstance == null) {
                if (self.enableRespCheck == true) {
                    _resetRespTimer(self);
                }
                parseData(data, self);
            } else {
                self.doubleConnectInstance.parseData(data);
            }
        });

        this.telnetSocket.on('error', function (error) {
            if (self.respTimer != null) {
                clearTimeout(self.respTimer);
                self.respTimer = null; 
            }
            self.emit('error', error);
        });

        this.telnetSocket.on('end', function () {
            self.emit('end');
        });

        this.telnetSocket.on('close', function () {
            if (self.respTimer != null) {
                clearTimeout(self.respTimer);
                self.respTimer = null; 
            }

            self.emit('close');
        });
    };
    
    Telnet.prototype.execDoubleConnect = function(cmd, callback, param) {
        var self = this;
        
        self.doubleConnectInstance = new doubleConnect(self);
        self.doubleConnectInstance.exec(cmd, callback, param);
    }
    
    Telnet.prototype.writeToDevice = function (data) { //* XXX 状态?
        var self = this;
        
        self.telnetSocket.write(data);
    };
    
    function _timerCallback (self) {
        if (self.respTimer != null) {
            self.respTimer = null;
        }
        
        if (self.callbackFlag == true) {
            self.emit('responseready');
            return;
        } 

        if (self.cmd == 'terminal no monitor' && self.stringData.indexOf('#') != -1) {
            self.emit('responseready');
            return;
        }

        if (self.collectState == true) {
            self.collectState = false;

            self.telnetSocket.write(self.ors, self.encoding, function(error, result) {
                //console.log('cmd: ' + cmd + ' length: ' + cmd.length);
            });
            
            var timeout = self.commonRspTimeOut - self.respTimeout;
            self.respTimer = setTimeout(function() {
                _timerCallback(self);
            }, timeout);
            return;
        }

        if (!self.isSendYesOrNo) {
            if (self.stringData.indexOf('Y/N') != -1 || self.stringData.indexOf('N/y') != -1) {
                self.isSendYesOrNo = true;

                self.telnetSocket.write('Y\r\n', function(error, result) {
                //console.log('cmd: ' + cmd + ' length: ' + cmd.length);
                });
                
                var timeout = self.commonRspTimeOut - self.respTimeout;
                self.respTimer = setTimeout(function() {
                    _timerCallback(self);
                }, timeout);
                return;
            }
        }
    
        self.emit('error', 'response timeout');
        return;
    }
    
    function _resetRespTimer(self) {
        if (self.respTimer == null) {
            return;
        }
        
        clearTimeout(self.respTimer);
        self.respTimer = setTimeout(function() {
            _timerCallback(self);
        }, self.respTimeout);
    }

    Telnet.prototype.getLocalIp = function() {
        var self = this;
        if (self.telnetSocket === null) {
            return null;
        }
        return self.telnetSocket.localAddress;
    };
    
    function _determinRespTimeOut(self, cmd) {
        var specialCmds = ['vtty', 'line-detect','@4esc', 'efmp_demo', 'terminal no monitor'];
        
        for (var i = 0; i < specialCmds.length; i++) {
            if (cmd.indexOf(specialCmds[i]) != -1) {
                self.respTimeout = self.quickRspTimeOut;
                return;
            }
        }
        
        self.respTimeout = self.commonRspTimeOut;
    }

    Telnet.prototype.exec = function (cmd, opts, params, callback) {
        var self = this;
        this.cmd = cmd;
        cmd += this.ors;
        self.isSendYesOrNo = false;
        _determinRespTimeOut(self, cmd);
        if (opts && opts instanceof Function) {
            callback = opts;
        }
        else if (opts && opts instanceof Object) {
            self.shellPrompt = opts.shellPrompt || self.shellPrompt;
            self.loginPrompt = opts.loginPrompt || self.loginPrompt;
            self.timeout = opts.timeout || self.timeout;
            self.irs = opts.irs || self.irs;
            self.ors = opts.ors || self.ors;
            self.echoLines = opts.echoLines || self.echoLines;
            self.ignoreOutput = opts.ignoreOutput || self.ignoreOutput;
            self.ignoreOutputTimeout = opts.ignoreOutputTimeout || self.ignoreOutputTimeout;
        }

        if (this.telnetSocket.writable) {
            if (self.ignoreOutput === true) {
                setTimeout(function () {
                    self.ignoreOutput = false;
                    callback(null);
                }, self.ignoreOutputTimeout);
            } else {
                self.once('responseready', function () {
                    if (callback && self.cmdOutput !== undefined) {
                        var fs = require('fs');
                        if (debug == 1) {
                            fs.writeFileSync('test.txt', self.cmdOutput.join('\n'));
                        }
                        
                        if (self.enableRespCheck == true) {
                            clearTimeout(self.respTimer);
                            self.respTimer = null;
                        }
                        
                        if (self.collectState == true) {
                            self.collectState = false;
                        }
                        
                        self.waitFlag = false;
                        self.stringData = '';
                        callback(self.cmdOutput.join('\n'));
                    }
                    else if (callback && self.cmdOutput === undefined) {
                        callback(null);
                    }
                });
            }
            
            if (params != undefined) {
                if (params.endChar != undefined) {
                    self.endChar = params.endChar;
                } else {
                    self.endChar = '#';
                }
                
                if (params.waitFlag != undefined) {
                    self.waitFlag = params.waitFlag;
                } else {
                    self.waitFlag = false;
                }
            } else {
                self.endChar = '#';
                self.waitFlag = false;
            } 
            
            if (cmd.indexOf('@4esc') != -1 || cmd.indexOf('@5@') != -1) {
                self.cmdOutput = ['not support for telnet'];
                self.emit('responseready');
                return;
            } else if (cmd.indexOf('@ctrl+x') != -1) {
                self.telnetSocket.write(Buffer('18', 'hex'),self.encoding, function(error, result) {
                    //console.log('cmd: ' + cmd + ' length: ' + cmd.length);
                });
            }else {
                if (cmd.indexOf('vtty') != -1 || cmd.indexOf('efmp_demo') != -1) {
                    self.collectState = true;
                }
                this.telnetSocket.write(cmd, self.encoding, function () {
                });
            }
            
            changeState(self, 'response');
            self.emit('writedone');

            if (self.enableRespCheck == true) {
                self.respTimer = setTimeout(function() {
                    if (self.respTimer != null) {
                        self.respTimer = null;
                    }
                    
                    if (self.collectState == true) {
                        self.telnetSocket.write(self.ors, self.encoding, function(error, result) {
                            //console.log('cmd: ' + cmd + ' length: ' + cmd.length);
                        });
                        
                        self.collectState = false;
                        var timeout = self.commonRspTimeOut - self.respTimeout;
                        self.respTimer = setTimeout(function(){
                            _timerCallback(self);
                        },timeout);
                    } else {
                        self.emit('error', 'response timeout');
                    }
                }, self.respTimeout);
            }
        } else {
            callback(new Error("Socket not writable"));
        }
    };

    Telnet.prototype.end = function () {
        var self = this;
        try {
            this.telnetSocket.end();
            this.telnetSocket.destroy();  // 一半情况下只有错误的时候才调用，单独调用end可以满足一般情况
        } catch(e) {
            console.error(e);
        }
      
        self.emit('close');
    };

    Telnet.prototype.destroy = function () {
        this.telnetSocket.destroy();
    };
    
    Telnet.prototype.resetInstance = function () {
        this.doubleConnectInstance = null;
    };
    
        
    function changeEncoding(resp, encoding) {
        if (encoding == 'utf8') {
            return resp.toString();
        }

        var iconv = require('iconv-lite');
        return iconv.decode(resp, encoding);
    }

    function parseData(chunk, telnetObj) {
        var promptIndex = '';
        var tempStringData = '';
        var tempString = '';
        
        /*if (chunk[0] === 255 && chunk[1] !== 255) {
            telnetObj.stringData = '';
            var negReturn = negotiate(telnetObj, chunk);

            if (negReturn === undefined) {
                return;
            }
            else {
                chunk = negReturn;
            }
        }*/

        if (telnetObj.ignoreOutput === true) {
            telnetObj.stringData = '';
            return;
        }

        if (telnetObj.telnetState === 'start') {
            changeState(telnetObj, 'getprompt'); //* XXX 这里的状态转换可能有问题
        }

        if (telnetObj.telnetState === 'getprompt') {
            tempStringData = changeEncoding(chunk, telnetObj.encoding);
            if (debug) console.log('tempStringData', tempStringData);

            var processed = false;

            //* user/pass?
            if (!processed) {
                if (tempStringData.indexOf(telnetObj.loginPrompt) !== -1) {
                    changeState(telnetObj, 'login');
                    login(telnetObj, 'username');
                    processed = true;
                }
            }

            //* exec?
            if (!processed) {
                promptIndex = tempStringData.indexOf(telnetObj.enableShellPrompt);
                if (promptIndex !== -1) {
                    changeState(telnetObj, 'enable'); //* XXX 这里改变会更好
                    login(telnetObj, 'enable');
                    processed = true;
                }
            }

            //* enable?
            if (!processed) {
                if (tempStringData.indexOf(telnetObj.passwordPrompt) !== -1) {
                    changeState(telnetObj, 'login');
                    login(telnetObj, 'password');
                    processed = true;
                }
            }

            //* shell?
            if (!processed) {
                promptIndex = tempStringData.indexOf(telnetObj.shellPrompt);
                if (promptIndex !== -1) {
                    telnetObj.shellPrompt = tempStringData.substring(promptIndex);
                    changeState(telnetObj, 'sendcmd');
                    telnetObj.stringData = '';
                    telnetObj.emit('ready', telnetObj.shellPrompt);
                    processed = true;
                }
            }
        }
        else if (telnetObj.telnetState === 'enable') {
            tempStringData = changeEncoding(chunk, telnetObj.encoding);
            if (debug) console.log('tempStringData', tempStringData);

            var processed = false;

            //* enable pass?
            if (!processed) {
                if (tempStringData.indexOf(telnetObj.enablePrompt) !== -1) {
                    changeState(telnetObj, 'login');
                    login(telnetObj, 'enablePassword');
                    processed = true;
                }
            }

            //* shell?
            if (!processed) {
                promptIndex = tempStringData.indexOf(telnetObj.shellPrompt);
                if (promptIndex !== -1) {
                    telnetObj.shellPrompt = tempStringData.substring(promptIndex);
                    changeState(telnetObj, 'sendcmd');
                    telnetObj.stringData = '';
                    telnetObj.emit('ready', telnetObj.shellPrompt);
                    processed = true;
                }
            }
        }
        else if (telnetObj.telnetState === 'getenprompt') {
            tempStringData = changeEncoding(chunk, telnetObj.encoding);
            if (debug) console.log('tempStringData', tempStringData);
    
            if (tempStringData.indexOf(telnetObj.enableShellPrompt) !== -1) {
                changeState(telnetObj, 'login');
                login(telnetObj, 'enable');
            }
        }
        else if (telnetObj.telnetState === 'response') {
            tempStringData = changeEncoding(chunk, telnetObj.encoding);
            if (debug) console.log('tempStringData', tempStringData);

            telnetObj.stringData += tempStringData;

            var hasPageSep = false;
            if (telnetObj.stringData.indexOf(telnetObj.pageSeparator) !== -1) {
                telnetObj.stringData = telnetObj.stringData.replace(new RegExp(telnetObj.moreParser,"gm"), '');

                if (debug == 1) {
                    console.log("pageSeparator is  " + telnetObj.pageSeparator);
                }

                hasPageSep = true;
            }
            
            if (telnetObj.stringData.indexOf('\n') == -1) {
                return;
            }
            
            var lineNum = telnetObj.stringData.split('\n').length;
            if (lineNum < 2) {
                console.log('lineNum = ' + lineNum);
                return;
            }
            
            var tmpStr = tempStringData.trim();
            var exFlg = false;
            if (tmpStr.length > telnetObj.endChar.length && tmpStr.length >= 1 && telnetObj.endChar.length > 1) {
                var str = tmpStr.substr(tmpStr.length - telnetObj.endChar.length, telnetObj.endChar.length);
                if (str == telnetObj.endChar) {
                    exFlg = true;
                    console.log('return by exFlag');
                }
            }
            
            if (tmpStr.length >= 1 && (exFlg || tmpStr[tmpStr.length - 1] == telnetObj.endChar || tmpStr[tmpStr.length - 1] == '#')) {
                telnetObj.cmdOutput = telnetObj.stringData.split(telnetObj.irs);
                if (telnetObj.containShell === true) {
                    if (telnetObj.echoLines === 1) {
                        telnetObj.cmdOutput.shift();
                    }
                    
                    telnetObj.cmdOutput.pop();
                }
                
                if (telnetObj.waitFlag == false) {
                    telnetObj.emit('responseready');
                } else {
                    telnetObj.callbackFlag = true;    
                }
                return;
            }
            
             if (hasPageSep) {
                telnetObj.telnetSocket.write(Buffer('20', 'hex'));
                return;
            }
        }
    }

    function login(telnetObj, handle) {
        if (handle === 'username') {
            if (telnetObj.telnetSocket.writable) {
                telnetObj.telnetSocket.write(telnetObj.username + telnetObj.ors, telnetObj.encoding, function () {
                    changeState(telnetObj, 'getprompt');
                });
            }
        }
        else if (handle === 'password') {
            if (telnetObj.telnetSocket.writable) {
                telnetObj.telnetSocket.write(telnetObj.password + telnetObj.ors, telnetObj.encoding, function () {
                    // if (telnetObj.enable) { //* XXX 为什么需要 enable 这个变量
                    //     changeState(telnetObj, 'getenprompt');
                    // } else {
                        changeState(telnetObj, 'getprompt');
                    // }
                });
            }
        }
        else if (handle === 'enable') {
            if (telnetObj.telnetSocket.writable) {
                telnetObj.telnetSocket.write("en" + telnetObj.ors, telnetObj.encoding, function () {
                    changeState(telnetObj, 'enable');
                });
            }
        }
        else if (handle === 'enablePassword') {
            if (telnetObj.telnetSocket.writable) {
                telnetObj.telnetSocket.write(telnetObj.enablePassword + telnetObj.ors,telnetObj.encoding, function () {
                    changeState(telnetObj, 'getprompt');
                });
            }
        }
    }

    function negotiate(telnetObj, chunk) {
        // info: http://tools.ietf.org/html/rfc1143#section-7
        // refuse to start performing and ack the start of performance
        // DO -> WONT; WILL -> DO
        var packetLength = chunk.length, negData = chunk, cmdData, negResp;

        for (var i = 0; i < packetLength; i += 3) {
            if (chunk[i] !== 255) {
                negData = chunk.slice(0, i);
                cmdData = chunk.slice(i);
                break;
            }
        }

        negResp = negData.toString('hex').replace(/fd/g, 'fc').replace(/fb/g, 'fd');
        
        if (telnetObj.telnetSocket.writable) {
            telnetObj.telnetSocket.write(Buffer(negResp, 'hex'));
        }

        if (cmdData !== undefined) {
            return cmdData;
        }
    }

    module.exports = Telnet;
}());
