<?php

namespace Ignite\Core\Models\Dashboard;

use Exception;
use Ignite\Core\Contracts\Dashboard\Gauge;
use Illuminate\Support\Collection;
use Ignite\Core\Contracts\Dashboard\Stat;
use Ignite\Core\Contracts\Dashboard\Chart;
use Ignite\Core\Contracts\Dashboard\Donut;
use Ignite\Core\Contracts\Dashboard\Table;
use Nwidart\Modules\Facades\Module;

class Manager extends Collection
{
    /**
     * Add a Stat class to the collection.
     *
     * @param  Chart $chart
     * @return $this
     */
    public function addChart(Chart $chart)
    {
        $this->offsetSet($chart->getElement(), $chart);

        return $this;
    }

    /**
     * Add many Stat classes to the collection.
     *
     * @param array|\ArrayIterator|\Illuminate\Support\Collection $charts
     *
     * @return $this
     * @throws Exception
     */
    public function addManyCharts($charts)
    {
        foreach ($charts as $row => $chart) {
            $this->addChart($chart);
        }

        return $this;
    }

    /**
     * @inheritDoc
     * @throws Exception
     */
    public function add($item)
    {
        throw new Exception('You must use addChart()');
    }

    /**
     * @inheritDoc
     * @throws Exception
     */
    public function put($key, $value)
    {
        throw new Exception('You must use addChart()');
    }

    /**
     * Load the dashboard configuration for a given module.
     *
     * @param string $moduleName
     *
     * @return array|null
     */
    public function load($moduleName)
    {
        $module = Module::find($moduleName);

        try {
            return $this->parseJsonSchema($module);
        } catch (Exception $e) {
            return null;
        }
    }

    /**
     * Locate charts in other modules.
     *
     * @return Collection
     */
    public function locate()
    {
        /* List of all dashboard file contents
         * -------------------------------------------------- */
        $modules = collect(Module::all())
            ->filter(function (\Nwidart\Modules\Module $module) {
                return $module->isEnabled();
            })
            ->sortBy(function (\Nwidart\Modules\Module $module) {
                return $module->get('order', 0);
            })->map(function (\Nwidart\Modules\Module $module) {
                try {
                    return $this->parseJsonSchema($module);
                } catch (Exception $e) {
                    return null;
                }
            })
            ->filter();

        /* If there is a settings.json file (Custom Dashboard)
         * -------------------------------------------------- */
        if (file_exists($this->getSetupFile())) {
            $final = [];
            $extras = [];
            $settings = $this->setupCustomSchema($this->getSetupFile());
            foreach ($settings['dashboard'] as $dashboard) {
                $sections = collect($dashboard['sections'])->pluck('name');
                $exclude = $dashboard['exclude'];

                /* First, cycle through and build the dashboard using
                 * the settings.json file
                 * -------------------------------------------------- */
                foreach ($sections as $sectionKey => $section) {
                    $sectionCount = 1;
                    $requestedWidgets = collect($dashboard['sections'])
                        ->where('name', $section)
                        ->pluck('order')
                        ->flatten()
                        ->toArray();

                    foreach ($modules as $key => $module) {
                        foreach ($module as $widgets) {
                            foreach ($widgets as $widgetIndex => $widget) {
                                if (in_array($widget['key'], $requestedWidgets) &&
                                    !in_array($widget['key'], $exclude)) {
                                    $widget['order'] = $sectionCount;
                                    $widget['group'] = $section;

                                    if (isset($final[$sectionKey]) &&
                                        !in_array($widget['key'], collect($final[$sectionKey])->pluck('key')->toArray())) {
                                        $final[$sectionKey][] = $widget;
                                    }

                                    if (!isset($final[$sectionKey])) {
                                        $final[$sectionKey][] = $widget;
                                    }
                                    $sectionCount++;
                                }
                            }
                        }
                    }
                }

                /* Next, place all other widgets into an 'Extras'
                 * section of the dashboard
                 * -------------------------------------------------- */
                $takenModules = collect($final)->flatMap(function ($values) {
                    return collect($values)->pluck('key');
                })->unique()->toArray();
                $extrasCount = 1;
                foreach ($sections as $sectionKey => $section) {
                    $requestedWidgets = collect($dashboard['sections'])
                        ->where('name', $section)
                        ->pluck('order')
                        ->flatten()
                        ->toArray();

                    foreach ($modules as $key => $module) {
                        foreach ($module as $widgets) {
                            foreach ($widgets as $widgetIndex => $widget) {
                                if (!in_array($widget['key'], $requestedWidgets) &&
                                    !in_array($widget['key'], $exclude) &&
                                    !in_array($widget['key'], $takenModules)) {
                                    $widget['order'] = $extrasCount;
                                    $widget['group'] = 'Extras';
                                    $extras[] = $widget;
                                    $extrasCount++;
                                }
                            }
                        }
                    }
                }
            }
            $final[] = $extras;
            $modules = collect(['Custom' => $final]);
        }

        return $modules;
    }

    private function getSetupFile()
    {
        $activeTheme = resolve(\Ignite\Theme\Manager::class)->current();

        return $activeTheme->path().DIRECTORY_SEPARATOR.
            'dashboards'.DIRECTORY_SEPARATOR.
            config('core.dashboard.setupFilename', 'settings').'.json';
    }

    private function setupCustomSchema(string $filename)
    {
        $json = file_get_contents($filename);

        $data = json_decode($json, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception(sprintf(
                'Unable to parse json for custom dashboard: %s.',
                json_last_error_msg()
            ));
        }

        return $data;
    }

    /**
     * Parse the dashboard json schema.
     *
     * @param  \Nwidart\Modules\Module  $module
     * @return array
     * @throws Exception
     */
    private function parseJsonSchema(\Nwidart\Modules\Module $module)
    {
        $activeTheme = resolve(\Ignite\Theme\Manager::class)->current();
        $file = $activeTheme->path().DIRECTORY_SEPARATOR.'dashboards'.DIRECTORY_SEPARATOR.
            $module->getLowerName().'.json';

        $json = null;
        if (file_exists($file)) {
            $json = file_get_contents($file);
        } else {
            $json = $module->json('dashboard.json')->getContents();
        }

        $data = json_decode($json, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new Exception(sprintf(
                'Unable to parse json for module %s: %s.',
                $module->getName(),
                json_last_error_msg()
            ));
        }

        if (!isset($data['dashboard'])) {
            throw new Exception(sprintf(
                'Missing expected json key "dashboard" for module %s.',
                $module->getName()
            ));
        }

        return $data['dashboard'];
    }

    /**
     * Calculate all chart data.
     *
     * @return self
     */
    public function calculate()
    {
        return $this->map(function (Chart $chart) {
            return $chart->process();
        });
    }

    /**
     * Determine if the chart is a stat chart.
     *
     * @param  Chart $chart
     * @return bool
     */
    public function isStat($chart)
    {
        return $chart instanceof Stat;
    }

    /**
     * Filter down to only include stat charts.
     *
     * @return static
     */
    public function stats()
    {
        return $this->filter(function ($chart) {
            return $this->isStat($chart);
        })->sortBy('sort', SORT_NUMERIC);
    }

    /**
     * Determine if the chart is a donut chart.
     *
     * @param  Chart $chart
     * @return bool
     */
    public function isDonut($chart)
    {
        return $chart instanceof Donut;
    }

    /**
     * Filter down to only include donut charts.
     *
     * @return static
     */
    public function donuts()
    {
        return $this->filter(function ($chart) {
            return $this->isDonut($chart);
        })->sortBy('sort', SORT_NUMERIC);
    }

    /**
     * Determine if the chart is a gauge chart.
     *
     * @param  Chart  $chart
     * @return bool
     */
    public function isGauge($chart)
    {
        return $chart instanceof Gauge;
    }

    /**
     * Filter down to only include donut charts.
     *
     * @return static
     */
    public function gauges()
    {
        return $this->filter(function ($chart) {
            return $this->isGauge($chart);
        })->sortBy('sort', SORT_NUMERIC);
    }

    /**
     * Determine if the chart is a line chart.
     *
     * @param  Chart $chart
     * @return bool
     */
    public function isLine($chart)
    {
        return $chart instanceof Line;
    }

    /**
     * Filter down to only include line charts.
     *
     * @return static
     */
    public function lines()
    {
        return $this->filter(function ($chart) {
            return $this->isLine($chart);
        })->sortBy('sort', SORT_NUMERIC);
    }

    /**
     * Determine if the chart is a table chart.
     *
     * @param  Chart $chart
     * @return bool
     */
    public function isTable($chart)
    {
        return $chart instanceof Table;
    }

    /**
     * Filter down to only include line charts.
     *
     * @return static
     */
    public function tables()
    {
        return $this->filter(function ($chart) {
            return $this->isTable($chart);
        })->sortBy('sort', SORT_NUMERIC);
    }
}
