"use strict";
var net = require('net');
var path = require('path');
var chalk = require('chalk');
var Chrome = require('chrome-remote-interface');
var Promise = require('bluebird');
var _ = require('underscore');
var ctUtils = require('./chrome-tester-utils');
var logger = require('../logger');
var ChromeTesterServer = (function () {
    function ChromeTesterServer(params) {
        this.debuggingProtocol = {};
        this.debuggingProtocol.hostname = params.hostname;
        this.debuggingProtocol.port = params.port;
        this.scriptsInfo = [];
        this.statementsScope = [];
    }
    ChromeTesterServer.prototype.listen = function (params, cb) {
        var that = this;
        this.testerServer = {};
        this.testerServer.hostname = params.hostname;
        this.testerServer.port = params.port;
        Chrome({
            host: this.debuggingProtocol.hostname,
            port: this.debuggingProtocol.port
        }, function (chrome) {
            net.createServer(function (sock) {
                sock.on('data', function (data) {
                    var response = {};
                    data = JSON.parse(data);
                    logger.info('Client connected');
                    logger.info(chalk.gray('(' + data.method + ')').toString());
                    that.handleData(data.method, data.parameters, chrome).then(function handleResponse(res) {
                        response.error = false;
                        response.value = res;
                        sock.write(JSON.stringify(response));
                    }, function handleError(err) {
                        response.error = true;
                        response.value = err;
                        sock.write(JSON.stringify(response));
                    });
                });
                sock.on('close', function () {
                    logger.info('Client terminates connection');
                });
                sock.on('error', function (err) {
                    cb(err);
                });
            }).listen({
                host: that.testerServer.hostname,
                port: that.testerServer.port
            }, function (err) {
                cb((err) ? err : null);
            });
            chrome.on('Debugger.scriptParsed', function (params) {
                if (params.hasOwnProperty('url') && params.url !== undefined) {
                    if (params.url.length > 0) {
                        that.scriptsInfo.push({
                            'url': params.url,
                            'id': params.scriptId || undefined
                        });
                    }
                }
            });
            chrome.on('Debugger.paused', function (params) {
                var coverageObjInfo = that.getEvalParameters(that.functionI.coverageObjectName);
                chrome.Runtime.evaluate(coverageObjInfo, function (err, res) {
                    if (err) {
                        logger.error(chalk.gray('(Debugger.paused) ').toString() +
                            ' Unable to get the coverage object');
                        process.exit(-1);
                    }
                    else {
                        var coverageObjValue;
                        var updateStatement;
                        var updateBranch;
                        coverageObjValue = res.result.value;
                        updateStatement = ctUtils.updateFunctionStatement(that.lastCoverageObjectValue, coverageObjValue);
                        updateBranch = ctUtils.updateFunctionBranch(that.lastCoverageObjectValue, coverageObjValue);
                        if (updateStatement.update) {
                            that.cfgStatements.push(updateStatement.statementKey);
                        }
                        if (updateBranch.update) {
                            var bKey;
                            for (var k = 0; k < updateBranch.branchesKeys.length; k++) {
                                bKey = updateBranch.branchesKeys[k];
                                for (var j = 0; j < that.functionI.branches.length; j++) {
                                    if (bKey == that.functionI.branches[j].key) {
                                        that.functionI.branches[j].valuesCondition.push(updateBranch.conditionsResults[k]);
                                    }
                                }
                            }
                        }
                        that.lastCoverageObjectValue = _.clone(coverageObjValue);
                        if (that.updateScope) {
                            var scopeChainOfFoo = params.callFrames[0].scopeChain;
                            chrome.send('Runtime.getProperties', {
                                'objectId': scopeChainOfFoo[0].object.objectId
                            }, function (err, res) {
                                if (err) {
                                    logger.error(chalk.gray('(Debugger.paused) ').toString() +
                                        ' Unable to get properties of the current scope');
                                    process.exit(-1);
                                }
                                else {
                                    var currentScope = {};
                                    var injectedVariables = [];
                                    var varName;
                                    var varValue;
                                    var varType;
                                    for (var k = 0; k < res.result.length; k++) {
                                        try {
                                            varName = res.result[k].name;
                                            varValue = res.result[k].value.value;
                                            varType = res.result[k].value.type;
                                            if (varType === 'object') {
                                                injectedVariables.push({
                                                    'name': varName,
                                                    'objectId': res.result[k].value.objectId
                                                });
                                            }
                                            else {
                                                currentScope[varName] = varValue;
                                            }
                                        }
                                        catch (e) {
                                        }
                                    }
                                    ctUtils.resolveInjectedVariables(chrome, currentScope, injectedVariables, true, {}, function (err) {
                                        if (err) {
                                            logger.error(chalk.gray('(Debugger.paused) ').toString() +
                                                ' Unable to get values of objects');
                                            process.exit(-1);
                                        }
                                        that.statementsScope.push(currentScope);
                                        chrome.send('Debugger.stepOver', function (err, res) {
                                            if (err) {
                                                logger.error(chalk.gray('(Debugger.stepOver) ~ 1').toString());
                                                process.exit(-1);
                                            }
                                            else {
                                                that.updateScope = false;
                                            }
                                        });
                                    });
                                }
                            });
                        }
                        else {
                            that.updateScope = updateStatement.update;
                            chrome.send('Debugger.stepOver', function (err, res) {
                                if (err) {
                                    logger.error(chalk.gray('(Debugger.stepOver) ~ 1').toString());
                                    process.exit(-1);
                                }
                            });
                        }
                    }
                });
            });
        }).on('error', function (error) {
            cb(new Error('Unable to connect on the current tab of Chrome instance'));
        });
    };
    ChromeTesterServer.prototype.updateContext = function (leenaContext) {
        this.leenaContext = leenaContext;
    };
    ChromeTesterServer.prototype.updateConfiguration = function (leenaConfig) {
        this.leenaConfig = leenaConfig;
    };
    ChromeTesterServer.prototype.handleData = function (method, parameters, chrome) {
        var objName = _.isArray(parameters)
            ? parameters[0]
            : parameters;
        var params = _.isArray(parameters)
            ? parameters[1]
            : undefined;
        switch (method) {
            case 'getConfiguration':
                var getConfiguration = Promise.promisify(this.getConfiguration.bind(this));
                return getConfiguration(chrome);
            case 'getCoverageObject':
                var getCoverageObject_ = Promise.promisify(this.getCoverageObject.bind(this));
                return getCoverageObject_(objName, chrome);
            case 'getSourceFunction':
                var getSourceFunction_ = Promise.promisify(this.getSourceFunction.bind(this));
                return getSourceFunction_(objName, chrome);
            case 'getFunctionInstance':
                var getFunctionInstance_ = Promise.promisify(this.getFunctionInstance.bind(this));
                return getFunctionInstance_(objName, chrome);
            case 'executeFunction':
                var executeFunction_ = Promise.promisify(this.executeFunction.bind(this));
                return executeFunction_(objName, params, chrome);
            case 'executeFunctionWithDebugger':
                var executeFunctionWithDebugger_ = Promise.promisify(this.executeFunctionWithDebugger.bind(this));
                return executeFunctionWithDebugger_(objName, params, chrome);
            case 'setUrl':
                var setUrl_ = Promise.promisify(this.setUrl.bind(this));
                return setUrl_('file://' + path.join(objName, 'index.html'), chrome);
            default:
                var errorMessage = 'Undefined method \'' + method + '\'';
                return Promise.reject(new Error(errorMessage));
        }
    };
    ChromeTesterServer.prototype.getConfiguration = function (chrome, cb) {
        cb(null, this.leenaConfig);
    };
    ChromeTesterServer.prototype.getCoverageObject = function (coverageObject, chrome, cb) {
        var evalParams = this.getEvalParameters(coverageObject);
        chrome.Runtime.evaluate(evalParams, function (err, res) {
            if (err) {
                cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "evaluate" method'), null);
            }
            else {
                var isVariable = res.result.hasOwnProperty('type') &&
                    res.result.type === 'object' &&
                    res.result.hasOwnProperty('value') !== undefined &&
                    typeof res.result.value === 'object';
                if (isVariable) {
                    cb(null, res.result.value);
                }
                else {
                    cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "evaluate" method. "' +
                        coverageObject + '" is not a coverage object'), null);
                }
            }
        });
    };
    ChromeTesterServer.prototype.getSourceFunction = function (functionName, chrome, cb) {
        var evalParams = this.getEvalParameters(functionName + '.toString ()');
        chrome.Runtime.evaluate(evalParams, function (err, res) {
            if (err) {
                cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "getSourceFunction" method'), null);
            }
            else {
                var isFunction = res.result.hasOwnProperty('type') &&
                    typeof res.result.type === 'string' &&
                    res.result.hasOwnProperty('value') &&
                    typeof res.result.value === 'string';
                if (isFunction) {
                    res.result.value = res.result.value.replace(/\s+/g, ' ');
                    cb(null, res);
                }
                else {
                    cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "getSourceFunction" method. "' +
                        functionName + '" is not a function'), null);
                }
            }
        });
    };
    ChromeTesterServer.prototype.getFunctionInstance = function (functionName, chrome, cb) {
        var f;
        try {
            f = this.leenaContext.getFunction(functionName);
            cb(null, f);
        }
        catch (e) {
            cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "getFunctionInstance" method. "' +
                functionName + '" is not a function'), null);
        }
    };
    ChromeTesterServer.prototype.executeFunction = function (functionName, params, chrome, cb) {
        var evalParams = this.getEvalParameters(functionName + ' (' + params + ')');
        var that = this;
        chrome.Runtime.evaluate(evalParams, function (err, res) {
            if (err) {
                cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "executeFunction" method'), null);
            }
            else {
                that.leenaContext.executeFunction(functionName, function (err, newRes) {
                    if (err) {
                        cb(err, null);
                    }
                    else {
                        try {
                            res['function'] = that.leenaContext.getFunction(functionName);
                            cb(null, res);
                        }
                        catch (e) {
                            cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "executeFunction" method. "' +
                                functionName + '" is not a function'), null);
                        }
                    }
                });
            }
        });
    };
    ChromeTesterServer.prototype.executeFunctionWithDebugger = function (functionName, params, chrome, cb) {
        var that = this;
        this.cfgStatements = [];
        this.statementsScope = [];
        try {
            this.functionI = this.leenaContext.getFunction(functionName);
            this.setBreakpoint(this.functionI, params, chrome, function (err, res) {
                if (err) {
                    cb(err, null);
                }
                else {
                    var breakpointId = res.breakpointId;
                    var evalParams = that.getEvalParameters(functionName + ' (' + params + ')');
                    chrome.send('Runtime.evaluate', evalParams, function (err, res) {
                        if (err) {
                            cb(new Error(ChromeTesterServer.ERROR_PREFIX +
                                'unable to execute "Runtime.evaluate" method'), null);
                        }
                        else {
                            var rbParams = {
                                'breakpointId': breakpointId
                            };
                            var resFunctionExecution = res;
                            chrome.send('Debugger.removeBreakpoint', rbParams, function (err, res) {
                                if (err) {
                                    cb(new Error(ChromeTesterServer.ERROR_PREFIX +
                                        'unable to execute "Debugger.removeBreakpoint" method'), null);
                                }
                                else {
                                    chrome.send('Debugger.disable', function (err, res) {
                                        if (err) {
                                            cb(new Error(ChromeTesterServer.ERROR_PREFIX +
                                                'unable to execute "Debugger.disable" method'), null);
                                        }
                                        else {
                                            try {
                                                that.functionI.execute(that.lastCoverageObjectValue, function (err, res) {
                                                    if (err) {
                                                        cb(err, null);
                                                    }
                                                    else {
                                                        try {
                                                            resFunctionExecution['function'] = that.functionI;
                                                            resFunctionExecution['scope'] = that.statementsScope;
                                                            resFunctionExecution['cfgStatements'] = that.cfgStatements;
                                                            cb(null, resFunctionExecution);
                                                        }
                                                        catch (e) {
                                                            cb(new Error(ChromeTesterServer.ERROR_PREFIX +
                                                                'unable to execute "executeFunction" method. "' +
                                                                functionName + '" is not a function'), null);
                                                        }
                                                    }
                                                });
                                            }
                                            catch (e) {
                                                cb(e, null);
                                            }
                                        }
                                    });
                                }
                            });
                        }
                    });
                }
            });
        }
        catch (e) {
            cb(e, null);
        }
    };
    ChromeTesterServer.prototype.setBreakpoint = function (functionInstance, params, chrome, cb) {
        var that = this;
        var functionLocation;
        this.scriptsInfo = [];
        try {
            functionLocation = ctUtils.getLocationOfInstrumentedFunction(this.functionI);
            chrome.send('Debugger.enable', function (err, res) {
                if (err) {
                    cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "Debugger.enable" method'), null);
                }
                else {
                    var scriptId = -1;
                    var pathFile = functionInstance.pathFile;
                    var urlWithoutProtocol;
                    for (var k = 0; k < that.scriptsInfo.length; k++) {
                        urlWithoutProtocol = that.scriptsInfo[k].url.replace(/.*?:\/\//g, '');
                        if (urlWithoutProtocol === pathFile) {
                            scriptId = that.scriptsInfo[k].id;
                            break;
                        }
                    }
                    if (scriptId === -1) {
                        cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to get the scriptId'), null);
                    }
                    else {
                        var breakpointParams = {
                            'location': {
                                'columnNumber': functionLocation.column,
                                'lineNumber': (functionLocation.line - 1),
                                'scriptId': scriptId
                            }
                        };
                        chrome.send('Debugger.setBreakpoint', breakpointParams, function (errB, resB) {
                            if (err) {
                                cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "Debugger.setBreakpoint" method'), null);
                            }
                            else {
                                var evalParamsCoverageObject = that.getEvalParameters(functionInstance.coverageObjectName);
                                chrome.Runtime.evaluate(evalParamsCoverageObject, function (err, res) {
                                    if (err) {
                                        cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "evaluate" method'), null);
                                    }
                                    else {
                                        var isVariable = res.result.hasOwnProperty('type') &&
                                            res.result.type === 'object' &&
                                            res.result.hasOwnProperty('value') !== undefined &&
                                            typeof res.result.value === 'object';
                                        if (isVariable) {
                                            that.lastCoverageObjectValue = res.result.value;
                                            for (var k = 0; k < that.functionI.statements.length; k++) {
                                                that.functionI.statements[k].initializeValues(that.lastCoverageObjectValue.s);
                                            }
                                            for (var k = 0; k < that.functionI.branches.length; k++) {
                                                that.functionI.branches[k].initializeValues(that.lastCoverageObjectValue.b);
                                            }
                                            cb(null, resB);
                                        }
                                        else {
                                            cb(new Error(ChromeTesterServer.ERROR_PREFIX +
                                                'unable to execute "evaluate" method. "' +
                                                functionInstance.coverageObjectName +
                                                '" is not a coverage object'), null);
                                        }
                                    }
                                });
                            }
                        });
                    }
                }
            });
        }
        catch (e) {
            cb(e, null);
        }
    };
    ChromeTesterServer.prototype.setUrl = function (url, chrome, cb) {
        chrome.Page.navigate({
            url: url
        }, function (err, res) {
            if (err) {
                cb(new Error(ChromeTesterServer.ERROR_PREFIX + 'unable to execute "setUrl" method'), null);
            }
            else {
                cb(null, res);
            }
        });
    };
    ChromeTesterServer.prototype.getEvalParameters = function (expression) {
        return {
            expression: expression,
            objectGroup: '0',
            includeCommandLineAPI: true,
            doNotPauseOnExceptionsAndMuteConsole: true,
            returnByValue: true,
            generatePreview: true
        };
    };
    ChromeTesterServer.ERROR_PREFIX = '[ChromeTesterServer] Exception. Reason: ';
    return ChromeTesterServer;
}());
module.exports = ChromeTesterServer;
