"use strict";
var esprima = require('esprima');
var escodegen = require('escodegen');
var Promise = require('bluebird');
var Table = require('cli-table');
var loop_record_1 = require('./loop-record');
var lpUtils = require('./loop-record-utils');
var SymEval = require('../symbolic-evaluation');
var GT = (function () {
    function GT() {
        this.entries = [];
        this.initTable();
    }
    GT.prototype.update = function (pc, conditionAST, valueCondition, iteration, pathConstraint, stackBranch, executedConditions, M, S, cb) {
        if (!this.conditionsForUpdateAreSatisfied(pc, conditionAST, iteration, M, S)) {
            cb(null, executedConditions);
        }
        else {
            var that = this;
            var indexEntry;
            var D;
            var D_S;
            var LHS;
            var RHS;
            var operator;
            var lhs_less_rhs;
            var lhs_less_rhs_ast;
            var getD;
            var getD_S;
            LHS = conditionAST.expression.left;
            RHS = conditionAST.expression.right;
            operator = conditionAST.expression.operator;
            lhs_less_rhs = [
                escodegen.generate(LHS),
                escodegen.generate(RHS)
            ].join('-');
            lhs_less_rhs_ast = esprima.parse(lhs_less_rhs).body[0];
            getD = Promise.promisify(lpUtils.evaluateConcrete)(lhs_less_rhs_ast, M, S);
            getD_S = Promise.promisify(SymEval.evaluateSymbolic)(lhs_less_rhs_ast, 0, [], M, S, [], new loop_record_1.LoopRecord());
            Promise.all([getD, getD_S]).then(function (results) {
                D = results[0];
                D_S = results[1];
                that.handleEntry(pc, iteration, valueCondition, D, D_S, operator, pathConstraint, stackBranch, executedConditions, M, S, function (err, res) {
                    if (err) {
                        cb(err, null);
                    }
                    else {
                        cb(null, res);
                    }
                });
            }).catch(function (error) {
                cb(new Error(GT.ERROR_PREFIX + error.message), null);
            });
        }
    };
    GT.prototype.handleEntry = function (pc, iteration, B, D, D_S, operator, pathConstraint, stackBranch, executedConditions, M, S, cb) {
        var indexEntry;
        if (iteration >= 2) {
            if ((indexEntry = this.getEntryIndex(pc)) === -1) {
                throw new Error(GT.ERROR_PREFIX + 'unable to get property "' + pc + '" in the Guard table');
            }
        }
        if (iteration === 1) {
            var gtEntry;
            gtEntry = {};
            gtEntry.pc = pc;
            gtEntry.attr = {};
            gtEntry.attr.hit = 0;
            gtEntry.attr.B = B;
            gtEntry.attr.D = D;
            gtEntry.attr.D_S = D_S;
            gtEntry.attr.pclocs = [];
            gtEntry.attr.loc = pathConstraint.length - 1;
            if (gtEntry.attr.loc < 0) {
                gtEntry.attr.loc = 0;
            }
            indexEntry = this.entries.push(gtEntry) - 1;
        }
        else if (iteration === 2) {
            var dD;
            var dD_S;
            var EC;
            var EC_S;
            dD = D - this.entries[indexEntry].attr.D;
            this.entries[indexEntry].attr.dD = dD;
            dD_S = '(' + D_S + '- (' + this.entries[indexEntry].attr.D_S + '))';
            switch (operator) {
                case '=':
                    break;
                case '!=':
                    break;
                case '>':
                    break;
                case '>=':
                    break;
                case '<':
                    break;
                case '<=':
                    if (D > 0) {
                        if (dD < 0) {
                            this.entries[indexEntry].attr.Dcond_S = '((' + D_S + ') > 0)';
                            this.entries[indexEntry].attr.dDcond_S = '((' + dD_S + ') < 0)';
                            EC = this.entries[indexEntry].attr.D - this.entries[indexEntry].attr.dD - 1;
                            EC /= -(this.entries[indexEntry].attr.dD);
                            EC_S = '(' + this.entries[indexEntry].attr.D_S + '- (' + dD_S + ')-1) / (- (' + dD_S + '))';
                            this.entries[indexEntry].attr.EC = EC;
                            this.entries[indexEntry].attr.EC_S = EC_S;
                        }
                    }
                    break;
            }
        }
        var guessPreconditionsProm = Promise.resolve();
        var that = this;
        if (++this.entries[indexEntry].attr.hit !== iteration) {
            this.entries.splice(indexEntry, 1);
        }
        that.addEntryToTable(iteration, that.entries[indexEntry], B, D);
        if (this.entries[indexEntry].attr.B !== B &&
            this.entries[indexEntry].attr.pending &&
            this.entries[indexEntry].attr.EC + 1 === iteration) {
            guessPreconditionsProm = Promise.promisify(lpUtils.guessPreconditions)(pc, this, pathConstraint, stackBranch, executedConditions, M, S);
        }
        guessPreconditionsProm.then(function (newExecutedConditions) {
            if (that.entries[indexEntry].attr.B !== B ||
                (that.entries[indexEntry].attr.dD !== undefined &&
                    that.entries[indexEntry].attr.dD !== D - that.entries[indexEntry].attr.D)) {
                that.entries.splice(indexEntry, 1);
            }
            else {
                that.entries[indexEntry].attr.D = D;
                if (that.entries[indexEntry].attr.pclocs === undefined) {
                    that.entries[indexEntry].attr.pclocs = [];
                }
                var loc = pathConstraint.length - 1;
                if (loc < 0) {
                    loc = 0;
                }
                that.entries[indexEntry].attr.pclocs.push(loc);
            }
            cb(null, newExecutedConditions);
        }).catch(function (error) {
            cb(new Error(GT.ERROR_PREFIX + 'unable to handle entry. ' + error.message), null);
        });
    };
    GT.prototype.conditionsForUpdateAreSatisfied = function (pc, conditionAST, iteration, M, S) {
        if (!this.conditionHasSupportedOperator(conditionAST)) {
            return false;
        }
        return !(iteration > 1 && (this.getEntryIndex(pc) === -1));
    };
    GT.prototype.conditionHasSupportedOperator = function (conditionAST) {
        var supportedOperators;
        var hasSupportedOperator;
        supportedOperators = ['<', '<=', '>', '>=', '!=', '='];
        if (!conditionAST.hasOwnProperty('expression') || conditionAST.expression.type !== 'BinaryExpression') {
            return false;
        }
        hasSupportedOperator = (supportedOperators.indexOf(conditionAST.expression.operator) !== -1);
        return hasSupportedOperator;
    };
    GT.prototype.getEntryIndex = function (pc) {
        for (var k = 0; k < this.entries.length; k++) {
            if (this.entries[k].pc === pc) {
                return k;
            }
        }
        return -1;
    };
    GT.prototype.initTable = function () {
        var headTable;
        headTable = {
            head: ['Ith.', 'pc', 'oldB', 'oldD', 'B', 'D', 'dD', 'D_S', 'hit', 'EC'],
            colWidths: [8, 10, 10, 10, 10, 10, 10, 10, 10, 10]
        };
        this.table = new Table(headTable);
    };
    GT.prototype.addEntryToTable = function (iteration, modEntry, B, D) {
        this.table.push([
            iteration,
            modEntry.pc,
            B,
            D,
            modEntry.attr.B,
            modEntry.attr.D,
            (modEntry.attr.dD === undefined)
                ? 'undefined'
                : modEntry.attr.dD,
            modEntry.attr.D_S,
            modEntry.attr.hit,
            (modEntry.attr.EC === undefined)
                ? 'undefined'
                : modEntry.attr.EC,
        ]);
    };
    GT.prototype.toString = function () {
        return this.table.toString();
    };
    GT.ERROR_PREFIX = '[GT] Exception. Reason: ';
    return GT;
}());
module.exports = GT;
