<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */

namespace Piwik\Plugins\Insights;

use Piwik\DataTable;
use Piwik\Piwik;

/**
 * Insight report generator
 */
class InsightReport
{
    public const ORDER_BY_RELATIVE   = 'relative';
    public const ORDER_BY_ABSOLUTE   = 'absolute';
    public const ORDER_BY_IMPORTANCE = 'importance';

    /**
     * @param array $reportMetadata
     * @param string $period
     * @param string $date
     * @param string $lastDate
     * @param string $metric
     * @param DataTable $currentReport
     * @param DataTable $lastReport
     * @param int $totalValue
     * @param int $lastTotalValue
     * @param string $orderBy
     * @param int $limitIncreaser
     * @param int $limitDecreaser
     * @return DataTable
     */
    public function generateMoverAndShaker($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $lastTotalValue, $orderBy, $limitIncreaser, $limitDecreaser)
    {
        $totalEvolution = $this->getTotalEvolution($totalValue, $lastTotalValue);

        $minMoversPercent = 1;

        if ($totalEvolution >= 100) {
            // eg change from 50 to 150 = 200%
            $factor = (int) ceil($totalEvolution / 500);
            $minGrowthPercentPositive = $totalEvolution + ($factor * 40); // min +240%
            $minGrowthPercentNegative = -70;         // min -70%
            $minDisappearedPercent = 8;              // min 12
            $minNewPercent = min(($totalEvolution / 100) * 3, 10);    // min 6% = min 10 of total visits up to max 10%
        } elseif ($totalEvolution >= 0) {
            // eg change from 50 to 75 = 50%
            $minGrowthPercentPositive = $totalEvolution + 20;  // min 70%
            $minGrowthPercentNegative = -1 * $minGrowthPercentPositive;  // min -70%
            $minDisappearedPercent = 7;
            $minNewPercent         = 5;
        } else {
            // eg change from 50 to 25 = -50%
            $minGrowthPercentNegative = $totalEvolution - 20;                  // min -70%
            $minGrowthPercentPositive = abs($minGrowthPercentNegative); // min 70%
            $minDisappearedPercent = 7;
            $minNewPercent         = 5;
        }

        if ($totalValue < 200 && $totalValue != 0) {
            // force at least a change of 2 visits
            $minMoversPercent = (int) ceil(2 / ($totalValue / 100));
            $minNewPercent    = max($minNewPercent, $minMoversPercent);
            $minDisappearedPercent = max($minDisappearedPercent, $minMoversPercent);
        }

        $dataTable = $this->generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy, $limitIncreaser, $limitDecreaser);

        $this->addMoversAndShakersMetadata($dataTable, $totalValue, $lastTotalValue);

        return $dataTable;
    }

    /**
     * Extends an already generated insight report by adding a column "isMoverAndShaker" whether a row is also a
     * "Mover and Shaker" or not.
     *
     * Avoids the need to fetch all reports again when we already have the currentReport/lastReport
     */
    public function markMoversAndShakers(DataTable $insight, $currentReport, $lastReport, $totalValue, $lastTotalValue)
    {
        if (!$insight->getRowsCount()) {
            return;
        }

        $limitIncreaser = max($insight->getRowsCount(), 3);
        $limitDecreaser = max($insight->getRowsCount(), 3);

        $lastDate = $insight->getMetadata('lastDate');
        $date     = $insight->getMetadata('date');
        $period   = $insight->getMetadata('period');
        $metric   = $insight->getMetadata('metric');
        $orderBy  = $insight->getMetadata('orderBy');
        $reportMetadata = $insight->getMetadata('report');

        $shakers = $this->generateMoverAndShaker($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $lastTotalValue, $orderBy, $limitIncreaser, $limitDecreaser);

        foreach ($insight->getRows() as $row) {
            $label = $row->getColumn('label');

            if ($shakers->getRowFromLabel($label)) {
                $row->setColumn('isMoverAndShaker', true);
            } else {
                $row->setColumn('isMoverAndShaker', false);
            }
        }

        $this->addMoversAndShakersMetadata($insight, $totalValue, $lastTotalValue);
    }

    /**
     * @param array $reportMetadata
     * @param string $period
     * @param string $date
     * @param string $lastDate
     * @param string $metric
     * @param DataTable $currentReport
     * @param DataTable $lastReport
     * @param int $totalValue
     * @param int $minMoversPercent      Exclude rows who moved and the difference is not at least min percent
     *                                         visits of totalVisits. -1 excludes movers.
     * @param int $minNewPercent         Exclude rows who are new and the difference is not at least min percent
     *                                         visits of totalVisits. -1 excludes all new.
     * @param int $minDisappearedPercent Exclude rows who are disappeared and the difference is not at least min
     *                                         percent visits of totalVisits. -1 excludes all disappeared.
     * @param int $minGrowthPercentPositive    The actual growth of a row must be at least percent compared to the
     *                                         previous value (not total value)
     * @param int $minGrowthPercentNegative    The actual growth of a row must be lower percent compared to the
     *                                         previous value (not total value)
     * @param string $orderBy                  Order by absolute, relative, importance
     * @param int $limitIncreaser
     * @param int $limitDecreaser
     *
     * @return DataTable
     */
    public function generateInsight($reportMetadata, $period, $date, $lastDate, $metric, $currentReport, $lastReport, $totalValue, $minMoversPercent, $minNewPercent, $minDisappearedPercent, $minGrowthPercentPositive, $minGrowthPercentNegative, $orderBy, $limitIncreaser, $limitDecreaser)
    {
        $minChangeMovers = $this->getMinVisits($totalValue, $minMoversPercent);
        $minIncreaseNew = $this->getMinVisits($totalValue, $minNewPercent);
        $minDecreaseDisappeared = $this->getMinVisits($totalValue, $minDisappearedPercent);

        $dataTable = new DataTable();
        $dataTable->filter(
            'Piwik\Plugins\Insights\DataTable\Filter\Insight',
            array(
                $currentReport,
                $lastReport,
                $metric,
                $considerMovers = (-1 !== $minMoversPercent),
                $considerNew = (-1 !== $minNewPercent),
                $considerDisappeared = (-1 !== $minDisappearedPercent)
            )
        );

        $dataTable->filter(
            'Piwik\Plugins\Insights\DataTable\Filter\MinGrowth',
            array(
                'growth_percent_numeric',
                $minGrowthPercentPositive,
                $minGrowthPercentNegative
            )
        );

        if ($minIncreaseNew) {
            $dataTable->filter(
                'Piwik\Plugins\Insights\DataTable\Filter\ExcludeLowValue',
                array(
                    'difference',
                    $minIncreaseNew,
                    'isNew'
                )
            );
        }

        if ($minChangeMovers) {
            $dataTable->filter(
                'Piwik\Plugins\Insights\DataTable\Filter\ExcludeLowValue',
                array(
                    'difference',
                    $minChangeMovers,
                    'isMover'
                )
            );
        }

        if ($minDecreaseDisappeared) {
            $dataTable->filter(
                'Piwik\Plugins\Insights\DataTable\Filter\ExcludeLowValue',
                array(
                    'difference',
                    $minDecreaseDisappeared,
                    'isDisappeared'
                )
            );
        }

        $dataTable->filter(
            'Piwik\Plugins\Insights\DataTable\Filter\OrderBy',
            array(
                $this->getOrderByColumn($orderBy),
                $orderBy === self::ORDER_BY_RELATIVE ? $this->getOrderByColumn(self::ORDER_BY_ABSOLUTE) : $this->getOrderByColumn(self::ORDER_BY_RELATIVE),
                $metric
            )
        );

        $dataTable->filter(
            'Piwik\Plugins\Insights\DataTable\Filter\Limit',
            array(
                'growth_percent_numeric',
                $limitIncreaser,
                $limitDecreaser
            )
        );

        $metricName = $metric;
        if (!empty($reportMetadata['metrics'][$metric])) {
            $metricName = $reportMetadata['metrics'][$metric];
        }

        $dataTable->setMetadataValues(array(
            'reportName' => $reportMetadata['name'],
            'metricName' => $metricName,
            'date' => $date,
            'lastDate' => $lastDate,
            'period' => $period,
            'report' => $reportMetadata,
            'totalValue' => $totalValue,
            'orderBy' => $orderBy,
            'metric' => $metric,
            'minChangeMovers' => $minChangeMovers,
            'minIncreaseNew' => $minIncreaseNew,
            'minDecreaseDisappeared' => $minDecreaseDisappeared,
            'minGrowthPercentPositive' => $minGrowthPercentPositive,
            'minGrowthPercentNegative' => $minGrowthPercentNegative,
            'minMoversPercent' => $minMoversPercent,
            'minNewPercent' => $minNewPercent,
            'minDisappearedPercent' => $minDisappearedPercent
        ));

        return $dataTable;
    }

    private function getOrderByColumn($orderBy)
    {
        if (self::ORDER_BY_RELATIVE == $orderBy) {
            $orderByColumn = 'growth_percent_numeric';
        } elseif (self::ORDER_BY_ABSOLUTE == $orderBy) {
            $orderByColumn = 'difference';
        } elseif (self::ORDER_BY_IMPORTANCE == $orderBy) {
            $orderByColumn = 'importance';
        } else {
            throw new \Exception('Unsupported orderBy');
        }

        return $orderByColumn;
    }

    private function getMinVisits($totalValue, $percent)
    {
        if ($percent <= 0) {
            return 0;
        }

        $minVisits = ceil(($totalValue / 100) * $percent);

        return (int) $minVisits;
    }

    private function addMoversAndShakersMetadata(DataTable $dataTable, $totalValue, $lastTotalValue)
    {
        $totalEvolution = $this->getTotalEvolution($totalValue, $lastTotalValue);

        $dataTable->setMetadata('lastTotalValue', $lastTotalValue);
        $dataTable->setMetadata('evolutionTotal', $totalEvolution);
        $dataTable->setMetadata('evolutionDifference', $totalValue - $lastTotalValue);
    }

    private function getTotalEvolution($totalValue, $lastTotalValue)
    {
        return Piwik::getPercentageSafe($totalValue - $lastTotalValue, $lastTotalValue, 1);
    }
}
