<?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\UserCountry\RecordBuilders;

use Piwik\ArchiveProcessor;
use Piwik\ArchiveProcessor\Record;
use Piwik\ArchiveProcessor\RecordBuilder;
use Piwik\Config as PiwikConfig;
use Piwik\DataTable;
use Piwik\DataAccess\LogAggregator;
use Piwik\Metrics;
use Piwik\Plugins\UserCountry\Archiver;
use Piwik\Plugins\UserCountry\LocationProvider;
class Locations extends RecordBuilder
{
    protected $dimensions = [Archiver::COUNTRY_RECORD_NAME => Archiver::COUNTRY_FIELD, Archiver::REGION_RECORD_NAME => Archiver::REGION_FIELD, Archiver::CITY_RECORD_NAME => Archiver::CITY_FIELD];
    private $latLongForCities = [];
    public function __construct()
    {
        parent::__construct();
        $this->columnToSortByBeforeTruncation = Metrics::INDEX_NB_VISITS;
    }
    public function getRecordMetadata(ArchiveProcessor $archiveProcessor) : array
    {
        $maxRowsInTable = PiwikConfig::getInstance()->General['datatable_archiving_maximum_rows_standard'];
        return [Record::make(Record::TYPE_BLOB, Archiver::COUNTRY_RECORD_NAME), Record::make(Record::TYPE_BLOB, Archiver::REGION_RECORD_NAME)->setMaxRowsInTable($maxRowsInTable), Record::make(Record::TYPE_BLOB, Archiver::CITY_RECORD_NAME)->setMaxRowsInTable($maxRowsInTable), Record::make(Record::TYPE_NUMERIC, Archiver::DISTINCT_COUNTRIES_METRIC)->setIsCountOfBlobRecordRows(Archiver::COUNTRY_RECORD_NAME)];
    }
    protected function aggregate(ArchiveProcessor $archiveProcessor) : array
    {
        try {
            /** @var DataTable[] $records */
            $records = [];
            foreach ($this->dimensions as $recordName => $dimension) {
                $records[$recordName] = new DataTable();
            }
            $logAggregator = $archiveProcessor->getLogAggregator();
            $this->aggregateFromVisits($records, $logAggregator);
            $this->aggregateFromConversions($records, $logAggregator);
            $this->setLatitudeLongitude($records[Archiver::CITY_RECORD_NAME]);
            $records[Archiver::DISTINCT_COUNTRIES_METRIC] = $records[Archiver::COUNTRY_RECORD_NAME]->getRowsCount();
            return $records;
        } finally {
            unset($this->latLongForCities);
        }
    }
    /**
     * @param DataTable[] $records
     */
    protected function aggregateFromVisits(array $records, LogAggregator $logAggregator) : void
    {
        $additionalSelects = array('MAX(log_visit.location_latitude) as location_latitude', 'MAX(log_visit.location_longitude) as location_longitude');
        $query = $logAggregator->queryVisitsByDimension(array_values($this->dimensions), $where = \false, $additionalSelects);
        if ($query === \false) {
            return;
        }
        while ($row = $query->fetch()) {
            $this->makeRegionCityLabelsUnique($row);
            $this->rememberCityLatLong($row);
            $columns = [Metrics::INDEX_NB_UNIQ_VISITORS => $row[Metrics::INDEX_NB_UNIQ_VISITORS], Metrics::INDEX_NB_VISITS => $row[Metrics::INDEX_NB_VISITS], Metrics::INDEX_NB_ACTIONS => $row[Metrics::INDEX_NB_ACTIONS], Metrics::INDEX_NB_USERS => $row[Metrics::INDEX_NB_USERS], Metrics::INDEX_MAX_ACTIONS => $row[Metrics::INDEX_MAX_ACTIONS], Metrics::INDEX_SUM_VISIT_LENGTH => $row[Metrics::INDEX_SUM_VISIT_LENGTH], Metrics::INDEX_BOUNCE_COUNT => $row[Metrics::INDEX_BOUNCE_COUNT], Metrics::INDEX_NB_VISITS_CONVERTED => $row[Metrics::INDEX_NB_VISITS_CONVERTED]];
            foreach ($records as $recordName => $dataTable) {
                $dimension = $this->dimensions[$recordName];
                $dataTable->sumRowWithLabel($row[$dimension], $columns);
            }
        }
    }
    /**
     * Makes sure the region and city of a query row are unique.
     *
     * @param array $row
     */
    private function makeRegionCityLabelsUnique(array &$row) : void
    {
        // remove the location separator from the region/city/country we get from the query
        foreach ($this->dimensions as $column) {
            $row[$column] = str_replace(Archiver::LOCATION_SEPARATOR, '', $row[$column] ?? '');
        }
        // set city first, as containing region might be manipulated afterwards if not empty
        if (!empty($row[Archiver::CITY_FIELD])) {
            $row[Archiver::CITY_FIELD] = $row[Archiver::CITY_FIELD] . Archiver::LOCATION_SEPARATOR . $row[Archiver::REGION_FIELD] . Archiver::LOCATION_SEPARATOR . $row[Archiver::COUNTRY_FIELD];
        }
        if (!empty($row[Archiver::REGION_FIELD])) {
            $row[Archiver::REGION_FIELD] = $row[Archiver::REGION_FIELD] . Archiver::LOCATION_SEPARATOR . $row[Archiver::COUNTRY_FIELD];
        }
    }
    protected function rememberCityLatLong(array $row) : void
    {
        if (!empty($row[Archiver::CITY_FIELD]) && !empty($row[Archiver::LATITUDE_FIELD]) && !empty($row[Archiver::LONGITUDE_FIELD]) && empty($this->latLongForCities[$row[Archiver::CITY_FIELD]])) {
            $this->latLongForCities[$row[Archiver::CITY_FIELD]] = array($row[Archiver::LATITUDE_FIELD], $row[Archiver::LONGITUDE_FIELD]);
        }
    }
    /**
     * @param DataTable[] $records
     */
    protected function aggregateFromConversions(array $records, LogAggregator $logAggregator) : void
    {
        $query = $logAggregator->queryConversionsByDimension(array_values($this->dimensions));
        while ($row = $query->fetch()) {
            $this->makeRegionCityLabelsUnique($row);
            foreach ($records as $recordName => $table) {
                $idGoal = (int) $row['idgoal'];
                $columns = [Metrics::INDEX_GOALS => [$idGoal => Metrics::makeGoalColumnsRow($idGoal, $row)]];
                $dimension = $this->dimensions[$recordName];
                $table->sumRowWithLabel($row[$dimension], $columns);
            }
        }
        foreach ($records as $table) {
            $table->filter(DataTable\Filter\EnrichRecordWithGoalMetricSums::class);
        }
    }
    /**
     * Utility method, appends latitude/longitude pairs to city table labels, if that data
     * exists for the city.
     */
    private function setLatitudeLongitude(DataTable $tableCity) : void
    {
        foreach ($tableCity->getRows() as $row) {
            $label = $row->getColumn('label');
            if (isset($this->latLongForCities[$label])) {
                // get lat/long for city
                list($lat, $long) = $this->latLongForCities[$label];
                $lat = round($lat, LocationProvider::GEOGRAPHIC_COORD_PRECISION);
                $long = round($long, LocationProvider::GEOGRAPHIC_COORD_PRECISION);
                // set latitude + longitude metadata
                $row->setMetadata('lat', $lat);
                $row->setMetadata('long', $long);
            }
        }
    }
}
