<?php

  // Namespace
  namespace BMI\Plugin\Scanner;

  // Use
  use BMI\Plugin\BMI_Logger AS Logger;
  use BMI\Plugin\Zipper\BMI_Zipper AS Zipper;

  // Exit on direct access
  if (!defined('ABSPATH')) exit;

  /**
   * Main File Scanner Logic
   */
  class BMI_FileScanner {

    public static function equalEnd($name, $nsiz, $rsiz, $rule) {

      if (substr($name, -$rsiz) == $rule) return true;

    }

    public static function equalStart($name, $nsiz, $rsiz, $rule) {

      if (substr($name, 0, $rsiz) == $rule) return true;

    }

    public static function equalAnywhere($name, $rule) {

      if (strpos($name, $rule) !== false) return true;

    }

    public static function equalFolder($name, $ignores) {

      $s = strlen($name);
      for ($i = 0; $i < sizeof($ignores); ++$i) {

        if ($ignores[$i]['z'] > $s) continue;
        if ($ignores[$i]['w'] == '1') {

          if (BMI_FileScanner::equalAnywhere($name, $ignores[$i]['s'])) return true;
          else continue;

        } elseif ($ignores[$i]['w'] == '2') {

          if (BMI_FileScanner::equalStart($name, $s, $ignores[$i]['z'], $ignores[$i]['s'])) return true;
          else continue;

        } elseif ($ignores[$i]['w'] == '3') {

          if (BMI_FileScanner::equalEnd($name, $s, $ignores[$i]['z'], $ignores[$i]['s'])) return true;
          else continue;

        }

      }

      return false;

    }

    public static function equalFolderByPath($abs, $path, $ignores) {

      $alen = strlen($abs);
      $path = substr($path, $alen + 1);
      $paths = explode(DIRECTORY_SEPARATOR, $path);

      for ($i=0; $i < sizeof($paths); ++$i) {

        $c = $paths[$i];
        if (BMI_FileScanner::equalFolder($c, $ignores)) {

          return true;

        }

      }

      return false;

    }

    public static function scanDirectory($path) {

      $files = [];
      if (!is_readable($path) || is_link($path)) return $files;
      foreach (new \DirectoryIterator($path) as $fileInfo) {

        if (!is_readable($fileInfo->getPathname())) continue;
        if ($fileInfo->isDot()) continue;
        if ($fileInfo->isLink()) continue;
        if (!$fileInfo->isDir()) {

          try {
            $files[] = $fileInfo->getFilename() . DIRECTORY_SEPARATOR . $fileInfo->getSize();
          } catch (\Exception $e) {
            Logger::debug('Failed to check file: ' . $fileInfo->getFilename());
          } catch (\Throwable $e) {
            Logger::debug('Failed to check file: ' . $fileInfo->getFilename());
          }

        } else {

          $files[] = [];
          $index = sizeof($files) - 1;
          $dirName = $fileInfo->getFilename();
          $newBase = $path . DIRECTORY_SEPARATOR . $dirName;
          $files[$index] = BMI_FileScanner::scanDirectory($newBase);
          array_unshift($files[$index], $dirName);

        }

      }

      return $files;

    }

    public static function scanDirectorySizeOnly($path, $bm) {

      $files = [];
      if (!is_readable($path) || is_link($path)) return $files;
      foreach (new \DirectoryIterator($path) as $fileInfo) {

        if (!is_readable($fileInfo->getPathname())) continue;
        if ($fileInfo->isDot()) continue;
        if ($fileInfo->isLink()) continue;
        if (!$fileInfo->isDir()) {

          try {
            $files[] = $fileInfo->getSize();
          } catch (\Exception $e) {
            Logger::debug('Failed to check file: ' . $fileInfo->getFilename());
          } catch (\Throwable $e) {
            Logger::debug('Failed to check file: ' . $fileInfo->getFilename());
          }

        } else {

          $files[] = [];
          $index = sizeof($files) - 1;
          $dirName = $fileInfo->getFilename();
          $newBase = $path . DIRECTORY_SEPARATOR . $dirName;
          if ($newBase == $bm) continue;
          $files[$index] = BMI_FileScanner::scanDirectorySizeOnly($newBase, $bm);
          array_unshift($files[$index], $dirName);

        }

      }

      return $files;

    }

    public static function scanDirectorySizeOnlyAndIgnore($path, $ignored = [], $bm = '') {

      $files = [];
      if (!is_readable($path) || is_link($path)) return $files;
      foreach (new \DirectoryIterator($path) as $fileInfo) {

        if (!is_readable($fileInfo->getPathname())) continue;
        if ($fileInfo->isDot()) continue;
        if ($fileInfo->isLink()) continue;
        if (!$fileInfo->isDir()) {

          try {
            $files[] = $fileInfo->getSize();
          } catch (\Exception $e) {
            Logger::debug('Failed to check file: ' . $fileInfo->getFilename());
          } catch (\Throwable $e) {
            Logger::debug('Failed to check file: ' . $fileInfo->getFilename());
          }

        } else {

          $dirPath = $fileInfo->getPath();
          $dirName = $fileInfo->getFilename();
          if (in_array($dirName, $ignored) || in_array($dirPath, $ignored)) {
            Logger::debug('Dodging ' . $dirName);
            continue;
          }

          $files[] = [];
          $index = sizeof($files) - 1;
          $newBase = $path . DIRECTORY_SEPARATOR . $dirName;
          if ($newBase == $bm) continue;
          $files[$index] = BMI_FileScanner::scanDirectorySizeOnlyAndIgnoreDirOnly($newBase, $ignored, $bm);
          array_unshift($files[$index], $dirName);

        }

      }

      return $files;

    }

    public static function scanDirectorySizeOnlyAndIgnoreDirOnly($path, $ignored = [], $bm = '') {

      $files = [];
      try {

        if (!is_readable($path) || is_link($path)) return $files;
        foreach (new \DirectoryIterator($path) as $fileInfo) {

          if (!is_readable($fileInfo->getPathname())) continue;
          if ($fileInfo->isDot()) continue;
          if ($fileInfo->isLink()) continue;
          if (!$fileInfo->isDir()) {

            try {
              $files[] = $fileInfo->getSize();
            } catch (\Exception $e) {
              Logger::debug('Failed to check file: ' . $fileInfo->getFilename());
            } catch (\Throwable $e) {
              Logger::debug('Failed to check file: ' . $fileInfo->getFilename());
            }

          } else {

            $dirName = $fileInfo->getFilename();
            $dirPath = $fileInfo->getPath();
            if (in_array($dirPath, $ignored)) {
              Logger::debug('Dodging ' . $dirName);
              continue;
            }

            $files[] = [];
            $index = sizeof($files) - 1;
            $newBase = $path . DIRECTORY_SEPARATOR . $dirName;
            if ($newBase == $bm) continue;
            $files[$index] = BMI_FileScanner::scanDirectorySizeOnlyAndIgnoreDirOnly($newBase, $ignored, $bm);
            array_unshift($files[$index], $dirName);

          }

        }

      } catch (\Exception $e) {
        Logger::log($e->getMessage());
        return $files;
      } catch (\Throwable $e) {
        Logger::log($e->getMessage());
        return $files;
      }

      return $files;

    }

    public static function scanDirectoryNameOnly($path) {

      $files = [];

      if (!is_readable($path) || is_link($path)) return $files;
      foreach (new \DirectoryIterator($path) as $fileInfo) {

        if (!is_readable($fileInfo->getPathname())) continue;
        if ($fileInfo->isDot()) continue;
        if ($fileInfo->isLink()) continue;
        if (!$fileInfo->isDir()) {

          if (!$fileInfo->isLink()) {
            $files[] = $fileInfo->getFilename();
          }

        } else {

          $files[] = [];
          $index = sizeof($files) - 1;
          $dirName = $fileInfo->getFilename();
          $newBase = $path . DIRECTORY_SEPARATOR . $dirName;
          $files[$index] = BMI_FileScanner::scanDirectoryNameOnly($newBase);
          array_unshift($files[$index], $dirName);

        }

      }

      return $files;

    }

    public static function scanDirectoryNameOnlyAndIgnore($path, $ignored = []) {

      $files = [];

      if (!is_readable($path) || is_link($path)) return $files;
      foreach (new \DirectoryIterator($path) as $fileInfo) {
          $fullPath = $fileInfo->getPathname();
  
          if (!is_readable($fullPath)) continue;
          if ($fileInfo->isDot()) continue;
          if ($fileInfo->isLink()) continue;
          if (in_array($fullPath, $ignored)) {
              Logger::debug('Ignoring ' . $fullPath);
              continue;
          }

          if ($fileInfo->isDir()) {
              $dirName = $fileInfo->getFilename();

              $files[] = [];
              $index = sizeof($files) - 1;
              $newBase = $fullPath;
              $files[$index] = BMI_FileScanner::scanDirectoryNameOnlyAndIgnore($newBase, $ignored);
              array_unshift($files[$index], $dirName);
          } else {
              $files[] = $fileInfo->getFilename();
          }
      }

      return $files;
  }
  

    public static function scanDirectoryNameOnlyAndIgnoreFBC($path, $ignored_folders = [], $ignored_paths = []) {

      $files = [];

      if (!is_readable($path) || is_link($path)) return $files;
      foreach (new \DirectoryIterator($path) as $fileInfo) {

        if (!is_readable($fileInfo->getPathname())) continue;
        if ($fileInfo->isDot()) continue;
        if ($fileInfo->isLink()) continue;
        if (!$fileInfo->isDir()) {

          $files[] = $fileInfo->getFilename() . ',' . $fileInfo->getSize();

        } else {

          $dirName = $fileInfo->getFilename();
          if (BMI_FileScanner::equalFolder($dirName, $ignored_folders)) {
            Logger::debug('Dodging folder ' . $dirName);
            continue;
          }

          $files[] = [];
          $index = sizeof($files) - 1;
          $newBase = $path . DIRECTORY_SEPARATOR . $dirName;
          if (in_array($newBase, $ignored_paths)) {
            Logger::debug('Dodging path ' . $newBase);
            continue;
          }

          $files[$index] = BMI_FileScanner::scanDirectoryNameOnlyAndIgnoreFBC($newBase, $ignored_folders, $ignored_paths);
          array_unshift($files[$index], $dirName);

        }

      }

      return $files;

    }

    public static function getSizeOfFileList($fileList) {

      $size = 0;
      for ($i = 0; $i < sizeOf($fileList); $i++) {

        if ($i == 0) continue;
        if (!is_array($fileList[$i])) {

          $size += intval($fileList[$i]);

        } else {

          $size += BMI_FileScanner::getSizeOfFileList($fileList[$i]);

        }

      }
      return $size;

    }

    public static function getFileFullPaths($base, $fileList, $setSize = true) {

      $paths = []; $merge = [];
      for ($i = 0; $i < sizeOf($fileList); $i++) {

        if ($i == 0) {

          $dir = $base . DIRECTORY_SEPARATOR . $fileList[$i];
          $base = $dir;
          if (is_readable($dir)) {
            if (sizeof(scandir($dir)) <= 2) {
              $paths[] = $dir . (!$setSize ? '' : ', 0');
            }
          }
          continue;

        }

        if (!is_array($fileList[$i])) {

          $paths[] = $base . DIRECTORY_SEPARATOR . $fileList[$i];

        } else {

          $merge[] = BMI_FileScanner::getFileFullPaths($base, $fileList[$i], $setSize);

        }

      }

      $paths = array_merge($paths, ...$merge);
      return $paths;

    }

    public static function scanFiles($path, $bm) {

      // Get TOP Dir name
      $z = explode(DIRECTORY_SEPARATOR, $path);
      $z = $z[sizeof($z) - 1];

      // Scan Directory
      $x = BMI_FileScanner::scanDirectorySizeOnly($path, $bm);

      // Push TOP Lever (root) Directory
      array_unshift($x, $z);

      // Calculate size
      $y = BMI_FileScanner::getSizeOfFileList($x);

      // Return size in Bytes
      return $y;

    }

    public static function scanFilesGetNamesWithIgnore($path, $ignored = []) {
      // Normalize ignored paths to absolute paths
      $ignored = array_map('realpath', $ignored);

      // Require Zipper
      require_once BMI_INCLUDES . '/zipper/zipping.php';

      // Get TOP Dir name
      $z = explode(DIRECTORY_SEPARATOR, $path);
      $z = $z[sizeof($z) - 1];

      // Scan Directory
      $x = BMI_FileScanner::scanDirectoryNameOnlyAndIgnore($path, $ignored);

      // Push TOP Lever (root) Directory
      array_unshift($x, $z);

      // Parse Output to Array of Full Paths
      $path = substr($path, 0, -(strlen($z) + 1));
      $y = BMI_FileScanner::getFileFullPaths($path, $x, false);

      // Return array of Full Paths
      return $y;

    }

    public static function scanFilesGetNamesWithIgnoreFBC($path, $ignored = [], $ignored_paths = []) {

      // Require Zipper
      require_once BMI_INCLUDES . '/zipper/zipping.php';

      // Exclude paths which won't exist in current $path
      $max = sizeof($ignored_paths);
      for ($i = 0; $i < $max; ++$i) {

        if (strpos($ignored_paths[$i], $path) === false) {
          array_splice($ignored_paths, $i, 1);
          $max--; $i--;
        }

      }

      // Get TOP Dir name
      $z = explode(DIRECTORY_SEPARATOR, $path);
      $z = $z[sizeof($z) - 1];

      // Scan Directory
      $x = BMI_FileScanner::scanDirectoryNameOnlyAndIgnoreFBC($path, $ignored, $ignored_paths);

      // Push TOP Lever (root) Directory
      array_unshift($x, $z);

      // Parse Output to Array of Full Paths
      $path = substr($path, 0, -(strlen($z) + 1));
      $y = BMI_FileScanner::getFileFullPaths($path, $x);

      // Return array of Full Paths
      return $y;

    }

    public static function scanFilesWithIgnore($path, $ignored, $bm) {

      // Get TOP Dir name
      $z = explode(DIRECTORY_SEPARATOR, $path);
      $z = $z[sizeof($z) - 1];

      // Scan Directory
      $x = BMI_FileScanner::scanDirectorySizeOnlyAndIgnore($path, $ignored, $bm);

      // Push TOP Lever (root) Directory
      array_unshift($x, $z);

      // Calculate size
      $y = BMI_FileScanner::getSizeOfFileList($x);

      // Return size in Bytes
      return $y;

    }

    public static function fileTooLarge($size, $max) {

      if ($size > $max) return true;
      else return false;

    }

  }
