<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}


class WDP_Rule_Product_Package implements WDP_Rule {
	use WDP_Price_Calc;
	use WDP_Product_Filtering;

	protected $data;

	/** @var WDP_Cart_Adjustment[] */
	protected $cart_adjustments = array();
	/** @var WDP_Condition[] */
	protected $conditions = array();
	/** @var WDP_Limit[] */
	protected $limits = array();

	protected $options = array();

	function __construct( $rule ) {
		$this->data = $rule;

		if ( ! empty( $this->data['cart_adjustments'] ) ) {
			foreach ( $this->data['cart_adjustments'] as $cart_adj_data ) {
				try {
					$cart_adj                 = WDP_Cart_Adj_Registry::get_instance()->create_adj( $cart_adj_data );
					$this->cart_adjustments[] = $cart_adj;
				} catch ( Exception $exception ) {
				}
			}
		}

		if ( ! empty( $this->data['conditions'] ) ) {
			foreach ( $this->data['conditions'] as $condition_data ) {
				try {
					$condition          = WDP_Condition_Registry::get_instance()->create_condition( $condition_data );
					$this->conditions[] = $condition;
				} catch ( Exception $exception ) {
				}
			}
		}

		if ( ! empty( $this->data['limits'] ) ) {
			foreach ( $this->data['limits'] as $limit_data ) {
				try {
					$limit          = WDP_Limit_Registry::get_instance()->create_limit( $limit_data );
					$this->limits[] = $limit;
				} catch ( Exception $exception ) {
				}
			}
		}

		$this->options = WDP_Helpers::get_settings();
	}

	public function get_id() {
		return (int) $this->data['id'];
	}

	public function is_exclusive() {
		return (bool) $this->data['exclusive'];
	}

	public function get_priority() {
		return (int) $this->data['priority'];
	}

	protected function has_filters() {
		$filters = $this->get_filters();

		return ! empty( $filters );
	}

	protected function get_filters() {
		$filters = isset( $this->data['filters'] ) ? $this->data['filters'] : array();

		if ( empty( $filters ) ) {
			$filters[] = array(
				'qty'    => 1,
				'type'   => 'products',
				'method' => 'any',
				'value'  => array(),
			);
		}

		return $filters;
	}

	public function get_count_of_product_dependencies() {
		$filters_count = count( $this->get_filters() );

		$conditions_count = 0;
		foreach ( $this->conditions as $condition ) {
			if ( $condition->has_product_dependency() ) {
				$conditions_count ++;
			}
		}

		$total = $filters_count + $conditions_count;

		return $total;
	}

	public function get_product_dependencies() {
		$dependencies = array();
		foreach ( $this->get_filters() as $filter ) {
			$dependencies[] = array(
				'qty'    => $filter['qty'],
				'type'   => $filter['type'],
				'method' => $filter['method'],
				'values' => $filter['value'],
			);
		}

		//TODO: not now
//		foreach ( $this->conditions as $condition ) {
//			if ( $condition->has_product_dependency() ) {
//				$dependencies[] = $condition->get_product_dependency();
//			}
//		}

		return $dependencies;
	}

	public function has_bulk() {
		return ! empty( $this->data['bulk_adjustments']['ranges'] );
	}

	public function get_bulk_details() {
		if ( ! $this->has_bulk() ) {
			return false;
		}

		$bulk   = $this->data['bulk_adjustments'];
		$ranges = $bulk['ranges'];

		$ret = array(
			'type'          => $bulk['type'],
			'discount'      => $bulk['discount_type'],
			'ranges'        => array(),
			'table_message' => $bulk['table_message'],
		);
		foreach ( $ranges as $range ) {
			$ret['ranges'][] = array(
				'from'  => $range['from'],
				'to'    => $range['to'],
				'value' => $range['value'],
			);
		}

		return $ret;
	}

	public function is_product_matched( $cart, $product_id ) {
		$this->cart = $cart;

		$limits_verified = $this->check_limits();
		if ( ! $limits_verified ) {
			return false;
		}

		$conditions_verified = $this->check_conditions_for_a_match();
		if ( ! $conditions_verified ) {
			return false;
		}

		$matched = $this->match_product_with_filters( $product_id );

		return $matched;
	}

	/** @var  WDP_Cart */
	private $cart;
	private $items;
	private $add_products;
	private $used_items_all;
	private $used_items_by_attempt;
	private $used_items_by_filters;
	private $attempt;

	protected function init_applying_to_cart( $cart ) {
		$this->cart                  = $cart;
		$this->items                 = $this->cart->get_cart_items();
		$this->add_products          = array();
		$this->used_items_all        = array();
		$this->used_items_by_attempt = array();
		$this->used_items_by_filters = array();
		$this->attempt               = 0;

		foreach ( $this->items as &$item ) {
			$item['initial_price'] = $item['price'];
		}

		uasort( $this->items, array( $this, 'sort_items' ) );
	}

	protected function sort_items( $item1, $item2 ) {
		$price1 = $item1['price'];
		$price2 = $item2['price'];

		if ( 'cheap' === $this->data['options']['apply_to'] ) {
			if ( $price1 > $price2 ) {
				return 1;
			}
		} elseif ( 'expensive' === $this->data['options']['apply_to'] ) {
			if ( $price1 < $price2 ) {
				return 1;
			}
		}

		return - 1;
	}

	public function apply_to_cart( $cart ) {
		$this->init_applying_to_cart( $cart );

		//TODO: check limits +
		$limits_verified = $this->check_limits();
		if ( ! $limits_verified ) {
			return false;
		}

		//TODO: check conditions +
		$conditions_verified = $this->check_conditions();
		if ( ! $conditions_verified ) {
			return false;
		}
		$is_valid = false;
		while ( $this->check_attempt() ) {
			//TODO: check product filters +
			$is_checked = $this->check_product_filters();
			if ( ! $is_checked ) {
				break;
			}

			//TODO: apply product adjustments +
			$this->apply_product_adjustment();

			if ( $this->check_deal_attempt() ) {
				//TODO: apply get products +
				$this->apply_deal_adjustment();
			}

			$is_valid = true;
			$this->attempt ++;
		}

		if ( ! $is_valid ) {
			return false;
		}

		//TODO: check max discount sum for product adjustments +
		$this->check_max_discount_sum();

		//TODO: apply bulk adjustment +
		$this->apply_bulk_adjustment();

		//TODO: apply cart adjustment +
		$this->apply_cart_adjustment();

		//TODO: apply changes to cart +
		$this->apply_changes_to_cart();

//		echo json_encode( $this->items, JSON_PRETTY_PRINT );

		return true;
	}

	protected function check_limits() {
		foreach ( $this->limits as $limit ) {
			if ( ! $limit->check( $this->cart, $this->get_id() ) ) {
				return false;
			}
		}

		return true;
	}

	protected function check_conditions() {
		foreach ( $this->conditions as $condition ) {
			if ( ! $condition->check( $this->cart ) ) {
				return false;
			}

			$involved_items = $condition->get_involved_cart_items();
			if ( ! empty( $involved_items ) ) {
				foreach ( $involved_items as $item_key ) {
					$this->used_items_all[] = $item_key;
				}
			}
		}

		return true;
	}

	protected function check_conditions_for_a_match() {
		foreach ( $this->conditions as $condition ) {
			if ( ! $condition->match( $this->cart ) ) {
				return false;
			}
		}

		return true;
	}

	protected function check_max_discount_sum() {
		$limit = $this->data['product_adjustments']['max_discount_sum'];

		if ( empty( $limit ) ) {
			return true;
		}

		$changed_items = array();

		$discount_sum = 0;
		foreach ( $this->get_keys_of_changeable_prices() as $item_key ) {
			$item = $this->items[ $item_key ];

			$item_discount = 0;
			if ( isset( $item['initial_price'] ) ) {
				$item_discount = $item['initial_price'] - $item['price'];
			}

			if ( ! empty( $item_discount ) ) {
				$discount_sum    += $item_discount;
				$changed_items[] = $item_key;
			}
		}

		if ( $discount_sum <= $limit ) {
			return true;
		}

		//TODO: apply max discount sum option +
		//TODO: split sum between last attempts +
		$left_to_increase = $discount_sum - $limit;

		$changed_items = array_reverse( $changed_items );
		foreach ( $changed_items as $item_key ) {
			$item = $this->items[ $item_key ];

			$increases_sum = min( $item['initial_price'] - $item['price'], $left_to_increase );
			$new_price     = $item['price'] + $increases_sum;

			$item['adjusted_price'] = $new_price;
			$item['price']          = $new_price;

			$this->items[ $item_key ] = $item;

			$left_to_increase -= $increases_sum;
			if ( $left_to_increase <= 0 ) {
				break;
			}
		}

		return true;
	}

	protected function check_attempt() {
		$attempts_limit = (int) $this->data['options']['repeat'];

		return $attempts_limit < 0 || $this->attempt < $attempts_limit;
	}

	protected function check_deal_attempt() {
		$attempts_limit = (int) $this->data['get_products']['repeat'];

		return $attempts_limit < 0 || $this->attempt < $attempts_limit;
	}

	protected function apply_changes_to_cart() {
		$is_override_cents = $this->options['is_override_cents'];
		foreach ( $this->used_items_all as $item_key ) {
			$item     = $this->items[ $item_key ];
			$item['price'] = $is_override_cents ? $this->override_cents($item['price']) : $item['price'];
			$discount = $item['initial_price'] - $item['price'];
			if ( $discount > 0 ) {
				$this->cart->modify_cart_item( $item_key, $item );
				$this->cart->add_rule_discount_to_cart_item( $item_key, $this->get_id(), $discount );
			}
		}
		if ( $this->is_exclusive() ) {
			$this->cart->fix_cart_items( $this->used_items_all );
		}

		$added_item_keys = array();
		foreach ( $this->add_products as $product_id => $qty ) {
			$added_item_keys = array_merge(
				$added_item_keys,
				$this->cart->add_free( $product_id, $qty )
			);

			foreach ( $added_item_keys as $added_item_key ) {
				$this->cart->add_rule_discount_to_cart_item( $added_item_key, $this->get_id(), $this->cart->get_original_price( $product_id ) );
			}
		}
		if ( $this->is_exclusive() ) {
			$this->cart->fix_cart_items( $added_item_keys );
		}
	}

	protected function override_cents( $price ){
		$prices_ends_with  = $this->options['prices_ends_with'];

		$price_fraction = $price - intval( $price );
		$new_price_fraction = $prices_ends_with / 100;

		$round_new_price_fraction = round($new_price_fraction);


		if ( 0 == intval( $price ) AND 0 < $new_price_fraction ){
			$price = $new_price_fraction;
			return $price;
		}


		if ( $round_new_price_fraction ) {

			if ( $price_fraction <= $new_price_fraction - round(1/2, 2) ){
				$price = intval( $price ) - 1 + $new_price_fraction;
			} else {
				$price = intval( $price ) + $new_price_fraction;
			}

		} else {

			if ( $price_fraction >= $new_price_fraction + round(1/2, 2) ){
				$price = intval( $price ) + 1 + $new_price_fraction;
			} else {
				$price = intval( $price ) + $new_price_fraction;
			}

		}

		return $price;
	}

	protected function match_product_with_filters( $product_id ) {
		$matched = true;
		foreach ( $this->get_filters() as $filter_key => $filter ) {
			$matched = $this->check_product_suitability(
				$product_id,
				$filter['type'],
				$filter['value'],
				$filter['method']
			);

			if ( $matched ) {
				break;
			}
		}

		return $matched;
	}

	/**
	 * @return bool
	 */
	protected function check_product_filters() {
		$used_cart_items_by_attempt = array();
		$used_cart_items_by_filter  = array();

		foreach ( $this->get_filters() as $filter_key => $filter ) {
			$filter_qty = (int) $filter['qty'];
			$found_qty  = 0;

			$last_iteration_product = null;
			$last_iteration_result = null;
			foreach ( $this->items as $cart_item_key => $cart_item ) {
				$is_used_item = in_array( $cart_item_key, $used_cart_items_by_attempt ) ||
				                in_array( $cart_item_key, $this->used_items_all );

				if ( ! $is_used_item ) {
					$hash= md5( json_encode( $cart_item ) );
					if ( $hash == $last_iteration_product ) {
						$is_product_valid  = $last_iteration_result;
					} else {
						$is_product_valid      = $this->check_product_suitability(
							$cart_item['product_id'],
							$filter['type'],
							$filter['value'],
							$filter['method'],
							$cart_item
						);
						$last_iteration_product = $hash;
						$last_iteration_result = $is_product_valid;
					}

					if ( $is_product_valid ) {
						$used_cart_items_by_filter[ $filter_key ][] = $cart_item_key;
						$used_cart_items_by_attempt[]               = $cart_item_key;
						$found_qty ++;
					}
				}

				if ( $found_qty === $filter_qty ) {
					break;
				}
			}

			if ( $found_qty < $filter_qty ) {
				return false;
			}
		}

		$this->used_items_by_filters[ $this->attempt ] = $used_cart_items_by_filter;
		$this->used_items_by_attempt[ $this->attempt ] = $used_cart_items_by_attempt;

		$this->used_items_all = array_merge( $this->used_items_all, $used_cart_items_by_attempt );

		return true;
	}

	/**
	 * @return bool
	 */
	protected function apply_product_adjustment() {
		$ret = true;

		$adj_type = isset( $this->data['product_adjustments']['type'] ) ? $this->data['product_adjustments']['type'] : false;
		if ( 'total' === $adj_type ) {
			//TODO: implement product adjustment total mode +
			$ret = $this->apply_product_adjustment_total();
		} elseif ( 'split' === $adj_type ) {
			$ret = $this->apply_product_adjustment_split();
		}

		return $ret;
	}

	/**
	 * @return bool
	 */
	protected function apply_product_adjustment_total() {
		$used_item_keys = $this->get_keys_of_changeable_prices( $this->attempt );

		$adjustment = $this->data['product_adjustments']['total'];
		if ( empty( $adjustment ) || empty( $adjustment['type'] ) ) {
			return true;
		}

		$prices = array();
		foreach ( $used_item_keys as $cart_item_key ) {
			$prices[ $cart_item_key ] = $this->items[ $cart_item_key ]['price'];
		}
		$prices = $this->calculate_prices(
			$prices,
			$adjustment['type'],
			$adjustment['value']
		);

		foreach ( $used_item_keys as $cart_item_key ) {
			$cart_item = $this->items[ $cart_item_key ];

			$new_price = $prices[ $cart_item_key ];

			$cart_item['discounted_price'] = $new_price;
			$cart_item['price']            = $new_price;

			$this->items[ $cart_item_key ] = $cart_item;
		}

		return true;
	}

	protected function get_keys_of_changeable_prices( $attempt = null ) {
		if ( is_null( $attempt ) ) {
			$used_item_keys = $this->used_items_all;
		} else {
			$used_item_keys = $this->used_items_by_attempt[ $attempt ];
		}

		$ret = array();
		foreach ( $used_item_keys as $cart_item_key ) {
			if ( $this->is_changeable_price( $cart_item_key ) ) {
				$ret[] = $cart_item_key;
			}
		}

		return $ret;
	}

	protected function get_keys_of_changeable_prices_by_filter( $filter_id, $attempt ) {
		$ret = array();

		$by_filter = $this->used_items_by_filters[ $attempt ][ $filter_id ];
		foreach ( $by_filter as $cart_item_key ) {
			if ( $this->is_changeable_price( $cart_item_key ) ) {
				$ret[] = $cart_item_key;
			}
		}

		return $ret;
	}

	protected function is_changeable_price( $cart_item_key ) {
		return empty( $this->items[ $cart_item_key ]['price_readonly'] );
	}

	/**
	 * @return bool
	 */
	protected function apply_product_adjustment_split() {
		$adjustments = $this->data['product_adjustments']['split'];
		if ( empty( $adjustments ) ) {
			return true;
		}

		foreach ( $adjustments as $adj_key => $adjustment ) {
			$filter_key = $adj_key;

			$used_cart_items = $this->get_keys_of_changeable_prices_by_filter( $filter_key, $this->attempt );
			foreach ( $used_cart_items as $cart_item_key ) {
				$cart_item = $this->items[ $cart_item_key ];

				$new_price = $this->calculate_single_price(
					$cart_item['price'],
					$adjustment['type'],
					$adjustment['value']
				);

				$cart_item['discounted_price'] = $new_price;
				$cart_item['price']            = $new_price;

				$this->items[ $cart_item_key ] = $cart_item;
			}
		}

		return true;
	}

	protected function apply_deal_adjustment() {
		$adjustments = isset( $this->data['get_products']['value'] ) ? $this->data['get_products']['value'] : false;
		if ( empty( $adjustments ) ) {
			return true;
		}

		foreach ( $adjustments as $deal_adjustment ) {
			$product_id = (int) reset( $deal_adjustment['value'] );
			$qty        = (int) $deal_adjustment['qty'];

			if ( ! isset( $this->add_products[ $product_id ] ) ) {
				$this->add_products[ $product_id ] = 0;
			}
			$this->add_products[ $product_id ] += $qty;
		}

		return true;
	}

	protected function apply_bulk_adjustment() {
		if ( ! $this->has_bulk() ) {
			return true;
		}
		$ranges = $this->data['bulk_adjustments']['ranges'];

		$attempt_count = $this->attempt;

		$adj_type = $this->data['bulk_adjustments']['type'];

		for ( $i = 0; $i < $attempt_count; $i ++ ) {
			$used_item_keys  = $this->get_keys_of_changeable_prices( $i );
			$current_attempt = $i + 1;

			if ( 'tier' === $adj_type ) {
				$comparison_value = $current_attempt;
			} else {
				$comparison_value = $attempt_count;
			}

			foreach ( $ranges as $range ) {
				$more_than_from = empty( $range['from'] ) || $comparison_value >= $range['from'];
				$less_than_to   = empty( $range['to'] ) || $comparison_value <= $range['to'];
				if ( $more_than_from && $less_than_to ) {
					//TODO: implement calculation for bulk&tier adjustment +
					$prices = array();
					foreach ( $used_item_keys as $cart_item_key ) {
						$prices[ $cart_item_key ] = $this->items[ $cart_item_key ]['price'];
					}
					$prices = $this->calculate_prices(
						$prices,
						$this->data['bulk_adjustments']['discount_type'],
						$range['value']
					);

					foreach ( $used_item_keys as $cart_item_key ) {
						$cart_item = $this->items[ $cart_item_key ];

						$new_price = $prices[ $cart_item_key ];

						$cart_item['bulk_price'] = $new_price;
						$cart_item['price']      = $new_price;

						$this->items[ $cart_item_key ] = $cart_item;
					}
					break;
				}
			}
		}

		return true;
	}

	protected function apply_cart_adjustment() {
		foreach ( $this->cart_adjustments as $cart_adjustment ) {
			$cart_adjustment->apply_to_cart( $this->cart, $this->get_id() );
		}

		return true;
	}
}