<?php
/**
 * Helper
 *
 * @author        Webcraftic <wordpress.webraftic@gmail.com>
 * @copyright (c) 2018 Webraftic Ltd
 * @version       1.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class WMAC_PluginHelper
 */
class WMAC_PluginHelper {

	/**
	 * Returns true when mbstring is available.
	 *
	 * @param bool|null $override Allows overriding the decision.
	 *
	 * @return bool
	 */
	public static function mbstringAvailable( $override = null ) {
		static $available = null;

		if ( null === $available ) {
			$available = \extension_loaded( 'mbstring' );
		}

		if ( null !== $override ) {
			$available = $override;
		}

		return $available;
	}

	/**
	 * Multibyte-capable strpos() if support is available on the server.
	 * If not, it falls back to using \strpos().
	 *
	 * @param string      $haystack Haystack.
	 * @param string      $needle   Needle.
	 * @param int         $offset   Offset.
	 * @param string|null $encoding Encoding. Default null.
	 *
	 * @return int|false
	 */
	public static function strpos( $haystack, $needle, $offset = 0, $encoding = null ) {
		if ( self::mbstringAvailable() ) {
			return ( null === $encoding ) ? \mb_strpos( $haystack, $needle, $offset ) : \mb_strlen( $haystack, $needle, $offset, $encoding );
		} else {
			return \strpos( $haystack, $needle, $offset );
		}
	}

	/**
	 * Attempts to return the number of characters in the given $string if
	 * mbstring is available. Returns the number of bytes
	 * (instead of characters) as fallback.
	 *
	 * @param string      $string   String.
	 * @param string|null $encoding Encoding.
	 *
	 * @return int Number of charcters or bytes in given $string
	 *             (characters if/when supported, bytes otherwise).
	 */
	public static function strlen( $string, $encoding = null ) {
		if ( self::mbstringAvailable() ) {
			return ( null === $encoding ) ? \mb_strlen( $string ) : \mb_strlen( $string, $encoding );
		} else {
			return \strlen( $string );
		}
	}

	/**
	 * Our wrapper around implementations of \substr_replace()
	 * that attempts to not break things horribly if at all possible.
	 * Uses mbstring if available, before falling back to regular
	 * substr_replace() (which works just fine in the majority of cases).
	 *
	 * @param string      $string      String.
	 * @param string      $replacement Replacement.
	 * @param int         $start       Start offset.
	 * @param int|null    $length      Length.
	 * @param string|null $encoding    Encoding.
	 *
	 * @return string
	 */
	public static function substrReplace( $string, $replacement, $start, $length = null, $encoding = null ) {
		if ( self::mbstringAvailable() ) {
			$strlen = self::strlen( $string, $encoding );

			if ( $start < 0 ) {
				if ( - $start < $strlen ) {
					$start = $strlen + $start;
				} else {
					$start = 0;
				}
			} else if ( $start > $strlen ) {
				$start = $strlen;
			}

			if ( null === $length || '' === $length ) {
				$start2 = $strlen;
			} else if ( $length < 0 ) {
				$start2 = $strlen + $length;
				if ( $start2 < $start ) {
					$start2 = $start;
				}
			} else {
				$start2 = $start + $length;
			}

			if ( null === $encoding ) {
				$leader  = $start ? \mb_substr( $string, 0, $start ) : '';
				$trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null ) : '';
			} else {
				$leader  = $start ? \mb_substr( $string, 0, $start, $encoding ) : '';
				$trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null, $encoding ) : '';
			}

			return "{$leader}{$replacement}{$trailer}";
		}

		return ( null === $length ) ? \substr_replace( $string, $replacement, $start ) : \substr_replace( $string, $replacement, $start, $length );
	}

	/**
	 * Decides whether this is a "subdirectory site" or not.
	 *
	 * @param bool $override Allows overriding the decision when needed.
	 *
	 * @return bool
	 */
	public static function siteurlNotRoot( $override = null ) {
		static $subdir = null;

		if ( null === $subdir ) {
			$parts  = self::getMacWpSiteUrlParts();
			$subdir = ( isset( $parts['path'] ) && ( '/' !== $parts['path'] ) );
		}

		if ( null !== $override ) {
			$subdir = $override;
		}

		return $subdir;
	}

	/**
	 * Parse site URL into components using \parse_url(), but do
	 * so only once per request/lifecycle.
	 *
	 * @return array
	 */
	public static function getMacWpSiteUrlParts() {
		static $parts = [];

		if ( empty( $parts ) ) {
			$parts = \parse_url( WMAC_PluginMain::getSiteUrl() );
		}

		return $parts;
	}

	/**
	 * Given an array or components returned from \parse_url(), assembles back
	 * the complete URL.
	 * If optional
	 *
	 * @param array $parsed_url   URL components array.
	 * @param bool  $schemeless   Whether the assembled URL should be
	 *                            protocol-relative (schemeless) or not.
	 *
	 * @return string
	 */
	public static function assembleParsedUrl( array $parsed_url, $schemeless = false ) {
		$scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : '';
		if ( $schemeless ) {
			$scheme = '//';
		}
		$host     = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
		$port     = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
		$user     = isset( $parsed_url['user'] ) ? $parsed_url['user'] : '';
		$pass     = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : '';
		$pass     = ( $user || $pass ) ? "$pass@" : '';
		$path     = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
		$query    = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
		$fragment = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : '';

		return "$scheme$user$pass$host$port$path$query$fragment";
	}

	/**
	 * Returns true if given $url is protocol-relative.
	 *
	 * @param string $url URL to check.
	 *
	 * @return bool
	 */
	public static function isProtocolRelative( $url ) {
		return ( '/' === $url[1] ); // second char is `/`.
	}

	/**
	 * Canonicalizes the given path regardless of it existing or not.
	 *
	 * @param string $path Path to normalize.
	 *
	 * @return string
	 */
	public static function pathCanonicalize( $path ) {
		$patterns     = [
			'~/{2,}~',
			'~/(\./)+~',
			'~([^/\.]+/(?R)*\.{2,}/)~',
			'~\.\./~',
		];
		$replacements = [
			'/',
			'/',
			'',
			'',
		];

		return preg_replace( $patterns, $replacements, $path );
	}

	/**
	 * @param     $bytes
	 * @param int $decimals
	 *
	 * @return string
	 */
	public static function format_filesize( $bytes, $decimals = 2 ) {
		$units = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ];

		for ( $i = 0; ( $bytes / 1024 ) > 0.9; $i ++, $bytes /= 1024 ) {
		} // @codingStandardsIgnoreLine

		return sprintf( "%1.{$decimals}f %s", round( $bytes, $decimals ), $units[ $i ] );
	}
}
