<?php
/**
 * SPDX-FileCopyrightText: 2014 Robin Appelman <robin@icewind.nl>
 * SPDX-License-Identifier: MIT
 */

namespace Icewind\Streams;

/**
 * Wrapper that provides callbacks for url actions such as fopen, unlink, rename
 *
 * Usage:
 *
 * $path = UrlCallBack('/path/so/source', function(){
 *    echo 'fopen';
 * }, function(){
 *    echo 'opendir';
 * }, function(){
 *    echo 'mkdir';
 * }, function(){
 *    echo 'rename';
 * }, function(){
 *    echo 'rmdir';
 * }, function(){
 *    echo 'unlink';
 * }, function(){
 *    echo 'stat';
 * });
 *
 * mkdir($path);
 * ...
 *
 * All callbacks are called after the operation is executed on the source stream
 */
class UrlCallback extends Wrapper implements Url {

	/**
	 * @param string $source
	 * @param callable $fopen
	 * @param callable $opendir
	 * @param callable $mkdir
	 * @param callable $rename
	 * @param callable $rmdir
	 * @param callable $unlink
	 * @param callable $stat
	 * @return \Icewind\Streams\Path
	 *
	 * @throws \BadMethodCallException
	 */
	public static function wrap(
		$source,
		$fopen = null,
		$opendir = null,
		$mkdir = null,
		$rename = null,
		$rmdir = null,
		$unlink = null,
		$stat = null
	) {
		return new Path(static::class, [
			'source'  => $source,
			'fopen'   => $fopen,
			'opendir' => $opendir,
			'mkdir'   => $mkdir,
			'rename'  => $rename,
			'rmdir'   => $rmdir,
			'unlink'  => $unlink,
			'stat'    => $stat
		]);
	}

	protected function loadUrlContext($url) {
		list($protocol) = explode('://', $url);
		$options = stream_context_get_options($this->context);
		return $options[$protocol];
	}

	protected function callCallBack($context, $callback) {
		if (is_callable($context[$callback])) {
			call_user_func($context[$callback]);
		}
	}

	public function stream_open($path, $mode, $options, &$opened_path) {
		$context = $this->loadUrlContext($path);
		$this->callCallBack($context, 'fopen');
		$source = fopen($context['source'], $mode);
		if ($source === false) {
			return false;
		}
		$this->setSourceStream($source);
		return true;
	}

	public function dir_opendir($path, $options) {
		$context = $this->loadUrlContext($path);
		$this->callCallBack($context, 'opendir');
		$source = opendir($context['source']);
		if ($source === false) {
			return false;
		}
		$this->setSourceStream($source);
		return true;
	}

	public function mkdir($path, $mode, $options) {
		$context = $this->loadUrlContext($path);
		$this->callCallBack($context, 'mkdir');
		return mkdir($context['source'], $mode, ($options & STREAM_MKDIR_RECURSIVE) > 0);
	}

	public function rmdir($path, $options) {
		$context = $this->loadUrlContext($path);
		$this->callCallBack($context, 'rmdir');
		return rmdir($context['source']);
	}

	public function rename($source, $target) {
		$context = $this->loadUrlContext($source);
		$this->callCallBack($context, 'rename');
		list(, $target) = explode('://', $target);
		return rename($context['source'], $target);
	}

	public function unlink($path) {
		$context = $this->loadUrlContext($path);
		$this->callCallBack($context, 'unlink');
		return unlink($context['source']);
	}

	public function url_stat($path, $flags) {
		throw new \Exception('stat is not supported due to php bug 50526');
	}
}
