/*
 * Exegy Management Console graph
 * 
 * The invoking page is expected to provide a global configuration object
 * named "exegy" with the following properties:
 * 
 *  var exegy = {
 *      timeInterval:   number - data update interval in seconds,
 *      baseURL:        string - base portion of URL,
 *      datasets:       array of strings - avialable datasets,
 *      forceFeed:      string - if non-null, show only this feed from first dataset,
 *      firstFeed:      string - if non-null, select this feed when starting a feed list
 *      forceColor:     string - if non-null, specifies color to use for all data series,
 *      firstChart:     string - if non-null, select this chart type as first default
 *  };
 */

$(function ()
{
    var // DOM elements of interest in chart widget
        domGraph = $("#graph"),
        domLegend = $("#legend"),
        domInstr = $("#instructions"),
        domSelect = $("#selection"),            // May not be present
        domSelectList = $("#selection_list"),   // May not be present
        domSelectHead = $("#selection_header"), // May not be present
        captionFeedList = domSelectHead.html(), // Save caption HTML, or null
        domChartHead = $("#chart_header"),      // May not be present
        captionHeader = domChartHead.html(),    // Save caption HTML, or null
        domChangeCtl = $("#changeCtl"),         // May not be present
        domDialog = $("#dialog"),
        domRates = [],
        domTimes = [];

    var // Flot chart widget
        options =
        {   // Core options
            series: { 
                stack: null,
                lines: { show: true, lineWidth: 1 },
                points: { show: false, radius: 3, lineWidth: 1 }
            },
            legend: { container: domLegend },
            xaxis: { 
                mode: "time", 
                timeformat: "%H:%M %p"
            },
            yaxis: { 
                tickFormatter: suffixFormatter
            },
            grid: { hoverable: true, autoHighlight: false, clickable: false },
            hooks: { bindEvents: [ graphEvents ] },

            // Exegy data cursor plugin options
            cursor: { mode: "hover", cursorUpdate: cursorUpdate },

            // Selection plugin options
            selection: { mode: "x", color: "rgba(170, 85, 0, 0.50)" }
        },
        plot = null;

    var // Elements of interest in the dialog
        domDialog = $("#dialog"),               // May not be present
        domDatasetList = $("#dataset_list"),    // May not be present
        domChartType = $("#charttypes"),        // May not be present
        domDatePicker = $("#datepicker"),       // May not be present
        domDateClear = $("#dateclear"),         // May not be present
        dlgDataset = null,
        dlgChartType = null,
        captionChartType = [ ];

    var // Persisted processing state
        dataset = exegy.datasets.length > 0 ? exegy.datasets[0] : "",
        chartType = exegy.firstChart || "messages",
        rqstDate = "today";

    var // Ephemeral processing state
        rateTypes = [ "cursor", "chart", "historic" ],
        dataDate = "",
        data = [],
        feedDrawn = {},
        lastCursor = null,
        zoomed = false,
        freeColors = [],
        nextColor = 0,
        timer = null;

//---------- Dialog: Process chart attribute selections

    // Initialize the dialog object
    function dialogInit()
    {
        if (domDialog.length > 0)
        {
            // Restore saved chart parameters from previous session (if any)
              dataset = localStorage.getItem("xmc.dataset") || dataset; // TODO: check viability
              chartType = localStorage.getItem("xmc.chartType") || chartType;
              rqstDate = localStorage.getItem("xmc.rqstDate") || rqstDate;
            
            // Capture the chart selection options from the dialog HTML
            $.each(domDialog.find("a.setChartType"), function(idx, node)
            {
                captionChartType[node.childNodes[1].value] = jQuery.trim(node.textContent);
            });

            // Create the dialog object
            domDialog.dialog({
                autoOpen: false,
                title: 'Chart Selection',
                buttons: { "Select": dialogDone, "Cancel": dialogCancel },
                modal: true,
                minWidth: 440,
                minHeight: 260
            });

            // Set the dialog's initial chart type selection
            if (domChartType.length > 0)
            {
                domChartType.find("a.setChartType > input")
                    .attr("checked", false);
                domChartType.find("a.setChartType > input[value=\""+chartType+"\"]")
                    .attr("checked", true);
                domChartType.find("a.setChartType").click(chartTypeClick);
            }

            // Build the dataset list in the dialog
            if (domDatasetList.length > 0)
            {
                domDatasetList.empty();
                $.each(exegy.datasets, function(idx, name)
                {
                    domDatasetList.append(
                        "<a class=\"setDataset\">&nbsp;&nbsp;<input type=\"radio\" " +
                            "value=\""+name+"\"/>&nbsp;&nbsp;"+name.toUpperCase()+"</a><br/>"
                    );
                });
                
                // Set the dialog's initial dataset selection
                domDatasetList.find("a.setDataset > input")
                    .attr("checked", false);
                domDatasetList.find("input[value=\""+dataset+"\"]")
                    .attr("checked", true);
                $("a.setDataset").click(datasetClick);
            }

            // Initialize the date picker
            if (domDatePicker.length > 0)
            {
                domDatePicker.datepicker();
                domDatePicker.datepicker("option", "maxDate", "+0d");
                if (domDateClear.length > 0)
                {
                    domDateClear.click(dateClearClick);
                }
            }
        }
    }
    
    // Start a data selection dialog
    function dialogSelect()
    {
        // Select a dataset, if we're not forcing a particular feed
        if (exegy.datasets.length > 1 && exegy.forceFeed == null)
        {
            // Initialize the dialog's date to the previous target
            var targDate = (rqstDate == "today") ? null : $.datepicker.parseDate("yy-mm-dd", rqstDate);
            domDatePicker.datepicker("setDate", targDate);

            // Put up the dialog
            dlgDataset = dataset;
            dlgChartType = chartType;
            domDialog.dialog("open");
        }
        else
        {
            // Just use the first defined dataset with any forced feed
            if (exegy.datasets.length > 0)
            {
                dataset = exegy.datasets[0];
            }
            showFeedList();
        }        
    }
    
    // Handle a dataset selection
    function datasetClick()
    {
        var sel = $(this);
        dlgDataset = sel.find("input").val();

        // Set all radio button states
        domDatasetList.find("a.setDataset > input").attr("checked", false);
        sel.find("input").attr("checked", true);
    }

    // Handle a chart type selection
    function chartTypeClick()
    {
        var sel = $(this);
        dlgChartType = sel.find("input").val();

        // Set all radio button states
        domChartType.find("a.setChartType > input").attr("checked", false);
        sel.find("input").attr("checked", true);
    }

    // Handle a clear of the target date
    function dateClearClick()
    {
        domDatePicker.datepicker("setDate", null);
    }

    // Dialog has been completed
    function dialogDone()
    {
        // Close the dialog
        domDialog.dialog("close");

        // Set new chart parameters
        dataset = dlgDataset;
        chartType = dlgChartType;
        var targDate = domDatePicker.datepicker("getDate");
        rqstDate = (!targDate) ? "today" : $.plot.formatDate(targDate, "%y-%m-%d", null);
        
        // Persist new chart parameters for future sessions
        localStorage.setItem("xmc.dataset",dataset);
        localStorage.setItem("xmc.chartType",chartType);
        localStorage.setItem("xmc.rqstDate",rqstDate);

        // Re-initialize the chart
        graphReset();
        graphHeader();

        // Ask for the list of feeds associated with this dataset
        showFeedList();
    }

    // Dialog has been cancelled
    function dialogCancel()
    {
        // Close the dialog
        domDialog.dialog("close");

        // Reset the original dataset selection
        domDatasetList.find("a.setDataset > input")
            .attr("checked", false);
        domDatasetList.find("input[value=\""+dataset+"\"]")
            .attr("checked", true);

        // Reset the original chart type selection
        domChartType.find("a.setChartType > input")
            .attr("checked", false);
        domChartType.find("a.setChartType > input[value=\""+chartType+"\"]")
            .attr("checked", true);
    }

//---------- Graph widget initialization and drawing

    // Reset and initialize the graph widget to empty
    function graphReset()
    {
        // Reset the refresh timer
        if (timer != null)
        {
            clearTimeout(timer);
            timer = null;
        }

        // Reset the graph
        data = [];
        dataDate = "";
        nextColor = 0;
        freeColors = [];
        domChartHead.hide();
        domChangeCtl.hide();
        domLegend.hide();
        domLegend.empty();
        domRates = [];
        domTimes = [];
        domInstr.hide();
        domSelect.hide();
        plot = $.plot(domGraph, data, options);
    }

    // Register events associated with the graph
    function graphEvents(plot, eventHolder)
    {
        // Zoom area selections transition to zoomed mode
        plot.getPlaceholder().bind("plotselected", function(event, ranges) {
            var opt = plot.getOptions(),
                x_axis = plot.getXAxes();

            if (!zoomed)
            {
                zoomed = true;
                
                // disable the selection function
                plot.clearSelection(true);
                options.selection.mode = null;
                
                // zoom-in the chart using 'ranges'
                options.xaxis.min = ranges.xaxis.from;
                options.xaxis.max = ranges.xaxis.to;
                graphRedraw();
            }
        });

        // Double-clicks while zoomed will zoom back out
        eventHolder.dblclick(function (e)
        {
            if (zoomed)
            {
                zoomed = false;

                // enable the selection function
                options.selection.mode = "x";

                // zoom-out the chart, replotting all data
                options.xaxis.min = null;
                options.xaxis.max = null;
                graphRedraw();
            }
        });
    }

    // Generate the new chart header
    function graphHeader()
    {
        // Is there a chart header?
        if (domChartHead.length > 0)
        {
            var strDate = "";
            if (dataDate != "")
            {
                tmpDate = $.datepicker.parseDate("yy-mm-dd", dataDate);
                strDate = $.datepicker.formatDate("MM d, yy", tmpDate);
            }

            domChartHead.hide();
            domChartHead.html(
                captionHeader
                .replace(/{dataset}/,dataset.toUpperCase())
                .replace(/{charttype}/,captionChartType[chartType])
                .replace(/{datadate}/,strDate)
            );
            domChartHead.show();
        }

        // Is there a dialog invocation mechanism?        
        if (domChangeCtl.length > 0)
        {
            domChangeCtl.hide();
            if (exegy.datasets.length == 1 )
                domChangeCtl.find("a").hide();
            else
            {
                domChangeCtl.find("a").click(function (event)
                {
                    event.preventDefault();
                    dialogSelect();
                });
            }
            domChangeCtl.show();
        }
    }

    // Recompute and redraw the graph
    function graphRedraw()
    {
        // Regenerate the chart header
        graphHeader();

        // Regenerate the chart with new data, options, etc.
        plot = $.plot(domGraph, data, options);

        // Recreate the legend...
        // Remember DOM element handles and reset the legend values
        $.each(rateTypes, function(idx, type)
        {
            domRates[type] = domLegend.find("td.legend_"+type+"_rate");
            domTimes[type] = domLegend.find("td.legend_"+type+"_time");
            for (i=0; i < data.length; i++)
            {
                domRates[type].eq(i).text(data[i].maxrate[type]);
                domTimes[type].eq(i).text(data[i].maxtime[type]);
            }
        });

        // Add the header row to the newly created legend table
        var tbl = domLegend.find("table"),
            fragments = [];
        fragments.push("<tr>");
        fragments.push("<td class=\"legendFeedHeader\" colspan=2>Data feed</td>");
        $.each(rateTypes, function(idx, type)
        {
            var t = type.charAt(0).toUpperCase() + type.substr(1);
            fragments.push("<td class=\"legendHeader\" colspan=2>"+t+"</td>");
        });
        fragments.push("</tr>");
        tbl.prepend(fragments.join(""));

        // Show the legend table, unless there's no data
        if (data.length > 0)
        {
            domLegend.show();
            domInstr.show();
        }
        else
        {
            domLegend.hide();
            domInstr.hide();
        }

        // Center the legend table under the graph
        tbl.css("position", "relative");
        tbl.css("left", Math.floor((domLegend.width()-tbl.width())/2));
    }
    
    // Return a label suitable for the vertical axis
    function suffixFormatter(val, axis)
    {
        if (val >= 1000000)
            return (val / 1000000).toFixed(2) + "M";
        else if (val >= 1000)
            return (val / 1000).toFixed(val < 10000?1:0) + "K";
        else
            return val.toFixed(0);
    }

//---------- Feed list retrieval and update

    // Initiate a feed list retrieval
    function showFeedList()
    { 
        if (exegy.forceFeed != null)
            // Retrieve the only data series allowed
            $.getJSON(exegy.baseURL+"data/"+dataset+"/"+exegy.forceFeed+"/"+chartType+"/"+rqstDate, gotNewSeriesData);
        else
        {
            // Reset the selection area to be a feed list
            domSelect.hide();
            if (dataset == "") {
                domSelectHead.html("No authorized data sets.");
            }
            else
            {
                domSelectHead.html("Retrieving feed list...");
            }
            domSelectList.empty();
            domSelect.removeClass("select_box");
            domSelect.show();

            // Get the list of feeds from the server
            $.getJSON(exegy.baseURL+"feeds/"+dataset, gotFeedList);
        }
    }

    // Retrieved feed list; update selection component
    function gotFeedList(avail)
    {
        // Show the feed list header
        domSelect.hide();
        domSelectHead.html(captionFeedList);
        domSelectHead.find("a.setStack")
            .click(stackClick)
            .find("input")
            .attr("checked", (options.series.stack != null));
        
        // How many feeds were drawn previously?
        var cnt = 0,
            origDrawn = feedDrawn;
        $.each(origDrawn, function(name, drawn) { if (drawn) cnt++; });
        
        // Create checkboxes in the feed_list element for each feed
        feedDrawn = {};
        $.each(avail, function(idx, feed)
        { 
            // if this feed is 'firstFeed', or was drawn previously, then check the box
            var sChk = "";
            if (origDrawn[feed.name] ||
                (cnt == 0 && exegy.firstFeed && feed.name == exegy.firstFeed))
            {
                sChk = " checked=\"checked\"";
                feedDrawn[feed.name] = true;
            }
            // Create the check box for this feed
            domSelectList.append("<a class=\"setFeed\">&nbsp;&nbsp;<input type=\"checkbox\"" +
                sChk + " value="+feed.name+">&nbsp;&nbsp;"+feed.label+"</a><br/>\n");
        });
        domSelectList.append("<br/><a class=\"setFeedClear\">&nbsp;&nbsp;Clear all feed selections</a><br/>\n");
        domSelect.addClass("select_box");
        domSelect.show();
        domSelectList.find("a.setFeed").click(feedClick);
        domSelectList.find("a.setFeedClear").click(clearClick);

        // Request first data for each checked feed        
        var feeds = [];
        $.each(feedDrawn, function(name, drawn) { if (drawn) feeds.push(name); });
        if (feeds.length > 0)
            $.getJSON(exegy.baseURL+"data/"+dataset+"/"+feeds.join(",")+"/"+chartType+"/"+rqstDate, gotNewSeriesData);
    } 

//---------- Graph widget user interaction

    // Handle a toggle of the stack mode
    function stackClick(event)
    {
        options.series.stack = (options.series.stack) ? null: true;
        plot.getOptions().series.stack = options.series.stack;
        $(this).find("input").attr("checked", (options.series.stack != null));
        graphRedraw();
    }
    
    // Handle a feed selection or deselection
    function feedClick()
    {
        var feedCheckbox = $(this).find("input"),
            name = feedCheckbox.val(),
            newstate = !feedDrawn[name];
        feedCheckbox.attr("checked", newstate);
        feedDrawn[name] = newstate;
        if (newstate)
        {
            // Retrieve the new data series
            $.getJSON(exegy.baseURL+"data/"+dataset+"/"+name+"/"+chartType+"/"+rqstDate, gotNewSeriesData);
        }
        else
        {
            // Remove the data series
            data = $.grep(data, function (item, idx)
            {
                if (item.name != name)
                    return true;
                else
                {
                    freeColors.push(data[idx].color);
                    return false;
                }
            });
            graphRedraw();
        }
    }

    // Clear all feed selections
    function clearClick()
    {
        // Clear all of the check boxes
        domSelectList.find("a.setFeed").find("input").attr("checked",false);

        // Remove all of the data series
        data = $.grep(data, function (item, idx)
        {
            freeColors.push(data[idx].color);
            return false;
        });
        feedDrawn = {};
        graphRedraw();
    }

    // Update the legend when a data cursor update occurs
    function cursorUpdate(updates)
    {
        lastCursor = updates;
        $.each(updates, function (idx, upd)
        {
            var d = new Date(upd.x_value),
                strRate = suffixFormatter(upd.y_value,0),
                strTime = $.plot.formatDate(d, options.xaxis.timeformat, null);
            data[upd.index].maxrate.cursor = strRate;
            data[upd.index].maxtime.cursor = strTime;  
            domRates.cursor.eq(upd.index).text(strRate);
            domTimes.cursor.eq(upd.index).text(strTime);
        });
    }

//---------- Data series handling

    // New data series retrieved; add to the graph
    function gotNewSeriesData(series)
    {
        // Use the data date from the first series returned
        if (dataDate == "")
        {
            dataDate = series[0].date;
        }

        // For each new series/feed...
        $.each(series, function (idx, s)
        {
            // Set the color for the new series
            if (exegy.forceColor != null) 
                s.color = exegy.forceColor;
            else {
                if (freeColors.length > 0)
                    s.color = freeColors.shift();
                else
                    s.color = nextColor++;
            }
            
            // Fix up the label so Flot creates more legend table columns in the DOM
            $.each(rateTypes, function (idx, type)
            {
                s.label += '</td><td class="legend_'+type+'_rate">';
                s.label += '</td><td class="legend_'+type+'_time">';
                if (type in s.maxrate)
                    s.maxrate[type] = suffixFormatter(s.maxrate[type],0);
                else
                    s.maxrate[type] = "------";
                var timFmt = (type == "historic") ? "%b %d, %y": options.xaxis.timeformat;
                if (type in s.maxtime)
                    s.maxtime[type] = $.plot.formatDate(new Date(s.maxtime[type]), timFmt, null);
                else
                    s.maxtime[type] = "--------------";
            });
            
            // Add the new data series to the graph        
            data.push(s);
        });

        // Redraw the graph with the new data series
        graphRedraw();

        // Ask for more data in the future
        if (!timer && rqstDate == "today")
        {
            timer = setTimeout(fetchData, exegy.timeInterval*1000);
        }
    }

    // Request updated data for each selected feed
    function fetchData()
    {
        timer = null;
        var feeds = [];
        $.each(data, function(idx, series) { feeds.push(series.name); });
        if (feeds.length > 0)
        {
            $.getJSON(exegy.baseURL+"data/"+dataset+"/"+feeds.join(",")+"/"+chartType+"/"+rqstDate,gotMoreSeriesData);
        }
    }

    // Updated data received
    function gotMoreSeriesData(series)
    {
        if (!timer && rqstDate == "today")
        {
            timer = setTimeout(fetchData, exegy.timeInterval*1000);
        }
        $.each(series, function (idx, s)
        {
            for (i = 0; i < data.length; i++)
            {
                if (data[i].name == s.name)                     // for each series update...
                {
                    data[i].data = s.data;                      // ...update the data points
                    $.each(rateTypes, function (idx, type)      // ...update the max rate info 
                    {
                        if (type in s.maxrate) 
                        {
                            data[i].maxrate[type] = suffixFormatter(s.maxrate[type],0);
                            var timFmt = (type == "historic") ? "%b %d, %y": options.xaxis.timeformat;
                            data[i].maxtime[type] = $.plot.formatDate(new Date(s.maxtime[type]), timFmt, null);
                        }
                    });
                    break;
                }
            }
        });
        graphRedraw();
    }

//---------- Mainline

    // Initialize the dialog object
    dialogInit();
    
    // Start it off by reseting the chart and opening a selection dialog
    graphReset();
    dialogSelect();
    
});

