<?php
/**
 * WP Courseware Cart Controller.
 *
 * @package WPCW
 * @subpackage Controllers
 * @since 4.3.0
 */
namespace WPCW\Controllers;

use WPCW\Models\Course;
use WPCW\Models\Order_Item;

// Exit if accessed directly
defined( 'ABSPATH' ) || die;

/**
 * Class Cart.
 *
 * @since 4.3.0
 */
class Cart extends Controller {

	/**
	 * @var array Cart contents.
	 * @since 4.3.0
	 */
	public $contents = array();

	/**
	 * @var array Cart Details.
	 * @since 4.3.0
	 */
	public $details = array();

	/**
	 * @var int Cart Quantity.
	 * @since 4.3.0
	 */
	public $quantity = 0;

	/**
	 * @var float Cart subtotal.
	 * @since 4.3.0
	 */
	public $subtotal = 0.00;

	/**
	 * @var float Cart total.
	 * @since 4.3.0
	 */
	public $total = 0.00;

	/**
	 * @var float Cart Tax.
	 * @since 4.3.0
	 */
	public $tax = 0.00;

	/**
	 * @var float Cart Discounts.
	 * @since 4.3.0
	 */
	public $discounts = 0.00;

	/**
	 * @var bool Last item in the cart.
	 * @since 4.3.0
	 */
	public $last = false;

	/**
	 * Load Cart.
	 *
	 * @since 4.3.0
	 */
	public function load() {
		// Cart Setup
		add_action( 'wp_loaded', array( $this, 'setup_cart' ) );

		// Cart Cookies
		add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 );
		add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 );
		add_action( 'wpcw_add_to_cart', array( $this, 'maybe_set_cart_cookies' ) );
		add_action( 'wpcw_empty_cart', array( $this, 'destroy_cart_session' ) );

		// Cart Display
		add_action( 'wpcw_checkout_cart', array( $this, 'display_cart' ) );
		add_action( 'wpcw_checkout_cart_empty', array( $this, 'display_empty_cart' ) );
		add_action( 'wpcw_cart_course_title_after', array( $this, 'display_course_subscription_renewal_message' ), 10, 2 );

		// Cart Api
		add_action( 'wpcw_init', array( $this, 'add_to_cart_endpoints' ) );
		add_action( 'template_redirect', array( $this, 'process_add_to_cart_endpoints' ) );
		add_action( 'wpcw_ajax_api_events', array( $this, 'register_cart_ajax_events' ) );
	}

	/**
	 * Setup Cart.
	 *
	 * @since 4.3.0
	 */
	public function setup_cart() {
		$this->get_session_contents();
		$this->get_contents();
		$this->get_quantity();
	}

	/**
	 * Get Cart Contents from Session.
	 *
	 * Checks the session to see if there
	 * are any cart items in the session.
	 *
	 * @since 4.3.0
	 */
	public function get_session_contents() {
		$this->contents = wpcw()->session->get( 'wpcw_cart' );

		/**
		 * Action: Get Cart Session Contents.
		 *
		 * @since 4.3.0
		 *
		 * @param Cart The cart object.
		 * @param array The cart contents.
		 */
		do_action( 'wpcw_cart_get_session_contents', $this, $this->contents );
	}

	/**
	 * Get Cart Contents.
	 *
	 * @since 4.3.0
	 *
	 * @return array The cart contents.
	 */
	public function get_contents() {
		if ( ! did_action( 'wpcw_cart_get_session_contents' ) ) {
			$this->get_session_contents();
		}

		$cart        = is_array( $this->contents ) && ! empty( $this->contents ) ? array_values( $this->contents ) : array();
		$cart_count  = count( $cart );
		$cart_length = $cart_count - 1;
		$count       = 0;

		// Populate the cart details.
		if ( ! empty( $cart ) ) {
			foreach ( $cart as $cart_key => $cart_course ) {
				if ( empty( $cart_course['id'] ) || empty( $cart_course['data'] ) ) {
					continue;
				}

				$course = new Course( $cart_course['data'] );

				if ( ! $course || ( $course && ! $course->is_purchasable() ) ) {
					unset( $cart[ $cart_key ] );
					continue;
				}

				if ( $cart_key > $cart_length ) {
					$this->last = true;
				}

				$price    = $course->get_payments_price();
				$quantity = 1;
				$discount = 0.00;
				$subtotal = floatval( $price ) * $quantity;
				$tax      = wpcw_calculate_tax_amount( $subtotal );
				$total    = ( ( $subtotal + $tax ) - $discount );

				if ( $total < 0 ) {
					$total = 0;
				}

				$this->details[ $count ] = array(
					'id'       => absint( $course->get_course_id() ),
					'name'     => esc_html( $course->get_course_title() ),
					'title'    => esc_html( $course->get_course_title() ),
					'price'    => wpcw_round( $price ),
					'quantity' => absint( $quantity ),
					'discount' => wpcw_round( $discount ),
					'subtotal' => wpcw_round( $subtotal ),
					'tax'      => wpcw_round( $tax ),
					'total'    => wpcw_round( $total ),
					'last'     => $this->last,
				);

				if ( $this->last ) {
					$this->last = false;
				}

				$count++;
			}
		}

		// Refresh contents after removing items.
		if ( count( $cart ) < $cart_count ) {
			$this->contents = $cart;
			$this->update();
		}

		/**
		 * Filter: Cart Contents.
		 *
		 * @since 4.3.0
		 *
		 * @param array $cart The cart contents.
		 * @param array $details The cart contents details.
		 *
		 * @return array $cart The cart contents.
		 */
		$this->contents = apply_filters( 'wpcw_cart_contents', $cart, $this->details );

		/**
		 * Action: Cart Contents Loaded
		 *
		 * @since 4.3.0
		 *
		 * @param Cart The cart object.
		 * @param array $cart The cart contents.
		 * @param array $details The cart contents details.
		 */
		do_action( 'wpcw_cart_contents_loaded', $this, $cart, $this->details );

		return $this->contents;
	}

	/**
	 * Get Cart Contents Details.
	 *
	 * @since 4.3.0
	 *
	 * @return array The cart content details.
	 */
	public function get_contents_details() {
		if ( empty( $this->details ) ) {
			$this->get_contents();
		}

		return apply_filters( 'wpcw_cart_contents_details', $this->details );
	}

	/**
	 * Get Cart Quantity.
	 *
	 * @since 4.3.0
	 *
	 * @return int The quantity of items in the cart.
	 */
	public function get_quantity() {
		$contents = $this->get_contents();

		if ( ! empty( $contents ) ) {
			$quantities     = wp_list_pluck( $this->contents, 'quantity' );
			$this->quantity = absint( array_sum( $quantities ) );
		}

		/**
		 * Filter: Get Cart Quantity.
		 *
		 * @since 4.3.0
		 *
		 * @param int $quanitity The total of items in the cart.
		 * @param array $contents The contents of the cart.
		 */
		return apply_filters( 'wpcw_cart_get_quantity', $this->quantity, $this->contents );
	}

	/**
	 * Is Cart Empty?
	 *
	 * @since 4.3.0
	 *
	 * @return bool
	 */
	public function is_empty() {
		return 0 === sizeof( $this->contents );
	}

	/**
	 * Maybe Set Cart Cookies.
	 *
	 * Will set cart cookies if needed and when possible.
	 *
	 * @since 4.3.0
	 */
	public function maybe_set_cart_cookies() {
		if ( ! headers_sent() && did_action( 'wp_loaded' ) ) {
			if ( ! $this->is_empty() ) {
				$this->set_cart_cookies( true );
			} elseif ( isset( $_COOKIE['wpcw_courses_in_cart'] ) ) {
				$this->set_cart_cookies( false );
			}
		}
	}

	/**
	 * Destroy Cart Session.
	 *
	 * @since 4.3.0
	 */
	public function destroy_cart_session() {
		wpcw()->session->set( 'wpcw_cart', null );
	}

	/**
	 * Returns the contents of the cart in an array without the 'data' element.
	 *
	 * @since 4.3.0
	 *
	 * @return array $cart_session Contents of the cart session.
	 */
	public function get_cart_for_session() {
		$cart_session = array();

		foreach ( $this->get_cart() as $key => $values ) {
			$cart_session[ $key ] = $values;
			unset( $cart_session[ $key ]['data'] ); // Unset course object.
		}

		return $cart_session;
	}

	/**
	 * Set cart hash cookie and items in cart.
	 *
	 * @since 4.3.0
	 *
	 * @param bool $set Should cookies be set (true) or unset.
	 */
	private function set_cart_cookies( $set = true ) {
		if ( $set ) {
			wpcw_setcookie( 'wpcw_courses_in_cart', 1 );
			wpcw_setcookie( 'wpcw_cart_hash', md5( wp_json_encode( $this->get_cart_for_session() ) ) );
		} elseif ( isset( $_COOKIE['wpcw_courses_in_cart'] ) ) {
			wpcw_setcookie( 'wpcw_courses_in_cart', 0, time() - HOUR_IN_SECONDS );
			wpcw_setcookie( 'wpcw_cart_hash', '', time() - HOUR_IN_SECONDS );
		}

		do_action( 'wpcw_set_cart_cookies', $set );
	}

	/**
	 * Add to Cart
	 *
	 * @since 4.3.0
	 *
	 * @param int $course_id The course id.
	 * @param array $options Any options that need to be added.
	 *
	 * @return int The position in the cart object.
	 */
	public function add_to_cart( $course_id ) {
		try {
			if ( $this->is_course_in_cart( $course_id ) ) {
				throw new Exception( esc_html__( 'You cannot purchase more than one of the same course.', 'wp-courseware' ) );
			}

			$course = new Course( absint( $course_id ) );

			if ( ! $course->get_course_id() ) {
				throw new Exception( esc_html__( 'Sorry, this course does not exist.', 'wp-courseware' ) );
			}

			if ( ! $course->is_purchasable() ) {
				throw new Exception( __( 'Sorry, this course cannot be purchased.', 'wp-courseware' ) );
			}

			$course_quantity = 1;
			$course_data     = $course->to_array();

			$this->contents[] = array(
				'id'       => $course_id,
				'data'     => $course_data,
				'quantity' => $course_quantity,
			);

			$this->update();

			/**
			 * Action: Add to Cart.
			 *
			 * @since 4.3.0
			 *
			 * @param int $course_id The course id.
			 * @param array $course_data The course data.
			 * @param Course The course object.
			 */
			do_action( 'wpcw_add_to_cart', $course_id, $course_data, $course );

			if ( 'yes' === wpcw_get_setting( 'redirect_to_checkout' ) ) {
				/* translators: %s - Course Title */
				wpcw_add_notice( sprintf( __( '%s succesfully added to your cart.', 'wp-courseware' ), esc_html( $course->get_course_title() ) ), 'success' );
			}

			return count( $this->contents ) - 1;
		} catch ( Exception $e ) {
			if ( $e->getMessage() ) {
				wpcw_add_notice( $e->getMessage(), 'error' );
			}

			return false;
		}
	}

	/**
	 * Remove Course from the Cart.
	 *
	 * @since 4.3.0
	 *
	 * @param int $key The cart key.
	 */
	public function remove_from_cart( $key ) {
		$cart = $this->get_contents();

		if ( isset( $cart[ $key ] ) ) {
			if ( empty( $cart ) ) {
				return true;
			}

			unset( $cart[ $key ] );

			$this->contents = $cart;
			$this->update();

			return true;
		}

		return false;
	}

	/**
	 * Empty Cart.
	 *
	 * @since 4.3.0
	 */
	public function empty_cart() {
		$this->contents = array();
		wpcw()->session->set( 'wpcw_cart', null );

		/**
		 * Action: Empty Cart.
		 *
		 * @since 4.3.0
		 */
		do_action( 'wpcw_empty_cart' );
	}

	/**
	 * Update Cart.
	 *
	 * @since 4.3.0
	 */
	public function update() {
		wpcw()->session->set( 'wpcw_cart', $this->contents );

		do_action( 'wpcw_cart_updated' );
	}

	/**
	 * Is Course in Cart?
	 *
	 * @since 4.3.0
	 *
	 * @param int $course_id The course id.
	 * @param array $options The cart item options.
	 *
	 * @return bool
	 */
	public function is_course_in_cart( $course_id = 0, $options = array() ) {
		$cart    = $this->get_contents();
		$in_cart = false;

		if ( is_array( $cart ) ) {
			foreach ( $cart as $course ) {
				if ( absint( $course_id ) === absint( $course['id'] ) ) {
					$in_cart = true;
				}
			}
		}

		return (bool) apply_filters( 'wpcw_course_in_cart', $in_cart, $course_id, $options );
	}

	/**
	 * Get Course Position in Cart.
	 *
	 * @since 4.3.0
	 *
	 * @param int $course_id The course id.
	 * @param array $options The cart item options.
	 *
	 * @return bool|int|string
	 */
	public function get_course_position_in_cart( $course_id = 0, $options = array() ) {
		$cart = $this->get_contents();

		if ( ! is_array( $cart ) || empty( $cart ) ) {
			return false;
		}

		foreach ( $cart as $cart_position => $cart_course ) {
			if ( absint( $course_id ) === absint( $cart_course['id'] ) ) {
				return $cart_position;
			}
		}

		return false;
	}

	/**
	 * Get Cart Course Object.
	 *
	 * @since 4.3.0
	 *
	 * @param array $cart_course The cart course data.
	 *
	 * @return Course The course model object.
	 */
	public function get_cart_course_object( $cart_course ) {
		$course_data = isset( $cart_course['data'] ) ? $cart_course['data'] : array();

		if ( empty( $course_data ) ) {
			return false;
		}

		return new Course( $course_data );
	}

	/**
	 * Get Courses Subtotal.
	 *
	 * @since 4.3.0
	 *
	 * @param array $courses The array of items in the cart.
	 *
	 * @return float|mixed|void
	 */
	public function get_courses_subtotal( $courses ) {
		$subtotal = 0.00;

		if ( is_array( $courses ) && ! empty( $courses ) ) {
			$prices = wp_list_pluck( $courses, 'subtotal' );

			if ( is_array( $prices ) ) {
				$subtotal = array_sum( $prices );
			} else {
				$subtotal = 0.00;
			}

			if ( $subtotal < 0 ) {
				$subtotal = 0.00;
			}
		}

		/**
		 * Filter: Get Cart Courses Subtotal
		 *
		 * @since 4.3.0
		 *
		 * @param double|float|int $subtotal The cart courses subtotal.
		 *
		 * @return double|float|int $subtotal The modified cart courses subtotal.
		 */
		$this->subtotal = apply_filters( 'wpcw_cart_get_courses_subtotal', $subtotal );

		return $this->subtotal;
	}

	/**
	 * Get Cart Subtotal.
	 *
	 * @since 4.3.0
	 *
	 * @return float Total before taxes.
	 */
	public function get_subtotal() {
		$courses  = $this->get_contents_details();
		$subtotal = $this->get_courses_subtotal( $courses );

		/**
		 * Filter: Get Cart Subtotal
		 *
		 * @since 4.3.0
		 *
		 * @param double|float|int $subtotal The cart subtotal.
		 *
		 * @return double|float|int $subtotal The cart subtotal.
		 */
		return apply_filters( 'wpcw_cart_get_subtotal', $subtotal );
	}

	/**
	 * Get Cart Subtotal Formatted.
	 *
	 * @since 4.3.0
	 *
	 * @return string The subtotal formatted.
	 */
	public function subtotal() {
		return wpcw_price( $this->get_subtotal() );
	}

	/**
	 * Get Cart Discounts.
	 *
	 * @since 4.3.0
	 *
	 * @return float|int|mixed|void
	 */
	public function get_discounts() {
		$cart_discounts = 0;
		$items          = $this->details;

		if ( $items ) {
			$discounts = wp_list_pluck( $items, 'discount' );

			if ( is_array( $discounts ) ) {
				$cart_discounts = array_sum( $discounts );
			}
		}

		$subtotal = $this->get_subtotal();

		if ( empty( $subtotal ) ) {
			$cart_discounts = 0;
		}

		/**
		 * Filter: Cart Discount.
		 *
		 * @since 4.3.0
		 *
		 * @param double|int $cart_discounts The cart tax.
		 */
		$this->discounts = apply_filters( 'wpcw_cart_get_discounts', $cart_discounts );

		return $this->discounts;
	}

	/**
	 * Get Cart Discounts Formatted.
	 *
	 * @since 4.3.0
	 *
	 * @return string The tax amount formatted.
	 */
	public function discounts() {
		return wpcw_price( $this->get_discounts() );
	}

	/**
	 * Get Cart Tax.
	 *
	 * @since 4.3.0
	 *
	 * @return float|int|mixed|void
	 */
	public function get_tax() {
		$cart_tax = 0;
		$items    = $this->details;

		if ( $items ) {
			$taxes = wp_list_pluck( $items, 'tax' );

			if ( is_array( $taxes ) ) {
				$cart_tax = array_sum( $taxes );
			}
		}

		$subtotal = $this->get_subtotal();

		if ( empty( $subtotal ) ) {
			$cart_tax = 0;
		}

		/**
		 * Filter: Cart Tax.
		 *
		 * @since 4.3.0
		 *
		 * @param double|int $cart_tax The cart tax.
		 */
		$this->tax = apply_filters( 'wpcw_cart_get_tax', $cart_tax );

		return $this->tax;
	}

	/**
	 * Get Cart Tax Formatted.
	 *
	 * @since 4.3.0
	 *
	 * @return string The tax amount formatted.
	 */
	public function tax() {
		return wpcw_price( $this->get_tax() );
	}

	/**
	 * Get Cart Total.
	 *
	 * @since 4.3.0
	 *
	 * @return float
	 */
	public function get_total() {
		$subtotal = (float) $this->get_subtotal();
		$tax      = (float) $this->get_tax();

		if ( wpcw_taxes_enabled() ) {
			$total = $subtotal + $tax;
		} else {
			$total = $subtotal;
		}

		if ( $total < 0 ) {
			$total = 0.00;
		}

		/**
		 * Filter: Get Cart Total.
		 *
		 * @since 4.3.0
		 *
		 * @param float $total The total amount.
		 *
		 * @return float $total The total amount modified.
		 */
		$this->total = (float) apply_filters( 'wpcw_cart_get_total', $total );

		return $this->total;
	}

	/**
	 * Get Cart Total Formatted.
	 *
	 * @since 4.3.0
	 *
	 * @return string The cart total formatted.
	 */
	public function total() {
		return wpcw_price( $this->get_total() );
	}

	/**
	 * Does the cart needs payment.
	 *
	 * @since 4.3.0
	 *
	 * @return bool True if the cart needs payemnt.
	 */
	public function needs_payment() {
		return apply_filters( 'wpcw_cart_needs_payment', 0 < $this->get_total(), $this );
	}

	/**
	 * Get Cart.
	 *
	 * @since 4.3.0
	 *
	 * @return array The cart contents.
	 */
	public function get_cart() {
		if ( ! did_action( 'wp_loaded' ) ) {
			wpcw_doing_it_wrong( __FUNCTION__, esc_html__( 'The "get_cart" functions should not be called before the wp_loaded action.', 'wp-courseware' ), '4.3.0' );
		}

		return array_filter( $this->get_contents() );
	}

	/**
	 * Display Cart.
	 *
	 * @since 4.3.0
	 */
	public function display_cart() {
		if ( $this->is_empty() ) {
			return;
		}

		do_action( 'wpcw_cart_before_display', $this );

		wpcw_get_template( 'checkout/cart.php', array( 'cart' => $this ) );

		do_action( 'wpcw_cart_after_display', $this );
	}

	/**
	 * Display Empty Cart.
	 *
	 * @since 4.3.0
	 */
	public function display_empty_cart() {
		wpcw_get_template( 'checkout/cart-empty.php', array( 'cart' => $this ) );
	}

	/**
	 * Display Course Subscription Renewal Message.
	 *
	 * @since 4.3.0
	 *
	 * @param string $course_key The course key.
	 * @param Course $course The Course object.
	 *
	 * @return string The course subscription renewal message.
	 */
	public function display_course_subscription_renewal_message( $course_key, $course ) {
		if ( ! $course->is_purchasable() || ! $course->is_subscription() ) {
			return;
		}

		printf( __( '<p><em>Billed %s until cancelled.</em></p>', 'wp-courseware' ), strtolower( $course->get_subscription_interval() ) );
	}

	/**
	 * Add Cart Endpoints.
	 *
	 * @since 4.3.0
	 */
	public function add_to_cart_endpoints() {
		add_rewrite_endpoint( 'wpcw-cart-add', EP_ALL );
		add_rewrite_endpoint( 'wpcw-cart-remove', EP_ALL );
	}

	/**
	 * Process Cart Endpoints.
	 *
	 * @since 4.3.0
	 */
	public function process_add_to_cart_endpoints() {
		global $wp_query;

		// Adds an item to the cart with a /wpcw-cart-add/# URL
		if ( isset( $wp_query->query_vars['wpcw-cart-add'] ) ) {
			$course_id = absint( $wp_query->query_vars['wpcw-cart-add'] );
			if ( $cart = $this->add_to_cart( $course_id, array() ) ) {
				wpcw_add_notice( __( 'Course succesfully added to your cart.', 'wp-courseware' ), 'success' );
			}
			wp_redirect( wpcw_get_checkout_url() );
			die();
		}

		// Removes an item from the cart with a /wpcw-cart-remove/# URL
		if ( isset( $wp_query->query_vars['wpcw-cart-remove'] ) ) {
			$key = absint( $wp_query->query_vars['wpcw-cart-remove'] );
			if ( $cart = $this->remove_from_cart( $key ) ) {
				wpcw_add_notice( __( 'Course succesfully removed from your cart.', 'wp-courseware' ), 'success' );
			}
			wp_redirect( wpcw_get_checkout_url() );
			die();
		}
	}

	/**
	 * Register REST API Cart Endpoints.
	 *
	 * @since 4.3.0
	 *
	 * @return array $ajax_events The array of ajax events.
	 */
	public function register_cart_ajax_events( $ajax_events ) {
		$ajax_events['add-to-cart']      = array( $this, 'ajax_add_to_cart' );
		$ajax_events['remove-from-cart'] = array( $this, 'ajax_remove_from_cart' );

		return $ajax_events;
	}

	/**
	 * Ajax Api: Add to Cart.
	 *
	 * @since 4.1.0
	 *
	 * @param object \WP_REST_Request The api request.
	 *
	 * @return object \WP_REST_Response The api response.
	 */
	public function ajax_add_to_cart() {
		$course_id = isset( $_REQUEST['course_id'] ) ? absint( $_REQUEST['course_id'] ) : 0;

		if ( empty( $course_id ) ) {
			$message = wpcw_get_notice( esc_html__( 'You must provide a course ID.', 'wp-courseware' ), 'error' );
			wp_send_json_success( array( 'error' => true, 'message' => $message ) );
		}

		$course = new Course( absint( $course_id ) );

		if ( ! $course ) {
			$message = wpcw_get_notice( esc_html__( 'Sorry, this course does not exist.', 'wp-courseware' ), 'error' );
			wp_send_json_success( array( 'error' => true, 'message' => $message ) );
		}

		if ( ! $course->is_purchasable() ) {
			$message = wpcw_get_notice( esc_html__( 'Sorry, this course cannot be purchased.', 'wp-courseware' ), 'error' );
			wp_send_json_success( array( 'error' => true, 'message' => $message ) );
		}

		if ( $this->is_course_in_cart( $course_id ) ) {
			$message = wpcw_get_notice( esc_html__( 'You cannot purchase more than one of the same course.', 'wp-courseware' ), 'error' );
			wp_send_json_success( array( 'error' => true, 'message' => $message ) );
		}

		$this->add_to_cart( $course_id );

		$button   = wpcw_get_checkout_link();
		$redirect = false;

		if ( 'yes' === wpcw_get_setting( 'redirect_to_checkout' ) ) {
			$redirect = wpcw_get_checkout_url();
		}

		wp_send_json_success( array( 'redirect' => $redirect, 'button' => $button ) );
	}

	/**
	 * Ajax Api: Remove from Cart.
	 *
	 * @since 4.3.0
	 */
	public function ajax_remove_from_cart() {
		wp_send_json_success( array( 'success' => true ) );
	}
}