<?php
/**
 * WPB Data Store.
 *
 * @package WPBookit\Classes
 * @since   3.0.0
 * @version 3.0.0
 */

defined( 'ABSPATH' ) || exit;

/**
 * Data store class.
 */
class WPBOOKIT_Data_Store {

	/**
	 * Contains an instance of the data store class that we are working with.
	 *
	 * @var WPBOOKIT_Data_Store
	 */
	private $instance = null;

	
	private $stores = array(
		'booking'                => 'WPB_Booking_Data_Store_CPT',
	);

	/**
	 * Contains the name of the current data store's class name.
	 *
	 * @var string
	 */
	private $current_class_name = '';

	/**
	 * The object type this store works with.
	 *
	 * @var string
	 */
	private $object_type = '';


	/**
	 * Tells WPBOOKIT_Data_Store which object (coupon, product, order, etc)
	 * store we want to work with.
	 *
	 * @throws Exception When validation fails.
	 * @param string $object_type Name of object.
	 */
	public function __construct( $object_type ) {
		$this->object_type = $object_type;
		$this->stores      = apply_filters( 'wpbookit_data_stores', $this->stores );

		// If this object type can't be found, check to see if we can load one
		// level up (so if product-type isn't found, we try product).
		if ( ! array_key_exists( $object_type, $this->stores ) ) {
			$pieces      = explode( '-', $object_type );
			$object_type = $pieces[0];
		}

		if ( array_key_exists( $object_type, $this->stores ) ) {
			$store = apply_filters( 'wpbookit_' . $object_type . '_data_store', $this->stores[ $object_type ] );
			if ( is_object( $store ) ) {
				if ( ! $store instanceof WPBOOKIT_Object_Data_Store_Interface ) {
					throw new Exception( esc_html__( 'Invalid data store.', 'wpbookit' ) );
				}
				$this->current_class_name = get_class( $store );
				$this->instance           = $store;
			} else {
				if ( ! class_exists( $store ) ) {
					throw new Exception( esc_html__( 'Invalid data store.', 'wpbookit' ) );
				}
				$this->current_class_name = $store;
				$this->instance           = new $store();
			}
		} else {
			throw new Exception( esc_html__( 'Invalid data store.', 'wpbookit' ) );
		}
	}

	/**
	 * Only store the object type to avoid serializing the data store instance.
	 *
	 * @return array
	 */
	public function __sleep() {
		return array( 'object_type' );
	}

	/**
	 * Re-run the constructor with the object type.
	 *
	 * @throws Exception When validation fails.
	 */
	public function __wakeup() {
		$this->__construct( $this->object_type );
	}

	/**
	 * Loads a data store.
	 *
	 * @param string $object_type Name of object.
	 *
	 * @since 3.0.0
	 * @throws Exception When validation fails.
	 * @return WPBOOKIT_Data_Store
	 */
	public static function load( $object_type ) {
		return new WPBOOKIT_Data_Store( $object_type );
	}

	/**
	 * Returns the class name of the current data store.
	 *
	 * @since 3.0.0
	 * @return string
	 */
	public function get_current_class_name() {
		return $this->current_class_name;
	}

	/**
	 * Reads an object from the data store.
	 *
	 * @since 3.0.0
	 * @param WPB_Data $data WPBookit data instance.
	 */
	public function read( &$data ) {
		$this->instance->read( $data );
	}

	/**
	 * Reads multiple objects from the data store.
	 *
	 * @since 6.9.0
	 * @param array[WPB_Data] $objects Array of object instances to read.
	 */
	public function read_multiple( &$objects = array() ) {
		// If the datastore allows for bulk-reading, use it.
		if ( is_callable( array( $this->instance, 'read_multiple' ) ) ) {
			$this->instance->read_multiple( $objects );
		} else {
			foreach ( $objects as &$obj ) {
				$this->read( $obj );
			}
		}
	}

	/**
	 * Create an object in the data store.
	 *
	 * @since 3.0.0
	 * @param WPB_Data $data WPBookit data instance.
	 */
	public function create( &$data ) {
		$this->instance->create( $data );
	}

	/**
	 * Update an object in the data store.
	 *
	 * @since 3.0.0
	 * @param WPB_Data $data WPBookit data instance.
	 */
	public function update( &$data ) {
		$this->instance->update( $data );
	}

	/**
	 * Delete an object from the data store.
	 *
	 * @since 3.0.0
	 * @param WPB_Data $data WPBookit data instance.
	 * @param array   $args Array of args to pass to the delete method.
	 */
	public function delete( &$data, $args = array() ) {
		$this->instance->delete( $data, $args );
	}

	/**
	 * Data stores can define additional functions (for example, coupons have
	 * some helper methods for increasing or decreasing usage). This passes
	 * through to the instance if that function exists.
	 *
	 * @since 3.0.0
	 * @param string $method     Method.
	 * @param mixed  $parameters Parameters.
	 * @return mixed
	 */
	public function __call( $method, $parameters ) {
		if ( is_callable( array( $this->instance, $method ) ) ) {
			$object     = array_shift( $parameters );
			$parameters = array_merge( array( &$object ), $parameters );
			return $this->instance->$method( ...$parameters );
		}
	}

	/**
	 * Check if the data store we are working with has a callable method.
	 *
	 * @param string $method Method name.
	 *
	 * @return bool Whether the passed method is callable.
	 */
	public function has_callable( string $method ) : bool {
		return is_callable( array( $this->instance, $method ) );
	}
}
