<?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\API\DataTableManipulator;

use Piwik\API\DataTableManipulator;
use Piwik\DataTable;
use Piwik\DataTable\Row;
use Piwik\Plugin\ReportsProvider;

/**
 * This class is responsible for flattening data tables.
 *
 * It loads subtables and combines them into a single table by concatenating the labels.
 * This manipulator is triggered by using flat=1 in the API request.
 */
class Flattener extends DataTableManipulator
{
    private $includeAggregateRows = false;

    /**
     * If the flattener is used after calling this method, aggregate rows will
     * be included in the result. This can be useful when they contain data that
     * the leafs don't have (e.g. conversion stats in some cases).
     */
    public function includeAggregateRows()
    {
        $this->includeAggregateRows = true;
    }

    /**
     * Separator for building recursive labels (or paths)
     * @var string
     */
    public $recursiveLabelSeparator = '';

    /**
     * Defines if dimensions should be added as additional columns if there are more than one
     * @var bool
     */
    public $showDimensions = false;

    /**
     * @param  DataTable $dataTable
     * @param string $recursiveLabelSeparator
     * @return DataTable|DataTable\Map
     */
    public function flatten($dataTable, $recursiveLabelSeparator, bool $showDimensions)
    {
        $this->recursiveLabelSeparator = $recursiveLabelSeparator;
        $this->showDimensions = $showDimensions;

        return $this->manipulate($dataTable);
    }

    /**
     * Template method called from self::manipulate.
     * Flatten each data table.
     *
     * @param DataTable $dataTable
     * @return DataTable
     */
    protected function manipulateDataTable($dataTable)
    {
        $newDataTable = $dataTable->getEmptyClone($keepFilters = true);
        if ($dataTable->getTotalsRow()) {
            $newDataTable->setTotalsRow($dataTable->getTotalsRow());
        }

        // this recursive filter will be applied to subtables
        $dataTable->filter('ReplaceSummaryRowLabel');
        $dataTable->filter('ReplaceColumnNames');

        $report        = ReportsProvider::factory($this->apiModule, $this->apiMethod);
        if (!empty($report)) {
            $dimension = $report->getDimension();
        }

        $dimensionName = !empty($dimension) ? str_replace('.', '_', $dimension->getId()) : 'label1';

        $this->flattenDataTableInto($dataTable, $newDataTable, $level = 1, $dimensionName);

        $dimensions = $newDataTable->getMetadata('dimensions');
        $hasMultipleDimensions = is_array($dimensions) && count($dimensions) > 1;

        if ($this->showDimensions && $hasMultipleDimensions) {
            $newDataTable->filter(function ($dataTable) use ($dimensions) {
                /** @var DataTable $dataTable */
                $rows = $dataTable->getRows();
                foreach ($rows as $row) {
                    foreach ($dimensions as $dimension) {
                        $row->setColumn($dimension, $row->getMetadata($dimension));
                    }
                }
            });
        }

        return $newDataTable;
    }

    /**
     * @param DataTable $dataTable
     * @param DataTable $newDataTable
     * @param int $level
     * @param string $dimensionName
     * @param string $prefix
     * @param string|false $logo
     */
    protected function flattenDataTableInto($dataTable, $newDataTable, $level, $dimensionName, $prefix = '', $logo = false)
    {
        foreach ($dataTable->getRows() as $rowId => $row) {
            $this->flattenRow($row, $rowId, $newDataTable, $level, $dimensionName, $prefix, $logo);
        }
    }

    /**
     * @param int|string $rowId
     * @param int $level
     * @param string $dimensionName
     * @param string $labelPrefix
     * @param string|false $parentLogo
     */
    private function flattenRow(
        Row $row,
        $rowId,
        DataTable $dataTable,
        $level,
        $dimensionName,
        $labelPrefix = '',
        $parentLogo = false
    ) {
        $dimensions = $dataTable->getMetadata('dimensions');

        if (empty($dimensions)) {
            $dimensions = [];
        }

        if (!in_array($dimensionName, $dimensions)) {
            $dimensions[] = $dimensionName;
        }

        $dataTable->setMetadata('dimensions', $dimensions);

        $origLabel = $label = $row->getColumn('label');

        if ($label !== false) {
            $origLabel = $label = trim($label);

            if ($this->recursiveLabelSeparator == '/') {
                if (substr($label, 0, 1) == '/' && substr($labelPrefix, -1) == '/') {
                    $origLabel = $label = substr($label, 1);
                } elseif ($rowId === DataTable::ID_SUMMARY_ROW && $labelPrefix && $label != DataTable::LABEL_SUMMARY_ROW) {
                    $label = ' - ' . $label;
                }
            }

            if ($rowId === DataTable::ID_SUMMARY_ROW) {
                if ($row->getMetadata('url')) {
                    // remove url metadata for flattened summary rows
                    $row->deleteMetadata('url');
                }
                $row->setMetadata('is_summary', true);
            }

            $label = $labelPrefix . $label;
            $row->setColumn('label', $label);

            if ($row->getMetadata($dimensionName)) {
                if ($rowId === DataTable::ID_SUMMARY_ROW && $this->recursiveLabelSeparator == '/') {
                    $origLabel = $row->getMetadata($dimensionName) . $this->recursiveLabelSeparator . ' - ' . $origLabel;
                } else {
                    $origLabel = $row->getMetadata($dimensionName) . $this->recursiveLabelSeparator . $origLabel;
                }
            }

            $row->setMetadata($dimensionName, $origLabel);
        }

        $logo = $row->getMetadata('logo');
        if ($logo === false && $parentLogo !== false) {
            $logo = $parentLogo;
            $row->setMetadata('logo', $logo);
        }

        /** @var DataTable $subTable */
        $subTable = $row->getSubtable();

        if ($subTable) {
            $subTable->applyQueuedFilters();
            $row->deleteMetadata('idsubdatatable_in_db');
        } else {
            $subTable = $this->loadSubtable($dataTable, $row);
        }

        $row->removeSubtable();

        if ($subTable === null) {
            if ($this->includeAggregateRows) {
                $row->setMetadata('is_aggregate', 0);
            }
            $dataTable->addRow($row);
        } else {
            if ($this->includeAggregateRows) {
                $row->setMetadata('is_aggregate', 1);
                $dataTable->addRow($row);
            }
            $prefix = $label . $this->recursiveLabelSeparator;

            $report        = ReportsProvider::factory($this->apiModule, $this->apiMethod);
            if (!empty($report)) {
                $subDimension = $report->getSubtableDimension();
            }

            if ($level > 1) {
                $subDimension = $report->getNthLevelTableDimension($level);
            }

            if (empty($subDimension)) {
                $report           = ReportsProvider::factory($this->apiModule, $this->getApiMethodForSubtable($this->request));
                $subDimension     = $report->getDimension();
            }

            $subDimensionName = $subDimension ? str_replace('.', '_', $subDimension->getId()) : 'label' . (substr_count($prefix, $this->recursiveLabelSeparator) + 1);

            if ($origLabel !== false) {
                foreach ($subTable->getRows() as $subRow) {
                    foreach ($row->getMetadata() as $name => $value) {
                        // do not set 'segment' parameter if there is a segmentValue on the row, since that will prevent the segmentValue
                        // from being used in DataTablePostProcessor
                        if ($name == 'segment' && $subRow->getMetadata('segmentValue') !== false) {
                            continue;
                        }

                        if ($subRow->getMetadata($name) === false) {
                            $subRow->setMetadata($name, $value);
                        }
                    }

                    $subRow->setMetadata($dimensionName, $origLabel);
                }
            }

            $this->flattenDataTableInto($subTable, $dataTable, $level + 1, $subDimensionName, $prefix, $logo);
        }
    }

    /**
     * Remove the flat parameter from the subtable request
     *
     * @param array $request
     * @return array
     */
    protected function manipulateSubtableRequest($request)
    {
        unset($request['flat']);

        return $request;
    }
}
