<?php

declare(strict_types=1);

namespace Tfish\Content\ViewModel;

/**
 * \Tfish\Content\ViewModel\Listing class file.
 * 
 * @copyright   Simon Wilkinson 2019+ (https://tuskfish.biz)
 * @license     https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html GNU General Public License (GPL) V2
 * @author      Simon Wilkinson <simon@isengard.biz>
 * @version     Release: 2.0
 * @since       2.0
 * @package     content
 */

/**
 * ViewModel for displaying a list of content objects.
 *
 * @copyright   Simon Wilkinson 2019+ (https://tuskfish.biz)
 * @license     https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html GNU General Public License (GPL) V2
 * @author      Simon Wilkinson <simon@isengard.biz>
 * @version     Release: 2.0
 * @since       2.0
 * @package     content
 * @uses        trait \Tfish\Traits\Content\ContentTypes	Provides definition of permitted content object types.
 * @uses        trait \Tfish\Traits\Listable Provides a standard implementation of the \Tfish\View\Listable interface.
 * @uses        trait \Tfish\Traits\ValidateString  Provides methods for validating UTF-8 character encoding and string composition.
 * @var         object $model Classname of the model used to display this page.
 * @var         \Tfish\Content\Entity\content $content A single content object for display.
 * @var         array $contentTags Array of tag IDs/names associated with a single content object.
 * @var         array $contentList An array of content objects to be displayed in this page view. 
 * @var         int $contentCount The number of content objects that match filtering criteria. Used to build pagination control. 
 * @var         \Tfish\Content\Entity\Content $parent The parent of this content (the collection to which it belongs).
 * @var         array $children Array of content objects that are members of this collection. 
 * @var         string $description Long-form description of this content.
 * @var         string $author Creator of this content.
 * @var         string $backUrl $backUrl URL to return to if the user cancels the action.
 * @var         string $response Message to display to the user after processing action (success/failure). 
 * @var         int $id ID of a single content object to be displayed.
 * @var         int $start Position in result set to retrieve objects from.
 * @var         int $tag Filter search results by tag ID. 
 * @var         string $type Filter search results by content type.
 * @var         int $onlineStatus Filter search results by online (1) or offline (0) status.
 */
class Listing implements \Tfish\ViewModel\Listable
{
    use \Tfish\Content\Traits\ContentTypes;
    use \Tfish\Traits\Listable;
    use \Tfish\Traits\ValidateString;
    
    private $model;
    private $content = '';
    private $contentTags = '';
    private $contentList = [];
    private $contentCount = 0;
    private $parent = '';
    private $children = [];
    private $description = '';
    private $author = '';
    private $backUrl = '';
    private $response = '';
    private $id = 0;
    private $start = 0;
    private $tag = 0;
    private $type = '';
    private $onlineStatus = 1;

    /**
     * Constructor.
     * 
     * @param   object $model Instance of a model class.
     */
    public function __construct($model)
    {
        $this->model = $model;
        $this->theme = 'default';
    }

    /** Actions */

    /**
     * Display list of content in short (teaser) form.
     */
    public function displayList()
    {
        $this->pageTitle = TFISH_LATEST_POSTS;
        $this->template = 'listView';
        $this->listContent();
        $this->countContent();
        $this->setMetadata();
    }

    /**
     * Display a single content object.
     */
    public function displayObject()
    {
        $this->content = $this->getObject($this->id);

        if ($this->content) {
            $this->pageTitle = $this->content->metaTitle();
            $this->description = $this->content->metaDescription();
            $this->author = $this->content->creator();
            $this->parent = $this->getObject($this->content->parent());

            if ($this->content->type() === 'TfCollection' || $this->content->type() === 'TfTag') $this->listChildren();

            $this->template = $this->listTemplates()[$this->content->type()];
            $this->setMetadata();
        } else {
            $this->pageTitle = TFISH_ERROR  ;
            $this->response = TFISH_ERROR_NO_SUCH_CONTENT;
            $this->backUrl = TFISH_URL;
            $this->template = 'response';
        }
    }

    /** Utilities. */

    /**
     * Return IDs and titles of tags that are actually in use with content objects.
     * 
     * @param   string $zeroOption Text for the default (unselected) option.
     * @return  array IDs and titles as key-value pairs.
     */
    public function activeTagOptions(string $zeroOption = TFISH_SELECT_TAGS): array
    {
        $zeroOption = $this->trimString($zeroOption);
        $rows = $this->model->activeTagOptions();

        return $this->selectBoxOptions($zeroOption, $rows);
    }

    /**
     * Return canonical URL for this page view.
     * 
     * Used to populate the canonical link tag in theme files.
     * 
     * @return  string
     */
    public function canonicalUrl(): string
    {
        $canonicalUrl = TFISH_URL;

        if ($this->id) return $canonicalUrl . '?id=' . $this->id;

        if ($this->start || $this->tag) $canonicalUrl .= '?';
        if ($this->start) $canonicalUrl .= 'start=' . $this->start;
        if ($this->start && $this->tag) $canonicalUrl .= '&amp;';
        if ($this->tag) $canonicalUrl .= 'tag=' . $this->tag;

        return $canonicalUrl;
    }


    /**
     * Return a collection of tags.
     * 
     * Retrieves tags that have been grouped into a collection as ID-title key-value pairs.
     * Used to build select box controls.
     * 
     * @param   int $id ID of the collection content object.
     * @param   string $zeroOption Text for the default (unselected) option.
     * @return  array Tag IDs and titles as associative array.
     */
    public function collectionTagOptions(int $id, $zeroOption = TFISH_SELECT_TAGS): array
    {
        $zeroOption = $this->trimString($zeroOption);
        $rows = $this->model->collectionTagOptions($id);

        return $this->selectBoxOptions($zeroOption, $rows);
    }

    /**
     * Count content objects meeting filter criteria.
     */
    public function countContent()
    {
        $params = [
            'tag' => $this->tag,
            'type' => $this->type,
            'onlineStatus' => $this->onlineStatus
        ];

        if (!empty($this->content) && $this->content->type() === 'TfTag') {
            $params['tag'] = $this->content->id();
        }

        if (!empty($this->content) && $this->content->type() === 'TfCollection') {
            $params['parent'] = $this->content->id();
        }

        $this->contentCount = $this->model->getCount($params);
    }

    /**
     * Return tags associated with a content object.
     * 
     * @return  array Array of tags as id/title key-value pairs.
     */
    public function contentTags()
    {
        $tags = $this->model->getTagsForObject($this->id);

        return $tags;
    }

    /**
     * Return extra parameters to be included in pagination control links.
     * 
     * @return  array
     */
    public function extraParams(): array
    {
        if (!empty($this->id)) {
            return ['id' => $this->id];
        }

        // tag, country, status
        $extraParams = [];

        if (!empty($this->tag)) {
            $extraParams['tag'] = (int) $this->tag;
        }

        if (!empty($this->type)) {
            $extraParams['type'] = $this->trimString($this->type);
        }

        return $extraParams;

    }

    /**
     * Get a content object.
     * 
     * @param   int $id ID of content object.
     */
    public function getObject(int $id)
    {
        return $this->model->getObject($id);
    }

    /**
     * Return user-side pagination limit.
     * 
     * @return  int Number of items to display on user-side pages.
     */
    public function limit(): int
    {
        return $this->model->userLimit();
    }

    /**
     * Get children of a content object (collection or tag).
     * 
     * @return  array Array of content objects.
     */
    public function listChildren()
    {
        $params = [
            'start' => $this->start,
            'type' => $this->type,
            'onlineStatus' => $this->onlineStatus,
            'order' => $this->order,
            'orderType' => $this->orderType,
            'secondaryOrder' => $this->secondaryOrder,
            'secondaryOrderType' => $this->secondaryOrderType
        ];

        if ($this->content->type() === 'TfTag') $params['tag'] = $this->content->id();  
        if ($this->content->type() === 'TfCollection') $params['parent'] = $this->content->id();

        $this->children = $this->model->getObjects($params);
    }

    /**
     * Get content objects matching cached filter criteria.
     * 
     * Result is cached as $contentList property.
     */
    public function listContent()
    {
        $this->contentList = $this->model->getObjects(
            [
                'id' => $this->id,
                'start' => $this->start,
                'limit' => $this->limit(),
                'tag' => $this->tag,
                'type' => $this->type,
                'onlineStatus' => $this->onlineStatus,
                'order' => $this->order,
                'orderType' => $this->orderType,
                'secondaryOrder' => $this->secondaryOrder,
                'secondaryOrderType' => $this->secondaryOrderType
            ]
        );
    }

    /**
     * Prepare select box options.
     * 
     * @param   string $zeroOption The default option (text) to show in the select box.
     * @param   array $rows IDs and titles as key-value pairs.
     * @return  array Select box options as key-value pairs.
     */
    private function selectBoxOptions(string $zeroOption, array $rows): array
    {
        $options = [];

        foreach ($rows as $row) {
            $options[$row['id']] = $row['title'];
        }

        \asort($options);

        return [$zeroOption] + $options;
    }

    /** Getters and setters */

    /**
     * Return the backUrl.
     * 
     * If the cancel button is clicked, the user will be redirected to the backUrl.
     * 
     * @return  string
     */
    public function backUrl(): string
    {
        return $this->backUrl;
    }

    /**
     * Return children.
     * 
     * @return  array Array of content objects.
     */
    public function children()
    {
        return $this->children;
    }

    /**
     * Return content object.
     * 
     * @return  \Tfish\Content\Entity\Content
     */
    public function content()
    {
        return $this->content;
    }

    /**
     * Return content count.
     * 
     * @return  int Number of content objects that match filtering criteria.
     */
    public function contentCount(): int
    {
        return $this->contentCount;
    }

    /**
     * Return content list.
     * 
     * @return  array Array of content objects.
     */
    public function contentList(): array
    {
        return $this->contentList;
    }

    /**
     * Returns the template for formatting the date from preferences.
     */
    public function dateFormat(): string
    {
        return $this->model->dateFormat();
    }

    /**
     * Return ID.
     * 
     * @return  int ID of content object.
     */
    public function id(): int
    {
        return $this->id;
    }

    /**
     * Set ID.
     * 
     * @param   int $id ID of content object.
     */
    public function setId(int $id)
    {
        $this->id = $id;
    }

    /**
     * Returns the Google Maps API key (if set) from preferences.
     * 
     * @return  string Google Maps API key.
     */
    public function mapsApiKey(): string
    {
        return $this->model->mapsApiKey();
    }

    /**
     * Return parent of a content object.
     * 
     * @return  \Tfish\Content\Entity\Content Parent content object.
     */
    public function parent()
    {
        return $this->parent;
    }

    /**
     * Return start.
     * 
     * @return int ID of first object to view in the set of available records.
     */
    public function start(): int
    {
        return $this->start;
    }

    /**
     * Set start.
     * 
     * @param   int $start of first object to view in the set of available records.
     */
    public function setStart(int $start)
    {
        $this->start = $start;
    }

    /**
     * Return tag ID.
     * 
     * @return  int
     */
    public function tag(): int
    {
        return $this->tag;
    }

    /**
     * Set tag ID.
     * 
     * @param   int $tag ID of tag.
     */
    public function setTag(int $tag)
    {
        $this->tag = $tag;
    }

    /**
     * Return type.
     * 
     * @return  string Type of content object.
     */
    public function type(): string
    {
        return $this->type;
    }

    /**
     * Set type.
     * 
     * Filter list by content type.
     * 
     * @param   string $type Type of content object.
     */
    public function setType(string $type)
    {
        $this->type = $this->trimString($type);
    }

    /**
     * Return online status.
     * 
     * @return  int Online (1) or offline (0).
     */
    public function onlineStatus(): int
    {
        return $this->onlineStatus;
    }

    /**
     * Return the response message (success or failure) for an action.
     * 
     * @return  string
     */
    public function response(): string
    {
        return $this->response;
    }

    /**
     * Set page-specific overrides of the site metadata.
     * 
     * Overrides trait setMetadata().
     * 
     * @param   array $metadata Metadata overrides as key-value pairs.
     */
    public function setMetadata(array $metadata = [])
    {
        if (!empty($this->pageTitle)) $metadata['title'] = $this->pageTitle;
        if (!empty($this->description)) $metadata['description'] = $this->description;
        if (!empty($this->author)) $metadata['author'] = $this->author;
        if (!empty($this->robots)) $metadata['robots'] = $this->robots;

        $metadata['canonicalUrl'] = $this->canonicalUrl();

        $this->metadata = $metadata;
    }
}
