<?php namespace Intellex\Filesystem;

use Intellex\Filesystem\Exception\InvalidArgumentException;
use Intellex\Filesystem\Exception\PathExistsException;
use Intellex\Filesystem\Exception\PathNotWritableException;

/**
 * Class Dir
 * Represents a directory on the filesystem.
 *
 * @package Intellex\Filesystem
 */
class Dir extends Path {

	/**
	 * Check if this directory is filesystem root.
	 *
	 * @return bool True if this directory is the filesystem root.
	 */
	public function isRoot() {

		// Handle both Windows or Unix based
		return stristr(PHP_OS, 'WIN')
			? preg_match('~^[A-Z]:\~', $this->getPath())
			: $this->getPath() === static::DS;
	}

	/**
	 * Get the paths in this directory.
	 *
	 * @return Path[] The found paths.
	 */
	public function listDirectory() {
		$paths = glob($this->getPath() . static::DS . '*');
		foreach ($paths as $i => $path) {
			$paths[$i] = is_file($path)
				? new File($path)
				: new Dir($path);
		}

		return $paths;
	}

	/** @inheritdoc */
	public function exists() {
		$this->typeMatches();
		return is_dir($this->getPath());
	}

	/** @inheritdoc */
	public function isWritable() {
		$this->typeMatches();

		// Check all
		$dir = $this;
		while (true) {

			// Check the permissions on the first existing directory
			if ($dir->exists()) {
				return is_writable($dir->getPath());
			}

			// Filesystem root, no need to further
			if ($dir->isRoot()) {
				break;
			}

			$dir = $dir->getParent();
		}
		return false;
	}

	/** @inheritdoc */
	public function touch() {
		if (!$this->exists()) {
			$success = mkdir($this->getPath(), 0775, true);
			if (!$success) {
				new PathNotWritableException($this->getPath());
			}
		}
	}

	/** @inheritdoc */
	public function delete() {
		$this->clear();
		rmdir($this->getPath());
	}

	/**
	 * Clears the directory content, but not the directory itself.
	 *
	 * @param string[] $exclude The array of regular expressions to match files or dirs to skip.
	 */
	public function clear($exclude = []) {
		$paths = $this->listDirectory();
		foreach ($paths as $path) {

			// Exclude
			foreach ($exclude as $regexp) {
				if (preg_match($regexp, $path->getName())) {
					break;
				}
			}

			$path->delete();
		}
	}

	/**
	 * Move to a directory.
	 *
	 * @param Path $path      The path to write to the directory.
	 * @param bool $overwrite True to overwrite the existing file, false to throw exception.
	 *
	 * @return Path Itself, for chaining purposes.
	 * @throws InvalidArgumentException When something other than File or Dir is supplied.
	 * @throws PathExistsException If the destination exists and we do not want to overwrite it.
	 * @throws PathNotWritableException If the directory is not writable.
	 */
	public function write(Path &$path, $overwrite = true) {

		// Validate that it is writable
		if (!$this->isWritable()) {
			throw new PathNotWritableException($this);
		}

		// Files
		if ($path instanceof File) {
			$this->touch();

			// Overwrite or not
			$destination = $this->getPath() . static::DS . $path->getName();
			if (!$overwrite && file_exists($destination)) {
				throw new PathExistsException($destination);
			}

			// Execute copy and reinitialize the path
			copy($path->getPath(), $this->getPath() . static::DS . $path->getName());
			$path->init($destination);

			return $this;
		}

		// Directories
		if ($path instanceof Dir) {
			// TODO

			return $this;
		}

		// Error, if reached
		throw new InvalidArgumentException("Only File and Dir objects can to written to directory.");
	}
}
