<?php

namespace SPAATG\Controller;

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

use SPAATG\ShortPixelLogger\ShortPixelLogger as Log;

use SPAATG\Model\File\DirectoryModel as DirectoryModel;
use SPAATG\Model\File\FileModel as FileModel;

use SPAATG\Model\Image\MediaLibraryModel as MediaLibraryModel;
use SPAATG\Model\Image\MediaLibraryThumbnailModel as MediaLibraryThumbnailModel;

/** Controller for FileSystem operations
 *
 * This controller is used for -compound- ( complex ) FS operations, using the provided models File en Directory.
 * USE via \wpSPAATG()->filesystem();
 */
class FileSystemController extends \SPAATG\Controller
{
  protected $env;
  static $mediaItems = array();

  public function __construct()
  {
    $this->env = wpSPAATG()->env();
  }

  /** Get FileModel for a certain path. This can exist or not
   *
   * @param String Path Full Path to the file
   * @return FileModel FileModel Object. If file does not exist, not all values are set.
   */
  public function getFile($path)
  {
    return new FileModel($path);
  }

  /** Get MediaLibraryModel for a Post_id
   * @param int $id
   * @param bool $useCache  If false then it will require a fresh copt from database. Use when meta has changed / saved
   * @param bool $cacheOnly Prevent fetching from Database. Used for checkLegacy and other places where conflicts with mainFile arise.
   */
  public function getMediaImage($id, $useCache = true, $cacheOnly = false)
  {

    if (! is_numeric($id)) {
      Log::addWarn('Get Media Image called without valid ID', $id);
      return false;
    }

    if ($useCache === true && isset(self::$mediaItems[$id])) {
      return self::$mediaItems[$id];
    }

    if (true === $cacheOnly)
      return false;

    $filepath = get_attached_file($id);
    $filepath = apply_filters('spaatg_get_attached_file', $filepath, $id);

    // Somehow get_attached_file can return other random stuff.
    if ($filepath === false || strlen($filepath) == 0)
      return false;

    $imageObj = new MediaLibraryModel($id, $filepath);

    if (is_object($imageObj)) {
      self::$mediaItems[$id] = $imageObj;
    }

    return $imageObj;
  }

  // Use sporadically, every time an angel o performance dies.
  // Required for files that change i.e. enable media replace or other filesystem changing operation.
  public function flushImageCache()
  {
    self::$mediaItems = array();
    MediaLibraryModel::onFlushImageCache();
  }

  public function flushImage($imageObj)
  {
    $id = $imageObj->get('id');
    $type = $imageObj->get('type');

    if ('media' == $type && isset(self::$mediaItems[$id])) {
      unset(self::$mediaItems[$id]);
      MediaLibraryModel::onFlushImageCache();
    }
  }

  /** Generic function to get the correct image Object, to prevent many switches everywhere
   * int $id
   * string $type
   */
  public function getImage($id,  $type, $useCache = true)
  {
    $imageObj = false;

    if ($type == 'media') {
      $imageObj = $this->getMediaImage($id, $useCache);
    } else {
      Log::addError('FileSystemController GetImage - no correct type given: ' . $type);
    }
    return $imageObj;
  }


  /* wp_get_original_image_path with specific ShortPixel filter
		* @param int $id
    * @return MediaLibraryThumbnailModel;
     */
  public function getOriginalImage($id)
  {
    $filepath = \wp_get_original_image_path($id);
    $filepath = apply_filters('spaatg_get_original_image_path', $filepath, $id);
    return new MediaLibraryThumbnailModel($filepath, $id, 'original');
  }

  /** Get DirectoryModel for a certain path. This can exist or not
   *
   * @param String $path Full Path to the Directory.
   * @return DirectoryModel Object with status set on current directory.
   */
  public function getDirectory($path)
  {
    return new DirectoryModel($path);
  }

  /** Get the base folder from where custom paths are possible (from WP-base / sitebase)

   */
  public function getWPFileBase()
  {
    if (\wpSPAATG()->env()->is_mainsite) {
      $path = (string) $this->getWPAbsPath();
    } else {
      $up = wp_upload_dir();
      $path = realpath($up['basedir']);
    }
    $dir = $this->getDirectory($path);
    if (! $dir->exists())
      Log::addWarn('getWPFileBase - Base path doesnt exist');

    return $dir;
  }

  /** This function returns the WordPress Basedir for uploads ( without date and such )
   * @returns DirectoryModel
   */
  public function getWPUploadBase()
  {
    $upload_dir = wp_upload_dir(null, false);

    return $this->getDirectory($upload_dir['basedir']);
  }

  /** This function returns the Absolute Path of the WordPress installation where the **CONTENT** directory is located.
   * Normally this would be the same as ABSPATH, but there are installations out there with -cough- alternative approaches
   * The Abspath is uses to replace against the domain URL ( home_url ).
   * @returns DirectoryModel  Either the ABSPATH or where the WP_CONTENT_DIR is located
   */
  public function getWPAbsPath()
  {
    $abspath = trailingslashit(wp_normalize_path(ABSPATH));
    $content_dir = trailingslashit(wp_normalize_path(WP_CONTENT_DIR));
    $content_parent = trailingslashit(wp_normalize_path(dirname(untrailingslashit($content_dir))));

    if (0 !== strpos($content_dir, $abspath)) {
      $abspath = $content_parent;
    }

    // If constants UPLOADS is defined -AND- there is a blogs.dir in it, add it like this. UPLOAD constant alone is not enough since it can cause ugly doublures in the path if there is another style config.
    if (defined('UPLOADS') && strpos(UPLOADS, 'blogs.dir') !== false) {
      $abspath = trailingslashit(wp_normalize_path(trailingslashit(ABSPATH) . UPLOADS));
    }

    $abspath = apply_filters('spaatg/filesystem/abspath', $abspath);

    return $this->getDirectory($abspath);
  }

  public function checkStorageFolder($folder = SPAATG_STORAGE_FOLDER)
  {
    $dirObj = $this->getDirectory($folder);
    $result = $dirObj->check(true);  // check creates the whole structure if needed.
    return $result;
  }


  /** Utility function that tries to convert a file-path to a webURL.
   *
   * If possible, rely on other better methods to find URL ( e.g. via WP functions ).
   */
  public function pathToUrl(FileModel $file)
  {
    $filepath = $file->getFullPath();
    $url = null;

    // stolen from wp_get_attachment_url
    if (($uploads = wp_get_upload_dir()) && (false === $uploads['error'] || strlen(trim($uploads['error'])) == 0)) {
      $url = $this->replacePathRootWithUrl($filepath, $uploads['basedir'], $uploads['baseurl']);

      if (is_null($url) && defined('WP_CONTENT_DIR')) {
        $url = $this->replacePathRootWithUrl($filepath, WP_CONTENT_DIR, content_url());
      }

      if (is_null($url) && defined('ABSPATH')) {
        $url = $this->replacePathRootWithUrl($filepath, ABSPATH, get_site_url());
      }

      if (is_null($url)) {
        // It's a newly-uploaded file, therefore $file is relative to the basedir.
        $url = $uploads['baseurl'] . "/$filepath";
      }
    }

    if (is_null($url)) {
      return false;
    }

    $wp_home_path = (string) $this->getWPAbsPath();
    // If the whole WP homepath is still in URL, assume the replace when wrong ( not replaced w/ URL)
    // This happens when file is outside of wp_uploads_dir
    if (strpos($url, $wp_home_path) !== false) {

      // Check if content URL and dir are defined, go look there. 
      if (defined('WP_CONTENT_URL') && defined('WP_CONTENT_DIR')) {
        $relative = str_replace(WP_CONTENT_DIR, '', $filepath);
        $url = content_url($relative);
      }


      if (strpos($url, $wp_home_path) !== false) {
        // This is SITE URL, for the same reason it should be home_url in FILEMODEL. The difference is when the site is running on a subdirectory
        // (1) ** This is a fix for a real-life issue, do not change if this causes issues, another fix is needed then.
        // (2) ** Also a real life fix when a path is /wwwroot/assets/sites/2/ etc, in get site url, the home URL is the site URL, without appending the sites stuff. Fails on original image.
        if ($this->env->is_multisite && ! $this->env->is_mainsite) {
          $wp_home_path = trailingslashit($uploads['basedir']);
          $home_url = trailingslashit($uploads['baseurl']);
        } else
          $home_url = trailingslashit(get_site_url()); // (1)
        $url = str_replace($wp_home_path, $home_url, $filepath);
      }
    }

    $parsed = wp_parse_url($url); // returns array, null, or false.

    // Some hosts set the content dir to a relative path instead of a full URL. Api can't handle that, so add domain and such if this is the case.
    if (!isset($parsed['scheme'])) { //no absolute URLs used -> we implement a hack

      if (isset($parsed['host'])) // This is for URL's for // without http or https. hackhack.
      {
        $scheme = is_ssl() ? 'https:' : 'http:';
        return $scheme . $url;
      } else {
        // From Metafacade. Multiple solutions /hacks.
        $home_url = trailingslashit((function_exists("is_multisite") && is_multisite()) ? trim(network_site_url("/")) : trim(home_url()));
        return $home_url . ltrim($url, '/'); //get the file URL
      }
    }

    if (! is_null($parsed) && $parsed !== false)
      return $url;

    return false;
  }

  private function replacePathRootWithUrl($filepath, $base_dir, $base_url)
  {
    if (! is_string($filepath) || ! is_string($base_dir) || ! is_string($base_url) || $base_dir === '' || $base_url === '') {
      return null;
    }

    $filepath = wp_normalize_path($filepath);
    $base_dir = untrailingslashit(wp_normalize_path($base_dir));

    if ($filepath !== $base_dir && strpos($filepath, trailingslashit($base_dir)) !== 0) {
      return null;
    }

    $relative = ltrim(substr($filepath, strlen($base_dir)), '/');

    if ($relative === '') {
      return untrailingslashit($base_url);
    }

    return trailingslashit($base_url) . $relative;
  }

  public function checkURL($url)
  {
    if (! $this->pathIsURL($url)) {
      //$siteurl = get_option('siteurl');
      if (strpos($url, get_site_url()) == false) {
        $url = get_site_url(null, $url);
      }
    }

    return apply_filters('spaatg/filesystem/url', $url);
  }

  /** Utility function to check if a path is an URL
   *  Checks if this path looks like an URL.
   * @param $path String  Path to check
   * @return Boolean If path seems domain.
   */
  public function pathIsUrl($path)
  {
    $is_http = (substr($path, 0, 4) == 'http') ? true : false;
    $is_https = (substr($path, 0, 5) == 'https') ? true : false;
    $is_neutralscheme = (substr($path, 0, 2) == '//') ? true : false; // when URL is protocol-relative.
    $has_urldots = (strpos($path, '://') !== false) ? true : false; // Like S3 offloads

    if ($is_http || $is_https || $is_neutralscheme || $has_urldots)
      return true;
    else
      return false;
  }

  /** Sort files / directories in a certain way.
   * Future dev to include options via arg.
   */
  public function sortFiles($array, $args = array())
  {
    if (count($array) == 0)
      return $array;

    // what are we sorting.
    $class = get_class($array[0]);
    $is_files = ($class == 'SPAATG\FileModel') ? true : false; // if not files, then dirs.

    usort(
      $array,
      function ($a, $b) use ($is_files) {
        if ($is_files)
          return strcmp($a->getFileName(), $b->getFileName());
        else {
          return strcmp($a->getName(), $b->getName());
        }
      }
    );

    return $array;
  }


  /** Get all files from a directory tree, starting at given dir.
   * @param DirectoryModel $dir to recursive into
   * @param Array $filters Collection of optional filters as accepted by FileFilter in directoryModel
   * @return Array Array of FileModel Objects
   **/
  public function getFilesRecursive(DirectoryModel $dir, $filters = array())
  {
    $fileArray = array();

    if (! $dir->exists())
      return $fileArray;

    $files = $dir->getFiles($filters);
    $fileArray = array_merge($fileArray, $files);

    $subdirs = $dir->getSubDirectories();

    foreach ($subdirs as $subdir) {
      $fileArray = array_merge($fileArray, $this->getFilesRecursive($subdir, $filters));
    }

    return $fileArray;
  }

  // Url very sparingly.
	  public function url_exists($url)
	  {
	    $response = wp_remote_head(
	      $url,
	      [
	        'timeout' => 10,
	        'redirection' => 3,
	      ]
	    );

	    if ( is_wp_error( $response ) ) {
	      return false;
	    }

	    $responseCode = wp_remote_retrieve_response_code( $response );

	    if ($responseCode == 200) {
	      return true;
    } else {
      return false;
    }
  }

  /** Any files / directories loaded while this is active will not check for exists or other filesystem operations
   *
   */
  public function startTrustedMode()
  {
    if (\wpSPAATG()->env()->useTrustedMode()) {
      FileModel::$TRUSTED_MODE = true;
      DirectoryModel::$TRUSTED_MODE = true;
    }
  }

  public function endTrustedMode()
  {
    if (\wpSPAATG()->env()->useTrustedMode()) {
      FileModel::$TRUSTED_MODE = false;
      DirectoryModel::$TRUSTED_MODE = false;
    }
  }

  public function moveLogFiles($args = [])
  {
    $defaults = [
      'to_temp' => true, 
    ];

    $args = wp_parse_args($args, $defaults); 

    $tempDir = trailingslashit(sys_get_temp_dir());
    $tmpLocation = $tempDir . 'logmove'; 

    if (true === $args['to_temp'])
    {
      $sourcePath = SPAATG_STORAGE_FOLDER; 
      $targetPath = $tmpLocation;
    }
    else
    {
      $sourcePath = $tmpLocation; 
      $targetPath = SPAATG_STORAGE_FOLDER;
    }

    $logFiles = $files = glob(trailingslashit($sourcePath) . "*.log");



    if (false !== $logFiles && is_array($logFiles) && count($logFiles) > 0)
    {
        $sourceDir = $this->getDirectory($sourcePath);
        $sourceDir->check();
    
        $destinationDir = $this->getDirectory($targetPath);
        $destinationDir->check();

       foreach($logFiles as $filePath)
       {
         $file = $this->getFile($filePath); 
         $fileName = $file->getFileName(); 
         
         $targetFile = $this->getFile($destinationDir->getPath() . $fileName); 

         $bool = $file->move($targetFile); 
         
         if ( false === $bool )
         {
           Log::addWarn('FAILED moving LogFile from '  . $file->getFullPath() . ' to ' . $targetFile->getFullPath());
         }
         else
         {
          Log::addInfo('LogFile moved from '  . $file->getFullPath() . ' to ' . $targetFile->getFullPath() );
         }
       }

    } 

    if (false === $args['to_temp'])
    {
	       $this->getDirectory($tmpLocation)->delete(); //cleanup
    }


    return false; 
  }

} // class 
