<?php

namespace League\Flysystem;

use League\Flysystem\Util\MimeType;
use LogicException;

class Util
{
	/**
	 * Get normalized pathinfo.
	 *
	 * @param string $path
	 *
	 * @return array pathinfo
	 */
	public static function pathinfo($path)
	{
		$pathinfo = compact('path');

		if ('' !== $dirname = dirname($path))
		{
			$pathinfo['dirname'] = static::normalizeDirname($dirname);
		}

		$pathinfo['basename'] = static::basename($path);

		$pathinfo += pathinfo($pathinfo['basename']);

		return $pathinfo + ['dirname' => ''];
	}

	/**
	 * Normalize a dirname return value.
	 *
	 * @param string $dirname
	 *
	 * @return string normalized dirname
	 */
	public static function normalizeDirname($dirname)
	{
		return $dirname === '.' ? '' : $dirname;
	}

	/**
	 * Get a normalized dirname from a path.
	 *
	 * @param string $path
	 *
	 * @return string dirname
	 */
	public static function dirname($path)
	{
		return static::normalizeDirname(dirname($path));
	}

	/**
	 * Map result arrays.
	 *
	 * @param array $object
	 * @param array $map
	 *
	 * @return array mapped result
	 */
	public static function map(array $object, array $map)
	{
		$result = [];

		foreach ($map AS $from => $to)
		{
			if (! isset($object[$from]))
			{
				continue;
			}

			$result[$to] = $object[$from];
		}

		return $result;
	}

	/**
	 * Normalize path.
	 *
	 * @param string $path
	 *
	 * @throws \LogicException
	 *
	 * @return string
	 */
	public static function normalizePath($path)
	{
		return static::normalizeRelativePath($path);
	}

	/**
	 * Normalize relative directories in a path.
	 *
	 * @param string $path
	 *
	 * @throws \LogicException
	 *
	 * @return string
	 */
	public static function normalizeRelativePath($path)
	{
		$path = str_replace('\\', '/', $path);
		$path =  static::removeFunkyWhiteSpace($path);
		$parts = [];

		foreach (explode('/', $path) AS $part)
		{
			switch ($part)
			{
				case '':
				case '.':
					break;

				case '..':
					if (empty($parts))
					{
						throw new \LogicException(
							'Path is outside of the defined root, path: [' . $path . ']'
						);
					}
					array_pop($parts);
					break;

				default:
					$parts[] = $part;
					break;
			}
		}

		$path = implode('/', $parts);

		return $path;
	}

	/**
	 * Rejects unprintable characters and invalid unicode characters.
	 *
	 * @param string $path
	 *
	 * @return string $path
	 */
	protected static function removeFunkyWhiteSpace($path)
	{
		if (preg_match('#\p{C}+#u', $path))
		{
			throw CorruptedPathDetected::forPath($path);
		}

		return $path;
	}

	/**
	 * Normalize prefix.
	 *
	 * @param string $prefix
	 * @param string $separator
	 *
	 * @return string normalized path
	 */
	public static function normalizePrefix($prefix, $separator)
	{
		return rtrim($prefix, $separator) . $separator;
	}

	/**
	 * Get content size.
	 *
	 * @param string $contents
	 *
	 * @return int content size
	 */
	public static function contentSize($contents)
	{
		return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents);
	}

	/**
	 * Guess MIME Type based on the path of the file and it's content.
	 *
	 * @param string          $path
	 * @param string|resource $content
	 *
	 * @return string|null MIME Type or NULL if no extension detected
	 */
	public static function guessMimeType($path, $content)
	{
		$mimeType = MimeType::detectByContent($content);

		if (! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm'])))
		{
			return $mimeType;
		}

		return MimeType::detectByFilename($path);
	}

	/**
	 * Emulate directories.
	 *
	 * @param array $listing
	 *
	 * @return array listing with emulated directories
	 */
	public static function emulateDirectories(array $listing)
	{
		$directories = [];
		$listedDirectories = [];

		foreach ($listing AS $object)
		{
			[$directories, $listedDirectories] = static::emulateObjectDirectories($object, $directories, $listedDirectories);
		}

		$directories = array_diff(array_unique($directories), array_unique($listedDirectories));

		foreach ($directories AS $directory)
		{
			$listing[] = static::pathinfo($directory) + ['type' => 'dir'];
		}

		return $listing;
	}

	/**
	 * Ensure a Config instance.
	 *
	 * @param null|array|Config $config
	 *
	 * @return Config config instance
	 *
	 * @throw  LogicException
	 */
	public static function ensureConfig($config)
	{
		if ($config === null)
		{
			return new Config();
		}

		if ($config instanceof Config)
		{
			return $config;
		}

		if (is_array($config))
		{
			return new Config($config);
		}

		throw new \LogicException('A config should either be an array or a Flysystem\Config object.');
	}

	/**
	 * Rewind a stream.
	 *
	 * @param resource $resource
	 */
	public static function rewindStream($resource)
	{
		if (ftell($resource) !== 0 && static::isSeekableStream($resource))
		{
			rewind($resource);
		}
	}

	public static function isSeekableStream($resource)
	{
		$metadata = stream_get_meta_data($resource);

		return $metadata['seekable'];
	}

	/**
	 * Get the size of a stream.
	 *
	 * @param resource $resource
	 *
	 * @return int|null stream size
	 */
	public static function getStreamSize($resource)
	{
		$stat = fstat($resource);

		if (! is_array($stat) || ! isset($stat['size']))
		{
			return null;
		}

		return $stat['size'];
	}

	/**
	 * Emulate the directories of a single object.
	 *
	 * @param array $object
	 * @param array $directories
	 * @param array $listedDirectories
	 *
	 * @return array
	 */
	protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories)
	{
		if ($object['type'] === 'dir')
		{
			$listedDirectories[] = $object['path'];
		}

		if (! isset($object['dirname']) || trim($object['dirname']) === '')
		{
			return [$directories, $listedDirectories];
		}

		$parent = $object['dirname'];

		while (isset($parent) && trim($parent) !== '' && ! in_array($parent, $directories))
		{
			$directories[] = $parent;
			$parent = static::dirname($parent);
		}

		if (isset($object['type']) && $object['type'] === 'dir')
		{
			$listedDirectories[] = $object['path'];

			return [$directories, $listedDirectories];
		}

		return [$directories, $listedDirectories];
	}

	/**
	 * Returns the trailing name component of the path.
	 *
	 * @param string $path
	 *
	 * @return string
	 */
	private static function basename($path)
	{
		$separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/';

		$path = rtrim($path, $separators);

		$basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path);

		if (DIRECTORY_SEPARATOR === '/')
		{
			return $basename;
		}
		// @codeCoverageIgnoreStart
		// Extra Windows path munging. This is tested via AppVeyor, but code
		// coverage is not reported.

		// Handle relative paths with drive letters. c:file.txt.
		while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename))
		{
			$basename = substr($basename, 2);
		}

		// Remove colon for standalone drive letter names.
		if (preg_match('#^[a-zA-Z]{1}:$#', $basename))
		{
			$basename = rtrim($basename, ':');
		}

		return $basename;
		// @codeCoverageIgnoreEnd
	}
}
