/**
 * VTK-Web Widget Library.
 *
 * This module extend jQuery object to add support for graphical components
 * related to 2D chart visualization. This widget depends on D3 and Rickshaw.
 *
 * @class jQuery.vtk.ui.Chart
 */
(function ($) {

    // =======================================================================
    // ==== Defaults constant values =========================================
    // =======================================================================
    var GRAPH_HTML_TEMPLATE = [
        "<div class='vtk-legend'></div>",
        "<div class='vtk-top' style='left: AXIS_SIZE px; top: 0px; height: AXIS_SIZE px; position: absolute;'></div>",
        "<div class='vtk-left' style='left: 0 px; top: AXIS_SIZE px; width: AXIS_SIZE px; position: absolute;'></div>",
        "<div class='vtk-center' style='left: AXIS_SIZE px; top: AXIS_SIZE px; position: relative;'></div>",
        "<div class='vtk-right' style='right: 0px; top: AXIS_SIZE px; position: absolute;'></div>",
        "<div class='vtk-bottom' style='left: AXIS_SIZE px; bottom: 0px; position: absolute;'></div>",
        "<div class='vtk-annotation' style='left: AXIS_SIZE px; bottom: 0px; position: absolute;'></div>"
    ];

    // =======================================================================

    function toNumber(str) {
        return Number(str.replace(/^\s+|\s+$/g, ''));
    }

    // =======================================================================

    function extractColumnHeaderMap(headerLine) {
        var header = headerLine.split(','),
        colIdxMap = {};
        for(var idx in header) {
            colIdxMap[header[idx]] = idx;
        }
        return colIdxMap;
    }

    // =======================================================================

    function singleDataCSVConverter(inputString, outputSeries, options) {
        var lines = inputString.split('\n'),
        data = [],
        serie = $.extend({data:data}, options),
        nbLines = lines.length;

        // Process data
        for(var i = 1; i < nbLines; ++i) {
            var values = lines[i].split(',');
            if(values.length === 2) {
                item = { x: toNumber(values[0]), y: toNumber(values[1]) };
                if(isNaN(item.y)) {
                    item.y = null;
                }
                data.push(item);
            }
        }
        outputSeries.push(serie);
    }

    // =======================================================================

    function multiDataCSVConverter(inputString, outputSeries, options) {
        var lines = inputString.split('\n'),
        header = lines[0].split(','),
        headerMap = extractColumnHeaderMap(lines[0]),
        nbLines = lines.length,
        nbValuesByLines = header.length,
        xHeaderName = options['x'],
        xIdx = headerMap[xHeaderName],
        series = [],
        palette = new Rickshaw.Color.Palette();

        if(options.hasOwnProperty('palette') && options['palette'] !== null) {
            palette = options['palette'];
        }

        // Remove time field
        header.splice(header.indexOf(xHeaderName), 1);

        // Create series
        for(var idx in header) {
            var serie = {
                data: [],
                color: palette.color(),
                name: header[idx]
            };

           series.push(serie);
           outputSeries.push(serie);
        }

        // Process data
        for(var i = 1; i < nbLines; ++i) {
            var values = lines[i].split(',');
            if(values.length === nbValuesByLines) {
                xValue =  toNumber(values[xIdx]);
                for(var idx in header) {
                    var item = { x: xValue, y: toNumber(values[headerMap[header[idx]]])};
                    if(isNaN(item.y)) {
                        item.y = null;
                    }
                    if(!isNaN(item.x)) {
                        series[idx].data.push(item);
                    }
                }
            }
        }
    }

    // =======================================================================

    function updateLegend(container) {
        var legendContainer = $('.vtk-legend', container),
        chart = container.data('chart'),
        legend = chart['legends'].basic;

        // Empty UI
        legendContainer.children("ul").empty();

        // Update model
        if(legend !== undefined) {
            legend.lines = [];
            var series = chart.graph.series.map( function(s) { return s } )
            series.forEach(function(s) {
                legend.addLine(s);
            });
        }
    }

    // =======================================================================

    /**
     * Method used to create a 2D chart based on some available data.
     *
     * @member jQuery.vtk.ui.Chart
     * @method vtkChart
     * @param {Object} configuration
     *
     * Usage:
     *      var options = {
     *         'renderer': 'line',  // Type of chart [line, area, bar, scatterplot]
     *         'stacked' : false,
     *         'series': [
     *            {
     *                data: [ { x:0, y:0 }, { x:100, y:10 }, { x:200, y:5 }, { x:300, y:20 }, { x:400, y:25 }, { x:1000, y:-10 } ],
     *                color: 'steelblue',
     *                name: 'field 0'
     *            },{
     *                data: [ { x:0, y:20 }, { x:100, y:30 }, { x:200, y:25 }, { x:300, y:40 }, { x:400, y:55 }, { x:1000, y:-10 } ],
     *                color: 'lightblue',
     *                name: 'field 1'
     *            }
     *        ],
     *        'axes': [ "bottom", "left", "top"], // Draw axis on border with scale
     *        'chart-padding': [0, 150, 50, 0],   // Graph padding [top, right, bottom, left] in px. Useful to save space for legend
     *      };
     *
     *      $('.chart-container-div').vtkChart(options);
     */

    $.fn.vtkChart = function(options) {
        // Handle data with default values
        var opts = $.extend({},$.fn.vtkChart.defaults, options);

        return this.each(function() {
            var me = $(this).empty().addClass('vtk-chart'),
            container = $("<div/>", {
                html: GRAPH_HTML_TEMPLATE.join('').replace(/AXIS_SIZE /g, opts.axisThickness)
            }),
            chartContainer = $('.vtk-center', container),
            legendContainer = $('.vtk-legend', container),
            axisContainer = {
                bottom: $('.vtk-bottom', container)[0],
                top: $('.vtk-top', container)[0],
                left: $('.vtk-left', container)[0],
                right: $('.vtk-right', container)[0]
            },
            annotationContainer = $('.vtk-annotation', container);
            me.append(container);
            // container.css('width', (opts['width']+(2*opts.axisThickness)) + 'px');

            var graphOptions = {
                element: chartContainer[0],
                width: opts['width'],
                height: opts['height'],
                renderer: opts['renderer'],
                min: 'auto',
                stroke: true,
                series: opts['series']
            },
            graph = new Rickshaw.Graph(graphOptions),
            axes = [],
            legends = {},
            annotator = null,
            data = {
                configuration: graphOptions,
                options: opts,
                palette: new Rickshaw.Color.Palette(),
                graph: graph,
                axes: axes,
                legends: legends
            };

            graph.renderer.unstack = !opts.stacked;
            graph.render();

            // Complete graph accessories
            // => Axis
            for(var idx in opts.axes) {
                var orientation = opts.axes[idx], axis = null;
                if(orientation === 'top' || orientation === 'bottom') {
                    axis = new Rickshaw.Graph.Axis.X({graph: graph, orientation: orientation, element: axisContainer[orientation]});
                } else {
                    axis = new Rickshaw.Graph.Axis.Y({graph: graph, orientation: orientation, element: axisContainer[orientation]});
                }
                axes.push(axis);
            }
            // => Legend
            if(opts.legend.basic) {
                legends['basic'] = new Rickshaw.Graph.Legend({graph: graph, element: legendContainer[0]});
                // if(opts.legend.toggle) {
                //     legends['toggle'] = new Rickshaw.Graph.Behavior.Series.Toggle({graph: graph, legend: legends['basic']});
                // }
                // if(opts.legend.highlight) {
                //     legends['highlight'] = new Rickshaw.Graph.Behavior.Series.Highlight({graph: graph, legend: legends['basic']});
                // }
            }
            // => Hover
            if(opts.hover !== null) {
                data['hover'] = new Rickshaw.Graph.HoverDetail({
                    graph: graph,
                    xFormatter: opts.hover.xFormatter,
                    yFormatter: opts.hover.yFormatter
                });
            }
            // => Annotation
            data['annotator'] = new Rickshaw.Graph.Annotate({
                graph: graph,
                element: annotationContainer[0]
            });
            for(var idx in opts.annotations) {
                var annotation = opts.annotations[idx];
                data['annotator'].add(annotation['time'], annotation['message']);
            }

            // Handle auto-resize
            if(opts.autosize) {
                function autoResize() {
                    var w = $(window),
                    padding = opts['chart-padding'],
                    thickness = opts.axisThickness,
                    size = { width: me.width() - (2*thickness) - (padding[1] + padding[3]), height: me.height() - (2*thickness) - (padding[0] + padding[2])};
                    $('.vtk-bottom, .vtk-top, .vtk-annotation', me).css('height', thickness +'px').css('width', size['width'] +'px');
                    $('.vtk-left, .vtk-right', me).css('width', thickness +'px').css('height', size['height'] +'px');
                    $('.vtk-right', me).css('right', padding[1] + 'px').css('top', (padding[0] + thickness) + 'px');
                    $('.vtk-left', me).css('left', padding[3] + 'px').css('top', (padding[0] + thickness) + 'px');
                    $('.vtk-top', me).css('top', padding[0] + 'px').css('left', (thickness+padding[3]) + 'px');
                    $('.vtk-bottom, .vtk-annotation', me).css('left', (thickness+padding[3]) + 'px');
                    $('.vtk-bottom', me).css('bottom', padding[2] + 'px');
                    $('.vtk-center', me).css('width', (size['width'] - 2*thickness - (padding[1] + padding[3]))+'px').css('height', (size['height'] - 2*thickness - (padding[0] + padding[2]))+'px').css('left', (padding[3]+thickness) + 'px').css('top', (padding[0]+thickness) + 'px');

                    data.graph.configure(size);
                    data.graph.update();
                }

                $(window).resize(autoResize).trigger('resize');
            }

            // Save data
            me.data('chart', data);
            graph.render();
        });
    };

    // =======================================================================
    /**
     * Method used to update the data of the 2D chart.
     *
     * @member jQuery.vtk.ui.Chart
     * @method vtkChartUpdateData
     * @param {Array} series
     * @param {boolean} replace previous series
     *
     * Usage:
     *      var series: [
     *         {
     *            data: [ { x:0, y:0 }, { x:100, y:10 }, { x:200, y:5 }, { x:300, y:20 }, { x:400, y:25 }, { x:1000, y:-10 } ],
     *            color: 'steelblue',
     *            name: 'field 0'
     *         },{
     *            data: [ { x:0, y:20 }, { x:100, y:30 }, { x:200, y:25 }, { x:300, y:40 }, { x:400, y:55 }, { x:1000, y:-10 } ],
     *            color: 'lightblue',
     *            name: 'field 1'
     *         }
     *      ];
     *
     *      $('.chart-container-div').vtkChartUpdateData(series);
     */
    $.fn.vtkChartUpdateData = function(series, replace) {
        return this.each(function() {
            var me = $(this),
            data = me.data('chart'),
            dataset = data['configuration']['series'];
            if(replace) {
                while(dataset.length > 0) {
                    dataset.pop();
                }
            }
            for(var idx in series) {
                data.graph.series.push(series[idx]);
            }
            data.graph.validateSeries(data.graph.series);
            data.graph.update();
            updateLegend(me);
        });
    }

    // =======================================================================

    /**
     * Method used to update the data of the 2D chart.
     *
     * @member jQuery.vtk.ui.Chart
     * @method vtkChartFetchData
     * @param {Object} options
     *
     * Usage:
     *      var options_json = { replace: true, url: "data.json", type: 'json', converter: null };
     *      var options_csv_1 = { replace: true, url: "data1.csv", type: 'csv-xy', options: { name: 'Temperature', color: palette.color(), ... } };
     *      var options_csv_n = { replace: true, url: "data2.csv", type: 'csv-x*', options: { x: 'time', palette: null } };
     *
     *      $('.chart-container-div').vtkChartFetchData(options_*);
     *
     * Where data looks like:
     *
     *     data.json
     *       [
     *         {
     *            data: [ { x:0, y:0 }, { x:100, y:10 }, { x:200, y:5 }, { x:300, y:20 }, { x:400, y:25 }, { x:1000, y:-10 } ],
     *            color: 'steelblue',
     *            name: 'field 0'
     *         },{
     *            data: [ { x:0, y:20 }, { x:100, y:30 }, { x:200, y:25 }, { x:300, y:40 }, { x:400, y:55 }, { x:1000, y:-10 } ],
     *            color: 'lightblue',
     *            name: 'field 1'
     *         }
     *       ]
     *
     *
     *     data1.csv
     *     x,y
     *     0,0
     *     1,0.234
     *     2,0.5
     *     2.5,7
     *
     *
     *     data2.csv
     *     time,x,y,z
     *     0,0,0,0
     *     1,0.234,1.2,7.6
     *     2,0.5,3,6
     *     2.5,7,8,9
     */
    $.fn.vtkChartFetchData = function(info) {
        return this.each(function() {
            var me = $(this),
            data = me.data('chart'),
            options = info['options'];

            $.ajax({
                url: info.url,
                dataType: "text"
            }).done(function(data){
                var series = [];
                if (info.type === 'json') {
                    series = $.parseJSON(data);
                } else if(info.type === 'csv-xy') {
                    singleDataCSVConverter(data, series, options);
                } else if(info.type === 'csv-x*') {
                    multiDataCSVConverter(data, series, options);
                }
                me.vtkChartUpdateData(series, info['replace']);
            });
        });
    }

    // =======================================================================
    /**
     * Method used to update the data of the 2D chart.
     *
     * @member jQuery.vtk.ui.Chart
     * @method vtkChartConfigure
     * @param {Object} options
     *
     * Usage:
     *
     *     var options = {
     *        'renderer': 'line',  // Type of chart [line, area, bar, scatterplot]
     *        'stacked' : false,
     *        'axes': [ "bottom", "left", "top"], // Draw axis on border with scale
     *        'chart-padding': [0, 150, 50, 0],   // Graph padding [top, right, bottom, left] in px. Useful to save space for legend
     *      };
     *      $('.chart-container-div').vtkChartConfigure(options);
     */

    $.fn.vtkChartConfigure = function(conf) {
        return this.each(function() {
            var me = $(this),
            data = me.data('chart');
            var opts = $.extend(data['options']['configuration'], conf);
            $('.x_axis_d3', me).height(data.axisThickness + 'px').width(($(window).width()-(2*data.axisThickness)) + 'px');
            data.graph.configure(opts);
            data.graph.update();
        });
    };

    // =======================================================================

    $.fn.vtkChart.defaults = {
        width: 300,
        height: 200,
        axisThickness: 25,
        autosize: true,
        stacked: false,
        renderer: "line",
        interpolation: "linear",
        series: [],
        hover: { xFormatter: function(x) { return x; }, yFormatter: function(y) {return y;} },
        legend: { basic: true, toggle: true, highlight: true },
        annotations: [], // { time: 0, message: "Just a text" } ...
        axes: [ "bottom", "left" ],
        'chart-padding': [0, 0, 0, 0]
    };

    // =======================================================================

}(jQuery));
