<?php

/**
 * Author: Hoang Ngo
 */
declare( strict_types=1 );

namespace LearnDash\Hub\Framework;

class File {
	/**
	 * Engine use to create a dir tree
	 *
	 * @var string
	 */
	public $engine = '';
	/**
	 * Absolute path to a folder need to create a dir tre
	 *
	 * @var string
	 */
	public $path = '';

	/**
	 * Is the result including file?
	 *
	 * @var bool
	 */
	public $include_file = true;
	/**
	 * Is the result include dir
	 *
	 * @var bool
	 */
	public $include_dir = true;

	/**
	 * @var bool
	 */
	public $include_hidden = false;

	/**
	 * This is where to define the rules for exclude files out of the result
	 *
	 * 'ext'=>array('jpg','gif') file extension you don't want appear in the result
	 * 'path'=>array('/tmp/file1.txt','/tmp/file2') absolute path to files
	 * 'dir'=>array('/tmp/','/dir/') absolute path to the directory you dont want to include files
	 * 'filename'=>array('abc*') file name you don't want to include, can be regex,
	 *
	 * @var array
	 */
	public $exclude = array();

	/**
	 * This is where to define the rules for include files, please note that if $include is provided, the $exclude
	 * will get ignored
	 *
	 * 'ext'=>array('jpg','gif') file extension you don't want appear in the result
	 * 'path'=>array('/tmp/file1.txt','/tmp/file2') absolute path to files
	 * 'dir'=>array('/tmp/','/dir/') absolute path to the directory you dont want to include files
	 * 'filename'=>array('abc*') file name you don't want to include, can be regex,
	 *
	 * @var array
	 */
	public $include = array();

	/**
	 * Does this search recursive
	 *
	 * @var bool
	 */
	public $is_recursive = true;

	/**
	 * if provided, only search file smaller than this
	 *
	 * @var int
	 */
	public $max_filesize = 0;

	/**
	 * @param            $path
	 * @param bool|true  $include_file
	 * @param bool|false $include_dir
	 * @param array      $include
	 * @param array      $exclude
	 * @param bool|true  $is_recursive
	 * @param int        $max_filesize
	 */
	public function __construct(
		$path,
		bool $include_file = true,
		bool $include_dir = false,
		array $include = array(),
		array $exclude = array(),
		bool $is_recursive = true,
		$include_hidden = false,
		int $max_filesize = 0
	) {
		$this->path           = $path;
		$this->include_file   = $include_file;
		$this->include_dir    = $include_dir;
		$this->include        = $include;
		$this->exclude        = $exclude;
		$this->is_recursive   = $is_recursive;
		$this->include_hidden = $include_hidden;
		$this->max_filesize   = $max_filesize;
	}

	/**
	 * @return array
	 */
	public function get_dir_tree(): array {
		$result = array();
		if ( ! is_dir( $this->path ) ) {
			return $result;
		}

		return $this->get_dir_tree_by_scandir();
	}

	/**
	 * @param null $path
	 *
	 * @return array
	 */
	private function get_dir_tree_by_scandir( string $path = null ): array {
		if ( is_null( $path ) ) {
			$path = $this->path;
		}
		$path   = rtrim( $path, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR;
		$rfiles = scandir( $path );
		$data   = array();

		foreach ( $rfiles as $rfile ) {
			if ( $rfile === '.' || $rfile === '..' ) {
				continue;
			}
			if ( substr( pathinfo( $rfile, PATHINFO_BASENAME ), 0, 1 ) === '.'
			     && false === $this->include_hidden ) {
				// hidden files, move on.
				continue;
			}

			$real_path = $path . $rfile;

			$type = filetype( $real_path );

			if ( ( ! empty( $this->include ) || ! empty( $this->exclude ) ) && ( false === $this->filter_directory(
						$real_path,
						$type
					) ) ) {
				continue;
			}

			if ( 'file' === $type && true === $this->include_file ) {
				if ( is_numeric( $this->max_filesize ) && 0 < $this->max_filesize ) {
					$max_size = $this->max_filesize * ( pow( 1024, 2 ) );
					if ( filesize( $real_path ) > $max_size ) {
						continue;
					} else {
						$data[] = $real_path;
					}
				} else {
					$data[] = $real_path;
				}
			}

			if ( 'dir' === $type ) {
				if ( $this->include_dir ) {
					$data[] = $real_path;
				}
				if ( $this->is_recursive ) {
					$tdata = $this->get_dir_tree_by_scandir( $real_path );
					$data  = array_merge( $data, $tdata );
				}
			}
		}

		return $data;
	}

	/**
	 * Filter for recursive directory tree
	 *
	 * @param      $current
	 * @param null $filetype
	 *
	 * @return bool
	 */
	public function filter_directory( $current, $filetype = null ): bool {
		if ( ! empty( $this->include ) ) {
			return $this->_filter_include( $current, $filetype );
		} elseif ( ! empty( $this->exclude ) ) {
			return $this->_filter_exclude( $current, $filetype );
		}

		return true;
	}

	/**
	 * @param      $path
	 * @param null $filetype
	 *
	 * @return bool
	 */
	private function _filter_include( $path, $filetype = null ): bool {
		$include     = $this->include;
		$exclude     = $this->exclude;
		$applied     = 0;
		$dir_include = isset( $include['dir'] ) ? $include['dir'] : array();
		$dir_exclude = isset( $exclude['dir'] ) ? $exclude['dir'] : array();

		if ( ! is_null( $filetype ) ) {
			$type = $filetype;
		} else {
			$type = filetype( $path );
		}

		if ( is_array( $dir_include ) && count( $dir_include ) ) {
			if ( is_array( $dir_exclude ) ) {
				foreach ( $dir_exclude as $dir ) {
					if ( strpos( $path, $dir ) === 0 ) {
						// this mean, exlucde matched, we wont list this
						// move to next loop
						continue;
					}
				}
			}

			foreach ( $dir_include as $dir ) {
				if ( strpos( $path, $dir ) === 0 ) {
					return true;
				}
			}
			$applied ++;
		}

		// next extension
		$ext_include = isset( $include['ext'] ) ? $include['ext'] : array();

		if ( is_array( $ext_include ) && count( $ext_include ) && $type == 'file' ) {
			// we will uses foreach and strcasecmp instead of regex cause it faster
			foreach ( $ext_include as $ext ) {
				if ( strcasecmp( pathinfo( $path, PATHINFO_EXTENSION ), $ext ) === 0 ) {
					// match
					return true;
				}
			}
			$applied ++;
		}

		// now filename
		$filename_include = isset( $include['filename'] ) ? $include['filename'] : array();
		if ( is_array( $filename_include ) && count( $filename_include ) && $type == 'file' ) {
			foreach ( $filename_include as $filename ) {
				if ( preg_match( '/' . $filename . '/', pathinfo( $path, PATHINFO_BASENAME ) ) ) {
					return true;
				}
			}
			$applied ++;
		}

		// now abs path
		$path_include = isset( $include['path'] ) ? $include['path'] : array();
		if ( is_array( $path_include ) && count( $path_include ) && $type == 'file' ) {
			foreach ( $path_include as $p ) {
				if ( strcmp( $p, $path ) === 0 ) {
					return true;
				}
			}
			$applied ++;
		}

		if ( 0 === $applied ) {
			return true;
		}

		return false;
	}

	/**
	 * Run the filter for a file/dir
	 *
	 * @param      $path
	 * @param null $filetype
	 *
	 * @return bool
	 */
	private function _filter_exclude( $path, $filetype = null ): bool {
		$exclude = $this->exclude;
		// first filer dir, or file inside dir
		if ( ! is_null( $filetype ) ) {
			$type = $filetype;
		} else {
			$type = filetype( $path );
		}
		$dir_exclude = isset( $exclude['dir'] ) ? $exclude['dir'] : array();
		if ( is_array( $dir_exclude ) && count( $dir_exclude ) ) {
			foreach ( $dir_exclude as $dir ) {
				if ( strpos( $path, $dir ) === 0 ) {
					return false;
				}
			}
		}

		// next extension
		$ext_exclude = isset( $exclude['ext'] ) ? $exclude['ext'] : array();
		if ( is_array( $ext_exclude ) && count( $ext_exclude ) && $type == 'file' ) {
			// we will uses foreach and strcasecmp instead of regex cause it faster
			foreach ( $ext_exclude as $ext ) {
				if ( strcasecmp( pathinfo( $path, PATHINFO_EXTENSION ), $ext ) === 0 ) {
					// match
					return false;
				}
			}
		}
		// now filename
		$filename_exclude = isset( $exclude['filename'] ) ? $exclude['filename'] : array();
		if ( is_array( $filename_exclude ) && count( $filename_exclude ) && $type == 'file' ) {
			foreach ( $filename_exclude as $filename ) {
				if ( preg_match( '/' . $filename . '/', pathinfo( $path, PATHINFO_BASENAME ) ) ) {
					return false;
				}
			}
		}

		// now abs path
		$path_exclude = isset( $exclude['path'] ) ? $exclude['path'] : array();
		if ( is_array( $path_exclude ) && count( $path_exclude ) && $type == 'file' ) {
			foreach ( $path_exclude as $p ) {
				if ( strcmp( $p, $path ) === 0 ) {
					return false;
				}
			}
		}

		return true;
	}
}
