<?php
/**
 * Validator Class
 *
 * @package errigal
 * @since 1.1.0
 */

namespace Errigal\Shared;

defined( 'ABSPATH' ) || exit;

use Closure;

/**
 * This class is in charge of providing validation functions.
 *
 * @since 1.1.0
 */
class Errigal_Validator {
	/**
	 * Determines whether specified key exists in associative array.
	 *
	 * @since 1.2.0
	 *
	 * @param array $associative_array Associative array to search in.
	 * @param mixed $key               Key to check for.
	 * @return bool
	 */
	public static function has_array_key( $associative_array, $key ) {
		return self::is_associative_array( $associative_array ) &&
			( is_int( $key ) || is_string( $key ) ) &&
			isset( $associative_array[ $key ] );
	}

	/**
	 * Determines whether specified value exists in string list.
	 *
	 * @since 1.2.0
	 *
	 * @param array  $string_list       String list to search in.
	 * @param string $value             Value to check for.
	 * @param bool   $allow_empty_items Optional. Whether to allow null or empty strings in string list. Default false.
	 * @return bool
	 */
	public static function has_string_list_item( $string_list, $value, $allow_empty_items = false ) {
		return self::is_string_list( $string_list, $allow_empty_items ) &&
			self::is_non_empty_string( $value ) &&
			in_array( $value, $string_list, true );
	}

	/**
	 * Determines whether $value is associative array.
	 *
	 * @since 1.1.0
	 *
	 * @param array $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_associative_array( $value ) {
		return self::is_non_empty_array( $value ) &&
			array_keys( $value ) !== range( 0, count( $value ) - 1 );
	}

	/**
	 * Determines whether $closure is closure.
	 *
	 * @since 1.2.0
	 *
	 * @param mixed $closure Closure object.
	 * @return bool
	 */
	public static function is_closure( $closure ) {
		return $closure instanceof Closure;
	}

	/**
	 * Determines whether $value is empty string.
	 *
	 * @since 1.2.0
	 *
	 * @param string $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_empty_string( $value ) {
		return is_string( $value ) && '' === $value;
	}

	/**
	 * Determines whether $value is false.
	 *
	 * @since 1.2.0
	 *
	 * @param bool $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_false( $value ) {
		return false === $value;
	}

	/**
	 * Determines whether $value is non-empty array.
	 *
	 * @since 1.1.0
	 *
	 * @param array $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_non_empty_array( $value ) {
		return is_array( $value ) && ! empty( $value );
	}

	/**
	 * Determines whether $value is non-empty string.
	 *
	 * @since 1.1.0
	 *
	 * @param string $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_non_empty_string( $value ) {
		return is_string( $value ) && '' !== trim( $value );
	}

	/**
	 * Determines whether $value is null or empty string.
	 *
	 * @since 1.2.0
	 *
	 * @param mixed $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_null_or_empty( $value ) {
		return null === $value || '' === $value;
	}

	/**
	 * Determines whether page is selected.
	 *
	 * @since 1.2.0
	 *
	 * @param int $page_id Page ID.
	 * @return bool
	 */
	public static function is_page_selected( $page_id ) {
		return self::is_positive_int( $page_id );
	}

	/**
	 * Determines whether $value is positive int.
	 *
	 * @since 1.1.0
	 *
	 * @param int $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_positive_int( $value ) {
		return is_int( $value ) && $value > 0;
	}

	/**
	 * Determines whether $value is positive number.
	 *
	 * @since 1.2.0
	 *
	 * @param int $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_positive_number( $value ) {
		return ( is_int( $value ) || is_float( $value ) ) &&
			$value > 0;
	}

	/**
	 * Determines whether $value is list.
	 *
	 * @since 1.2.0
	 *
	 * @param array $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_list( $value ) {
		if (
			! is_array( $value ) ||
			array_keys( $value ) !== range( 0, count( $value ) - 1 )
		) {
			return false;
		}

		foreach ( $value as $item ) {
			if ( is_array( $item ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Determines whether $value is string list.
	 *
	 * @since 1.1.0
	 *
	 * @param string[] $value             Variable being evaluated.
	 * @param bool     $allow_empty_items Optional. Whether to allow null or empty strings in string list. Default false.
	 * @return bool
	 */
	public static function is_string_list( $value, $allow_empty_items = false ) {
		if ( ! self::is_list( $value ) ) {
			return false;
		}

		foreach ( $value as $item ) {
			if (
				! (
					self::is_non_empty_string( $item ) ||
					( self::is_true( $allow_empty_items ) && self::is_null_or_empty( $item ) )
				)
			) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Determines whether $value is true.
	 *
	 * @since 1.2.0
	 *
	 * @param bool $value Variable being evaluated.
	 * @return bool
	 */
	public static function is_true( $value ) {
		return true === $value;
	}

	/**
	 * Determines whether string contains given substring.
	 *
	 * @since 1.2.0
	 *
	 * @param string $haystack String to search in.
	 * @param string $needle   String to search for.
	 * @return bool
	 */
	public static function string_contains( $haystack, $needle ) {
		if ( ! is_string( $haystack ) || ! is_string( $needle ) ) {
			return false;
		}

		if ( '' === $needle ) {
			return true;
		}

		return ! self::is_false( strpos( $haystack, $needle ) );
	}

	/**
	 * Determines whether string string ends with given substring.
	 *
	 * @param string $haystack String to search in.
	 * @param string $needle   String to search for.
	 * @return bool
	 */
	public static function string_ends_with( $haystack, $needle ) {
		return is_string( $haystack ) &&
			is_string( $needle ) &&
			substr( $haystack, -strlen( $needle ) ) === $needle;
	}
}
