/**
 * Project      : AMDA-NG4
 * Name         : CalculatorUI.js
 * @class   amdaDesktop.ExplorerModule 
 * @extends Ext.util.Observable
 * @brief   Calculator Plugin used in SearchUI and ParameterUI
 * @author  elena        
 */


/**
 * @plugin calculator
 * @extends Ext.util.Observable 
 * @ptype calculator
 */

var CalculatorData = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '(', ')', '[', ']', '+', '-', '*', '/', '^', '.', '>', '>=', '=', '!=', '<=', '<', '&', '|'];

Ext.define('amdaUI.PromptArgsWin', {
    extend: 'Ext.window.Window',

    fieldsForm: null,
    applyCallback: null,

    constructor: function(config) {
        this.init(config);
        this.callParent(arguments);
    },

    promptArgs : function(prompts, fn, scope) {
        var me = this;
        if (fn)
            this.applyCallback = Ext.Function.bind(fn, scope);
        else
            this.applyCallback = null;
        this.fieldsForm.removeAll();
        prompts.each(function(promptObj, index) {
            switch (promptObj.get('type')) {
                case 'float':
                    me.addFloatField(promptObj.get('prompt'), index, promptObj.get('default'));
                    break;
                case 'list':
                    me.addComboField(promptObj.get('prompt'), promptObj.get('subtype'), index, promptObj.get('default'));
                    break;
                case 'boolean':
                    me.addBooleanField(promptObj.get('prompt'), index, promptObj.get('default'));
                    break;
                case 'string':
                    me.addStringField(promptObj.get('prompt'), index, promptObj.get('default'));
                    break;
                default:
                    console.log('Unknown arg type: ' + promptObj.get('type'));
                    break;
            }
        });
        this.show();
    },

    addFloatField: function(label, index, defaultVal) {
        var field = this.fieldsForm.add({
            xtype: 'numberfield',
            name: 'arg' + index,
            fieldLabel: label,
            decimalPrecision : 20,
            allowBlank: false,
            hideTrigger: true,
            keyNavEnabled: false,
            mouseWheelEnabled: false
        });
	if (defaultVal != "") {
		defaultVal = parseFloat(defaultVal);
		if (!isNaN(defaultVal)) {
			field.setValue(defaultVal);
		}
	}
    },

    addComboField: function(label, listtype, index, defaultVal) {
        var argslistStore = amdaUI.CalculatorUI.argslistStore;
        if (!argslistStore) {
            return;
        }

        var valuesStore = null;
        argslistStore.each(function(arglist) {
            if (arglist.get('id') == listtype) {
                valuesStore = arglist.valuesStore;
            }
        });
        if (!valuesStore) {
            return;
        }


	var listData = [];
	valuesStore.each(function (item) {
		listData.push({'key': item.get('key'), 'value': item.get('value'), 'info': item.get('info')});
	});

	var comboStore = Ext.create('Ext.data.Store', {
            fields: ['key', 'value', 'info'],
            data : listData
        });

        var field = this.fieldsForm.add({
             xtype: 'combo',
             name: 'arg' + index,
             fieldLabel: label,
             store: comboStore,
             queryMode: 'local',
             displayField: 'value',
             valueField: 'key',
             editable: false,
             listConfig: {
                 getInnerTpl: function() {
                     return '<div data-qtip="{info}">{value} </div>';
                 }
             }
        });

        if (defaultVal != "") {
            field.setValue(defaultVal);
        }
    },

    addBooleanField: function(label, index, defaultVal) {
        var field = this.fieldsForm.add({
            xtype: 'checkbox',
            name: 'arg' + index,
            boxLabel: label,
            inputValue: '1',
            uncheckedValue: '0'
        });

        if (defaultVal != "") {
            field.setValue(defaultVal != '0');
        }
    },

    addStringField: function(label, index, defaultVal) {
        var field = this.fieldsForm.add({
            xtype: 'textfield',
            name: 'arg' + index,
            fieldLabel: label
        });

        if (defaultVal != "") {
            field.setValue(defaultVal);
        }
    },

    init : function(config) {
        this.fieldsForm = Ext.create('Ext.form.Panel', {
            layout: {
                type: 'vbox',
                pack: 'start',
                align: 'stretch',
                defaultMargins: {top: 5, right: 5, bottom: 0, left: 5}
            }
        });

        var myConf = {
            layout: 'fit',
            title: 'Function arguments',
            modal: true,
            closeAction: 'hide',
            width: 300,
            height: 200, 
            items: [
                this.fieldsForm
            ],
            buttons: [
                {
                    text: 'Apply',
                    scope : this,
                    handler: function(bt,event) {
                        if (this.applyCallback)
                            this.applyCallback(this.fieldsForm.getValues());
                        this.close();
                    },
                },
                {
                     text: 'Cancel',
                     scope : this,
                     handler: function(bt,event) {
                         this.close();
                     }
                }
            ] 
        };

        Ext.apply(this, Ext.apply(arguments, myConf));
    }
});

    

Ext.define('amdaUI.CalculatorUI', {
    extend: 'Ext.util.Observable',

    requires: [
        'amdaModel.Constant',
        'amdaModel.Function',
        'amdaUI.PromptArgsWin'
    ],

    alias: 'plugin.calculator',

    statics: {
        constantStore: null,
        functionStore: null,
        argslistStore: null
    },

    win: null,
    promptArgsWin: null,

    constructor: function (config) {
        Ext.apply(this, config);
        this.callParent(arguments);
    },

    init: function (cmp)
    {
        this.promptArgsWin = Ext.create('amdaUI.PromptArgsWin', {});

        this.hostCmp = cmp;
        this.hostCmp.on({
            scope: this,
            added: function () {
                this.hostCmp.ownerCt.on({
                    render: this.onRender,
                    show: this.onShow,
                    hide: this.onHide,
                    scope: this});
            }
        });
    },

    onRender: function ()
    {
        this.win = new Ext.Window({
            width: 350,
            height: 170,
            x: 380, y: 0,
            baseCls: 'x-panel',
            title: 'Tools For ' + this.context + ' Construction',
            layout: 'fit',
            closable: false,
            collapsible: true,
            constrain: true,
            floating: true,
            ghost: false,
            renderTo: this.hostCmp.id,
            items: this.getFormConfig(),
            listeners: {
                boxready: function (w)
                {
                    if (w.y + w.height > myDesktopApp.desktop.el.getHeight())
                        w.el.setY((myDesktopApp.desktop.el.getHeight() - w.height > 0) ? myDesktopApp.desktop.el.getHeight() - w.height : 0);
                }
            },
            getConstrainVector: function (constrainTo) {
                var me = this;
                if (me.constrain || me.constrainHeader) {
                    constrainTo = constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container || me.el.getScopeParent();
                    return (me.constrainHeader ? me.header.el : me.el).getConstrainVector(constrainTo);
                }
            }
        });

        //load constants store
        if (!amdaUI.CalculatorUI.constantStore)
        {
            amdaUI.CalculatorUI.constantStore = Ext.create('Ext.data.Store', {model: 'amdaModel.Constant'});
            amdaUI.CalculatorUI.constantStore.load({
                scope: this,
                callback: function (records, operation, success)
                {
                    this.createAllConstantBtns();
                }
            });
        } else
            this.createAllConstantBtns();

        //load functions store
        if (!amdaUI.CalculatorUI.functionStore)
        {
            amdaUI.CalculatorUI.functionStore = Ext.create('Ext.data.Store', {model: 'amdaModel.Function'});
            amdaUI.CalculatorUI.functionStore.load({
                scope: this,
                callback: function (records, operation, success)
                {
                    this.createAllBtns();
                }
            });
        } else
            this.createAllBtns();
    
        if (!amdaUI.CalculatorUI.argslistStore)
        {
            amdaUI.CalculatorUI.argslistStore = Ext.create('Ext.data.Store', {model: 'amdaModel.ArgList'});
            amdaUI.CalculatorUI.argslistStore.load({
                scope: this,
                callback: function (records, operation, success)
                {
                }
            });
        }
    },

    onShow: function () {
        this.win.show();
        var parentWin = this.hostCmp.findParentByType('window');

        if (parentWin.getId() === myDesktopApp.dynamicModules.param.id) {
            this.win.setPosition(335, 10);
        } else {

            var posX = parentWin.getWidth() - this.win.getWidth() - 30;
            var posY = parentWin.getHeight() - this.win.getHeight() - 110 - 30/*20*/;
            this.win.setPosition(posX, posY);//(420,290);
        }
    },

    onHide: function ()
    {
        this.win.hide();
    },

    getFormConfig: function () {
        return {
            xtype: 'tabpanel',
            border: false, frame: true, plain: true,
            enableTabScroll: true,
            defaults: {frame: true, border: false, plain: true, autoScroll: true},
            activeTab: 0,
            items: [{
                    title: 'Calculator', layout: 'column',
                    defaults: {xtype: 'button', columnWidth: .11},
                    items: this.getItems('Calculator')
                }, {
                    title: 'Constants', xtype: 'tabpanel', //iconCls: 'tabs',
                    enableTabScroll: true, tabPosition: 'bottom',
                    defaults: {frame: true, border: false, plain: true, layout: 'column', autoScroll: true},
                    activeTab: 0,
                    id: 'calc_tab_const_id'
                }, {
                    title: 'Math', xtype: 'tabpanel', //iconCls: 'tabs',
                    enableTabScroll: true, tabPosition: 'bottom',
                    defaults: {frame: true, border: false, plain: true, layout: 'column', autoScroll: true},
                    activeTab: 0,
                    id: 'calc_tab_math_id'
                },
                {
                    title: 'Statistics', xtype: 'tabpanel', //iconCls: 'tabs',
                    enableTabScroll: true, tabPosition: 'bottom',
                    defaults: {frame: true, border: false, plain: true, layout: 'column', autoScroll: true},
                    activeTab: 0,
                    id: 'calc_tab_stat_id'
                },
           {
                    title: 'Space', xtype: 'tabpanel', //iconCls: 'tabs',
                    enableTabScroll: true, tabPosition: 'bottom',
                    defaults: {frame: true, border: false, plain: true, layout: 'column', autoScroll: true},
                    activeTab: 0,
                    id: 'calc_tab_func_id'
                }
                   ]
        };
    },

    /**
     * Prompt any argument to user before processing Formula
     * @param sel selected text
     * @param currentBtn calculator button pressed
     * @param params array of parameters
     */
    preProcessFormula: function (sel, currentBtn, params) {

        if (currentBtn.initialConfig.args != 0 && currentBtn.initialConfig.prompts.count() > 0) {
            // Prompt for user precision and process the result using a callback
            /*Ext.Msg.prompt('Argument', currentBtn.initialConfig.prompts.getAt(0).get('prompt'), function (bt2, text) {
                if (bt2 === 'ok') {
                    var afterParamsText = text + ")";
                    //TODO: more than one args and prompt
                    this.processFormula(sel, currentBtn, params, afterParamsText);
                }
            }, this);*/
            this.promptArgsWin.promptArgs(currentBtn.initialConfig.prompts, function (values) {
                var afterParamsText = '';
                Ext.Object.each(values, function(key, value) {
                    afterParamsText += (afterParamsText == '' ? '' : ',');
                    afterParamsText += value;
                });
                afterParamsText += ')';
                this.processFormula(sel, currentBtn, params, afterParamsText);
            }, this);
        } else {
            if ( currentBtn.initialConfig.prompt_param != "" ) {
                 Ext.Msg.alert('Input Parameters', currentBtn.initialConfig.prompt_param);
            }
            this.processFormula(sel, currentBtn, params, ")");
        }// endif prompt
    },

    processFormula: function (sel, currentBtn, params, afterParamsText) {
        // calculation of the required number of parameters
        var nbParams = currentBtn.initialConfig.args;
        var remainingParams = nbParams - params.length;
        var fnText = currentBtn.text.split('(');
        var newConstruction = sel.beforeText + fnText[0] + "(";
        // if there at least one parameter selected
        if (params.length) {
            for (var i = 0; i < params.length; i++) {
                if (i > 0) {
                    newConstruction += ",";
                }
                newConstruction += params[i];
            }
        }
        // we keep position
        var afterParameterPos = newConstruction.length;
        for (var i = 0; i < remainingParams-1; i++) {
            newConstruction += ',';
        }
        if(afterParamsText !=  ")")
            newConstruction += ','
        newConstruction += afterParamsText;
        var caretPos = newConstruction.length;
        newConstruction += sel.afterText;
        this.hostCmp.constructionField.setValue(newConstruction);

        // If we haven't the right number of selected parameters
        if (remainingParams > 0) {
            var stringParamRequired = (remainingParams) + " parameter(s)";
            Ext.Msg.alert('Caution', 'you\'ll have to add ' + stringParamRequired + ' to apply this function',
                    function () {
                        // set Caret Position at placement of required parameter in function
                        this.hostCmp.constructionField.setCaretPosition(afterParameterPos);
                    }, this
                    );
        } else if (remainingParams < 0) {
            var stringParamRequired = (-remainingParams) + " parameter(s)";

            Ext.Msg.show({
                title: 'Caution',
                msg: 'you\'ll have to remove ' + stringParamRequired + ' to apply this function',
                buttons: Ext.Msg.OK,
                // animEl: 'elId',
                icon: Ext.MessageBox.WARNING,
                fn:function () {
                        // set Caret Position at placement of required parameter in function
                        this.hostCmp.constructionField.setCaretPosition(afterParameterPos);
                    },
                scope: this
            });
        } else {
            // set Caret Position after inserted Text
            this.hostCmp.constructionField.setCaretPosition(caretPos);
        }
    },

    /**
     * This method construct an array of arguments into selected text
     * @param selectedText the selection to parse
     * @param parseIndex the index to start parsing
     * @return the arguments array
     */
    parseArgsInFormula: function (selectedText, parseIndex) {

        if (!selectedText || selectedText == "") {
            return [];
        } else {
            var params = [];
            var startIndex = parseIndex;
            var curIndex = parseIndex;
            var openBrace = 0;
            var sep = 0;
            var closeBrace = 0;

            // while there is a separator
            while (sep != -1) {
                openBrace = selectedText.indexOf("(", curIndex);
                sep = selectedText.indexOf(",", curIndex);
                closeBrace = selectedText.indexOf(")", curIndex);

                // if there's an open bracket and no close bracket or inversely
                if (openBrace != -1 && closeBrace == -1 || openBrace == -1 && closeBrace != -1) {
                    // invalid selection
                    return -1;
                }

                // if there's a separator and opening brackets into selection
                if (sep != -1 && openBrace != -1) {
                    // if brace is before separator 
                    if (openBrace < sep) {
                        curIndex = this.getEndBracket(selectedText, openBrace + 1);
                        if (curIndex === -1) {
                            return -1;
                        }
                    } else {// else separator is before brace
                        params.push(selectedText.substring(startIndex, sep));
                        startIndex = curIndex = sep + 1;
                    }
                    // if there's only separators into selection
                } else if (sep != -1) {
                    params.push(selectedText.substring(startIndex, sep));
                    startIndex = curIndex = sep + 1;
                }
            }
            params.push(selectedText.substring(startIndex, selectedText.length));
            return params;
        }
    },

    getEndBracket: function (string, indOpenBrace) {
        // we search for the corresponding end brace (after open bracket)
        var currentIndex = indOpenBrace;
        var nextCloseBrace = 0;
        var nextOpenBrace = 0;
        var braceLevel = 1;
        while (nextCloseBrace !== -1 && braceLevel !== 0) {
            // get index of next opening bracket
            nextOpenBrace = string.indexOf("(", currentIndex);
            // get index of next closing bracket
            nextCloseBrace = string.indexOf(")", currentIndex);
            // if both exist
            if (nextOpenBrace != -1 && nextCloseBrace != -1) {
                // if opening bracket is before closing one
                if (nextOpenBrace < nextCloseBrace) {
                    currentIndex = nextOpenBrace + 1;
                    braceLevel++;
                } else { // if closing bracket is before opening one
                    currentIndex = nextCloseBrace + 1;
                    braceLevel--;
                }
                // if there's only a next opening bracket
            } else if (nextOpenBrace != -1 && nextCloseBrace == -1) {
                currentIndex = nextOpenBrace + 1;
                braceLevel++;
                // if there's only a next closing bracket
            } else if (nextOpenBrace == -1 && nextCloseBrace != -1) {
                currentIndex = nextCloseBrace + 1;
                braceLevel--;
            }
        }
        // if no level imbrication left return index after closing bracket of block else -1
        return braceLevel == 0 ? currentIndex : -1;
    },

    getCalculatorBtn: function ()
    {
        var btns = [];

        Ext.each(CalculatorData, function (c) {
            btns.push({
                text: c,
                scope: this,
                handler: function (b, e) {
                    // keep selection into construction field
                    var selection = this.hostCmp.constructionField.getSelection();
                    // the new value of construction field
                    var newConstruction = "";
                    // replacement of selection into construction field by text of clicked button
                    newConstruction = selection.beforeText + b.text;
                    var caretPos = newConstruction.length;
                    newConstruction += selection.afterText;
                    this.hostCmp.constructionField.setValue(newConstruction);
                    // set Caret Position after inserted Text
                    this.hostCmp.constructionField.setCaretPosition(caretPos);
                }
            })
        },
                this
                );

        return btns;
    },

    createAllBtns: function ()
    {  
        // group math 
        amdaUI.CalculatorUI.functionStore.filter('group', 'math');       
        this.createFunctionBtns('MathFunctions', 'Simple Maths','#calc_tab_math_id');
        this.createFunctionBtns('VectorFunctions', 'Vector Functions', '#calc_tab_math_id');
        amdaUI.CalculatorUI.functionStore.clearFilter();
        
        // group space
        amdaUI.CalculatorUI.functionStore.filter('group', 'space');
        this.createFunctionBtns('PhysicsFunctions', 'Derived', '#calc_tab_func_id');
        this.createFunctionBtns('ModelFunctions', 'Models', '#calc_tab_func_id');
        this.createFunctionBtns('AmdaFunctions', 'TimeShift', '#calc_tab_func_id');
        this.createFunctionBtns('FramesFunctions', 'Frames', '#calc_tab_func_id');
        this.createFunctionBtns('FilterFunctions', 'Filter', '#calc_tab_func_id');
        amdaUI.CalculatorUI.functionStore.clearFilter();
        
        // group stat
        amdaUI.CalculatorUI.functionStore.filter('group', 'stat');
        this.createFunctionBtns('TimeFunctions', 'Statistics', '#calc_tab_stat_id');
        this.createFunctionBtns('FunctionsSliding', 'Statistics/Sliding','#calc_tab_stat_id');
        amdaUI.CalculatorUI.functionStore.clearFilter();
    },
    
    createAllConstantBtns: function ()
    {
        this.createConstantBtns('Space', 'Planets Constants');
        this.createConstantBtns('Physics', 'Physics Constants');
        this.createConstantBtns('Units', 'Units Conversion');
    },

    createConstantBtns: function (item, tabTitle)
    {
        var constTab = this.win.query('#calc_tab_const_id');

        if (constTab.length < 1)
            return;

        switch (item)
        {
            case 'Space' :
                amdaUI.CalculatorUI.constantStore.filter('kind', 'space');
                break;
            case 'Physics' :
                amdaUI.CalculatorUI.constantStore.filter('kind', 'physics');
                break;
            case 'Units' :
                amdaUI.CalculatorUI.constantStore.filter('kind', 'units');
                break;
        }

        var crtTab = constTab[0].add(
                {
                    title: tabTitle,
                    defaults: {xtype: 'button', columnWidth: .20}
                });

        amdaUI.CalculatorUI.constantStore.each(function (c) {
            crtTab.add(
                    {
                        text: c.get('name'),
                        tooltip: c.get('units') == '' ? c.get('info') + '<br/>' + c.get('value') :
                                c.get('info') + '<br/>' + c.get('value') + ' ' + c.get('units'),
                        scope: this,
                        handler: function (b, e) {
                            // keep selection into construction field
                            var selection = this.hostCmp.constructionField.getSelection();
                            // the new value of construction field
                            var newConstruction = "";
                            // replacement of selection into construction field by text of clicked button
                            newConstruction = selection.beforeText + '@' + b.text;
                            var caretPos = newConstruction.length;
                            newConstruction += selection.afterText;
                            this.hostCmp.constructionField.setValue(newConstruction);
                            // set Caret Position after inserted Text
                            this.hostCmp.constructionField.setCaretPosition(caretPos);
                        }
                    });
        }, this);

        //clear filter
        amdaUI.CalculatorUI.constantStore.clearFilter();
    },

    createFunctionBtns: function (item, tabTitle, tabID)
    {
        var funcTab = this.win.query(tabID);

        if (funcTab.length < 1)
            return;

        var width = .20;

        switch (item)
        {
            case 'MathFunctions' :
                amdaUI.CalculatorUI.functionStore.filter('kind', 'math');
                break;
            case 'AmdaFunctions' :
                amdaUI.CalculatorUI.functionStore.filter('kind', 'amda');
                width = .33;
                break;
            case 'TimeFunctions' :
                 amdaUI.CalculatorUI.functionStore.filter('kind', 'time');
                 width = .33;
                break;
            case 'FunctionsSliding' :
                 amdaUI.CalculatorUI.functionStore.filter('kind', 'sliding');
                break;
            case 'VectorFunctions' :
                 amdaUI.CalculatorUI.functionStore.filter('kind', 'vector');
                break;
            case 'ModelFunctions' :
                 amdaUI.CalculatorUI.functionStore.filter('kind', 'model');
                width = .45;
                break; 
            case 'PhysicsFunctions' :
                 amdaUI.CalculatorUI.functionStore.filter('kind', 'physics');
                width = .25;
                break;
            case 'FramesFunctions' :
                amdaUI.CalculatorUI.functionStore.filter('kind', 'frames');
                width = .45;
                break;
            case 'FilterFunctions' :
                amdaUI.CalculatorUI.functionStore.filter('kind', 'filter');
                width = .45;
                break;
        }

        var crtTab = funcTab[0].add(
                {
                    title: tabTitle,
                    defaults: {xtype: 'button', columnWidth: width}
                });

        
        amdaUI.CalculatorUI.functionStore.each(function (f) {
            crtTab.add(
                    {
                        text: f.get('name'),
                        args: f.get('args'),
                        defaultArgs: f.get('default_args'),
                        prompts: f.prompts(),
                        prompt_param: f.get('prompt_param'),
                        tooltip: f.get('info_brief'),
                        scope: this,
                        handler: function (b, e) {
                            var selection = this.hostCmp.constructionField.getSelection();
                            var selectedText = selection && selection.text != "" ? Ext.util.Format.trim(selection.text) : null;
                            if (selectedText && selectedText !== "") {
                                selectedText.replace("[", "(");
                                selectedText.replace("{", "(");
                                selectedText.replace("}", ")");
                                selectedText.replace("]", ")");
                            }
                            var params = [];
                            var raw_args = b.initialConfig.defaultArgs;
                            if (raw_args && raw_args != "") {
                                params = b.initialConfig.defaultArgs.split(',');
                            }
                            // Formula Parsing for arguments
//                             var params = this.parseArgsInFormula(selectedText, 0);
//                             var params = selectedText ? selectedText.split(',') : [];

                            if (params === -1) {
                                Ext.Msg.alert("Invalid Selection", "Action aborted");
                            } else {
                                // calculation of the required number of parameters
                                var nbParams = b.initialConfig.args;
                                this.preProcessFormula(selection, b, params);

                                /**
                                 
                                 if (params.length > nbParams)
                                 {
                                 // Show a dialog using config options:
                                 Ext.Msg.show({
                                 title: 'Caution',
                                 msg: 'you have selected more than ' + nbParams + ' parameter(s) to apply this function<br>Only the first will be kept, others will be deleted',
                                 buttons: Ext.Msg.OKCANCEL,
                                 //										animEl: 'elId',
                                 icon: Ext.MessageBox.WARNING,
                                 fn: function (bt1) {
                                 if (bt1 === 'ok') {
                                 this.preProcessFormula(selection, b, params);
                                 }
                                 },
                                 scope: this
                                 });
                                 } else
                                 {
                                 this.preProcessFormula(selection, b, params);
                                 }*/
                            }

                        }
                    });
        },
                this
                );
        //clear filter      
       amdaUI.CalculatorUI.functionStore.clearFilter();
    },

    getItems: function (item)
    {
        switch (item)
        {
            case 'Calculator':
                return this.getCalculatorBtn();
            default:
                break;
        }
        return [];
    }
});