<?php
namespace ShortPixel\Controller;

if ( ! defined( 'ABSPATH' ) ) {
 exit; // Exit if accessed directly.
}

use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;

class QuotaController
{
    protected static $instance;
    const CACHE_NAME = 'quotaData';

    protected $quotaData;

    /** Singleton instance
    * @return Object QuotaController object
    */
    public static function getInstance()
    {
      if (is_null(self::$instance))
        self::$instance = new QuotaController();

      return self::$instance;
    }


    /**
     * Return if account has any quota left
     * @return boolean Has quota left
     */
    public function hasQuota()
    {
      $settings = \wpSPIO()->settings();

      if ($settings->quotaExceeded)
			{
        return false;
			}
      return true;

    }

    /**
     * Retrieves QuotaData object from cache or from remote source
     * @return array The quotadata array (remote format)
     */
    protected function getQuotaData()
    {
        if (! is_null($this->quotaData))
          return $this->quotaData;

        $cache = new CacheController();
        $cacheData = $cache->getItem(self::CACHE_NAME);

        if (! $cacheData->exists() )
        {
            $quotaData = $this->getRemoteQuota();
            if (false === $this->hasQuota())
							$timeout = MINUTE_IN_SECONDS;
						else {
							$timeout = HOUR_IN_SECONDS;
						}
            $cache->storeItem(self::CACHE_NAME, $quotaData, $timeout);
        }
        else
				{
          $quotaData = $cacheData->getValue();
				}

        return $quotaData;
    }

    /**
     * Retrieve quota information for this account
     * @return object quotadata SPIO format
     */
    public function getQuota()
    {

          $quotaData = $this->getQuotaData();
          $DateNow = time();

          $DateSubscription = strtotime($quotaData['APILastRenewalDate']);
          $DaysToReset =  30 - ( (int) (  ( $DateNow  - $DateSubscription) / DAY_IN_SECONDS) % 30);

          $quota = (object) [
              'unlimited' => isset($quotaData['Unlimited']) ? $quotaData['Unlimited'] : false,
              'monthly' => (object) [
                'text' =>  sprintf(__('%s/month', 'shortpixel-image-optimiser'), $quotaData['APICallsQuota']),
                'total' =>  $quotaData['APICallsQuotaNumeric'],
                'consumed' => $quotaData['APICallsMadeNumeric'],
                'remaining' => max($quotaData['APICallsQuotaNumeric'] - $quotaData['APICallsMadeNumeric'], 0),
                'renew' => $DaysToReset,
              ],
              'onetime' => (object) [
                'text' => $quotaData['APICallsQuotaOneTime'],
                'total' => $quotaData['APICallsQuotaOneTimeNumeric'],
                'consumed' => $quotaData['APICallsMadeOneTimeNumeric'],
                'remaining' => $quotaData['APICallsQuotaOneTimeNumeric'] - $quotaData['APICallsMadeOneTimeNumeric'],
              ],
          ];


          $quota->total = (object) [
              'total' => $quota->monthly->total + $quota->onetime->total,
              'consumed'  => $quota->monthly->consumed + $quota->onetime->consumed,
              'remaining' =>$quota->monthly->remaining + $quota->onetime->remaining,
          ];


          return $quota;
    }

    /**
     * Get available remaining - total - credits
     * @return int Total remaining credits
     */
    public function getAvailableQuota()
    {
        $quota = $this->getQuota();
        return $quota->total->remaining;
    }

    /**
     * Force to get quotadata from API, even if cache is still active ( use very sparingly )
     * Does not actively fetches it, but invalidates cache, so next call will trigger a remote call
     * @return void
     */
    public function forceCheckRemoteQuota()
    {
        $cache = new CacheController();
        $cacheData = $cache->getItem(self::CACHE_NAME);
        $cacheData->delete();
        $this->quotaData = null;
    }

    /**
     * Validate account key in the API via quota check
     * @param  string $key               User account key
     * @return array      Quotadata array (remote format) with validated key
     */
    public function remoteValidateKey($key)
    {
			  // Remove the cache before checking.
				$this->forceCheckRemoteQuota();
        return $this->getRemoteQuota($key, true);
    }

		/**
     * Called when plugin detects the remote quota has been exceeded.
     * Triggers various call to actions to customer
     */
		public function setQuotaExceeded()
		{
			  $settings = \wpSPIO()->settings();
				$settings->quotaExceeded = 1;
				$this->forceCheckRemoteQuota(); // remove the previous cache.
		}

    /**
     * When quota is detected again via remote check, reset all call to actions
     */
    private function resetQuotaExceeded()
    {
        $settings = \wpSPIO()->settings();

        AdminNoticesController::resetAPINotices();

				// Only reset after a quotaExceeded situation, otherwise it keeps popping.
				if ($settings->quotaExceeded == 1)
				{
						AdminNoticesController::resetQuotaNotices();
				}
       	$settings->quotaExceeded = 0;
    }


    /**
     * [getRemoteQuota description]
     * @param  string $apiKey                 User account key
     * @param  boolean $validate               Api should also validate key or not
     * @return array            Quotadata array (remote format) [with validated key]
     */
    private function getRemoteQuota($apiKey = false, $validate = false)
    {
        if (! $apiKey && ! $validate) // validation is done by apikeymodel, might result in a loop.
        {
          $keyControl = ApiKeyController::getInstance();
          $apiKey = $keyControl->forceGetApiKey();
        }

        $settings = \wpSPIO()->settings();

          if($settings->httpProto != 'https' && $settings->httpProto != 'http') {
              $settings->httpProto = 'https';
          }

          $requestURL = $settings->httpProto . '://' . SHORTPIXEL_API . '/v2/api-status.php';

          $args = array(
              'timeout'=> 15, // wait for 15 secs.
              'body' => array('key' => $apiKey)
          );
          $argsStr = "?key=".$apiKey;

					$serverAgent = isset($_SERVER['HTTP_USER_AGENT']) ? urlencode(sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT']))) : '';
          $args['body']['useragent'] = "Agent" . $serverAgent;
          $argsStr .= "&useragent=Agent".$args['body']['useragent'];

          // Only used for keyValidation
          if($validate) {

              $statsController = StatsController::getInstance();
              $imageCount = $statsController->find('media', 'itemsTotal');
              $thumbsCount = $statsController->find('media', 'thumbsTotal');

              $args['body']['DomainCheck'] = get_site_url();
              $args['body']['Info'] = get_bloginfo('version') . '|' . phpversion();
              $args['body']['ImagesCount'] = $imageCount; //$imageCount['mainFiles'];
              $args['body']['ThumbsCount'] = $thumbsCount; // $imageCount['totalFiles'] - $imageCount['mainFiles'];
              $argsStr .= "&DomainCheck={$args['body']['DomainCheck']}&Info={$args['body']['Info']}&ImagesCount=$imageCount&ThumbsCount=$thumbsCount";


          }

          $args['body']['host'] = parse_url(get_site_url(),PHP_URL_HOST);
          $argsStr .= "&host={$args['body']['host']}";
					if (defined('SHORTPIXEL_HTTP_AUTH_USER') && defined('SHORTPIXEL_HTTP_AUTH_PASSWORD'))
					{
						$args['body']['user'] = stripslashes(SHORTPIXEL_HTTP_AUTH_USER);
						$args['body']['pass'] = stripslashes(SHORTPIXEL_HTTP_AUTH_PASSWORD);
						$argsStr .= '&user=' . urlencode($args['body']['user']) . '&pass=' . urlencode($args['body']['pass']);
					}
          elseif(! is_null($settings->siteAuthUser) && strlen($settings->siteAuthUser)) {

              $args['body']['user'] = stripslashes($settings->siteAuthUser);
              $args['body']['pass'] = stripslashes($settings->siteAuthPass);
              $argsStr .= '&user=' . urlencode($args['body']['user']) . '&pass=' . urlencode($args['body']['pass']);
          }
          /* TF?
          if($settings !== false) {

              $args['body']['Settings'] = $settings;
          } */

          $time = microtime(true);
          $comm = array();

          //Try first HTTPS post. add the sslverify = false if https
          if($settings->httpProto === 'https') {
              $args['sslverify'] = apply_filters('shortpixel/system/sslverify', true);
          }

          $response = wp_remote_post($requestURL, $args);

          $comm['A: ' . (number_format(microtime(true) - $time, 2))] = array("sent" => "POST: " . $requestURL, "args" => $args, "received" => $response);

          //some hosting providers won't allow https:// POST connections so we try http:// as well
          if(is_wp_error( $response )) {

              $requestURL = $settings->httpProto == 'https' ?
                  str_replace('https://', 'http://', $requestURL) :
                  str_replace('http://', 'https://', $requestURL);
              // add or remove the sslverify
              if($settings->httpProto === 'https') {
                  $args['sslverify'] = apply_filters('shortpixel/system/sslverify', true);
              } else {
                  unset($args['sslverify']);
              }
              $response = wp_remote_post($requestURL, $args);
              $comm['B: ' . (number_format(microtime(true) - $time, 2))] = array("sent" => "POST: " . $requestURL, "args" => $args, "received" => $response);

              if(!is_wp_error( $response )){
                  $settings->httpProto = ($settings->httpProto == 'https' ? 'http' : 'https');
              } else {
              }
          }
          //Second fallback to HTTP get
          if(is_wp_error( $response )){
              $args['body'] = null;
              $requestURL .= $argsStr;
              $response = wp_remote_get($requestURL, $args);
              $comm['C: ' . (number_format(microtime(true) - $time, 2))] = array("sent" => "POST: " . $requestURL, "args" => $args, "received" => $response);
          }

          $defaultData = array(
              "APIKeyValid" => false,
              "Message" => __('API Key could not be validated due to a connectivity error.<BR>Your firewall may be blocking us. Please contact your hosting provider and ask them to allow connections from your site to api.shortpixel.com (IP 176.9.21.94).<BR> If you still cannot validate your API Key after this, please <a href="https://shortpixel.com/contact" target="_blank">contact us</a> and we will try to help. ','shortpixel-image-optimiser'),
              "APICallsMade" => __('Information unavailable. Please check your API key.','shortpixel-image-optimiser'),
              "APICallsQuota" => __('Information unavailable. Please check your API key.','shortpixel-image-optimiser'),
              "APICallsMadeOneTime" => 0,
              "APICallsQuotaOneTime" => 0,
              "APICallsMadeNumeric" => 0,
              "APICallsQuotaNumeric" => 0,
              "APICallsMadeOneTimeNumeric" => 0,
              "APICallsQuotaOneTimeNumeric" => 0,
              "APICallsRemaining" => 0,
              "APILastRenewalDate" => 0,
              "DomainCheck" => 'NOT Accessible');
          $defaultData = is_array($settings->currentStats) ? array_merge( $settings->currentStats, $defaultData) : $defaultData;

          if(is_object($response) && get_class($response) == 'WP_Error') {

              $urlElements = parse_url($requestURL);
              $portConnect = @fsockopen($urlElements['host'],8,$errno,$errstr,15);
              if(!$portConnect) {
                  $defaultData['Message'] .= "<BR>Debug info: <i>$errstr</i>";
              }
              return $defaultData;
          }

          if($response['response']['code'] != 200) {
             return $defaultData;
          }

          $data = $response['body'];
          $data = json_decode($data);

          if(empty($data)) { return $defaultData; }

          if($data->Status->Code != 2) {
              $defaultData['Message'] = $data->Status->Message;
              return $defaultData;
          }

          $dataArray = array(
              "APIKeyValid" => true,
              "APICallsMade" => number_format($data->APICallsMade) . __(' credits','shortpixel-image-optimiser'),
              "APICallsQuota" => number_format($data->APICallsQuota) . __(' credits','shortpixel-image-optimiser'),
              "APICallsMadeOneTime" => number_format($data->APICallsMadeOneTime) . __(' credits','shortpixel-image-optimiser'),
              "APICallsQuotaOneTime" => number_format($data->APICallsQuotaOneTime) . __(' credits','shortpixel-image-optimiser'),
              "APICallsMadeNumeric" => (int) max($data->APICallsMade, 0),
              "APICallsQuotaNumeric" => (int) max($data->APICallsQuota, 0),
              "APICallsMadeOneTimeNumeric" =>  (int) max($data->APICallsMadeOneTime, 0),
              "APICallsQuotaOneTimeNumeric" => (int) max($data->APICallsQuotaOneTime, 0),

              "Unlimited" => (property_exists($data, 'Unlimited') && $data->Unlimited == 'true') ? true : false,

              "APILastRenewalDate" => $data->DateSubscription,
              "DomainCheck" => (isset($data->DomainCheck) ? $data->DomainCheck : null)
          );
					 // My Eyes!  Basically :  ApiCalls - ApiCalls used, both for monthly and onetime. Max of each is 0.  Negative quota seems possible, but should not be substracted from one or the other.
					 $dataArray["APICallsRemaining"] = max($dataArray['APICallsQuotaNumeric'] - $dataArray['APICallsMadeNumeric'], 0) + max($dataArray['APICallsQuotaOneTimeNumeric'] - $dataArray['APICallsMadeOneTimeNumeric'],0);

					//reset quota exceeded flag -> user is allowed to process more images.

          if ( $dataArray['APICallsRemaining'] > 0 || $dataArray['Unlimited'])
					{
              $this->resetQuotaExceeded();
					}
          else
					{
							//activate quota limiting
              $this->setQuotaExceeded();
					}

        //  Log::addDebug('GetQuotaInformation Result ', $dataArray);
          return $dataArray;
    }

}
