<?php

/**
 * @package     Joomla.Administrator
 * @subpackage  com_finder
 *
 * @copyright   (C) 2011 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Component\Finder\Administrator\Controller;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Session\Session;
use Joomla\Component\Finder\Administrator\Indexer\Indexer;
use Joomla\Component\Finder\Administrator\Response\Response;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * Indexer controller class for Finder.
 *
 * @since  2.5
 */
class IndexerController extends BaseController
{
    /**
     * Method to start the indexer.
     *
     * @return  void
     *
     * @since   2.5
     */
    public function start()
    {
        // Check for a valid token. If invalid, send a 403 with the error message.
        if (!Session::checkToken('request')) {
            static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));

            return;
        }

        $params = ComponentHelper::getParams('com_finder');

        if ($params->get('enable_logging', '0')) {
            $options['format']    = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
            $options['text_file'] = 'indexer.php';
            Log::addLogger($options);
        }

        // Log the start
        try {
            Log::add('Starting the indexer', Log::INFO);
        } catch (\RuntimeException $exception) {
            // Informational log only
        }

        // We don't want this form to be cached.
        $this->app->allowCache(false);

        // Put in a buffer to silence noise.
        ob_start();

        // Reset the indexer state.
        Indexer::resetState();

        // Import the finder plugins.
        PluginHelper::importPlugin('finder');

        // Add the indexer language to \JS
        Text::script('COM_FINDER_AN_ERROR_HAS_OCCURRED');
        Text::script('COM_FINDER_NO_ERROR_RETURNED');

        // Start the indexer.
        try {
            // Trigger the onStartIndex event.
            $this->app->triggerEvent('onStartIndex');

            // Get the indexer state.
            $state        = Indexer::getState();
            $state->start = 1;

            // Send the response.
            static::sendResponse($state);
        } catch (\Exception $e) {
            // Catch an exception and return the response.
            static::sendResponse($e);
        }
    }

    /**
     * Method to run the next batch of content through the indexer.
     *
     * @return  void
     *
     * @since   2.5
     */
    public function batch()
    {
        // Check for a valid token. If invalid, send a 403 with the error message.
        if (!Session::checkToken('request')) {
            static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));

            return;
        }

        $params = ComponentHelper::getParams('com_finder');

        if ($params->get('enable_logging', '0')) {
            $options['format']    = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
            $options['text_file'] = 'indexer.php';
            Log::addLogger($options);
        }

        // Log the start
        try {
            Log::add('Starting the indexer batch process', Log::INFO);
        } catch (\RuntimeException $exception) {
            // Informational log only
        }

        // We don't want this form to be cached.
        $this->app->allowCache(false);

        // Put in a buffer to silence noise.
        ob_start();

        // Remove the script time limit.
        if (\function_exists('set_time_limit')) {
            set_time_limit(0);
        }

        // Get the indexer state.
        $state = Indexer::getState();

        // Reset the batch offset.
        $state->batchOffset = 0;

        // Update the indexer state.
        Indexer::setState($state);

        // Import the finder plugins.
        PluginHelper::importPlugin('finder');

        /*
         * We are going to swap out the raw document object with an HTML document
         * in order to work around some plugins that don't do proper environment
         * checks before trying to use HTML document functions.
         */
        $lang = $this->app->getLanguage();

        // Get the document properties.
        $attributes = [
            'charset'   => 'utf-8',
            'lineend'   => 'unix',
            'tab'       => '  ',
            'language'  => $lang->getTag(),
            'direction' => $lang->isRtl() ? 'rtl' : 'ltr',
        ];

        // Start the indexer.
        try {
            // Trigger the onBeforeIndex event.
            $this->app->triggerEvent('onBeforeIndex');

            // Trigger the onBuildIndex event.
            $this->app->triggerEvent('onBuildIndex');

            // Get the indexer state.
            $state           = Indexer::getState();
            $state->start    = 0;
            $state->complete = 0;

            // Log batch completion and memory high-water mark.
            try {
                Log::add('Batch completed, peak memory usage: ' . number_format(memory_get_peak_usage(true)) . ' bytes', Log::INFO);
            } catch (\RuntimeException $exception) {
                // Informational log only
            }

            // Send the response.
            static::sendResponse($state);
        } catch (\Exception $e) {
            // Catch an exception and return the response.
            // Send the response.
            static::sendResponse($e);
        }
    }

    /**
     * Method to optimize the index and perform any necessary cleanup.
     *
     * @return  void
     *
     * @since   2.5
     */
    public function optimize()
    {
        // Check for a valid token. If invalid, send a 403 with the error message.
        if (!Session::checkToken('request')) {
            static::sendResponse(new \Exception(Text::_('JINVALID_TOKEN_NOTICE'), 403));

            return;
        }

        // We don't want this form to be cached.
        $this->app->allowCache(false);

        // Put in a buffer to silence noise.
        ob_start();

        // Import the finder plugins.
        PluginHelper::importPlugin('finder');

        try {
            // Optimize the index
            $indexer = new Indexer();
            $indexer->optimize();

            // Get the indexer state.
            $state           = Indexer::getState();
            $state->start    = 0;
            $state->complete = 1;

            // Send the response.
            static::sendResponse($state);
        } catch (\Exception $e) {
            // Catch an exception and return the response.
            static::sendResponse($e);
        }
    }

    /**
     * Method to handle a send a \JSON response. The body parameter
     * can be an \Exception object for when an error has occurred or
     * a CMSObject for a good response.
     *
     * @param   \Joomla\CMS\Object\CMSObject|\Exception  $data  CMSObject on success, \Exception on error. [optional]
     *
     * @return  void
     *
     * @since   2.5
     */
    public static function sendResponse($data = null)
    {
        $app = Factory::getApplication();

        $params = ComponentHelper::getParams('com_finder');

        if ($params->get('enable_logging', '0')) {
            $options['format']    = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
            $options['text_file'] = 'indexer.php';
            Log::addLogger($options);
        }

        // Send the assigned error code if we are catching an exception.
        if ($data instanceof \Exception) {
            try {
                Log::add($data->getMessage(), Log::ERROR);
            } catch (\RuntimeException $exception) {
                // Informational log only
            }

            $app->setHeader('status', $data->getCode());
        }

        // Create the response object.
        $response = new Response($data);

        if (\JDEBUG) {
            // Add the buffer and memory usage
            $response->buffer = ob_get_contents();
            $response->memory = memory_get_usage(true);
        }

        // Send the JSON response.
        echo json_encode($response);
    }
}
