<?php
/**
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer
 * versions in the future.
 *
 * @category  Smile
 * @package   Smile\ElasticsuiteCatalog
 * @author    Romain Ruaud <romain.ruaud@smile.fr>
 * @copyright 2020 Smile
 * @license   Open Software License ("OSL") v. 3.0
 */

namespace Smile\ElasticsuiteCore\Indexer;

use Magento\Framework\Indexer\SaveHandler\IndexerInterface;
use Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface;
use Smile\ElasticsuiteCore\Api\Index\DataSourceResolverInterfaceFactory;
use Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface;
use Smile\ElasticsuiteCore\Helper\Cache as CacheHelper;
use Magento\Framework\Indexer\SaveHandler\Batch;

/**
 * Eav Indexing operation handling for Elasticsearch engine.
 *
 * @category Smile
 * @package  Smile\ElasticsuiteCatalog
 * @author   Aurelien FOUCRET <aurelien.foucret@smile.fr>
 * @author   Romain Ruaud <romain.ruaud@smile.fr>
 * @author   Fanny DECLERCK <fadec@smile.fr>
 */
class GenericIndexerHandler implements IndexerInterface
{
    /**
     * @var \Smile\ElasticsuiteCore\Api\Index\IndexOperationInterface
     */
    private $indexOperation;

    /**
     * @var \Magento\Framework\Indexer\SaveHandler\Batch
     */
    private $batch;

    /**
     * @var string
     */
    private $indexName;

    /**
     * @var string
     */
    private $typeName;

    /**
     * @var CacheHelper
     */
    private $cacheHelper;

    /**
     * @var \Smile\ElasticsuiteCore\Api\Index\DataSourceResolverInterfaceFactory
     */
    private $dataSourceResolverFactory;

    /**
     * @var \Smile\ElasticsuiteCore\Api\Index\IndexSettingsInterface
     */
    private $indexSettings;

    /**
     * Constructor
     *
     * @param IndexOperationInterface            $indexOperation            Index operation service.
     * @param CacheHelper                        $cacheHelper               Index caching helper.
     * @param Batch                              $batch                     Batch handler.
     * @param DataSourceResolverInterfaceFactory $dataSourceResolverFactory DataSource resolver.
     * @param IndexSettingsInterface             $indexSettings             Index Settings.
     * @param string                             $indexName                 The index name.
     * @param string                             $typeName                  The type name.
     */
    public function __construct(
        IndexOperationInterface $indexOperation,
        CacheHelper $cacheHelper,
        Batch $batch,
        DataSourceResolverInterfaceFactory $dataSourceResolverFactory,
        IndexSettingsInterface $indexSettings,
        $indexName,
        $typeName
    ) {
        $this->indexOperation            = $indexOperation;
        $this->batch                     = $batch;
        $this->indexName                 = $indexName;
        $this->typeName                  = $typeName;
        $this->cacheHelper               = $cacheHelper;
        $this->dataSourceResolverFactory = $dataSourceResolverFactory;
        $this->indexSettings             = $indexSettings;
    }

    /**
     * {@inheritDoc}
     */
    public function saveIndex($dimensions, \Traversable $documents)
    {
        foreach ($dimensions as $dimension) {
            $storeId   = $dimension->getValue();

            try {
                $index = $this->indexOperation->getIndexByName($this->indexName, $storeId);
            } catch (\Exception $exception) {
                $index = $this->indexOperation->createIndex($this->indexName, $storeId);
            }

            $batchSize = $this->indexOperation->getBatchIndexingSize();

            foreach ($this->batch->getItems($documents, $batchSize) as $batchDocuments) {
                foreach ($this->getDatasources() as $datasource) {
                    if (!empty($batchDocuments)) {
                        $batchDocuments = $datasource->addData($storeId, $batchDocuments);
                    }
                }

                if (!empty($batchDocuments)) {
                    $bulk = $this->indexOperation->createBulk()->addDocuments($index, $batchDocuments);
                    $this->indexOperation->executeBulk($bulk);
                }
            }

            $this->indexOperation->refreshIndex($index);
            $this->indexOperation->installIndex($index, $storeId);
            $this->cacheHelper->cleanIndexCache($this->indexName, $storeId);
        }

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function deleteIndex($dimensions, \Traversable $documents)
    {
        foreach ($dimensions as $dimension) {
            $storeId   = $dimension->getValue();

            if ($this->indexOperation->indexExists($this->indexName, $storeId)) {
                $index     = $this->indexOperation->getIndexByName($this->indexName, $storeId);
                $batchSize = $this->indexOperation->getBatchIndexingSize();

                foreach ($this->batch->getItems($documents, $batchSize) as $batchDocuments) {
                    $bulk = $this->indexOperation->createBulk()->deleteDocuments($index, $batchDocuments);
                    $this->indexOperation->executeBulk($bulk);
                }

                $this->indexOperation->refreshIndex($index);
            }
        }

        return $this;
    }

    /**
     * This override does not delete data into the old index as expected but only create a new index.
     * It allows to keep old index in place during full reindex.
     *
     * {@inheritDoc}
     */
    public function cleanIndex($dimensions)
    {
        foreach ($dimensions as $dimension) {
            $this->indexOperation->createIndex($this->indexName, $dimension->getValue());
        }

        return $this;
    }

    /**
     * {@inheritDoc}
     */
    public function isAvailable($dimensions = [])
    {
        return $this->indexOperation->isAvailable();
    }

    /**
     * Retrieve data sources of an index by name.
     *
     * @return \Smile\ElasticsuiteCore\Api\Index\DatasourceInterface[]
     */
    private function getDatasources()
    {
        $resolver = $this->dataSourceResolverFactory->create();
        $sources  = $resolver->getDataSources($this->indexName);

        return $sources;
    }
}
