<?php

namespace Stripe\StripeTaxForWooCommerce\SDK\lib\Util;

use Stripe\StripeTaxForWooCommerce\SDK\lib\StripeObject;

abstract class Util {

	private static $isMbstringAvailable   = null;
	private static $isHashEqualsAvailable = null;

	/**
	 * Whether the provided array (or other) is a list rather than a dictionary.
	 * A list is defined as an array for which all the keys are consecutive
	 * integers starting at 0. Empty arrays are considered to be lists.
	 *
	 * @param array|mixed $array
	 *
	 * @return bool true if the given object is a list
	 */
	public static function isList( $array ) {
		if ( ! \is_array( $array ) ) {
			return false;
		}
		if ( array() === $array ) {
			return true;
		}
		if ( \array_keys( $array ) !== \range( 0, \count( $array ) - 1 ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Converts a response from the Stripe\StripeTaxForWooCommerce\SDK\lib API to the corresponding PHP object.
	 *
	 * @param array $resp the response from the Stripe\StripeTaxForWooCommerce\SDK\lib API
	 * @param array $opts
	 *
	 * @return array|StripeObject
	 */
	public static function convertToStripeObject( $resp, $opts ) {
		$types = \Stripe\StripeTaxForWooCommerce\SDK\lib\Util\ObjectTypes::mapping;
		if ( self::isList( $resp ) ) {
			$mapped = array();
			foreach ( $resp as $i ) {
				$mapped[] = self::convertToStripeObject( $i, $opts );
			}

			return $mapped;
		}
		if ( \is_array( $resp ) ) {
			if ( isset( $resp['object'] ) && \is_string( $resp['object'] ) && isset( $types[ $resp['object'] ] ) ) {
				$class = $types[ $resp['object'] ];
			} else {
				$class = \Stripe\StripeTaxForWooCommerce\SDK\lib\StripeObject::class;
			}

			return $class::constructFrom( $resp, $opts );
		}

		return $resp;
	}

	/**
	 * @param mixed|string $value a string to UTF8-encode
	 *
	 * @return mixed|string the UTF8-encoded string, or the object passed in if
	 *    it wasn't a string
	 */
	public static function utf8( $value ) {
		if ( null === self::$isMbstringAvailable ) {
			self::$isMbstringAvailable = \function_exists( 'mb_detect_encoding' ) && \function_exists( 'mb_convert_encoding' );

			if ( ! self::$isMbstringAvailable ) {
				\trigger_error(
					'It looks like the mbstring extension is not enabled. ' .
					'UTF-8 strings will not properly be encoded. Ask your system ' .
					'administrator to enable the mbstring extension, or write to ' .
					'support@stripe.com if you have any questions.',
					\E_USER_WARNING
				);
			}
		}

		if ( \is_string( $value ) && self::$isMbstringAvailable && 'UTF-8' !== \mb_detect_encoding( $value, 'UTF-8', true ) ) {
			return mb_convert_encoding( $value, 'UTF-8', 'ISO-8859-1' );
		}

		return $value;
	}

	/**
	 * Compares two strings for equality. The time taken is independent of the
	 * number of characters that match.
	 *
	 * @param string $a one of the strings to compare
	 * @param string $b the other string to compare
	 *
	 * @return bool true if the strings are equal, false otherwise
	 */
	public static function secureCompare( $a, $b ) {
		if ( null === self::$isHashEqualsAvailable ) {
			self::$isHashEqualsAvailable = \function_exists( 'hash_equals' );
		}

		if ( self::$isHashEqualsAvailable ) {
			return \hash_equals( $a, $b );
		}
		if ( \strlen( $a ) !== \strlen( $b ) ) {
			return false;
		}

		$result = 0;
		for ( $i = 0; $i < \strlen( $a ); ++$i ) {
			$result |= \ord( $a[ $i ] ) ^ \ord( $b[ $i ] );
		}

		return 0 === $result;
	}

	/**
	 * Recursively goes through an array of parameters. If a parameter is an instance of
	 * ApiResource, then it is replaced by the resource's ID.
	 * Also clears out null values.
	 *
	 * @param mixed $h
	 *
	 * @return mixed
	 */
	public static function objectsToIds( $h ) {
		if ( $h instanceof \Stripe\StripeTaxForWooCommerce\SDK\lib\ApiResource ) {
			return $h->id;
		}
		if ( static::isList( $h ) ) {
			$results = array();
			foreach ( $h as $v ) {
				$results[] = static::objectsToIds( $v );
			}

			return $results;
		}
		if ( \is_array( $h ) ) {
			$results = array();
			foreach ( $h as $k => $v ) {
				if ( null === $v ) {
					continue;
				}
				$results[ $k ] = static::objectsToIds( $v );
			}

			return $results;
		}

		return $h;
	}

	/**
	 * @param array $params
	 *
	 * @return string
	 */
	public static function encodeParameters( $params ) {
		$flattenedParams = self::flattenParams( $params );
		$pieces          = array();
		foreach ( $flattenedParams as $param ) {
			list($k, $v) = $param;
			$pieces[]    = self::urlEncode( $k ) . '=' . self::urlEncode( $v );
		}

		return \implode( '&', $pieces );
	}

	/**
	 * @param array       $params
	 * @param null|string $parentKey
	 *
	 * @return array
	 */
	public static function flattenParams( $params, $parentKey = null ) {
		$result = array();

		foreach ( $params as $key => $value ) {
			$calculatedKey = $parentKey ? "{$parentKey}[{$key}]" : $key;

			if ( self::isList( $value ) ) {
				$result = \array_merge( $result, self::flattenParamsList( $value, $calculatedKey ) );
			} elseif ( \is_array( $value ) ) {
				$result = \array_merge( $result, self::flattenParams( $value, $calculatedKey ) );
			} else {
				\array_push( $result, array( $calculatedKey, $value ) );
			}
		}

		return $result;
	}

	/**
	 * @param array  $value
	 * @param string $calculatedKey
	 *
	 * @return array
	 */
	public static function flattenParamsList( $value, $calculatedKey ) {
		$result = array();

		foreach ( $value as $i => $elem ) {
			if ( self::isList( $elem ) ) {
				$result = \array_merge( $result, self::flattenParamsList( $elem, $calculatedKey ) );
			} elseif ( \is_array( $elem ) ) {
				$result = \array_merge( $result, self::flattenParams( $elem, "{$calculatedKey}[{$i}]" ) );
			} else {
				\array_push( $result, array( "{$calculatedKey}[{$i}]", $elem ) );
			}
		}

		return $result;
	}

	/**
	 * @param string $key a string to URL-encode
	 *
	 * @return string the URL-encoded string
	 */
	public static function urlEncode( $key ) {
		$s = \urlencode( (string) $key );

		// Don't use strict form encoding by changing the square bracket control
		// characters back to their literals. This is fine by the server, and
		// makes these parameter strings easier to read.
		$s = \str_replace( '%5B', '[', $s );

		return \str_replace( '%5D', ']', $s );
	}

	public static function normalizeId( $id ) {
		if ( \is_array( $id ) ) {
			// see https://github.com/stripe/stripe-php/pull/1602
			if ( ! isset( $id['id'] ) ) {
				return array( null, $id );
			}
			$params = $id;
			$id     = $params['id'];
			unset( $params['id'] );
		} else {
			$params = array();
		}

		return array( $id, $params );
	}

	/**
	 * Returns UNIX timestamp in milliseconds.
	 *
	 * @return int current time in millis
	 */
	public static function currentTimeMillis() {
		return (int) \round( \microtime( true ) * 1000 );
	}
}