<?php
namespace WapplerSystems\Meilisearch\Domain\Search\MeilisearchDocument;

/***************************************************************
 *  Copyright notice
 *
 *  (c) 2017 Timo Hund <timo.hund@dkd.de>
 *  All rights reserved
 *
 *  This script is part of the TYPO3 project. The TYPO3 project is
 *  free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  The GNU General Public License can be found at
 *  http://www.gnu.org/copyleft/gpl.html.
 *
 *  This script is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  This copyright notice MUST APPEAR in all copies of the script!
 ***************************************************************/

use WapplerSystems\Meilisearch\Access\Rootline;
use WapplerSystems\Meilisearch\Domain\Site\SiteRepository;
use WapplerSystems\Meilisearch\Domain\Variants\IdBuilder;
use WapplerSystems\Meilisearch\Domain\Site\Site;
use WapplerSystems\Meilisearch\System\Meilisearch\Document\Document;
use WapplerSystems\Meilisearch\Typo3PageContentExtractor;
use WapplerSystems\Meilisearch\Util;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;

/**
 * Builder class to build an MeilisearchDocument
 *
 * Responsible to build \WapplerSystems\Meilisearch\System\Meilisearch\Document\Document
 *
 * @author Timo Hund <timo.hund@dkd.de>
 */
class Builder
{
    /**
     * @var IdBuilder
     */
    protected $variantIdBuilder;

    /**
     * Builder constructor.
     * @param IdBuilder|null $variantIdBuilder
     */
    public function __construct(IdBuilder $variantIdBuilder = null)
    {
        $this->variantIdBuilder = $variantIdBuilder ?? GeneralUtility::makeInstance(IdBuilder::class);
    }

    /**
     * This method can be used to build an Document from a TYPO3 page.
     *
     * @param TypoScriptFrontendController $page
     * @param string $url
     * @param Rootline $pageAccessRootline
     * @param string $mountPointParameter
     * @return array
     */
    public function fromPage(TypoScriptFrontendController $page, $url, Rootline $pageAccessRootline, $mountPointParameter): array
    {
        $document = [];
        $site = $this->getSiteByPageId($page->id);
        $pageRecord = $page->page;

        $accessGroups = $this->getDocumentIdGroups($pageAccessRootline);
        $documentId = $this->getPageDocumentId($page, $accessGroups, $mountPointParameter);

        $document['id'] = $documentId;
        $document['site'] = $site->getDomain();
        $document['siteHash'] = $site->getSiteHash();
        $document['appKey'] = 'EXT:meilisearch';
        $document['type'] = 'pages';

        // system fields
        $document['uid'] = $page->id;
        $document['pid'] = $pageRecord['pid'];

        // variantId
        $variantId = $this->variantIdBuilder->buildFromTypeAndUid('pages', $page->id);
        $document['variantId'] = $variantId;

        $document['typeNum'] = $page->type;
        $document['created'] = $pageRecord['crdate'];
        $document['changed'] = $pageRecord['SYS_LASTCHANGED'];

        $rootline = $this->getRootLineFieldValue($page->id, $mountPointParameter);
        $document['rootline'] = $rootline;

        // access
        $this->addAccessField($document, $pageAccessRootline);
        $this->addEndtimeField($document, $pageRecord);

        // content
        // @extensionScannerIgnoreLine
        $contentExtractor = $this->getExtractorForPageContent($page->content);
        $document['title'] = $contentExtractor->getPageTitle();
        $document['subTitle'] = $pageRecord['subtitle'];
        $document['navTitle'] = $pageRecord['nav_title'];
        $document['author'] = $pageRecord['author'];
        $document['description'] = $pageRecord['description'];
        $document['abstract'] = $pageRecord['abstract'];
        $document['content'] = $contentExtractor->getIndexableContent();
        $document['url'] = $url;

        $this->addKeywordsField($document, $pageRecord);
        $this->addTagContentFields($document, $contentExtractor->getTagContent());

        return $document;
    }


    /**
     * Creates a Meilisearch document with the basic / core fields set already.
     *
     * @param array $itemRecord
     * @param string $type
     * @param int $rootPageUid
     * @param string $accessRootLine
     * @return array
     */
    public function fromRecord(array $itemRecord, string $type, int $rootPageUid, string $accessRootLine): array
    {
        /* @var $document Document */
        $document = GeneralUtility::makeInstance(Document::class);

        $site = $this->getSiteByPageId($rootPageUid);

        $documentId = $this->getDocumentId($type, $site->getRootPageId(), $itemRecord['uid']);

        // required fields
        $document['id'] = $documentId;
        $document['type'] = $type;
        $document['appKey'] = 'EXT:meilisearch';

        // site, siteHash
        $document['site'] = $site->getDomain();
        $document['siteHash'] = $site->getSiteHash();

        // uid, pid
        $document['uid'] = $itemRecord['uid'];
        $document['pid'] = $itemRecord['pid'];

        // variantId
        $variantId = $this->variantIdBuilder->buildFromTypeAndUid($type, $itemRecord['uid']);
        $document['variantId'] = $variantId;

        // created, changed
        if (!empty($GLOBALS['TCA'][$type]['ctrl']['crdate'])) {
            $document['created'] = $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['crdate']];
        }
        if (!empty($GLOBALS['TCA'][$type]['ctrl']['tstamp'])) {
            $document['changed'] = $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['tstamp']];
        }

        // access, endtime
        $document['access'] = $accessRootLine;
        if (!empty($GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime'])
            && $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime']] != 0
        ) {
            $document['endtime'] = $itemRecord[$GLOBALS['TCA'][$type]['ctrl']['enablecolumns']['endtime']];
        }

        return $document;
    }

    /**
     * @param TypoScriptFrontendController  $page
     * @param string $accessGroups
     * @param string $mountPointParameter
     * @return string
     */
    protected function getPageDocumentId(TypoScriptFrontendController $frontendController, string $accessGroups, string $mountPointParameter): string
    {
        return Util::getPageDocumentId($frontendController->id, $frontendController->type, Util::getLanguageUid(), $accessGroups, $mountPointParameter);
    }

    /**
     * @param string $type
     * @param int $rootPageId
     * @param int $recordUid
     * @return string
     */
    protected function getDocumentId(string $type, int $rootPageId, int $recordUid): string
    {
        return Util::getDocumentId($type, $rootPageId, $recordUid);
    }

    /**
     * @param integer $pageId
     * @return Site
     */
    protected function getSiteByPageId($pageId)
    {
        $siteRepository = GeneralUtility::makeInstance(SiteRepository::class);
        return $siteRepository->getSiteByPageId($pageId);
    }

    /**
     * @param string $pageContent
     * @return Typo3PageContentExtractor
     */
    protected function getExtractorForPageContent($pageContent)
    {
        return GeneralUtility::makeInstance(Typo3PageContentExtractor::class, /** @scrutinizer ignore-type */ $pageContent);
    }

    /**
     * Builds the content for the rootline field.
     *
     * @param int $pageId
     * @param string $mountPointParameter
     * @return string
     */
    protected function getRootLineFieldValue($pageId, $mountPointParameter)
    {
        $rootline = $pageId;
        if ($mountPointParameter !== '') {
            $rootline .= ',' . $mountPointParameter;
        }
        return $rootline;
    }

    /**
     * Gets a comma separated list of frontend user groups to use for the
     * document ID.
     *
     * @param Rootline $pageAccessRootline
     * @return string A comma separated list of frontend user groups.
     */
    protected function getDocumentIdGroups(Rootline $pageAccessRootline)
    {
        $groups = $pageAccessRootline->getGroups();
        $groups = Rootline::cleanGroupArray($groups);

        if (empty($groups)) {
            $groups[] = 0;
        }

        $groups = implode(',', $groups);

        return $groups;
    }

    /**
     * Adds the access field to the document if needed.
     *
     * @param array $document
     * @param Rootline $pageAccessRootline
     */
    protected function addAccessField(array &$document, Rootline $pageAccessRootline)
    {
        $access = (string)$pageAccessRootline;
        if (trim($access) !== '') {
            $document['access'] = $access;
        }
    }

    /**
     * Adds the endtime field value to the Document.
     *
     * @param array $document
     * @param array $pageRecord
     */
    protected function addEndtimeField(array &$document, $pageRecord)
    {
        if ($pageRecord['endtime']) {
            $document['endtime'] = $pageRecord['endtime'];
        }
    }

    /**
     * Adds keywords, multi valued.
     *
     * @param array $document
     * @param array $pageRecord
     */
    protected function addKeywordsField(array &$document, $pageRecord)
    {
        if (!isset($pageRecord['keywords'])) {
            return;
        }

        $keywords = array_unique(GeneralUtility::trimExplode(',', $pageRecord['keywords'], true));
        foreach ($keywords as $keyword) {
            $document['keywords'] = $keyword;
        }
    }

    /**
     * Add content from several tags like headers, anchors, ...
     *
     * @param array $document
     * @param array $tagContent
     */
    protected function addTagContentFields(array &$document, $tagContent = [])
    {
        foreach ($tagContent as $fieldName => $fieldValue) {
            $document[$fieldName] = $fieldValue;
        }
    }
}
