"use strict";

App.module("GraphicsRenderingModule", function(GraphicsRenderingModule, App, Backbone, Marionette, $, _, Logger) {

    GraphicsRenderingModule.addInitializer(function(options){

        GraphicsRenderingModule.registerRenderer({
            id: "histogram",
            inherit: "_",

            defaultVegaConfig: {
                comparisonMode: null,
                dataDefinition: null, // forXs, forBars, forFloats, forXMean, forXStdDev, forceXsAreEdges

                colorForFlats: "#666",
                colorForBars: 0,

                ylabelDX: 3,
                xlabelDY: -3,
                paddingWhenAxisLabelsAreShown: {"top": 5, "left": 40, "bottom": 20, "right": 10},
                paddingWhenAxisLabelsAreHidden: {"top": 5, "left": 10, "bottom": 1, "right": 10},
            },


            _formVC: function(vc, data) {
                vc.enoughSpaceForAxisLabels = vc.totalWidth > 200;
                vc.padding = vc.enoughSpaceForAxisLabels ? vc.paddingWhenAxisLabelsAreShown : vc.paddingWhenAxisLabelsAreHidden;
                vc.width  = vc.totalWidth  - vc.padding.left - vc.padding.right;
                vc.height = vc.totalHeight - vc.padding.top  - vc.padding.bottom;

                var xs;
                var valuesForBars0 = [];
                var valuesForBars1 = [];
                var valuesForBars2 = [];
                var valuesForFlats0 = [];
                var valuesForFlats1 = [];
                var valuesForFlats2 = [];
                var valueForHoris0 = null;
                var valuesForOverlays0 = [];
                var xsAreEdges = false;
                var xsAreCategories = false;

                if (vc.comparisonMode) {
                    xs = _.isFunction(vc.dataDefinition.forXs) ? vc.dataDefinition.forXs(data.left) : data.left[vc.dataDefinition.forXs];
                } else {
                    xs = _.isFunction(vc.dataDefinition.forXs) ? vc.dataDefinition.forXs(data.self) : data.self[vc.dataDefinition.forXs];
                }

                if (vc.comparisonMode) {
                    valuesForBars1  = (_.isFunction(vc.dataDefinition.forBars)  ? vc.dataDefinition.forBars (data.left)  : data.left [vc.dataDefinition.forBars] );
                    valuesForBars2  = (_.isFunction(vc.dataDefinition.forBars)  ? vc.dataDefinition.forBars (data.right) : data.right[vc.dataDefinition.forBars] );
                    if (vc.dataDefinition.forFlats) {
                        valuesForFlats1 = (_.isFunction(vc.dataDefinition.forFlats) ? vc.dataDefinition.forFlats(data.left)  : data.left [vc.dataDefinition.forFlats]);
                        valuesForFlats2 = (_.isFunction(vc.dataDefinition.forFlats) ? vc.dataDefinition.forFlats(data.right) : data.right[vc.dataDefinition.forFlats]);
                    }
                } else {
                    valuesForBars0  = (_.isFunction(vc.dataDefinition.forBars)  ? vc.dataDefinition.forBars (data.self)           : data.self[vc.dataDefinition.forBars ]);
                    if (vc.dataDefinition.forFlats) {
                        valuesForFlats0 = (_.isFunction(vc.dataDefinition.forFlats) ? vc.dataDefinition.forFlats(data.self)           : data.self[vc.dataDefinition.forFlats]);
                    }
                }

                if (vc.comparisonMode == "direct") {
                    for (var i = 0; i < valuesForBars1.length; i++) {
                        valuesForBars0.push(valuesForBars2[i] - valuesForBars1[i]);
                    }
                    for (var i = 0; i < valuesForFlats1.length; i++) {
                        valuesForFlats0.push(valuesForFlats2[i] - valuesForFlats1[i]);
                    }
                    valuesForBars1 = [];
                    valuesForFlats1 = [];
                    valuesForBars2 = [];
                    valuesForFlats2 = [];
                }

                try {
                    if (valuesForBars0.length == xs.length - 1) {
                        xsAreEdges = true;
                    }
                    if (valuesForBars1.length == xs.length - 1) {
                        xsAreEdges = true;
                    }
                    if (vc.dataDefinition.forceXsAreEdges) {
                        xsAreEdges = true;
                    }
                } catch (e) {
                    throw new App.RepresentationModule.Error({type: "ok-count-0"});
                }

                if (_.isString(xs[0])) {
                    xsAreCategories = true;
                }

                var xMean = undefined;
                if (vc.dataDefinition.forXMean && data.self) {
                    xMean = _.isFunction(vc.dataDefinition.forXMean)  ? vc.dataDefinition.forXMean (data.self)  : data.self [vc.dataDefinition.forXMean];
                }
                var xStdDev = undefined;
                if (vc.dataDefinition.forXStdDev && data.self) {
                    xStdDev = _.isFunction(vc.dataDefinition.forXStdDev)  ? vc.dataDefinition.forXStdDev (data.self)  : data.self [vc.dataDefinition.forXStdDev];
                }

                //var vegaDataByName = {}
                var generateVegaData = function(name, values, remarkPrefix, remarkSuffix) {
                    var vegaValues = [];
                    var tooltipType = xsAreEdges ? "tooltip_range" : "tooltip_point";
                    var maxI = values.length;
                    for (var i = 0; i < maxI; i++) {
                        var nextX = xs[i+1];
                        if (nextX !== undefined || xsAreCategories) {
                            vegaValues.push([tooltipType, values[i], xs[i], nextX, remarkPrefix, remarkSuffix]);
                        }
                    }
                    //console.log(vegaValues);
                    //vegaDataByName[name] = result;
                    return {
                        "name": name,
                        "values": vegaValues
                    };
                };

                var generateVegaDataForHoris = function(name, values) {
                    return {
                        "name": name,
                        "values": ["tooltip_horis", value]
                    };
                };

                if (valuesForBars0 .length) {vc.data.push(generateVegaData("bars0", valuesForBars0));}
                if (valuesForBars1 .length) {vc.data.push(generateVegaData("bars1", valuesForBars1, "left: "));}
                if (valuesForBars2 .length) {vc.data.push(generateVegaData("bars2", valuesForBars2, "right: "));}

                if (valuesForFlats0 .length) {vc.data.push(generateVegaData("flats0", valuesForFlats0));}
                if (valuesForFlats1 .length) {vc.data.push(generateVegaData("flats1", valuesForFlats0, "left: "));}
                if (valuesForFlats2 .length) {vc.data.push(generateVegaData("flats2", valuesForFlats0, "right: "));}

                if (valueForHoris0 !== null) {vc.data.push(generateVegaDataForHoris("horis0", valuesForOverlays0));}


                var scalex = {
                     "name": "scalex",
                     "range": "width",
                     "zero": false,
                     //"nice": true,
                     "round": false,
                };

                if (xsAreCategories) {
                    scalex["domain"] = {"data": valuesForBars0.length ? "bars0" : "bars1", "field": "2"};
                    scalex["type"] = "ordinal";
                    scalex["padding"] = 0.2;
                } else if (xsAreEdges) {
                    scalex["type"] = "linear";
                    scalex["domain"] = [_.first(xs), _.last(xs)];
                    scalex["points"] = true;
                } else {
                    scalex["domain"] = {"data": valuesForBars0.length ? "bars0" : "bars1", "field": "2"};
                    //scalex["type"] = "ordinal";
                    //scalex["domain"] = xs;
                    //scalex["points"] = true;
                    scalex["domain"] = [_.first(xs), _.last(xs)];
                }

                var scalexRound = _.clone(scalex);
                scalexRound.name = "scalex_round";
                scalexRound.round = true;

                vc.scales.push(scalex);
                vc.scales.push(scalexRound);

                var mult = 1.1;
                var generateVegaYScale = function(name, valuesForBars) {
                    var min = _.min(valuesForBars);
                    if (min < 0) {
                        min = min * mult;
                    }
                    var max = _.max(valuesForBars);
                    if (max > 0) {
                        max = max * mult;
                    }
                    return {
                        "name": name,
                        "range": "height",
                        "round": true,
                        "nice": false,
                        "domain": [Math.min(0, min), Math.max(0, max)]
                    };

                };

                if (valuesForBars0 .length) {vc.scales.push(generateVegaYScale("scale0", valuesForBars0));}
                if (valuesForBars1 .length) {vc.scales.push(generateVegaYScale("scale1", valuesForBars1));}
                if (valuesForBars2 .length) {vc.scales.push(generateVegaYScale("scale2", valuesForBars2));}

                //console.log(vegaScales);
                //return;

                var barWidth = (vc.width / xs.length) + 0.5;
                var offsetForXRight = .5;
//                    if (vegaConfig.width / xs.length > 1) {
//                        offsetForXRight = 0.5;
//                    }
               var generateVegaMark = function(dataName, scale, type, color, opacity) {
                   var markProperties = {
                           //"fill": {"value": color},
                           "opacity": {"value": opacity},
                           "y":  {"scale": scale, "field": "1"},
                   };

                   if (type == "bar") {
                       markProperties["y2"] = {"scale": scale, "value": 0};
                   } else {
                       markProperties["height"] = {"value": 2};
                   }
                   if (xsAreCategories) {
                       markProperties["x"] = {"scale": "scalex", "field": "2"};
                       markProperties["width"] = {"value": 1};
                       markProperties["width"] = {"scale": "scalex", "band": true};
                   } else if (xsAreEdges) {
                       markProperties["x"] = {"scale": "scalex", "field": "2"};
                       markProperties["x2"] = {"scale": "scalex", "field": "3", "offset": offsetForXRight};
                   } else {
                       markProperties["x"] = {"scale": "scalex", "field": "2", "offset": offsetForXRight / 2};
                       //markProperties["x"] = {"scale": "scalex", "field": "2"};
                       markProperties["width"] = {"value": barWidth};
                       //markProperties["width"] = {"scale": "scalex", "band": true};
                   }
                   return {
                       "type": "rect",
                       "from": {"data": dataName},
                       "properties": {
                           "enter": markProperties,
                           "update": {"fill": {"value": color}},
                           "hover": {"fill": {"value": "black"}}
                       }
                     };
               };

               // background (to catch mouse events and remove tooltips)
               vc.marks.push({
                   "type": "rect",
                   "properties": {
                       "enter": {
                           "x": {"value": -vc.padding.left},
                           "y": {"value": -vc.padding.top},
                           "fill": {"value": "#fff"},
                           "y2": {"field": {"group": "height"}, "offset": vc.padding.left + vc.padding.right},
                           "x2": {"field": {"group": "width"}, "offset": vc.padding.top + vc.padding.bottom},
                       }
                   }
               });


               // Marks
               if (valuesForBars0 .length) {vc.marks.push(generateVegaMark("bars0", "scale0", "bar", vc.colorForBars, 1));}
               if (valuesForBars1 .length) {vc.marks.push(generateVegaMark("bars1", "scale1", "bar", vc.colorForBars, .5));}
               if (valuesForBars2 .length) {vc.marks.push(generateVegaMark("bars2", "scale2", "bar", vc.colorForBars, .5));}

               if (valuesForFlats0.length) {vc.marks.push(generateVegaMark("flats0", "scale0", "flat", vc.colorForFlats, 1));}
               if (valuesForFlats1.length) {vc.marks.push(generateVegaMark("flats1", "scale1", "flat", vc.colorForFlats, .5));}
               if (valuesForFlats2.length) {vc.marks.push(generateVegaMark("flats2", "scale2", "flat", vc.colorForFlats, .5));}

               // y axis
               vc.marks.push({
                       "type": "rect",
                       "properties": {
                           "enter": {
                               "fill": {"value": vc.colorForAxes},
                               "x": {"value": 0},
                               "x2": {"field": {"group": "width"}},
                               "height": {"value": 1},
                               "y":  {"scale": valuesForBars0.length ? "scale0" : "scale1", "value": 0},
                           }
                       }
                   });

               var generateVegaVerticalMark = function(x, color, dataName, strokeDash) {
                   return {
                       "type": "rect",
                       "from": {
                           "data": dataName,
                       },
                       "properties": {
                           "enter": {
                               "stroke": {"value": vc.colorForAxisLabels},
                               "strokeWidth": {"value": 1},
                               "y": {"value": -99},
                               "y2": {"field": {"group": "height"}},
                               "x": {"scale": "scalex_round", "value": x},
                               //"x2": {"scale": "scalex", "value": x, "offset": ""},
                               "width": {"value": 1},
                               "strokeDash": {"value": strokeDash}
                           }
                       }
                   };
               };

               // x mean, x std dev
               if (xStdDev !== undefined && xMean !== undefined) {
                   vc.data.push({
                       "name": "stdDev",
                       "values": [["tooltip_value", xStdDev, 0, 0, "standard deviation: "]]
                   });
                   vc.marks.push(generateVegaVerticalMark(xMean - xStdDev, "#ccc", "stdDev", [1, 1]));
                   vc.marks.push(generateVegaVerticalMark(xMean + xStdDev, "#ccc", "stdDev", [1, 1]));
               }
               if (xMean !== undefined) {
                   vc.data.push({
                       "name": "mean",
                       "values": [["tooltip_value", xMean, 0, 0, "mean: ", xStdDev === 0 ? " (standard deviation is zero)" : undefined]]
                   });
                   vc.marks.push(generateVegaVerticalMark(xMean, "#666", "mean", 0));
               }


               //Axes
               var axisOpacity = vc.enoughSpaceForAxisLabels ? {"value" : 1} :  {"value" : 0};

               vc.axes.push({"type": "x", "scale": "scalex", /*"ticks": 8,*/
                   "properties": {
                       "axis": {
                           "stroke": {"value": "#fff"},
                           "strokeWidth": {"value": 0}
                         },
                       "grid": {
                           "stroke": {"value": "#fff"},
                           "strokeOpacity": {"value": 0.4},
                           "strokeWidth": {"value": 1}
                       },
                         "ticks": {
                             "stroke": {"value": "#fff"},
                             "strokeWidth": {"value": 0},
                             "opacity": axisOpacity
                         },
                         "labels": {
                             "fill": {"value": vc.colorForAxisLabels},
                             "dy": {"value": vc.xlabelDY},
                             "font": {"value": vc.fontFace},
                             "fontSize": {"value": vc.fontSizeForLabelsInAxis},
                             "opacity": axisOpacity
                         }
                   }
                   });
               if (valuesForBars0.length) {
                   vc.axes.push({"type": "y", "scale": "scale0", /*"ticks": 5,*/ "grid": vc.enoughSpaceForAxisLabels, "zero": false,
                       "properties": {
                           "axis": {
                               "stroke": {"value": "#fff"},
                               "strokeWidth": {"value": 0}
                             },
                           "grid": {
                               "stroke": {"value": "#fff"},
                               "strokeOpacity": {"value": 0.4},
                               "opacity": {"value": 1}
                           },
                             "ticks": {
                                 "stroke": {"value": "#fff"},
                                 "strokeWidth": {"value": 0},
                                 "opacity": axisOpacity
                             },
                           "labels": {
                               "fill": {"value": vc.colorForAxisLabels},
                               "fontSize": {"value": vc.fontSizeForLabelsInAxis},
                               "dx": {"value": vc.ylabelDX},
                               "font": {"value": vc.fontFace},
                               "opacity": axisOpacity
                           }
                       }
                   });
               };
            },
        });
    });
}, Logger);
