<?php
namespace SPAATG\Controller;

use SPAATG\Controller\Api\RequestManager;

if ( ! defined( 'ABSPATH' ) ) {
 exit; // Exit if accessed directly.
}
use SPAATG\ShortPixelLogger\ShortPixelLogger as Log;

use SPAATG\Model\Image\ImageModel as ImageModel;
use SPAATG\Model\Queue\QueueItem as QueueItem;
use SPAATG\Controller\Queue\QueueItems as QueueItems;

use SPAATG\Controller\ApiKeyController as ApiKeyController;
use SPAATG\Controller\QuotaController as QuotaController;

use SPAATG\Controller\Queue\MediaLibraryQueue as MediaLibraryQueue;
use SPAATG\Controller\Queue\Queue as Queue;
use SPAATG\Controller\Api\ApiController as ApiController;

use SPAATG\Helper\UiHelper as UiHelper;

// Controller,  the glue between the Queue and the Optimizers.
class QueueController
{

  const IN_QUEUE_ACTION_ADDED = 1;
  const IN_QUEUE_SKIPPED = 2;

  protected static $lastId; // Last item_id received / send. For catching errors.
  protected $lastQStatus; // last status for reporting purposes.

  //protected $optimiser;
  protected $args;

  public function __construct($args = [])
  {
     $defaults = [
       'is_bulk' => false,
     ];

     $this->args = wp_parse_args($args, $defaults);
  }

  /**
   * Add a single item to the queue
   *
   * @param ImageModel $imageModel
   * @param array $args
   * @return Object Result object
   */
  public function addItemToQueue(ImageModel $imageModel, $args = [])
  {
      $defaults = array(
        'forceExclusion' => false,
	        'action' => 'requestAlt',
        'next_actions' => [],
        'returndatalist' => [],
      );
      $args = wp_parse_args($args, $defaults);

      $qItem = QueueItems::getImageItem($imageModel);

      /* QueueItem is basically reset each action to prevent interference between tasks. next_actions should be kept persistent until all tasks done */
      if (count($args['next_actions']) > 0)
      {
         $qItem->data()->next_actions = $args['next_actions'];
      }

      if (is_object($args['returndatalist']))
      {
         $args['returndatalist'] = (array) $args['returndatalist'];
      }
      if (is_array($args['returndatalist']) && count($args['returndatalist']) > 0)
      {
         $qItem->data()->returndatalist = $args['returndatalist'];
      }

      if (true === $args['forceExclusion'])
      {
         $qItem->data()->forceExclusion = $args['forceExclusion'];
      }

      $queue = $this->getQueue($imageModel->get('type'));

      $args = array_filter($args, function ($value) {
        return $value !== null;
      });


      // These checks are across all actions.
      if ($queue->isDuplicateActive($imageModel))
      {
        $qItem->addResult([
            'fileStatus' => ImageModel::FILE_STATUS_UNPROCESSED,
            'is_error' => false,
            'is_done' => true,
            'message' => __('A duplicate of this item is already active in queue. ', 'shortpixel-ai-alt-text-generator'),

        ]);

        return $qItem->result();

      }

      $in_queue = $this->isItemInQueue($imageModel, $args['action']);
      if (is_numeric($in_queue) && $in_queue !== false)
      {

        if (self::IN_QUEUE_ACTION_ADDED == $in_queue)
        {
          $qItem->addResult([
            'fileStatus' => ImageModel::FILE_STATUS_UNPROCESSED,
            'is_error' => false,
            'is_done' => false,
            'message' =>__('Action has been added to queue and will be processed after current actions', 'shortpixel-ai-alt-text-generator'),
          ]);
        }

        if (self::IN_QUEUE_SKIPPED == $in_queue)
        {
          $qItem->addResult([
            'fileStatus' => ImageModel::FILE_STATUS_UNPROCESSED,
            'is_error' => false,
            'is_done' => true,
            'message' =>__('This item is already awaiting processing in queue', 'shortpixel-ai-alt-text-generator'),
          ]);
        }

        return $qItem->result();

      }

      $optimizer = $qItem->getApiController($args['action']);

      if (is_null($optimizer))
      {
         Log::addError('No AI processor found for this action, or action missing!', $args);
         $qItem->addResult([
            'fileStatus' => ImageModel::FILE_STATUS_UNPROCESSED,
            'is_error' => true,
            'is_done' => true,
            'message' => __('No action found!', 'shortpixel-ai-alt-text-generator'),
         ]);
      }

      $bool = false;

      if (! is_null($optimizer))
      {
        $optimizer->setCurrentQueue($queue, $this);
        $bool = $optimizer->checkItem($qItem);
      }

      if (true === $bool)
      {
          $status = $optimizer->enQueueItem($qItem, $args);
          $this->lastQStatus = $status->qstatus;

          // Not API status does it own messaging.
          if ($status->qstatus !== RequestManager::STATUS_NOT_API)
          {
            $message = '';
            if ($status->numitems > 0)
            {

	              /* translators: 1: image file name, 2: number of items in the queue. */
	              $message = sprintf(__('Item %1$s added to Queue. %2$d items in Queue', 'shortpixel-ai-alt-text-generator'), $imageModel->getFileName(), $status->numitems);

              // Check if background process is active / this needs activating.
              $cronController = CronController::getInstance();
              $cronController->checkNewJobs();
            }
            else {
              $message = __('No items added to queue', 'shortpixel-ai-alt-text-generator');
              //$json->status = 0;
            }

            if (! property_exists($qItem->result(), 'message') || strlen($qItem->result->message) <= 0)
            {
              $qItem->addResult([
                'message' => $message,
              ]);
            }

            if ($status->numitems > 0)
            {
              $qItem->addResult([
                'fileStatus' => ImageModel::FILE_STATUS_PENDING,
                'is_error' => false,
                'is_done' => false,
              ]);
            }

          }

      }

      return $qItem->result();
  }

  /** Check if item and action is already listed in the queue
   *
   * @param ImageModel $mediaItem
   * @return mixed
   */
  public function isItemInQueue(ImageModel $mediaItem, $action = null)
  {
      $type = $mediaItem->get('type');

      $q = $this->getQueue($type);
      $bool = $q->isItemInQueue($mediaItem->get('id'));

      if (true === $bool)
      {
        // @todo This queueItem should maybe not to stuffed with 'addresult'm since it's a different object.
          $queueItem = $q->getItem($mediaItem->get('id'));

          if (is_object($queueItem))
          {
              $queueItem->setModel($mediaItem);
              // @todo If item can be appended, probably add function in queueItem to add next_action and update to database (this q )?
              if (false === is_null($action) && false === $queueItem->data()->hasAction($action))
              {
                  // @todo This probably move up to addItemToQueue, also needs to add additional args
                  $queueItem->data()->addNextAction($action);
                  $q->updateItem($queueItem);

                  $bool = self::IN_QUEUE_ACTION_ADDED;

              }
              elseif(false === is_null($action)) // Only set this is action add is requested, otherwise keep boolean
              {
                  $bool = self::IN_QUEUE_SKIPPED;

              }
          }


      }

      // Preventing double queries here

      return $bool;
  }

  // Processing Part

  // next tick of items to do.
  /* Processes one tick of the queue
  *
  * @return Object JSON object detailing results of run
  */
 // @todo This is the main function that starts the processing
  public function processQueue($queueTypes = array())
  {
      $keyControl = ApiKeyController::getInstance();
      if ($keyControl->keyIsVerified() === false)
      {
         $json = $this->getJsonResponse();
         $json->status = false;
         $json->error = AjaxController::APIKEY_FAILED;
         $json->message =  __('Invalid API Key', 'shortpixel-ai-alt-text-generator');
         $json->status = false;
         return $json;
      }

	      $quotaControl = QuotaController::getInstance();
	      if ($quotaControl->hasQuota() === false)
	      {
	        $quotaControl->forceCheckRemoteQuota(); // on next load check if something happenend when out and asking.
	        $json = $this->getJsonResponse();
	        $json->error = AjaxController::NOQUOTA;
	        $json->status = false;
	        $json->message =   __('Quota Exceeded','shortpixel-ai-alt-text-generator');
	        return $json;
	      }

      // @todo Here prevent bulk from running when running flag is off
      // @todo Here prevent a runTick is the queue is empty and done already ( reliably )
      // @todo If once queue exited because of mediaItem, don't run the other one but abort
      $results = new \stdClass;
      $results->status = 1;
      $overlimit = false;

	      $queueTypes = array_intersect((array) $queueTypes, array('media'));
	      if ( in_array('media', $queueTypes))
	      {
	        $mediaQ = $this->getQueue('media');
	        $results->media = $this->runTick($mediaQ); // run once on mediaQ

        $overlimit = (Queue::RESULT_PREPARING_OVERLIMIT === $results->media->qstatus) ? true : false;

      }
	      $results->total = $this->calculateStatsTotals($results);
      $results = $this->numberFormatStats($results);

      return $results;
  }


  public function getStartupData()
  {
      $mediaQ = $this->getQueue('media');
	      $data = new \stdClass;
	      $data->media = new \stdClass;
	      $data->custom = new \stdClass;
	      $data->total = new \stdClass;

	      $data->media->stats = $mediaQ->getStats();
	      $data->custom->stats = $this->getEmptyStats();

      $data->total = $this->calculateStatsTotals($data);
      $data = $this->numberFormatStats($data);
      return $data;
  }


  /** Run the Queue once with X amount of items, send to processor or handle.  */
  // @todo Call by processQueue
  protected function runTick($Q)
  {
    $result = $Q->run();
    $fs = \wpSPAATG()->filesystem();

    ResponseController::setQ($Q);

    // Items is array in case of a dequeue items.
    $items = (isset($result->items) && is_array($result->items)) ? $result->items : [];
    $qtype = $Q->getType();
    $qtype = strtolower($qtype);

    /* Only runs if result is array, dequeued items.
       Item is a MediaItem subset of QueueItem
    */
    foreach($items as $mainIndex => $qItem)
    {
          // Note, all these functions change content of QueueItem
          $action = $qItem->data()->action;
          $apiController = $qItem->getAPIController($action);
          $send_to_processing = true;

          if (is_null($apiController))
          {
            Log::addError('No AI processor found for this action, or action missing!', $qItem);
            $qItem->addResult([
                'fileStatus' => ImageModel::FILE_STATUS_UNPROCESSED,
                'is_error' => true,
                'is_done' => true,
                'message' => __('No action found!', 'shortpixel-ai-alt-text-generator'),
            ]);

            $Q->itemFailed($qItem, true);
          }
          else
          {
            $apiController->setCurrentQueue($Q, $this);
          }

          $item_id = $qItem->item_id;
          $imageModel = is_object($qItem->imageModel) ? $qItem->imageModel : $fs->getImage($item_id, $qtype);

          // Set the ImageModel if not set.
          if (! is_object($qItem->imageModel) && is_object($imageModel))
          {
            $qItem->setModel($imageModel);
          }

          if (! is_object($imageModel)) // Error in loading imageModel, can't process this.
          {
            Log::addWarn('ImageObject was empty when send to processing - ' . $item_id);
            $qItem->addResult([
                'apiStatus' => RequestManager::STATUS_NOT_API,
                'message' => __("File Error. Media Item could not be loaded with this ID ", 'shortpixel-ai-alt-text-generator'),
                'fileStatus' => ImageModel::FILE_STATUS_ERROR,
                'is_done' => true,
                'is_error' => true,
            ]);
            $Q->itemFailed($qItem, true);
            $send_to_processing = false;
          }
          elseif(true === $qItem->block())
          {
            $qItem->addResult([
                'apiStatus' => RequestManager::STATUS_UNCHANGED,
                'message' => __('Item is waiting (blocked)', 'shortpixel-ai-alt-text-generator'),
            ]);
            Log::addWarn('Encountered blocked item, processing success? ', $item_id);
            ResponseController::addData($item_id, 'fileName', $imageModel->getFileName());

            $send_to_processing = false;
          }
          else
          {
            // This used in bulk preview for formatting filename.
            $qItem->addResult(
                ['filename' => $imageModel->getFileName()]
            );

            // Used in WP-CLI
            ResponseController::addData($item_id, 'fileName', $imageModel->getFileName());
          }

          $this->setLastID($item_id);

          if (! is_null($apiController) && true === $send_to_processing)
          {
            $apiController->sendToProcessing($qItem);
            $apiController->handleAPIResult($qItem);
          }

          if (true === $qItem->result()->is_error &&  true === $this->args['is_bulk'] )
          {
             $this->LogBulk($qItem);
          }

          $result->items[$mainIndex] = $qItem->result(); // replace processed item, should have result now.
    }

    $result->stats = $Q->getStats();
    $json = $this->queueToJson($result);
    $this->checkQueueClean($result, $Q);

    return $json;
  }

  /**
   * getQueue
   *
   * Get Queue Object for adding items to it.  This is dependent on the type of image.
   *
   * @param [string] $type
   * @return Object|boolean Queue object, false if wrong type was given
   */
  public function getQueue($type)
  {
      $queue = null;

      if ($type == 'media')
      {
          $queueName = (true == $this->args['is_bulk']) ? 'media' : 'mediaSingle';
          $queue = new MediaLibraryQueue($queueName);
      }
	      else
	      {
	        Log::addInfo("Get Queue $type seems not a queue");
	        return false;
      }

      $options = $queue->getOptions();
      if ($options !== false)
      {
          $queue->setOptions($options);
      }
      return $queue;
  }

	  protected function checkQueueClean($result, $q)
	  {
	      if ($result->qstatus == Queue::RESULT_QUEUE_EMPTY && false === $this->args['is_bulk'])
	      {
	          $stats = $q->getStats();

	          if ($stats->done > 0 || $stats->fatal_errors > 0)
	          {
	             $q->cleanQueue(); // clean the queue
	          }
	      }
	  }

	  private function getEmptyStats()
	  {
	      $stats = new \stdClass;
	      $stats->is_preparing = false;
	      $stats->is_running = false;
	      $stats->is_finished = true;
	      $stats->in_queue = 0;
	      $stats->in_process = 0;
	      $stats->awaiting = 0;
	      $stats->errors = 0;
	      $stats->fatal_errors = 0;
	      $stats->done = 0;
	      $stats->bulk_running = false;
	      $stats->total = 0;
	      $stats->percentage_done = 100;
	      $stats->images = (object) array(
	          'images' => 0,
	          'images_done' => 0,
	          'images_inprocess' => 0,
	          'images_webp' => 0,
	          'images_avif' => 0,
	          'images_ai' => 0,
	          'total_images_without_ai' => 0,
	      );

	      return $stats;
	  }

  protected function getJsonResponse()
  {

    $json = new \stdClass;
    $json->status = null;
    $json->result = null;
    $json->results = null;
    $json->message = null;

    return $json;
  }

  /** If a result Queue Stdclass to a JSON send Object */
  protected function queueToJson($result, $json = false)
  {
      if (! $json)
        $json = $this->getJsonResponse();

      switch($result->qstatus)
      {
        case Queue::RESULT_PREPARING:
	          /* translators: %s: number of prepared items. */
	          $json->message = sprintf(__('Prepared %s items', 'shortpixel-ai-alt-text-generator'), $result->items );
        break;
        case Queue::RESULT_PREPARING_OVERLIMIT:
	          /* translators: %s: number of prepared items. */
	          $json->message = sprintf(__('Prepared %s items - but went over limit! ', 'shortpixel-ai-alt-text-generator'), $result->items );
        break;
        case Queue::RESULT_PREPARING_DONE:
	          /* translators: %s: total number of queued items. */
	          $json->message = sprintf(__('Preparing is done, queue has %s items ', 'shortpixel-ai-alt-text-generator'), $result->stats->total );
        break;
        case Queue::RESULT_EMPTY:
            $json->message  = __('Queue returned no active items', 'shortpixel-ai-alt-text-generator');
        break;
        case Queue::RESULT_QUEUE_EMPTY:
            $json->message = __('Queue empty and done', 'shortpixel-ai-alt-text-generator');
        break;
        case Queue::RESULT_ITEMS:
	          /* translators: %d: number of fetched queue items. */
	          $json->message = sprintf(__("Fetched %d items",  'shortpixel-ai-alt-text-generator'), count($result->items));
          $json->results = $result->items;
        break;
        case Queue::RESULT_RECOUNT: // This one should probably not happen.
           $json->has_error = true;
           $json->message = sprintf(__('Bulk preparation seems to be interrupted. Restart the queue or continue without accurate count', 'shortpixel-ai-alt-text-generator'));
        break;
        default:
	           /* translators: %s: queue status code. */
	           $json->message = sprintf(__('Unknown Status %s ', 'shortpixel-ai-alt-text-generator'), $result->qstatus);
        break;
      }
      $json->qstatus = $result->qstatus;

      if (property_exists($result, 'stats'))
        $json->stats = $result->stats;

      return $json;
  }

  protected function setLastID($item_id)
  {
     self::$lastId = $item_id;
  }

  public function getLastQueueStatus()
  {
     return $this->lastQStatus;
  }

  public static function getLastId()
  {
     return self::$lastId;
  }

  public static function resetQueues()
  {
	      $queues = array('media', 'mediaSingle');
      foreach($queues as $qName)
      {
          $q = new MediaLibraryQueue($qName);
          $q->activatePlugin();
      }
  }

  /** On Uninstall plugin, remove all queue data of this plugin
   *
   * @return void
   */
  public static function uninstallPlugin()
  {

	    $queues = ['media', 'mediaSingle'];
    foreach($queues as $qName)
    {
        $q = new MediaLibraryQueue($qName);
        $q->uninstall();
    }

  }

  /** Tries to calculate total stats of the process for bulk reporting
  *  Format of results is   results [media|custom](object) -> stats
  */
  private function calculateStatsTotals($results)
  {
	      $has_media = false;

      if (property_exists($results, 'media') &&
          is_object($results->media) &&
          property_exists($results->media,'stats') && is_object($results->media->stats))
      {
        $has_media = true;
      }

	      $object = new \stdClass;  // total

	      if ($has_media)
	      {
	         $object->stats = $results->media->stats;
	         return $object;
	      }

	      $object->stats = $this->getEmptyStats();
	      return $object;
	  }

  private function numberFormatStats($results) // run the whole stats thing through the numberFormat.
  {
    //qn: array('media', 'custom', 'total')
     foreach($results as $qn => $item)
     {
        if (is_object($item) && property_exists($item, 'stats'))
        {
          foreach($item->stats as $key => $value)
          {
               $raw_value = $value;
               if (is_object($value))
               {
                  foreach($value as $key2 => $val2) // embedded 'images' can happen here.
                  {
                   $value->$key2 = UiHelper::formatNumber($val2, 0);
                  }
               }
               elseif (strpos($key, 'percentage') !== false)
               {
                  $value = UiHelper::formatNumber($value, 2);
               }
               elseif (is_numeric($value))
               {
                  $value = UiHelper::formatNumber($value, 0);
               }

              $results->$qn->stats->$key = $value;
          }
        }
     }
     return $results;
  }

  private function logBulk(QueueItem $qItem)
  {
    $item_id = $qItem->item_id;
    $type = (is_object($qItem->imageModel)) ? $qItem->imageModel->get('type') : false;

    if (false === $type)
    {
      return;
    }

    $fs = \wpSPAATG()->filesystem();
    $storageDir = $fs->getDirectory(SPAATG_STORAGE_FOLDER);
    $fileLog = $fs->getFile($storageDir->getPath() . 'current_bulk_' . $type . '.log');

    $time = UiHelper::formatTs(time());

    $fileName = $qItem->imageModel->getFileName();
    $message = ResponseController::formatQItem($qItem);

    $fileLog->append($time . '|' . $fileName . '| ' . $item_id . '|' . $message . ';' .PHP_EOL);
  }


} // class
