/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2016-2020
 * @filesource
 */
/**
 * Defines a class useful for making point and line graph charts.
 *
 * @param String chart_id id of tag that the chart will be drawn into
 * @param Object data a sequence {x_1:y_1, ... x_1,y_n} points to plot
 *    x_i's can be arbitrary labels, y_i's are assumes to be floats
 * @param Object (optional) properties override values for any of the
 *      properties listed in the property_defaults variable below
 */
function Chart(chart_id, data)
{
    var self = this;
    var p = Chart.prototype;
    var properties = (typeof arguments[2] !== 'undefined') ?
        arguments[2] : {};
    var container = document.getElementById(chart_id);
    if (!container) {
        return false;
    }
    var property_defaults = {
        'axes_color' : [128,128,128], // color of the x and y axes lines
        'caption' : '', // caption text appears at bottom
        'caption_style' : 'font-size: 14pt; text-align: center;',
            // CSS styles to apply to caption text
        'data_color' : [0,0,255], //color used to draw graph
        'height' : 500, //height of area to draw into in pixels
        'line_width' : 1, // width of line in line graph
        'x_padding' : 30, //x-distance left side of canvas tag to y-axis
        'y_padding' : 30, //y-distance bottom of canvas tag to x-axis
        'point_radius' : 3, //radius of points that are plot in point graph
        'tick_length' : 10, // length of tick marks along axes
        'ticks_y' : 5, // number of tick marks to use for the y axis
        'tick_font_size' : 10, //size of font to use when labeling ticks
        'title' : '', // title text appears at top
        'title_style' : 'font-size:24pt; text-align: center;',
            // CSS styles to apply to title text
        'type' : 'LineGraph', // currently, can be either a LineGraph,
            //PointGraph or BarGraph
        'width' : 500 //width of area to draw into in pixels
    };
    for (var property_key in property_defaults) {
        if (typeof properties[property_key] !== 'undefined') {
            this[property_key] = properties[property_key];
        } else {
            this[property_key] = property_defaults[property_key];
        }
    }
    title_tag = (this.title) ? '<div style="' + this.title_style
         + 'width:' + this.width + '" >' + this.title + '</div>' : '';
    caption_tag = (this.caption) ? '<figcaption style="' + this.caption_style
         + 'width:' + this.width + '" >' + this.caption + '</figcaption>' : '';
    container.innerHTML = '<figure>'+ title_tag + '<canvas id="' + chart_id +
        '-content" ></canvas>' + caption_tag + '</figure>';
    canvas = document.getElementById(chart_id + '-content');
    if (!canvas || typeof canvas.getContext === 'undefined') {
        return
    }
    var context = canvas.getContext("2d");
    canvas.width = this.width;
    canvas.height = this.height;
    this.data = data;
    /**
     * Main function used to draw the graph type selected
     */
    p.draw = function()
    {
        self['draw' + self.type]();
    }
    /**
     * Used to store in fields the min and max y values as well as the start
     * and end x keys, and the range = max_y - min_y
     */
    p.initMinMaxRange = function()
    {
        self.min_value = null;
        self.max_value = null;
        var key;
        var val;
        var num_graphs = 1;
        var graph_data = {};
        if (!data.num_graphs) {
            graph_data[0] = data;
        } else {
            graph_data = data.graphs;
            num_graphs = data.num_graphs;
        }
        for (var i = 0; i < num_graphs; i++) {
            for (key in graph_data[i]) {
                val = parseFloat(graph_data[i][key]);
                if (self.min_value === null) {
                    self.min_value = val;
                    self.max_value = val;
                }
                if (val < self.min_value) {
                    self.min_value = val;
                }
                if (val > self.max_value) {
                    self.max_value = val;
                }
            }
        }
        self.range = self.max_value - self.min_value;
        self.range = (self.range == 0) ? 1 : self.range;
        self.max_value += self.range * 0.1;
        self.min_value -= self.range * 0.1;
        self.range = self.max_value - self.min_value;
    }
    /**
     * Used to draw a point at location x,y in the canvas
     */
    p.plotPoint = function(x,y)
    {
        var c = context;
        c.beginPath();
        c.arc(x, y, self.point_radius, 0, 2 * Math.PI, true);
        c.fill();
    }
    /**
     * Used to draw a bar from the x-axis, to the point x,y
     */
    p.plotBar = function(x,y)
    {
        var height = self.height - self.y_padding - self.tick_length;
        var y_bottom = self.tick_length + height *
            ((1 + self.min_value)/self.range);
        var c = context;
        c.beginPath();
        c.rect(x - self.point_radius/2, height + self.tick_length,
            self.point_radius, y - self.tick_length - height +
            self.point_radius);
        c.fill();
    }
    /**
     * Draws the x and y axes for the chart as well as ticks marks and values
     */
    p.renderAxes = function()
    {
        var c = context;
        var height = self.height - self.y_padding;
        c.strokeStyle = "rgb(" + self.axes_color[0] + "," +
            self.axes_color[1] + "," + self.axes_color[2] + ")";
        c.lineWidth = self.line_width;
        c.beginPath();
        c.moveTo(self.x_padding - self.tick_length,
            self.height - self.y_padding);
        c.lineTo(self.width - self.x_padding,  height);  // x axis
        c.stroke();
        c.beginPath();
        c.moveTo(self.x_padding, self.tick_length);
        c.lineTo(self.x_padding, self.height - self.y_padding +
            self.tick_length);  // y axis
        c.stroke();
        var spacing_y = self.range/self.ticks_y;
        height -= self.tick_length;
        var min_y = parseFloat(self.min_value);
        var max_y = parseFloat(self.max_value);
        var num_format = new Intl.NumberFormat("en-US",
            {"maximumFractionDigits":2});
        // Draw y ticks and values

        for (var val = min_y; val < max_y + spacing_y; val += spacing_y) {
            y = self.tick_length + height *
                (1 - (val - self.min_value)/self.range);
            c.font = self.tick_font_size + "px serif";
            c.fillText(num_format.format(val), 0, y + self.tick_font_size/2,
                self.x_padding - self.tick_length);
            c.beginPath();
            c.moveTo(self.x_padding - self.tick_length, y);
            c.lineTo(self.x_padding, y);
            c.stroke();
        }
        var num_graphs = 1;
        var graph_data = {};
        if (!data.num_graphs) {
            graph_data[0] = data;
        } else {
            graph_data = data.graphs;
            num_graphs = data.num_graphs;
        }
        var x_values;
        if (!data.x_values) {
            x_values = Object.keys(graph_data[0]);
        } else {
            x_values = data.x_values;
        }
        // Draw x ticks and values
        var dx = (self.width - 2 * self.x_padding) /
            (Object.keys(x_values).length - 1);
        var x = self.x_padding;
        for (x_key in x_values) {
            c.font = self.tick_font_size + "px serif";
            var x_value = x_values[x_key];
            c.fillText(x_value, x -
                self.tick_font_size/2 * (x_value.length - 0.5),
                self.height - self.y_padding +  self.tick_length +
                self.tick_font_size, self.tick_font_size *
                (x_value.length - 0.5));
            c.beginPath();
            c.moveTo(x, self.height - self.y_padding + self.tick_length);
            c.lineTo(x, self.height - self.y_padding);
            c.stroke();
            x += dx;
        }
    }
    /**
     * Draws a chart consisting of just x-y plots of points in data.
     */
    p.drawPointGraph = function()
    {
        self.initMinMaxRange();
        self.renderAxes();
        num_graphs = 1;
        var graph_data = {};
        if (!data.num_graphs) {
            graph_data[0] = data;
        } else {
            graph_data = data.graphs;
            num_graphs = data.num_graphs;
        }
        var x_values;
        for (var i = 0; i < num_graphs; i++) {
            if (!data.x_values) {
                x_values = graph_data[i];
            } else {
                x_values = data.x_values;
            }
            var dx = (self.width - 2 * self.x_padding) /
                ( Object.keys(x_values).length - 1);
            var c = context;
            c.lineWidth = self.line_width;
            var dim = 1 - (0.7 * (i % 3));
            c.strokeStyle = "rgb(" + dim * self.data_color[0] + "," +
                dim * self.data_color[1] + "," + dim * self.data_color[2] + ")";
            c.fillStyle = "rgb(" + dim * self.data_color[0] + "," +
                dim * self.data_color[1] + "," + dim * self.data_color[2] + ")";
            var height = self.height - self.y_padding - self.tick_length;
            var x = self.x_padding;
            for (var x_key in x_values) {
                if (graph_data[i][x_key]) {
                    y = self.tick_length + height *
                        (1 - (graph_data[i][x_key] -
                        self.min_value)/self.range);
                    self.plotPoint(x, y);
                }
                x += dx;
            }
        }
    }
    /**
     * Draws a chart consisting of x-y plots of points in data, each adjacent
     * point pairs connected by a line segment
     */
    p.drawLineGraph = function()
    {
        self.drawPointGraph();
        num_graphs = 1;
        var graph_data = {};
        if (!data.num_graphs) {
            graph_data[0] = data;
        } else {
            graph_data = data.graphs;
            num_graphs = data.num_graphs;
        }
        var x_values;
        var dash_state = 0;
        for (var i = 0; i < num_graphs; i++) {
            if (!data.x_values) {
                x_values = graph_data[i];
            } else {
                x_values = data.x_values;
            }
            var c = context;
            c.beginPath();
            if (dash_state == 0) {
                c.setLineDash([]);
            } else if (dash_state == 1) {
                c.setLineDash([5, 5]);
            } else {
                c.setLineDash([2, 2]);
            }
            var dim = 1 - (0.7 * (i % 3));
            c.strokeStyle = "rgb(" + dim * self.data_color[0] + "," +
                dim * self.data_color[1] + "," + dim * self.data_color[2] + ")";
            c.fillStyle = "rgb(" + dim * self.data_color[0] + "," +
                dim * self.data_color[1] + "," + dim * self.data_color[2] + ")";
            dash_state = (dash_state + 1) % 3;
            var x = self.x_padding;
            var dx =  (self.width - 2 * self.x_padding) /
                (Object.keys(x_values).length - 1);
            var height = self.height - self.y_padding  - self.tick_length;
            var first_time = true;
            for (var x_key in x_values) {
                if (graph_data[i][x_key]) {
                    y = self.tick_length + height *
                        (1 - (graph_data[i][x_key] - self.min_value)
                        / self.range);
                    if (first_time) {
                        first_time = false;
                        c.moveTo(x, y);
                    } else {
                        c.lineTo(x, y);
                    }
                }
                x += dx;
            }
            c.stroke();
        }
    }
    /**
     * Draws a chart consisting of x-y plots of points in data, where each
     * data point is a bar from the x-axis to the point in question
     */
    p.drawBarGraph = function()
    {
        self.initMinMaxRange();
        self.renderAxes();
        num_graphs = 1;
        graph_data = {};
        if (!data.num_graphs) {
            graph_data[0] = data;
        } else {
            graph_data = data.graphs;
            num_graphs = data.num_graphs;
        }
        var x_values;
        for (var i = 0; i < num_graphs; i++) {
            if (!data.x_values) {
                x_values = graph_data[i];
            } else {
                x_values = data.x_values;
            }
            var dx = (self.width - 2 * self.x_padding) /
                (Object.keys(x_values).length - 1);
            var c = context;
            c.lineWidth = self.line_width;
            var dim = 0.7 * (i % 3);
            c.strokeStyle = "rgb(" + dim * self.data_color[0] + "," +
                dim * self.data_color[1] + "," + dim * self.data_color[2] + ")";
            c.fillStyle = "rgb(" + dim * self.data_color[0] + "," +
                dim * self.data_color[1] + "," + dim * self.data_color[2] + ")";
            var height = self.height - self.y_padding - self.tick_length;
            var x = self.x_padding + i * (dx/num_graphs)
            for (x_key in x_values) {
                if (graph_data[i][x_key]) {
                    y = self.tick_length + height *
                        (1 - (graph_data[i][x_key] - self.min_value)
                        / self.range);
                        self.plotBar(x, y);
                }
                x += dx;
            }
        }
    }
}
