<?php

namespace App\Generators;

use Exception;
use Carbon\Carbon;
use App\Models\Resource;
use Illuminate\Support\Str;

class ChartScriptGenerator
{
    private $chart;
    private $xAxis;
    private $yAxis;
    public function __construct(Resource $chart, $axis)
    {
        $this->chart = $chart;
        $this->xAxis = $axis[0];
        $this->yAxis = $axis[1];
        $this->addHoursTodays();
    }

    private function addHoursTodays()
    {

        if (in_array($this->getOption('date_time_scale'), ['days', 'weeks'])) {
            $this->xAxis = array_map(function ($date) {
                // Convert the string to a Carbon instance
                $carbonDate = Carbon::parse($date);
                // Format the date to include hours
                return $carbonDate->format('Y-m-d H:i:s');
            }, $this->xAxis);

        }

    }

    public function getChartScript()
    {

        switch ($this->getOption('chart_type')) {
            case 'bar-chart':
                return $this->getBarChartScript();
                break;
            case 'horizontal-chart':
                return $this->getHorizontalBarChartScript();
                break;
            case 'area-chart':
                return $this->getAreaChartScript();
                break;
            case 'line-chart':
                return $this->getLineChartScript();
                break;
            case 'spline-chart':
                return $this->getSplineChartScript();
                break;
            case 'timeseries':
                return $this->getTimeseriesChartScript();
                break;
            case 'scatter-chart':
                return $this->getScatterChartScript();
                break;
            case 'pie-chart':
                return $this->getPieChartScript();
                break;
            case 'donut-chart':
                return $this->getDonutChartScript();
                break;

            default:
                break;
        }

    }

    private function getBarChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();

        return <<<END
        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            {$this->setPadding()},
            data: {
                x:'xAxis',
                {$this->getXAxisFormat()}
                columns: [
                    ['xAxis',$xAxis],
                    ['values',$yAxis]
                ],
                type: 'bar',
                colors: {
                    values: '#b1d1e4',
                },
                onmouseover: function (d) {
                    d3.selectAll('#$chartName .c3-bar').style('opacity', 0.2); // Dim all bars
                    d3.select('#$chartName .c3-bar-' + d.index).style('opacity', 1); // Highlight hovered bar
                },
                onmouseout: function () {
                    d3.selectAll('#$chartName .c3-bar').style('opacity', 1); // Reset opacity
                }
            },
            axis: {
                x: {
                    {$this->getBarChartType()}
                    categories: [$xAxis],
                    tick:{
                        culling:false,
                        {$this->setXTickWidth()}
                        {$this->getXTickFormat()}
                        {$this->formatAsQuarters()}
                        {$this->formatAsWeeks()}

                    },
                    label: {
                        text: '$xAxisLabel',
                        position: 'outer-center'
                    },

                },
                y:{
                    {$this->setYAxisPadding()}
                    min:0,
                    label: {
                        text: '$label',
                        position: 'outer-middle'
                    },
                    tick:{
                        {$this->handleTicks()}
                    }
                }
            },
            legend: {
                show: false
            },
            bar: {
                width: {
                    ratio: 0.5
                }
            },
            grid:{
                y:{
                    show:true
                },
                x:{
                    show:true
                }
            },
            onrendered: function() {
                {$this->setStrokeColorAndWidth()}
            },
            tooltip: {
                position: function (dataToShow,tWidth,tHeight,element) {
                        var $$ = this,
                        config = $$.config,
                        d3 = $$.d3
                    var svgLeft, tooltipLeft, tooltipRight, tooltipTop, chartRight
                    var forArc = $$.hasArcType(),
                        mouse = d3.mouse(element)


                    if (forArc) {
                        tooltipLeft =
                        ($$.width - ($$.isLegendRight ? $$.getLegendWidth() : 0)) / 2 + mouse[0]
                        tooltipTop =
                        ($$.hasType('gauge') ? $$.height : $$.height / 2) + mouse[1] + 20
                    } else {
                        svgLeft = $$.getSvgLeft(true)
                        if (config.axis_rotated) {
                        tooltipLeft = svgLeft + mouse[0] + 30
                        tooltipRight = tooltipLeft + tWidth
                        chartRight = $$.currentWidth - $$.getCurrentPaddingRight()
                        tooltipTop = $$.x(dataToShow[0].x) + 20
                        } else {
                        tooltipLeft =
                            svgLeft + $$.getCurrentPaddingLeft(true) + $$.x(dataToShow[0].x) -90
                        tooltipRight = tooltipLeft + tWidth
                        chartRight = svgLeft + $$.currentWidth - $$.getCurrentPaddingRight()
                        tooltipTop = 10
                        }
                    }
                    if (tooltipTop < 0) {
                        tooltipTop = 0
                    }
                    return {
                        top: tooltipTop,
                        left: tooltipLeft
                    }
                },
                grouped: false,
                {$this->setTooltipContent()}

        });
        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}
        END;
    }

    private function getHorizontalBarChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();

        return <<<END

        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            {$this->setPadding()},
            data: {
                x:'xAxis',
                {$this->getXAxisFormat()}
                columns: [
                    ['xAxis',$xAxis],
                    ['values',$yAxis]
                ],
                type: 'bar',
                colors: {
                    values: '#b1d1e4'
                },
                onmouseover: function (d) {
                    d3.selectAll('#$chartName .c3-bar').style('opacity', 0.2);
                    d3.select('#$chartName .c3-bar-' + d.index).style('opacity', 1);
                },
                onmouseout: function () {
                    d3.selectAll('#$chartName .c3-bar').style('opacity', 1);
                }

            },
            axis: {
                rotated:true,
                x: {
                    {$this->getBarChartType()}
                    {$this->setYAxisPadding()}
                    tick:{
                        culling:false,
                        {$this->setXTickWidth()}
                        {$this->getXTickFormat()}
                        {$this->formatAsQuarters()}
                        {$this->formatAsWeeks()}
                        rotate:90,
                        multiline:true

                    },
                    label: {
                        text: '$xAxisLabel',
                        position: 'outer-middle'
                    },

                },
                y:{
                    {$this->setYAxisPadding()}
                    label: {
                        text: '$label',
                        position: 'outer-center'
                    },
                    tick:{
                        {$this->handleTicks()}
                    }
                }
            },
            legend: {
                show: false
            },

            bar: {
                width: {
                    ratio: 0.5
                }

            },
            grid:{
                y:{
                    show:true
                },
                x:{
                    show:true
                }
            },
            onrendered: function() {
                {$this->setStrokeColorAndWidth()}
            },
            tooltip: {
                grouped: false,
                {$this->setToolTipPosition()}
                {$this->setTooltipContent()}
        });
        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}

        END;
    }

    private function getAreaChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();

        return <<<END

        {$this->adjustCategoricalPadding()}
        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            {$this->setPadding()},
            data: {
                x:'xAxis',
                {$this->getXAxisFormat()}
                columns: [
                    ['xAxis',$xAxis],
                    ['values',$yAxis]
                ],
                type: 'area',
                colors: {
                    values: '#b1d1e4',
                },
            },
            axis: {
                x: {
                    {$this->getBarChartType()}
                    {$this->setYAxisPadding()}
                    tick: {
                        {$this->getXTickFormat()}
                        {$this->formatAsQuarters()}
                        {$this->formatAsWeeks()}
                        {$this->AdjustRotation($xAxis)}
                    },
                    label: {
                        text: '$xAxisLabel',
                        position: 'outer-center'
                    },
                    {$this->getTimeSeriesMinimumValue()}
                },
                y:{
                    min:0,
                    {$this->setYAxisPadding()}
                    label: {
                        text: '$label',
                        position: 'outer-middle'
                    },
                    tick:{
                        {$this->handleTicks()}
                    }
                }
            },
            legend: {
                show: false
            },
            grid:{
                y:{
                    show:true
                },
                x:{
                    show:true
                }
            },
            onrendered: function() {

                {$this->setStrokeColorAndWidth()}

                d3.selectAll('.c3-areas .c3-area')
                .style('opacity', '0.8');

                d3.selectAll('.c3-circles .c3-circle')
                .style('stroke', '#3c8dbc')
                .style('opacity', '0.8');


            },
            tooltip: {
                position: function (data, width, height, element) {
                    var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);
                    var mouse = d3.mouse(element.parentNode);
                    position.top = mouse[1] + 35;
                    position.left = mouse[0] - 20;
                    return position;
                },
                grouped: false,
                {$this->setTooltipContent()}
        });
        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}

        {$this->resetCategoricalPadding()}

        END;
    }

    private function getLineChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();

        return <<<END

        {$this->adjustCategoricalPadding()}
        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            {$this->setPadding()},
            data: {
                x:'xAxis',
                {$this->getXAxisFormat()}
                columns: [
                    ['xAxis',$xAxis],
                    ['values',$yAxis]
                ],
                type: 'line',
                colors: {
                    values: '#b1d1e4',
                },

            },
            {$this->adjustChartPointSize()}
            axis: {
                x: {
                    {$this->getBarChartType()}
                    {$this->setYAxisPadding()}
                    tick:{
                        culling:false,
                        {$this->getXTickFormat()}
                        {$this->formatAsQuarters()}
                        {$this->formatAsWeeks()}
                        {$this->AdjustRotation($xAxis)}
                    },
                    label: {
                        text: '$xAxisLabel',
                        position: 'outer-center'
                    },
                    {$this->getTimeSeriesMinimumValue()}
                },
                y:{
                    {$this->setYAxisPadding()}
                    min:0,
                    label: {
                        text: '$label',
                        position: 'outer-middle'
                    },
                    tick:{
                        {$this->handleTicks()}
                    }
                }
            },
            legend: {
                show: false
            },
            grid:{
                y:{
                    show:true
                },
                x:{
                    show:true
                }
            },
            onrendered: function() {
                {$this->setStrokeColorAndWidth()}
            },
            tooltip: {
                position: function (data, width, height, element) {
                    var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);
                    var mouse = d3.mouse(element.parentNode);
                    position.top = mouse[1] + 35;
                    position.left = mouse[0] - 20;
                    return position;
                },

                grouped: false,
                {$this->setTooltipContent()}
        });
        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}
        {$this->adjustChartPointsClipping($name)}
        {$this->resetCategoricalPadding()}
        END;
    }

    private function getSplineChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();

        return <<<END
        {$this->adjustCategoricalPadding()}

        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            {$this->setPadding()},
            data: {
                x:'xAxis',
                {$this->getXAxisFormat()}

                columns: [
                    ['xAxis',$xAxis],
                    ['values',$yAxis]
                ],
                type: 'spline',
                colors: {
                    values: '#b1d1e4',
                },

            },
            {$this->adjustChartPointSize()}
            axis: {
                x: {
                    {$this->getBarChartType()}
                    tick:{
                        culling:false,
                        {$this->getXTickFormat()}
                        {$this->formatAsQuarters()}
                        {$this->formatAsWeeks()}
                        {$this->AdjustRotation($xAxis)}
                    },
                    label: {
                        text: '$xAxisLabel',
                        position: 'outer-center'
                    },
                    {$this->getTimeSeriesMinimumValue()}
                },
                y:{
                    min:0,
                    label: {
                        text: '$label',
                        position: 'outer-middle'
                    },
                    tick:{
                        {$this->handleTicks()}
                    }
                }
            },
            legend: {
                show: false
            },
            grid:{
                y:{
                    show:true
                },
                x:{
                    show:true
                }
            },
            onrendered: function() {

                {$this->setStrokeColorAndWidth()}
            },
            tooltip: {
                position: function (data, width, height, element) {
                    var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);
                    var mouse = d3.mouse(element.parentNode);
                    position.top = mouse[1] + 35;
                    position.left = mouse[0] - 20;
                    return position;
                },
                grouped: false,
                {$this->setTooltipContent()}
        });
        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}
        {$this->adjustChartPointsClipping($name)}
        {$this->resetCategoricalPadding()}

        END;
    }

    private function getScatterChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();

        return <<<END

        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            {$this->setPadding()},
            data: {
                x:'xAxis',
                {$this->getXAxisFormat()}
                columns: [
                    ['xAxis',$xAxis],
                    ['values',$yAxis]
                ],
                type: 'scatter',
                colors: {
                    values: '#3c8dbc',
                },

            },
            {$this->adjustChartPointSize()}
            axis: {
                x: {
                    {$this->getBarChartType()}
                    {$this->setYAxisPadding()}
                    tick:{
                        culling:false,
                        {$this->getXTickFormat()}
                        {$this->formatAsQuarters()}
                        {$this->formatAsWeeks()}
                        {$this->AdjustRotation($xAxis)}
                    },
                    label: {
                        text: '$xAxisLabel',
                        position: 'outer-center'
                    },
                    {$this->getTimeSeriesMinimumValue()}
                },
                y:{
                    {$this->setYAxisPadding()}
                    min:0,
                    label: {
                        text: '$label',
                        position: 'outer-middle'
                    },
                    tick:{
                        {$this->handleTicks()}
                    }
                }
            },
            legend: {
                show: false
            },
            grid:{
                y:{
                    show:true
                },
                x:{
                    show:true
                }
            },
            tooltip: {
                    position: function (data, width, height, element) {
                    var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);
                    var mouse = d3.mouse(element.parentNode);
                    position.top = mouse[1] + 35;
                    position.left = mouse[0] - 20;
                    return position;
                },
                grouped: false,
                {$this->setTooltipContent()}
        });
        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}
        {$this->adjustChartPointsClipping($name)}

        END;
    }

    private function getTimeseriesChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();
        return <<<END
        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            padding: {
                right: 200
            },
            {$this->setPadding()},
            data: {
                x:'xAxis',
                {$this->getXAxisFormat()}
                columns: [
                    ['xAxis',$xAxis],
                    ['values',$yAxis]
                ],
                colors: {
                    values: '#b1d1e4',
                },


            },
            {$this->adjustChartPointSize()}
            axis: {
                x: {
                    type: 'timeseries',
                    tick: {
                        culling: false,
                        {$this->getXTickFormat()}
                        {$this->formatAsQuarters()}
                        {$this->formatAsWeeks()}
                        {$this->AdjustRotation($xAxis)}
                    },
                    label: {
                        text: '$xAxisLabel',
                        position: 'outer-center'
                    },
                    {$this->getTimeSeriesMinimumValue()}

                },
                y:{
                    {$this->setYAxisPadding()}
                    min:0,
                    label: {
                        text: '$label',
                        position: 'outer-middle'
                    },
                    tick:{
                        {$this->handleTicks()}
                    }

                }
            },
            legend: {
                show: false
            },
            grid:{
                y:{
                    show:true
                },
                x:{
                    show:true
                }
            },
            onrendered: function() {
                {$this->setStrokeColorAndWidth()}
            },
            tooltip: {
                position: function (data, width, height, element) {
                    var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);
                    var mouse = d3.mouse(element.parentNode);
                    position.top = mouse[1] + 35;
                    position.left = mouse[0] - 20;
                    return position;
                },
                grouped: false,
                {$this->setTooltipContent()}
        });
        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}
        {$this->adjustChartPointsClipping($name)}

        END;
    }

    private function getPieChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();

        $data         = json_encode(array_map(null, $this->xAxis, $this->yAxis));
        $colorsJson   = $this->prepareSectionsColors($this->xAxis, $this->getChartSectionsColors());
        $labelsColors = $this->prepareLabelColors($this->xAxis, $this->getChartSectionsColors());
        $options      = ["dy" => "16.2em", "dx" => "0.5em", "font-size" => "9px"];

        return <<<END

        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            data: {
                columns: $data,
                type: 'pie',
                colors: $colorsJson,
            },

            pie: {
                labels: {
                    threshold: 0.05
                }
            },
            onrendered: function() {
            {$this->setLabelsColors($labelsColors,$chartName)}
            },
            tooltip: {
                {$this->setToolTipPiePosition($chartName)}
                {$this->setTooltipContent()}
        });

        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}

        END;
    }

    private function getDonutChartScript()
    {
        [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel] = $this->getChartOptions();

        $data         = json_encode(array_map(null, $this->xAxis, $this->yAxis));
        $colorsJson   = $this->prepareSectionsColors($this->xAxis, $this->getChartSectionsColors());
        $labelsColors = $this->prepareLabelColors($this->xAxis, $this->getChartSectionsColors());
        $options      = ["dy" => "16.2em", "dx" => "0.5em", "font-size" => "9px"];

        return <<<END

        var ch{$name}Timeout;

        var ch{$name}Chart = c3.generate({
            bindto:"#$chartName" ,
            data: {
                columns: $data,
                type: 'donut',
                colors: $colorsJson,
            },
            pie: {
                label: {
                    threshold: 0.05
                }
            },
            onrendered: function() {
            {$this->setLabelsColors($labelsColors,$chartName)}
            },
            tooltip: {
                {$this->setToolTipPiePosition($chartName)}
                {$this->setTooltipContent()}
        });
        {$this->setHideTooltipEffect($chartName)}
        {$this->getDetailsPage($name,$chartName)}

        END;
    }

    private function getChartOptions()
    {
        $name       = Str::of($this->getOption('name'))->camel();
        $chartName  = Str::of($this->getOption('name'))->kebab();
        $xAxis      = $this->convertArrayToString($this->xAxis);
        $yAxis      = implode(", ", $this->yAxis);
        $label      = $this->getOption('label');
        $title      = $this->getOption('title');
        $xAxisLabel = !empty($this->getOption('date_range'))
        ? $this->getOption('x_axis_column') . " - " . str_replace("_", " ", $this->getOption('date_range'))
        : $this->getOption('x_axis_column');

        return [$name, $chartName, $xAxis, $yAxis, $label, $title, $xAxisLabel];

    }

    private function convertArrayToString($array)
    {
        return "'" . implode("', '", $array) . "'";
    }

    private function getOption($option)
    {
        return $this->chart->getResourceConfiguration($option);
    }

    private function setToolTipPosition()
    {

        return <<<END

        position: function (data, width, height, element) {

            var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);

            var mouse = d3.mouse(element.parentNode);

            position.top = mouse[1] + 25;

            position.left = mouse[0] - 10;

            return position;

        },
        END;
    }

    private function setToolTipPiePosition($chartName)
    {

        return <<<END
            position: function (data, width, height, element) {
                var position = c3.chart.internal.fn.tooltipPosition.apply(this, arguments);

                const chart = d3.select(`#$chartName svg`);

                const chartWidth = (+chart.attr("width") / 2) + 8;

                var mouse = d3.mouse(element.parentNode);
                position.top = mouse[1] + 20;
                position.left = mouse[0] + chartWidth;
                return position;
            },
        END;
    }

    private function setStrokeColorAndWidth()
    {
        return <<<END
            d3.selectAll('.c3-chart-line .c3-line')
            .style('stroke', '#3c8dbc')
            .style('stroke-width', '2px');
        END;
    }

    private function setLabelsColors($labelsColors, $chartName)
    {
        return <<<END
        setTimeout(function(){
            let colors = $labelsColors;
            for(var i = 0; i < colors.length; i++){
                let className = colors[i][0].replace(/,/g, "\\\\,").replace(/ /g, "-").replace(/\//g, '\\\\/');
                d3.select("#{$chartName} .c3-target-"+ className +" > text").style("fill",colors[i][1]);
            }
        },100);

        END;
    }

    private function getDetailsPage($name, $chartName)
    {

        if ($this->chart->getResourceConfiguration('chart_link')) {

            $link = route('charts.details', $this->chart->id);

            return <<<END
            var ch{$name}chartTitle = d3.select("#{$chartName} .c3-title");

            ch{$name}chartTitle.on("click", function() {
                window.location.href="$link"
            }).style('cursor','pointer');

        END;
        }

    }

    private function setHideTooltipEffect($chartName)
    {
        $name = Str::of($chartName)->camel();
        return <<<END
            c3.chart.internal.fn.hideTooltip = function (){};
            ch{$name}Chart.ch{$name}TooltipTimeout = null;

            function hideTooltip(chartName) {
                d3.select("#" + chartName + " .c3-tooltip-container").style("display", "none");
            }

            d3.select("#" + "$chartName" + " .c3-tooltip-container")
            .on("mouseenter", function () {
                clearTimeout(ch{$name}Chart.ch{$name}TooltipTimeout);
            }).on("mouseleave", function () {
                ch{$name}Chart.ch{$name}TooltipTimeout = setTimeout(function () {
                    hideTooltip("$chartName");
                }, 200);
            });

            d3.select("#" + "$chartName" + " .c3-chart")
            .on("mouseenter", function () {
                clearTimeout(ch{$name}Chart.ch{$name}TooltipTimeout);
            }).on("mouseleave", function () {
                ch{$name}Chart.ch{$name}TooltipTimeout = setTimeout(function () {
                    hideTooltip("$chartName");
                }, 200);
            });
        END;
    }

    private function setTooltipContent()
    {

        if ($this->getOption('drill_down')) {
            return <<<END
                {$this->tooltipHeader()}
                {$this->tooltipBodyWithButton()}
            END;
        } else {
            return <<<END
                {$this->tooltipHeader()}
                {$this->tooltipBody()}

            END;

        }

    }

    private function getChartSectionsColors()
    {
        $palette = $this->chart->getResourceConfiguration('sections_color_palette');

        switch ($palette) {
            case 'blue-gradient':
                return $this->getBlueGradientChartColors();
                break;

            case 'blue-green':
                return $this->getBlueGreenChartColors();
                break;

            case 'multi-color':
                return $this->getMultiColorChartColors();
                break;
            default:
                break;
        }

    }

    private function getBlueGradientChartColors()
    {
        return [
            [
                "color" => "#0073B6",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#00A9FF",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#00C0EF",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#42E1FE",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#B0F5FF",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#ECF9FF",
                "label" => "#0073B6"
            ],
            [
                "color" => "#87CEEB",
                "label" => "#0073B6"
            ],
            [
                "color" => "#6CB4EE",
                "label" => "#0073B6"
            ]
        ];
    }

    private function getBlueGreenChartColors()
    {
        return [
            [
                "color" => "#0073B6",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#61A3BA",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#29ADB2",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#A8DDA8",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#D2E69C",
                "label" => "#0073B6"
            ],
            [
                "color" => "#A7E9AF",
                "label" => "#0073B6"
            ],
            [
                "color" => "#FFFFDD",
                "label" => "#0073B6"
            ],
            [
                "color" => "#F3F3F3",
                "label" => "#0073B6"
            ]
        ];

    }

    private function getMultiColorChartColors()
    {

        return [
            [
                "color" => "#165BAA",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#A155B9",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#F765A3",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#AED8CC",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#E48586",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#9681EB",
                "label" => "#FFFFFF"
            ],
            [
                "color" => "#FFDEB4",
                "label" => "#0073B6"
            ],
            [
                "color" => "#F5EAEA",
                "label" => "#0073B6"
            ]
        ];
    }

    private function prepareSectionsColors($xAxis, $colors)
    {

        if (count($xAxis) < 9) {
            $xAxis  = array_slice($xAxis, 0, count($xAxis));
            $colors = array_slice($this->getChartSectionsColors(), 0, count($xAxis));

            $colors = array_map(function ($color) {
                return $color['color'];
            }, $colors);

            return json_encode(array_combine($xAxis, $colors));
        }

        return json_encode([]);
    }

    private function prepareLabelColors($xAxis, $colors)
    {

        if (count($xAxis) < 9) {
            $xAxis  = array_slice($xAxis, 0, count($xAxis));
            $colors = array_slice($this->getChartSectionsColors(), 0, count($xAxis));
            $colors = array_map(function ($color) {
                return $color['label'];
            }, $colors);

            return json_encode(array_map(null, $xAxis, $colors));
        }

        $colors = array_map(function ($color) {
            return "#FFFFFF";
        }, $xAxis);

        return json_encode(array_map(null, $xAxis, $colors));
    }

    private function getTimeSeriesDateFormat()
    {

        switch ($this->getOption('date_time_scale')) {
            case 'years':
                return '%Y';
                break;
            case 'months':
                return '%Y-%m';
                break;
            case 'days':
            case 'weeks':
                return '%Y-%m-%d';
            case 'hours':
                return '%H:%M:%S';
                break;
            default:
                break;
        }

    }

    private function getTimeSeriesMinimumDate()
    {

        if (!empty($this->xAxis)) {

            switch ($this->getOption('date_time_scale')) {
                case 'years':
                    return Carbon::createFromFormat('Y-m-d', $this->xAxis[0])->subMonths(1)->toDateString();
                    break;
                case 'quarters':
                    return Carbon::createFromFormat('Y-m-d', $this->xAxis[0])->subDays(5)->toDateString();
                    break;
                case 'months':
                    $paddingDays = in_array($this->getOption('date_range'), ['last_3_months', 'this_3_months']) ? 1 : 3;
                    return Carbon::createFromFormat('Y-m-d', $this->xAxis[0])->subDays($paddingDays)->toDateString();
                    break;
                case 'days':
                    $paddingHours = in_array($this->getOption('date_range'), ['this_week', 'last_7_days']) ? 2 : 5;
                    return Carbon::createFromFormat('Y-m-d H:i:s', $this->xAxis[0])->subHours($paddingHours)->toDateTimeString();
                    break;
                case 'weeks':
                    return Carbon::createFromFormat('Y-m-d H:i:s', $this->xAxis[0])->subHours(3)->toDateTimeString();
                case 'hours':
                    // return '%H:%M:%S';
                    return Carbon::createFromFormat('Y-m-d H:i:s', $this->xAxis[0])->subMinutes(15)->toDateTimeString();

                    break;
                default:
                    break;
            }

        }

    }

    private function formatAsQuarters()
    {

        if ($this->getOption('date_time_scale') == "quarters") {
            return <<<END
            format: function (d) {
                return 'Q' + parseInt(d.getMonth() / 3 + 1) + ' of ' + d.getFullYear();
            },
            END;
        }

    }

    private function formatAsWeeks()
    {

        if ($this->getOption('date_time_scale') == "weeks") {
            return <<<END
            format: function (d) {
                return 'Week ' + Math.ceil(d.getDate() / 7) + ' of ' + d.toLocaleString('default', { month: 'short' });
            },
            END;
        }

    }

    private function AdjustRotation($xAxis)
    {
        $xAxis = explode(",", $xAxis);

        if (count($xAxis) > 10 && count($xAxis) < 15 && $this->chart->widget_size == Resource::CHART_FULL_SIZE) {
            return <<<END
                rotate: -45,
            END;
        }

        if (count($xAxis) > 5 && count($xAxis) < 15 && $this->chart->widget_size == Resource::CHART_HALF_SIZE) {
            return <<<END
                rotate: -90,
            END;
        } elseif (count($xAxis) >= 15) {
            return <<<END
                rotate: -90,
            END;
        }

    }

    private function setPadding()
    {
        return <<<END
            padding: {
                right: 40,
                top:10
            }
        END;
    }

    private function setXTickWidth()
    {
        return <<<END
            width: 150,
        END;
    }

    private function getXAxisFormat()
    {

        if (in_array($this->getOption('date_time_scale'), ['hours', 'days', 'weeks'])) {
            return <<<END
            xFormat: '%Y-%m-%d %H:%M:%S',
        END;
        }

    }

    private function getXTickFormat()
    {
        return <<<END
            format: '{$this->getTimeSeriesDateFormat()}',
        END;
    }

    private function getTimeSeriesMinimumValue()
    {

        try {

            if (!empty($this->xAxis) && is_array($this->xAxis)) {
                return <<<END
            min: '{$this->getTimeSeriesMinimumDate()}',
        END;
            }

        } catch (Exception $e) {
            return '';
        }

    }

    private function adjustChartPointsClipping($name)
    {
        return <<<END
            var chartLayer = d3.select(ch{$name}Chart.element).select("." + c3.chart.internal.fn.CLASS.chart);
            var chartLayerParentNode = chartLayer.node().parentNode;
            var chartLayerNode = chartLayer.remove();
            chartLayerParentNode.appendChild(chartLayerNode.node());

            chartLayer.attr("clip-path", null);
        END;
    }

    private function adjustChartPointSize()
    {
        return <<<END
        point:{
            r:5
        },
        END;
    }

    private function getBarChartType()
    {

        if (!empty($this->getOption('date_time_scale'))) {
            return <<<END
                type: 'timeseries',
            END;
        }

        return <<<END
            type: 'category',
        END;

    }

    private function setYAxisPadding()
    {
        return <<<END
            padding: {bottom: 0},
        END;
    }

    private function getDrillLink()
    {
        return route('charts.drill', [$this->chart->id]);
    }

    private function tooltipHeader()
    {
        $title = !in_array($this->chart->configurations->chart_type, ["pie-chart", "donut-chart"]) ?
        "titleFormat ? titleFormat(data[0].x, data[0].index) : data[0].x" : "data[0].name";

        return <<<END

        contents: function (data, defaultTitleFormat, defaultValueFormat, color) {
            var $$ = this, config = $$.config,

            titleFormat = config.tooltip_format_title || defaultTitleFormat,
            nameFormat = config.tooltip_format_name || function (name) { return name; },
            valueFormat = config.tooltip_format_value || defaultValueFormat,
            text, i, title, value, xValue;

            title = $title;
            xValue = {$this->getDrillXAxisValue()};
        END;
    }

    private function tooltipBodyWithButton()
    {
        $drillLink   = $this->getDrillLink();
        $xAxisColumn = $this->getOption('x_axis_column');
        $yAxisColumn = $this->getOption('y_axis_column');
        $function    = $this->prepareFunction($this->getOption('function'));

        return <<<END
        percentage = data[0].ratio ? "<tr><td style='text-align:center;padding-bottom:5px;color:green'>" + (data[0].ratio * 100).toFixed(1) + "%</td></tr>"  : "";

        text = "<div id='tooltip' class='d3-tip' style='font-size:0.8rem'>";
        text += "<table class='tooltip-table' style='width:100%'>";
        text += "<tr><td style='text-align:center'>" + "$xAxisColumn: <span style='color:#007bff'>" +title + "</span></td></tr>";
        text += "<tr><td style='text-align:center;padding-bottom:5px'>" + "$function of $yAxisColumn: <span style='color:#007bff'>"+ data[0].value + "</span></td></tr>";
        text += percentage;
        text += "<tr><td>" + "<a class='btn btn-primary btn-block' href='$drillLink?xValue=" + xValue + "'> Display</a>" + "</td>";
        text += "</table>";
        text += "</div>";

        return text;
            }
        }

        END;

    }

    private function prepareFunction($function)
    {

        switch ($function) {
            case 'count':
                return 'Count';
                break;
            case 'sum':
                return 'Sum';
                break;
            case 'average':
                return 'Average';
                break;
            case 'max':
                return 'Max';
                break;
            case 'min':
                return 'Min';
                break;
            case 'sample-standard-deviation':
                return 'STD';
                break;
            case 'population-standard-deviation':
                return 'Population STD';
                break;
            default:
                return '';
                break;
        }

    }

    private function tooltipBody()
    {
        $drillLink   = $this->getDrillLink();
        $xAxisColumn = $this->getOption('x_axis_column');
        $yAxisColumn = $this->getOption('y_axis_column');
        $function    = $this->prepareFunction($this->getOption('function'));

        return <<<END
        percentage = data[0].ratio ? "<tr><td style='text-align:center;padding-bottom:5px;color:green'>" + (data[0].ratio * 100).toFixed(1) + "%</td></tr>"  : "";

        text = "<div id='tooltip' class='d3-tip' style='font-size:0.8rem'>";
        text += "<table class='tooltip-table' style='width:100%'>";
        text += "<tr><td style='text-align:center'>" + "$xAxisColumn: <span style='color:#007bff'>" + title + "</span></td></tr>";
        text += "<tr><td style='text-align:center;padding-bottom:5px'>" + "$function of $yAxisColumn: <span style='color:#007bff'>" + data[0].value + "</span></td></tr>";
        text += percentage;
        text += "</table>";
        text += "</div>";

        return text;
            }
        }

        END;

    }

    private function getDrillXAxisValue()
    {

        if (!empty($this->getOption('date_time_scale'))) {
            return "moment(data[0].x).format('YYYY-MM-DD HH:mm:ss')";
        } else {
            return 'title';
        }

    }

    private function handleTicks()
    {

        if ($this->chart->getResourceConfiguration('chart_type') == 'spline-chart' ||
            $this->chart->getResourceConfiguration('function') == 'count') {
            return <<<END
            format: function(d) {
                if (Math.floor(d) != d || d < 0){
                return;
                }
                return d;
            }
        END;

        }

    }

    private function adjustCategoricalPadding()
    {

        if (empty($this->getOption('date_time_scale'))) {

            return <<<END
        var getMyXDomain = c3.chart.internal.fn.getXDomain;
        c3.chart.internal.fn.getXDomain = function getXDomain(targets) {
        padding = { left: -50, right: 0 }
        var $$ = this,
            xDomain = [$$.getXDomainMin(targets), $$.getXDomainMax(targets)],
            firstX = xDomain[0],
            lastX = xDomain[1],
            min=0,max=0;
        // show center of x domain if min and max are the same
        if (firstX - lastX === 0 && !$$.isCategorized()) {
            if ($$.isTimeSeries()) {
            firstX = new Date(firstX.getTime() * 0.5)
            lastX = new Date(lastX.getTime() * 1.5)
            } else {
            firstX = firstX === 0 ? 1 : firstX * 0.5
            lastX = lastX === 0 ? -1 : lastX * 1.5
            }
            }
            if (firstX || firstX === 0) {
                min = $$.isTimeSeries()
                ? new Date(firstX.getTime() - padding.left)
                : firstX - padding.left
            }
            if (lastX || lastX === 0) {
                max = $$.isTimeSeries()
                ? new Date(lastX.getTime() + padding.right)
                : lastX + padding.right
            }
            return [0.4,max]
        }
        END;
        }

    }

    private function resetCategoricalPadding()
    {

        if (empty($this->getOption('date_time_scale'))) {

            return <<<END
            c3.chart.internal.fn.getXDomain = getMyXDomain;
            END;
        }

    }

}
