/* Copyright 2020 Tecnativa - Alexandre Díaz
 * License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) */

odoo.define('web_pivot_computed_measure.PivotModel', function (require) {
    "use strict";

    var core = require('web.core');
    var PivotModel = require('web.PivotModel');

    var _t = core._t;


    PivotModel.include({
        _computed_measures: [],

        /**
         * Create a new computed measure
         * 
         * @param {string} id
         * @param {string} field1
         * @param {string} field2
         * @param {string} operation
         * @param {string} name
         * @param {string} format
         */
        createComputedMeasure: function(id, field1, field2, operation, name, format) {
            var measure = _.find(this._computed_measures, function(item) {
                return item.field1 === field1 && item.field2 === field2 && item.operation === operation;
            });
            if (measure) {
                return $.Deferred(function(d) {
                    d.resolve();
                });
            }
            var fieldM1 = this.fields[field1];
            var fieldM2 = this.fields[field2];
            var cmId = '__computed_' + id;
            var oper = operation.replace(/m1/g, field1).replace(/m2/g, field2);
            var oper_human = operation.replace(
                /m1/g,
                fieldM1.__computed_id?"("+fieldM1.string+")":fieldM1.string).replace(
                /m2/g,
                fieldM2.__computed_id?"("+fieldM2.string+")":fieldM2.string);
            var cmTotal = this._computed_measures.push({
                field1: field1,
                field2: field2,
                operation: oper,
                name: name || oper_human,
                id: cmId,
                format: format,
            });

            return this._createVirtualMeasure(this._computed_measures[cmTotal-1]);
        },

        /**
         * Create and enable a measure based on a 'fake' field
         * 
         * @param {Object} cmDef
         * @param {List} fields *Optional*
         */
        _createVirtualMeasure: function(cmDef, fields) {
            var arrFields = fields || this.fields;
            // This is a minimal 'fake' field info
            arrFields[cmDef.id] = {
                type: cmDef.format,         // Used to format the value
                string: cmDef.name,         // Used to print the header name
                __computed_id: cmDef.id,    // Used to know if is a computed measure field
            }
            this.trigger_up("add_measure", {
                id: cmDef.id,
                def: arrFields[cmDef.id],
            });
            return this._activeMeasures([cmDef.field1, cmDef.field2, cmDef.id]);
        },

        /**
         * @param {List of Strings} fields
         */
        _activeMeasures: function(fields) {
            var needLoad = false;
            var l = fields.length;
            for (var x = 0; x < l; ++x) {
                var field = fields[x];
                if (!this._isMeasureEnabled(field)) {
                    this.data.measures.push(field);
                    needLoad = true;
                }
            }
            if (needLoad) {
                return this._loadData();
            }
            return $.Deferred(function(d) {
                d.resolve();
            })
        },

        /**
         * @param {String} field
         */
        _isMeasureEnabled: function(field) {
            return _.contains(this.data.measures, field);
        },

        /**
         * Fill the dataPoints with the computed measures values
         * 
         * @override
         */
        _mergeData: function (data, comparisonData, groupBys) {
            var res = this._super.apply(this, arguments);
            var l = groupBys.length; // Cached loop (This is not python! hehe)
            for (var index = 0; index < l; ++index) {
                if (data.length) {
                    var l2 = data[index].length;
                    for (var k = 0; k < l2; ++k) {
                        var dataPoint  = data[index][k];
                        if (_.isEmpty(dataPoint)) {
                            break;
                        }
                        var l3 = this._computed_measures.length;
                        for (var x = 0; x < l3; ++x) {
                            var cm = this._computed_measures[x];
                            if (!this._isMeasureEnabled(cm.id)) {
                                continue;
                            }
                            dataPoint[cm.id] = py.eval(cm.operation, dataPoint);
                        }
                    }
                }
            }
            return res;
        },

        /**
         * Load the computed measures in context. This is used by filters.
         * 
         * @override
         */
        load: function (params) {
            var self = this;
            this._computed_measures = params.context.pivot_computed_measures || params.computed_measures || [];
            var toActive = [];
            var l = this._computed_measures.length;
            for (var x = 0; x < l; ++x) {
                var cmDef = this._computed_measures[x];
                params.fields[cmDef.id] = {
                    type: cmDef.format,
                    string: cmDef.name,
                    __computed_id: cmDef.id,
                }
                toActive.push(cmDef.field1, cmDef.field2, cmDef.id);
            }
            return this._super(params).then(function() {
                _.defer(function() {
                    for (var x = 0; x < l; ++x) {
                        var cmDef = self._computed_measures[x];
                        self.trigger_up("add_measure", {
                            id: cmDef.id,
                            def: self.fields[cmDef.id],
                        });
                    }
                });
                self._activeMeasures(toActive);
            });
        },

        /**
         * Load the computed measures in context. This is used by filters.
         * 
         * @override
         */
        reload: function (handle, params) {
            if ('context' in params) {
                this._computed_measures = params.context.pivot_computed_measures || params.computed_measures || [];
            }
            var l = this._computed_measures.length;
            for (var x = 0; x < l; ++x) {
                this._createVirtualMeasure(this._computed_measures[x]);
            }
            // Clean unused 'fake' fields
            var fieldNames = Object.keys(this.fields);
            for (var x = 0; x < fieldNames.length; ++x) {
                var field = this.fields[fieldNames[x]];
                if (field.__computed_id) {
                    var cm = _.find(this._computed_measures, {id:field.__computed_id});
                    if (!cm) {
                        delete this.fields[fieldNames[x]];
                        this.data.measures = _.without(this.data.measures, fieldNames[x]);
                        this.trigger_up("remove_measure", {
                            id: fieldNames[x],
                        });
                    }
                }
            }
            return this._super.apply(this, arguments);
        },

        /**
         * Add the computed measures to the state. This is used by filters.
         * 
         * @override
         */
        get: function () {
            var res = this._super.apply(this, arguments);
            res.computed_measures = this._computed_measures;
            return res;
        },

        /**
         * Adds a rule to deny that measures can be disabled if are being used by a computed measure.
         * In the other hand, when enables a measure analyzes it to active all involved measures.
         * 
         * @override
         */
        toggleMeasure: function (field) {
            if (this._isMeasureEnabled(field)) {
                // Measure is disabled
                var umeasures = _.filter(this._computed_measures, function(item) {
                    return item.field1 === field || item.field2 === field;
                })
                if (umeasures.length && this._isMeasureEnabled(umeasures[0].id)) {
                    return $.Deferred(function(d) {
                        d.reject(_t("This measure is currently used by a 'computed measure'. Please, disable the computed measure first."));
                    });
                }
            } else {
                // Mesaure is enabled
                var toEnable = [];
                var toAnalize = [field];
                while (toAnalize.length) {
                    var afield = toAnalize.shift();
                    var fieldDef = this.fields[afield];
                    if (fieldDef.__computed_id) {
                        var cm = _.find(this._computed_measures, {id:fieldDef.__computed_id});
                        toAnalize.push(cm.field1, cm.field2);
                        var toEnableFields = [];
                        if (!this.fields[cm.field1].__computed_id) {
                            toEnableFields.push(cm.field1);
                        }
                        if (!this.fields[cm.field2].__computed_id) {
                            toEnableFields.push(cm.field2);
                        }
                        toEnableFields.push(afield);
                        toEnable.push(toEnableFields);
                    }
                }
                if (toEnable.length) {
                    return this._activeMeasures(_.flatten(toEnable.reverse()));
                }
            }
            return this._super.apply(this, arguments);
        },
    });

});
