<?php

/**
 * Questo file è parte di Pongho.
 *
 * @author    Daniele Termini
 * @copyright Web Agency Meta Line S.r.l.
 * @package   Application\Admin
 */

namespace Application\Admin\Archive;

use ActiveRecord\QueryOptions;
use Application\Admin\Utilities\ArchiveInterface;
use Application\Admin\Utilities\FilterConverter;
use Application\Admin\Utilities\TablesManager;
use Application\Core\Utilities\Pagination;
use Pongho\Http\Request;

/**
 * Class Archive
 *
 * Gestore degli archivi
 */
class Archive implements ArchiveInterface
{
    /**
     * @var Request
     */
    protected $request;

    /**
     * @var FastFilterInterface
     */
    protected $fast_filters;

    /**
     * @var FilterConverter
     */
    protected $converter;

    /**
     * @var array
     */
    protected $filters;

    /**
     * @var QueryOptions
     */
    protected $query_options;

    /**
     * @var array
     */
    protected $fast_filters_query_options;

    /**
     * @var QueryInterface
     */
    protected $query;

    /**
     * @var array
     */
    protected $search_fields;

    /**
     * @var int
     */
    protected $page;

    /**
     * @var string
     */
    protected $url;

    /**
     * @var int
     */
    protected $records_per_page = 50;

    /**
     * @var array
     */
    protected $table_config;

    /**
     * @var TablesManager
     */
    protected $table_manager;

    /**
     * L'array dei filtri in query string
     *
     * @var array
     */
    protected $filters_query;

    /**
     * @var callable
     */
    protected $parse_row;

    public function __construct(Request $request, FastFilterInterface $fast_filters, FilterConverter $converter, QueryInterface $query, array $filters, array $search_fields)
    {
        $this->request = $request;
        $this->fast_filters = $fast_filters;
        $this->converter = $converter;
        $this->filters = $filters;
        $this->query = $query;
        $this->search_fields = $search_fields;
    }

    /**
     * @param int $page
     * @return \ActiveRecord\Base[]
     */
    public function getRecords($page = null)
    {
        $this->prepareQueryOptions();

        if ($page === null) {
            $page = $this->page;
        }

        if (($orders = $this->request->query->get('o', '')) !== '') {
            $this->query_options->setOrder($this->composeOrderByClause($orders));
        }

        $this->query_options
            ->setOffset(($page - 1) * $this->records_per_page)
            ->setLimit($this->records_per_page)
        ;

        $rows = $this->query->all($this->query_options->getOptions());

        // Aggiungo i campi estesi
        if (is_callable($this->parse_row)) {
            array_map($this->parse_row, $rows);
        }

        return $rows;
    }

    public function setParseArchiveRowCallback(callable $callback)
    {
        $this->parse_row = $callback;
    }

    /**
     * @return int
     */
    public function getTotalRecords()
    {
        $this->prepareQueryOptions();

        $this->query_options->resetLimit()->resetOffset();

        return $this->query->count($this->query_options->getOptions());
    }

    /**
     * Imposta le opzioni per la query dei filtri rapidi, nel caso serva personalizzarla rispetto a quella del modello di default
     *
     * @param $options
     */
    public function setFastFilterQuery($options)
    {
        $this->fast_filters_query_options = $options;
    }

    /**
     * Restituisce la query per i filtri rapidi
     *
     * @return array
     */
    protected function getFastFiltersQuery()
    {
        if ($this->fast_filters_query_options !== null) {
            return $this->fast_filters_query_options;
        }

        return $this->query->getDefaultQueryOptions();
    }

    /**
     * @return string
     */
    public function getFastFilters()
    {
        $this->prepareQueryOptions();

        $totals = [];
        foreach ($this->fast_filters->getFilters() as $filter => $data) {
            $qo = new QueryOptions($this->query->getDefaultQueryOptions());
            $qo->addCondition($data['conditions'] ?? []);
            $this->addFiltersConditions($qo);

            $totals[$filter] = $this->query->count($qo->getOptions());
        }

        return $this->fast_filters->buildMenu($totals, $this->request->query->get('f[ff]', 'all', true));
    }

    /**
     * @param int $page
     */
    public function setPage($page = 1)
    {
        $this->page = $page;
    }

    /**
     * @param $url
     */
    public function setUrl($url)
    {
        $this->url = $url;
    }

    public function setTableConfig(array $table_config)
    {
        $this->table_config = $table_config;
    }

    /**
     * @param $records_per_page
     */
    public function setRecordsPerPage($records_per_page)
    {
        $this->records_per_page = $records_per_page;
    }

    /**
     * Restituisce la paginazione.
     *
     * @return \Application\Core\Utilities\Pagination
     */
    public function getPagination()
    {
        $query = $this->request->query->all();

        return new Pagination(
            $this->url,
            $this->getTotalRecords(),
            $this->page,
            $this->records_per_page,
            $query
        );
    }

    /**
     * @throws \LogicException
     * @return string
     */
    public function getTable()
    {
        return $this->getTableManager()
            ->setRows($this->getRecords())
            ->render();
    }

    /**
     * @return TablesManager
     */
    protected function getTableManager()
    {
        if ($this->table_manager === null) {
            if (!$this->table_config) {
                throw new \LogicException('Please define the table configuration in the controller');
            }

            $this->table_manager = new TablesManager($this->table_config, $this->request);
        }

        return $this->table_manager;
    }

    public function setFilters(array $filters)
    {
        $this->filters_query = $this->getFiltersClean($filters);

        $this->query_options = new QueryOptions($this->query->getDefaultQueryOptions());
        $this->addFastFiltersConditions($this->query_options);
        $this->addFiltersConditions($this->query_options);
    }

    /**
     * {@inheritdoc}
     */
    public function isFiltersEnabled()
    {
        return $this->filters !== [];
    }

    /**
     * {@inheritdoc}
     */
    public function isFastFiltersEnabled()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function isSearchEnabled()
    {
        return true;
    }

    /**
     * Imposta l’ordinamento.
     *
     * @return string
     */
    protected function composeOrderByClause(array $order_by)
    {
        $orders = [];
        $columns = $this->getTableManager()->getColumns();

        foreach ($order_by as $order => $direction) {
            if (isset($columns[$order]) && isset($columns[$order]['orderable']) && $columns[$order]) {
                if (is_callable($columns[$order]['orderable'])) {
                    $orders[] = call_user_func($columns[$order]['orderable'], $direction, $order);
                } else {
                    $field = is_string($columns[$order]['orderable']) ? $columns[$order]['orderable'] : $order;

                    $orders[] = $this->quote($field) . ' ' . $direction;
                }
            }
        }

        return implode(', ', $orders);
    }

    /**
     * @return array
     */
    protected function prepareQueryOptions()
    {
        if ($this->query_options === null) {
            $this->filters_query = $this->getFiltersClean();

            $this->query_options = new QueryOptions($this->query->getDefaultQueryOptions());
            $this->addFastFiltersConditions($this->query_options);
            $this->addFiltersConditions($this->query_options);
        }
    }

    /**
     * Imposta le condizioni dei filtri rapidi nelle opzioni
     */
    protected function addFastFiltersConditions(QueryOptions $qo)
    {
        $qo->addCondition($this->getCurrentFastFilterConditions());
    }

    /**
     * Imposta un filtro.
     */
    protected function addFiltersConditions(QueryOptions $qo)
    {
        // Aggiungo la condizione per la ricerca normale, se presente nella query string
        if ($this->isSearchEnabled() && $value = $this->request->query->get('q')) {
            $conditions = [];

            foreach ($this->search_fields as $field) {
                $conditions[] = 'pongho_like(' . $field . ', :query)';
            }

            $qo->addCondition([
                '(' . implode(' OR ', $conditions) . ')',
                'query' => '%' . $value . '%',
            ]);
        }

        // Se non ho definito dei filtri mi fermo qui
        if (!$this->isFiltersEnabled()) {
            return;
        }

        // Aggiungo le condizioni dei filtri presenti nella query string
        foreach ($this->filters_query as $column => $value) {
            $field = $this->quote($column);
            if (array_key_exists($column, $this->filters)) {
                $field_attrs = $this->filters[$column];

                if (array_key_exists('column', $field_attrs)) {
                    $field = $field_attrs['column'];
                }

                if (array_key_exists('join', $field_attrs)) {
                    $model_options = $qo->getOptions();

                    if (!isset($model_options['select'])) {
                        $qo->setSelect('`from`.*');
                    }

                    $qo->addJoins([$field_attrs['join']]);

                    if (!isset($model_options['group'])) {
                        $qo->setGroup('`from`.id');
                    }
                }
            }

            $condition = $this->converter->toCondition($column, $value, $field);

            $qo->addCondition($condition);
        }
    }

    /**
     * Restituisce il nome della colonna o identificatore con le virgolette in modo da non incappare in casi
     * in cui il nome corrisponde ad un identificatore riservato (from, key, left...)
     *
     * @param $name
     * @return string
     */
    protected function quote($name)
    {
        return "`{$name}`";
    }

    /**
     * @return array
     * @throws \LogicException
     */
    protected function getCurrentFastFilterConditions()
    {
        $conditions = [];

        if ($filter = $this->request->query->get('f[ff]', null, true)) {
            $fast_filters = $this->fast_filters->getFilters();

            if (!array_key_exists($filter, $fast_filters)) {
                throw new \LogicException(
                    sprintf(
                        'The fast filter configuration for "%s" is not defined',
                        $filter
                    )
                );
            }

            $conditions = $fast_filters[$filter]['conditions'];
        }

        return $conditions;
    }

    /**
     * @param array $filters
     * @return array
     */
    protected function getFiltersClean(?array $filters = null)
    {
        if ($filters === null) {
            $filters = $this->request->query->get('f', []);
        }

        // Ricavo i filtri togliendo il filtro rapido perché viene ri-applicato
        unset($filters['ff']);

        return $filters;
    }
}
