<?php
/**
 * Tour booking form component.
 * Contains methods related to the data processing, validation and adding items to the shopping cart.
 *
 * @author    Themedelight
 * @package   Themedelight/AdventureTours
 * @version   4.2.1
 */

class AtBookingFormBase extends TdComponent
{
	/**
	 * Date format that should be used on the booking form.
	 *
	 * @see get_date_format
	 * @var string
	 */
	public $date_format = 'd/m/Y H:i';

	/**
	 * If booking form should be disabled(hidden) if there is not bookable dates.
	 *
	 * @var boolean
	 */
	public $disable_on_missed_booking_date = true;

	/**
	 * If allow to remove date field in case if there is not bookable dates.
	 *
	 * @var boolean
	 */
	public $hide_date_field_for_no_booking_dates = false;

	/**
	 * Product attribute based on that booking form should generate set of quantity fields instead of single quantity field.
	 *
	 * @var string
	 */
	public $expand_quantity_attribute;

	/**
	 * If default value for quantity should be filled in, otherwise will be empty.
	 *
	 * @var boolean
	 */
	public $set_default_value_for_quantity = true;

	/**
	 * If default value for expanded quantity should be filled in, otherwise will be empty.
	 *
	 * @var boolean
	 */
	public $set_default_value_for_expanded_quantity = true;

	/**
	 * If default value for date field should be selected as nearest available date.
	 *
	 * @var boolean
	 */
	public $set_default_value_for_date_field = true;

	/**
	 * If datepicker jQuery plugin should be applied for booking date selector field.
	 *
	 * @var boolean
	 */
	public $user_datepicker_for_date_field = true;

	/**
	 * If coupon code field is disabled.
	 *
	 * @var boolean
	 */
	public $disable_coupon_field = true;

	/**
	 * View that should be used for form rendering.
	 *
	 * @var string
	 */
	public $view_file = 'templates/parts/tour-booking-form';

	/**
	 * String used as template for rendering number of available tickets for the datepicker element.
	 * If empty - information about left tickets will be hidden, otherwise details about left tickets will be displayed in appropriate places.
	 *
	 * @var string
	 */
	public $calendar_show_left_tickets_format = '';

	public $date_field_text_format = '%s';

	public $time_select_text_format = '%s';

	public $tickets_number_suffix = ' (%s)';

	public $reset_variation_field_values_btn_title = '';

	/**
	 * Mapper that allows to move errors from one field to another one.
	 * Used to move errors from hidden fields to "general" fields, so they can be disaplyed in general way.
	 *
	 * @var array
	 */
	public $errors_movement = array(
		// Renders errors related to the variation selection over the related quantity field.
		// 'variation_id' => 'quantity',
		// Renders errors related to the variation selection above the booking button.
		// 'variation_id' => 'general',
	);

	/**
	 * Characters set that possible to use as date format delimiter symbols.
	 *
	 * @var array
	 */
	protected $date_format_delimiters = array( ' ', '/', '-' );

	/**
	 * Prefix used for saving tour related attributes in the order.
	 *
	 * @var string
	 */
	protected $booking_data_prefix_in_order_item = 'tour_';

	/**
	 * Intenral storage for saving different states (cache).
	 *
	 * @var assoc
	 */
	protected $state = array();

	/**
	 * If states usage should be disabled.
	 * Don't turn off!
	 *
	 * @var boolean
	 */
	protected $disable_states = false;

	/**
	 * Internal date saving format.
	 * Don't change this!
	 *
	 * @var string
	 */
	protected $_system_date_format = 'Y-m-d H:i';

	/**
	 * Flag that indicates state of init date format fields.
	 *
	 * @see init_date_format_options
	 * @var boolean
	 */
	protected $_date_format_inited = false;

	public function init() {
		if ( ! parent::init() ) {
			return false;
		}

		// adventure_tours_check( 'woocommerce_active' )
		if ( ! defined( 'WC_VERSION' ) ) {
			return false;
		}

		// has been moved to "get_fields_config" method
		// to allow update left ticket format options in di service init hooks
		// $this->init_date_format_options();

		add_action( 'woocommerce_check_cart_items', array( $this, 'action_woocommerce_check_cart_items' ) );

		// booking and orders processing hooks
		add_action( 'woocommerce_add_to_cart_handler_tour', array( $this, 'handler_add_to_cart_handler_tour' ) );
		add_filter( 'woocommerce_get_cart_item_from_session', array( $this, 'filter_woocommerce_get_cart_item_from_session' ), 1, 3 );
		add_filter( 'woocommerce_get_item_data', array($this, 'filter_woocommerce_get_item_data'), 20, 2);
		add_filter( 'woocommerce_attribute_label', array( $this, 'filter_woocommerce_attribute_label' ), 20, 2 );

		if ( version_compare( WC_VERSION, '3.0.0', '<') ) {
			add_action( 'woocommerce_add_order_item_meta', array( $this, 'filter_woocommerce_add_order_item_meta' ), 1 ,2 );
		} else {
			add_action( 'woocommerce_checkout_create_order_line_item', array( $this, 'filter_woocommerce_checkout_create_order_line_item' ), 1, 4 );
		}

		//WPML integration
		add_filter( 'wcml_exception_duplicate_products_in_cart', array( $this, 'filter_wcml_exception_duplicate_products_in_cart' ), 20, 2 );

		add_action( 'adventure_tours_booking_form_notices', array( $this, 'filter_notices') );

		return true;
	}

	public function get_booking_dates( $product, $adjust_items_from_shopping_cart = false ) {
		$state_key = $this->make_product_state_key( 'validbookingdates', $product ) . ( $adjust_items_from_shopping_cart ? '_1' : '_0' );
		$result = $this->get_state( $state_key, false );

		if ( false === $result ) {
			$result = adventure_touts_get_tour_booking_dates( $product->get_id() );

			if ( $adjust_items_from_shopping_cart ) {
				$current_id = $product->get_id();
				$deduct = array();
				// deduct items that already added to shopping cart
				$cart_items = WC()->cart->get_cart();
				if ( $cart_items ) {
					foreach ($cart_items as $item ) {
						if ( $item['product_id'] == $current_id && isset( $item['date'] ) ) {
							$quantity = isset( $item['quantity'] ) ? $item['quantity'] : 0;
							if ( isset( $deduct[ $item['date'] ] ) ) {
								$deduct[ $item['date'] ] += $quantity;
							} else {
								$deduct[ $item['date'] ] = $quantity;
							}
						}
					}

					if ( $deduct ) {
						foreach ( $deduct as $date => $quantity ) {
							if ( isset( $result[ $date ] ) ) {
								$result[ $date ] -= $quantity;
								if ( $result[ $date ] < 1 ) {
									unset( $result[ $date ] );
								}
							}
						}
					}
				}
			}

			$this->set_state( $state_key, $result );
		}

		return $result;
	}

	public function get_fields_config( $product ) {
		$state_key = $this->make_product_state_key( 'base_fields_config', $product );

		$result = $this->get_state( $state_key, false );

		if ( false === $result ) {
			$this->init_date_format_options();

			$booking_dates = $this->get_booking_dates( $product, true );

			$product_id = $product->get_id();
			/*if ( $product_id && adventure_tours_check('is_wpml_in_use') ) { //TODO remove
				// use 'wpml_object_id' filter instead
				$product_id = apply_filters( 'translate_object_id', $product_id, 'product', true, apply_filters( 'wpml_default_language', '' ) );
			}*/

			$result = array(
				'add-to-cart' => array(
					'type' => 'hidden',
					'default' => $product_id,
				)
			);

			$is_mutli_quantity_form = $this->is_mutli_quantity_form( $product );

			if ( $product->is_variable_tour() ) {
				$variation_rules = array();
				if ( ! $is_mutli_quantity_form ) {
					$variation_rules[] = 'required';
				}
				$variation_rules[] = 'variation_id';

				$result['variation_id'] = array(
					'type' => 'hidden',
					'rules' => $variation_rules,
				);

				$variation_attributes = $product->get_variation_attributes();

				foreach ( $variation_attributes as $attribute_name => $options ) {
					$attrib_field_name = 'attribute_' . sanitize_title( $attribute_name );
					$result[ $attrib_field_name ] = array(
						'type' => 'variation_select',
						'name' => $attrib_field_name,
						'attribute' => $attribute_name,
						'product' => $product,
						'options' => $options,
						'class' => 'selectpicker',
						'label' => wc_attribute_label( $attribute_name ),
						'default' => $product->get_variation_default_attribute( $attribute_name ),
						'icon_class' => AtTourHelper::get_product_attribute_icon_class( $attribute_name ),
					);
				}
			}

			if ( $booking_dates || !$this->hide_date_field_for_no_booking_dates ) {
				$date_default_value = '';
				if ( $booking_dates && $this->set_default_value_for_date_field ) {
					$date_default_value = date( 
						$this->get_date_format(),
						strtotime( key( $booking_dates ) ) // Getting 1-st bookable date.
					);
				}
				$date_label = esc_html__( 'Date', 'adventure-tours' );
				$result['date'] = array(
					'label' => $date_label,
					'placeholder' => $date_label,
					'rules' => array( 'required', 'date', 'booking_date' ),
					'default' => $date_default_value,
					'icon_class' => 'td-calendar',
				);

				if ( $booking_dates ) {
					$date_options = array();

					if ( ! $this->set_default_value_for_date_field ) {
						$date_options[''] = $date_label;
					}

					ksort( $booking_dates );
					foreach ( $booking_dates as $_date => $_open_tickets ) {
						$date_options[ $_date ] = sprintf( $this->date_field_text_format, $this->convert_date_for_human( $_date ), $_open_tickets );
					}
					$date_exact_field = array(
						'type' => 'select',
						'options' => $date_options,
						// 'class' => 'selectpicker',
					);

					$date_config = &$result['date'];

					// $result['date_exact'] = $date_exact_field;
					$date_config = array_merge( $date_config, $date_exact_field );
					$date_config['attributes']['data-placeholder'] = $date_config['placeholder'];
				}
			}

			// quantity field should follow after booking date field, validation requirement
			$quantity_label = esc_html__( 'Quantity', 'adventure-tours' );

			//TODO fix the notice text
			$invalid_tickets_number = esc_html__( 'Please enter the amount of tickets.', 'adventure-tours' );

			if ( $is_mutli_quantity_form ) {
				$expand_attribute_name = $this->get_expand_quantity_attribute_name( $product );
				$expand_field_key = $expand_attribute_name ? 'attribute_' . $expand_attribute_name : null;

				$default_expand_attribute_value = $product->get_variation_default_attribute( $expand_attribute_name );
				$quantity_redirect_to = null;

				$quantity_fields_set = $this->get_quantity_field_expand_values( $product );
				foreach ( $quantity_fields_set as $field_suffix => $field_label ) {
					$new_field_key = 'quantity_' . $field_suffix;

					$result[ 'variation_id_' . $field_suffix ] = array(
						'type' => 'hidden',
					);

					//TODO implement icon selection filter. By default should use the attribute icon?
					$result[ $new_field_key ] = array(
						'type' => 'number',
						'label' => $field_label,
						'placeholder' => $field_label,
						'default' => '',
						'icon_class' => 'td-users', //'td-user-plus',
						'attributes' => array(
							'data-quantityattribute' => $expand_field_key,
							'min' => '0',
						),
						'rules' => array(
							array(
								'type' => 'combined_ticket_number',
								'message' => $invalid_tickets_number,
							),
							'combined_booking_tickets',
						),
					);

					if ( $field_suffix == $default_expand_attribute_value || ! $quantity_redirect_to ) {
						$quantity_redirect_to = $new_field_key;
					}
				}

				if ( $quantity_redirect_to ) {
					//TODO complete icon selection logic.
					$result[ $quantity_redirect_to ]['icon_class'] = 'td-user-plus';
					if ( $this->set_default_value_for_expanded_quantity ) {
						$result[ $quantity_redirect_to ]['default'] = 1;
					}
					$this->errors_movement['quantity'] = $quantity_redirect_to;
					unset( $result[ $expand_field_key ] );
				}
			} else { // Single quantity field.
				$input_attributes = ['min' => 1]; 
				$woo_quantity_attributes = apply_filters('woocommerce_quantity_input_args', [
					'min_value'   => apply_filters( 'woocommerce_quantity_input_min', $product->get_min_purchase_quantity(), $product ),
					'max_value'   => apply_filters( 'woocommerce_quantity_input_max', $product->get_max_purchase_quantity(), $product ),
				], $product);

				foreach($woo_quantity_attributes as $key => $val) {
					if ('min_value' == $key) {
						$input_attributes['min'] = $val;
					} else if ('max_value' == $key) {
						if (-1<$val)
							$input_attributes['max'] = $val;
					}
				}

				$result['quantity'] = array(
					'type' => 'number',
					'label' => $quantity_label,
					'placeholder' => $quantity_label,
					'rules' => array( 
						'required',
						array(
							'type' => 'number',
							'min' => $input_attributes['min'],
							'message' => $invalid_tickets_number,
							'min_message' => $invalid_tickets_number,
						),
						'booking_tickets',
					),
					'attributes' => $input_attributes,
					'default' => $this->set_default_value_for_quantity
						? ( !empty($woo_quantity_attributes['input_value']) ? $woo_quantity_attributes['input_value'] : $input_attributes['min'] )
						: '',
					'icon_class' => 'td-user-plus', // 'td-circle-plus',
				);
			}

			if ( ! $this->disable_coupon_field ) {
				$coupons = WC()->cart->get_applied_coupons();
				$coupon_label = esc_html__( 'Coupon Code', 'adventure-tours' );
				$result['coupon_field'] = array(
					'label' => $coupon_label,
					'placeholder' => $coupon_label,
					'type' => 'text',
					'default' => $coupons ? $coupons[0] : '',
				);
			}

			$this->set_state( $state_key, $result );
		}
		return $result;
	}

	/**
	 * Returns list of fields related on the tour booking process.
	 * This fields will be saved to the shopping cat item and after purhase to the order item.
	 *
	 * @return array|assoc
	 */
	public function get_booking_fields( $withLabels = false ) {
		if ( ! $withLabels ) {
			return array( 'date' );
		}

		// Need improve booking fields configuration.
		static $cache, $wpml_is_in_use;
		if ( null === $wpml_is_in_use ) $wpml_is_in_use = adventure_tours_check( 'is_wpml_in_use' );

		$lang_code = $wpml_is_in_use ? ICL_LANGUAGE_CODE : '_any_';
		if ( ! $cache || ! isset( $cache[ $lang_code ] ) ) {
			$cache[ $lang_code ] = array(
				'date' => esc_html__( 'Booking Date', 'adventure-tours' ),
			);
		}

		// if ( ! $withLabels ) return array_keys( $cache[ $lang_code ] );

		return $cache[ $lang_code ];
	}

	public function is_active( $product ) {
		if ( ! $product || ! $product->is_type( 'tour') ) {
			return false;
		}

		$is_variable = $product->is_variable_tour();
		$price = $is_variable ? $product->get_variation_price('max') : $product->get_price();

		if ( '' === $price ) {
			return false;
		}

		if ( $this->disable_on_missed_booking_date && ! $this->get_booking_dates( $product ) ) {
			return false;
		}

		if ( $is_variable && ! $product->get_available_variations() ) {
			return false;
		}

		return true;
	}

	/**
	 * Returns the booking form html.
	 *
	 * @param  WC_Product_Tour $product
	 * @param  assoc           $options additional options that will be passed to view via "option" argument.
	 * @return string
	 */
	public function render( $product = null, $options = array() ) {
		if ( null == $product ) {
			$product = wc_get_product();
		}

		if ( ! $this->is_active( $product ) ) {
			return '';
		}

		return adventure_tours_render_template_part(
			$this->view_file,
			'',
			$this->get_view_params( $product, $options ),
			true
		);
	}

	/**
	 * Generates set of arguments that should be available for view.
	 *
	 * @param  WC_Product_Tour $product
	 * @param  assoc           $options
	 * @return assoc
	 */
	public function get_view_params( $product, $options = array() ) {
		$values = $this->get_field_values( $product );
		$fields_config = $this->get_fields_config( $product );

		return array(
			'field_config' => $fields_config,
			'field_values' => $values,
			'field_errors' => $this->get_validation_results( $product, $values, false ),
			'product' => $product,
			'options' => $options,
			'booking_form' => $this,
		);
	}

	public function get_validation_results( $product, $values, $run_validation = true ) {
		$state_key_suffix = $values ? '_' . md5( serialize( $values ) ) : '';
		$state_key = $this->make_product_state_key( 'validation', $product ) . $state_key_suffix;

		$errors = $this->get_state( $state_key, false );
		if ( false === $errors ) {
			if ( ! $run_validation ) {
				return array();
			}

			$fields_config = $this->get_fields_config( $product );

			$errors = $this->validate(
				$fields_config,
				$values,
				$product
			);

			// $errors = apply_filters( 'adventure_tours_tour_booking_validation_results', $errors, $field_config, $values, $product );

			$this->set_state( $state_key, $errors );
		}

		return $errors;
	}

	public function validate( array $fields_config, $values, $product ) {
		$errors = array();
		// Validation.
		foreach ( $fields_config as $key => $field_config ) {
			$field_errors = $this->validate_field(
				$key,
				$field_config,
				isset( $values[ $key ] ) ? $values[ $key ] : null,
				$product
			);
			if ( $field_errors ) {
				$errors[ $key ] = is_array( $field_errors ) ? $field_errors : (array) $field_errors;
			}
		}

		if ( $errors ) {
			$error_movement_map = empty( $this->errors_movement ) ? array() : $this->errors_movement;

			// Choose the variation selection field used for errors related to the missing variation id value.
			$attribute_id_movement_field_key = $this->find_error_movement_field_for_variation_id_fields_based_on_form_data( $values );
			if ( !empty( $attribute_id_movement_field_key ) ){
				foreach ( $errors as $source_key => $field_errors ) {
					if ( 0!==strpos($source_key, 'variation_id') ) continue;
					if ( empty( $field_errors ) || isset( $error_movement_map[ $source_key ] ) ) continue;

					$error_movement_map[ $source_key ] = $attribute_id_movement_field_key;
				}
			}

			// "Remaps" errors set based on the map configuration.
			foreach ( $error_movement_map as $source_key => $target_key ) {
				if ( !empty( $errors[$source_key] ) ) {
					$errors[ $target_key ] = !empty( $errors[ $target_key ] )
						? array_merge( $errors[ $target_key ], $errors[ $source_key ] )
						: $errors[ $source_key ];
					unset( $errors[ $source_key ] );
				}
			}
		}

		return $errors;
	}

	public function get_error_movement_field( $key, $max_depth = 3 ) {
		$new_key = $key;

		if ( $max_depth > 0 && isset( $this->errors_movement[ $key ] ) ) {
			$new_key = $this->errors_movement[ $key ];
			if ( $new_key != $key ) {
				return $this->get_error_movement_field( $new_key, $max_depth - 1 );
			}
		}
		return $new_key;
	}

	/**
	 * Finds apropriate attribute related field key is empty, so the value should be selected
	 * to complete the variation_id selection process.
	 * As result this field should be used as error rendering for the errors related to
	 * the missed variation id value.
	 *
	 * @param  array $field_values assoc with current form values.
	 * @return string
	 */
	protected function find_error_movement_field_for_variation_id_fields_based_on_form_data( $field_values ){
		$result = null;
		if ( !empty( $field_values ) ) {
			foreach ( $field_values as $field_key => $field_value ) {
				if ( empty( $field_value ) && 0===strpos( $field_key, 'attribute_' ) ) {
					$result = $field_key;
					break;
				}
			}
		}
		return $result;
	}

	public function validate_field( $key, $field_config, $value, $product ) {
		$rules = !empty( $field_config['rules'] ) ? $field_config['rules'] : array();

		foreach( $rules as $rule ) {
			$rule_errors = $this->validate_rule( $rule, $value, $field_config, $product );
			if ( $rule_errors ) {
				return $rule_errors;
			}
		}

		return array();
	}

	public function validate_rule( $rule, $value, $field_config, $product ) {
		$type = is_string( $rule ) ? $rule : null;
		$rule_error = null;

		if ( is_array( $rule ) ) {
			$function = isset( $rule['function'] ) ? $rule['function'] : null;
			if ( $function && is_callable( $function ) ) {
				return call_user_func_array( $function, array( $rule, $value, $field_config, $product ) );
			}

			if ( ! empty( $rule['message'] ) ) {
				$rule_error = $rule['message'];
			}
			if ( ! empty( $rule['type'] ) ) {
				$type = $rule['type'];
			}
		}

		$errors = array();
		switch ( $type ) {
		case 'required':
			if ( empty( $value ) ) {
				$message_template = $rule_error ? $rule_error : esc_html__( 'Fill in the required field.', 'adventure-tours' );
				$errors[] = $this->format_message( $message_template, $value, $field_config );
			}
			break;

		case 'email':
			if ( $value && ! filter_var( $value, FILTER_VALIDATE_EMAIL ) ) {
				$message_template = $rule_error ? $rule_error : esc_html__( 'Email invalid.', 'adventure-tours' );
				$errors[] = $this->format_message( $message_template, $value, $field_config );
			}
			break;

		case 'date':
			if ( $value ) {
				$converted_date = $this->convert_date_for_system( $value );
				if ( ! $converted_date ) {
					$message_template = $rule_error ? $rule_error : esc_html__( 'Date invalid.', 'adventure-tours' );
					$errors[] = $this->format_message( $message_template, $value, $field_config );
				}
			}
			break;

		case 'number':
			if ( $value ) {
				$is_config = is_array( $rule );
				$filter_type = $is_config && !empty( $rule['float'] ) ? FILTER_VALIDATE_FLOAT : FILTER_VALIDATE_INT; 

				if ( ! filter_var( $value, $filter_type ) ) {
					$message_template = $rule_error ? $rule_error : esc_html__( 'Check field format.', 'adventure-tours' );
					$errors[] = $this->format_message( $message_template, $value, $field_config );
				} else {
					$min = $is_config && !empty( $rule['min'] ) ? $rule['min'] : null;
					$max = $is_config && !empty( $rule['max'] ) ? $rule['max'] : null;

					if ( null !== $min && $value < $min ) {
						$message_template = $is_config && !empty( $rule['min_message'] ) ? $rule['min_message'] : esc_html__( 'Min value is' , 'adventure-tours' ) . ' "{min}".';
						$errors[] = $this->format_message( $message_template, $value, $field_config, array(
							'{min}' => $min,
							'{max}' => $max
						) );
					} elseif ( null !== $max && $value > $max ) {
						$message_template = $is_config && !empty( $rule['max_message'] ) ? $rule['max_message'] : esc_html__( 'Max value is', 'adventure-tours' ) . ' "{max}".';
						$errors[] = $this->format_message( $message_template, $value, $field_config, array(
							'{min}' => $min,
							'{max}' => $max
						) );
					}
				}
			}
			break;

		case 'booking_date':
			if ( $value ) {
				$max_allowed_tickets = $this->get_open_tour_tickets( $product, $value );
				$tickets_in_cart = $this->get_count_tickets_in_cart( $product, $value );

				if ( $tickets_in_cart <= $max_allowed_tickets ) {
					$max_allowed_tickets -= $tickets_in_cart;
				} else {
					$max_allowed_tickets = 0;
				}

				if ( $max_allowed_tickets < 1 ) {
					$message_template = $rule_error ? $rule_error : esc_html__( 'There are no tickets for this date.', 'adventure-tours' );
					$errors[] = $this->format_message( $message_template, $value, $field_config );
				} else {
					// hack :(
					$this->set_state( $this->make_product_state_key( 'valid_booking_date', $product ),
						array(
							'max_allowed_tickets' => $max_allowed_tickets
						)
					);
				}
			}
			break;

		case 'booking_tickets':
			if ( $value && $value > 0 ) {
				$date_limit_state = $this->get_state(
					$this->make_product_state_key( 'valid_booking_date', $product ),
					false
				);
				$max_allowed_tickets = $date_limit_state && !empty( $date_limit_state['max_allowed_tickets'] ) ? $date_limit_state['max_allowed_tickets'] : 0;
				// $max_allowed_tickets = $this->get_open_tour_tickets( $product, '{booking_date}' );
				if ( $max_allowed_tickets > 0 && $value > $max_allowed_tickets ) {
					$message_template = $rule_error ? $rule_error : esc_html(
						_n( 'Only 1 ticket is left.', 'Only {left_tickets} tickets are left.', $max_allowed_tickets, 'adventure-tours' )
					);
					$errors[] = $this->format_message( $message_template, $value, $field_config, array(
						'{left_tickets}' => $max_allowed_tickets
					) );
				}
			}
			break;

		case 'combined_ticket_number':
			if ( $value && '0' !== $value ) {
				if ( ! filter_var( $value, FILTER_VALIDATE_INT ) || $value < 1 ) {
					$message_template = $rule_error ? $rule_error : esc_html__( 'Check field format.', 'adventure-tours' );
					$errors[] = $this->format_message( $message_template, $value, $field_config );
				}
			}
			break;

		case 'combined_booking_tickets':
			if ( $value && $value > 0 ) {
				$combined_quantity_state_key = $this->make_product_state_key( 'combined_quantity_vals', $product );
				$combined_quantity_values = $this->get_state( $combined_quantity_state_key, array() );

				$prev_added_quantity = array_sum( $combined_quantity_values );

				$combined_quantity_values[] = $value;
				$this->set_state( $combined_quantity_state_key, $combined_quantity_values );

				$date_limit_state = $this->get_state(
					$this->make_product_state_key( 'valid_booking_date', $product ),
					false
				);

				$max_allowed_tickets = $date_limit_state && !empty( $date_limit_state['max_allowed_tickets'] ) ? $date_limit_state['max_allowed_tickets'] : 0;

				$max_allowed_tickets = $max_allowed_tickets - $prev_added_quantity;
				if ( $max_allowed_tickets > 0 ) {
					if ( $value > $max_allowed_tickets ) {
						$message_template = $rule_error ? $rule_error : esc_html(
							_n( 'Only 1 ticket is left.', 'Only {left_tickets} tickets are left.', $max_allowed_tickets, 'adventure-tours' )
						);
						$errors[] = $this->format_message( $message_template, $value, $field_config, array(
							'{left_tickets}' => $max_allowed_tickets
						) );
					}
				} else {
					// if settings allow to use booking form even when there are no bookable tickets - skipping validation
					if ( false === $date_limit_state && ! $this->disable_on_missed_booking_date ) {
						$form_values = $this->get_field_values( $product );
						if ( !isset( $form_values['date'] ) ) {
							return $errors;
						}
					}

					$errors[] = esc_html__( 'There are no more tickets available.', 'adventure-tours' );
				}
			}
			break;

		case 'variation_id':
			if ( $value > 0 ) {
				$variation_id = $value;
				$variation_data = $this->get_variation_data_for_product_variation(
					$product,
					$variation_id,
					$this->get_field_values( $product )
				);

				if ( is_a( $variation_data, 'WP_Error' ) ) {
					$errors = array_merge( $errors, $variation_data->get_error_messages() );
				} else if ( empty( $variation_data ) ) {
					$errors[] = esc_html__( 'An error occured, please contact support', 'adventure-tours' );
				}

				if ( empty( $errors ) ) {
					//TODO remove logic that uses values validated during the validation process.
					$this->set_state( $this->make_product_state_key( 'variations_settings', $product ), array(
						'variation_id' => $variation_id,
						'variations' => $variation_data
					) );
				}
			}
			break;
		}

		return $errors;
	}

	public function get_count_tickets_in_cart( $product, $for_date ) {
		$current_id = $product->get_id();
		$cart_items = WC()->cart->get_cart();
		$result = 0;
		foreach ($cart_items as $_ik => $_item ) {
			if ( $_item['product_id'] == $current_id ) {
				$is_same_date = ( ! empty( $_item['date'] ) && $for_date == $_item['date'] ) || ( null === $for_date && empty( $_item['date'] ) );
				if ( ! $is_same_date ) {
					continue;
				}
				$result += isset( $_item['quantity'] ) ? $_item['quantity'] : 0;
			}
		}
		return $result;
	}

	public function format_message( $message, $value, array $field_config, array $additional_params = array() ) {
		$params = array(
			'{label}' => isset( $field_config['label'] ) ? $field_config['label'] : '',
			'{value}' => (string) $value
		);

		if ( $additional_params ) {
			$params = array_merge( $params, $additional_params );
		}

		return strtr( $message, $params );
	}

	public function get_field_values( $product, $reread = false ) {
		$state_key = $this->make_product_state_key( 'field_values', $product );
		$values = $this->get_state( $state_key, false );
		if ( false === $values || $reread ) {
			$values = array();
			$fields = $this->get_fields_config( $product );
			foreach ( $fields as $key => $field_config ) {
				$values[ $key ] = $this->get_field_value( $key, $field_config );
			}

			$this->set_state( $state_key, $values );
		}

		return $values;
	}

	public function get_field_value( $field_key, $field_config ) {
		$request_data = $this->get_request_data();

		$field_key_in_request = $field_config && isset( $field_config['name'] ) ? $field_config['name'] : $field_key;

		if (isset( $request_data[ $field_key_in_request ] )) {
			return trim( $request_data[ $field_key_in_request ] );
		} else {
			return $field_config && isset( $field_config['default'] ) ? $field_config['default'] : '';
		}
	}

	public function get_request_data() {
		return $_REQUEST;
	}

	protected function init_date_format_options() {
		if ( ! $this->_date_format_inited ) {
			if ( $this->calendar_show_left_tickets_format && $this->tickets_number_suffix ) {
				if ( $this->time_select_text_format ) {
					$this->time_select_text_format .= $this->tickets_number_suffix;
				}
				if ( $this->date_field_text_format ) {
					$this->date_field_text_format .= $this->tickets_number_suffix;
				}
			}
			$this->_date_format_inited = true;

			return true;
		}

		return false;
	}

	protected function make_product_state_key( $prefix, $product ) {
		return $prefix . '_' . ( $product ? $product->get_id() : 'UNDEFINED');
	}

	/**
	 * Retrives state value by a key.
	 *
	 * @param  string $key
	 * @param  mixed  $default
	 * @return mixed
	 */
	protected function get_state( $key, $default = null ) {
		return isset( $this->state[ $key ] ) ? $this->state[ $key ] : $default;
	}

	/**
	 * Saved value to state storage.
	 *
	 * @param  string $key
	 * @param  mixed  $value
	 * @return void
	 */
	protected function set_state( $key, $value ) {
		if ( $this->disable_states ) {
			return;
		}

		$this->state[ $key ] = $value;
	}

	public function get_open_tour_tickets( $product, $date, $variation_id = 0 ) {
		//TODO implement: bookable dates range should be taken in consideration
		$system_date_string = $date; // $system_date_string = $this->convert_date_for_system( $date );

		return adventure_tours_di( 'tour_booking_service' )->get_open_tickets( $product->get_id(), $system_date_string, $variation_id );
	}

	public function convert_date_for_human( $system_date_string ) {
		return $this->convert_date( $this->_system_date_format, $system_date_string, $this->get_date_format() );
	}

	public function convert_date_for_system( $human_date_string ) {
		if ( $this->is_system_date_format( $human_date_string ) ) {
			return $human_date_string;
		}

		return $this->convert_date( $this->get_date_format(), $human_date_string, $this->_system_date_format );
	}

	public function is_system_date_format( $date_string ) {
		// checkdate ( int $month , int $day , int $year )
		return $date_string && preg_match( '`^\d{4}-\d{2}-\d{2}( \d{2}:\d{2})?$`', $date_string );
	}

	/**
	 * Converts date from $in_format to $out_format.
	 *
	 * @param  string  $in_format  format used for a date value.
	 * @param  string  $date
	 * @param  string  $out_format output format.
	 * @param  boolean $result_trim_empty_time
	 * @return string
	 */
	public function convert_date( $in_format, $date, $out_format = 'Y-m-d H:i', $result_trim_empty_time = true ) {
		$possible_delimiters = $this->date_format_delimiters ? $this->date_format_delimiters : array(' ','/','-');

		$fixed_format = str_replace( $possible_delimiters, '|', $in_format );
		$vars_list = explode( '|', $fixed_format );

		$dayIndex = array_search('d', $vars_list);
		$monthIndex = array_search('m', $vars_list);
		$yearIndex = array_search('Y', $vars_list);

		$time = null;
		if ( false !== $dayIndex || false !== $monthIndex || false !== $yearIndex ) {
			$fixed_delimiters = str_replace( $possible_delimiters, '|', $date );
			$parts = explode('|', $fixed_delimiters);

			$day = isset($parts[$dayIndex]) ? $parts[$dayIndex] : null;
			$month = isset($parts[$monthIndex]) ? $parts[$monthIndex] : null;
			$year = isset($parts[$yearIndex]) ? $parts[$yearIndex] : null;

			if ( $day && $month && $year ) {
				$time_parse_result = null;
				$time_string_postfix = null;
				// if ( preg_match('/ (\d{2}:\d{2}(:\d{2})?)$/', $date, $time_parse_result) ) {
				if ( preg_match('/ (\d{2}:\d{2})$/i', $date, $time_parse_result) ) {
					$time_string_postfix = ' ' . $time_parse_result[1];
				}

				$time = strtotime( "{$year}-{$month}-{$day}{$time_string_postfix}" );
			}
		}

		if ( $time ) {
			$result = date( $out_format, $time );
			if ( $result_trim_empty_time ) {
				// trimming 0:00, 00:00 and '12:00 am' time to make 'xxxx-xx-xx' date equal to 'xxxx-xx-xx 00:00'
				$result = preg_replace( '/ (([0]{1,2}:[0]{2})|(12:00 am))$/i', '', $result );
			}
			return $result;
		}
		return null;
	}

	/**
	 * Returns date format for the booking form.
	 *
	 * @param  string $for target element for that date should be returned.
	 * @return string
	 */
	public function get_date_format( $for = 'php' ) {
		$date_format = $this->date_format;
		switch ($for) {
		case 'datepicker':
			$replacement = array(
				'm' => 'mm',
				'd' => 'dd',
				'Y' => 'yy',
				'H:i' => '',
				'h:i' => '',
				'g:i' => '',
				'A' => '',
				'a' => '',
			);
			return trim( str_replace( array_keys( $replacement ), $replacement, $date_format ) );
			break;

		case 'datepicker-time':
			$replacement = array(
				'm' => '',
				'd' => '',
				'Y' => '',
				'h:i' => 'hh:ii',
				'H:i' => 'hh:ii',
				'g:i' => 'h:ii',
			);

			return trim(
				str_replace(
					array_keys( $replacement ),
					$replacement,
					$date_format
				),
				$this->date_format_delimiters ? join( '', $this->date_format_delimiters ) : ' /-' 
			);

		case 'php-no-time':
			$replacement = array(
				'H:i' => '',
				'h:i' => '',
				'g:i' => '',
				'A' => '',
				'a' => '',
			);
			return trim( str_replace( array_keys( $replacement ), $replacement, $date_format ) );
			break;

		default:
			return $date_format;
			break;
		}
	}

	/*** Ordering processing ***/
	public function filter_wcml_exception_duplicate_products_in_cart( $state, $cart_item ) {
		$bfields = $this->get_booking_fields();
		if ( $bfields ) {
			foreach ( $bfields as $field ) {
				if ( isset( $cart_item[ $field ] ) ) {
					return true;
				}
			}
		}

		return $state;
	}

	public function process_add_to_cart_request( $product, &$cart, $coupon_code = null ) {
		if ( ! $product || ! $cart ) {// method call parameters error
			return array( 1, 0, array() );
		}

		$booking_form_validation_errors = $this->get_validation_results( $product, $this->get_field_values( $product ) );

		$product_item_variation_data = $this->get_product_item_variation_data( $product );

		$errors_stack = new AtStack();
		$errors_stack_filter = array( $errors_stack, 'push_item' );
		add_filter( 'woocommerce_add_error', $errors_stack_filter );

		$product_id = $product->get_id();
		$passed_woo_validation = ! empty( $product_item_variation_data );
		if ( $product_item_variation_data ) {
			foreach( $product_item_variation_data as $i ) {
				$_cur_pass = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $i['quantity'], $i['variation_id'], $i['variations'], $i['tour_data'] );
				$has_errors_on_validation = ! $errors_stack->is_empty();
				if ( ! $_cur_pass || $has_errors_on_validation ) {
					$passed_woo_validation = false;
					if ( $has_errors_on_validation ) {
						$error_key = isset($i['quantity_field_key'])
							? $i['quantity_field_key']
							: join( '_', array( 'cart_validation', $product_id, $i['variation_id'] ) );
						$booking_form_validation_errors[ $error_key ] = $errors_stack->get_items(); 
					}
				}
			}
		}

		$added_to_cart = array();
		$left_add_counter = count( $product_item_variation_data );
		$has_errors_during_add_action = false;
		if ( $passed_woo_validation && empty( $booking_form_validation_errors ) ) {
			foreach( $product_item_variation_data as $i ) {
				$new_cart_item_key = $cart->add_to_cart(
					$product_id,
					$i['quantity'],
					$i['variation_id'],
					$i['variations'],
					$i['tour_data']
				);

				$is_added_sucessfully = $new_cart_item_key && $errors_stack->is_empty();
				if ( $is_added_sucessfully && ! $has_errors_during_add_action ) {
					if ( isset( $added_to_cart[ $product_id ] ) ) {
						$added_to_cart[ $product_id ] += $i['quantity'];
					} else {
						$added_to_cart[ $product_id ] = 0 + $i['quantity'];
					}
					$left_add_counter--;
				} else {
					if ( ! $has_errors_during_add_action ) $has_errors_during_add_action = true;
					if ( $new_cart_item_key ) {
						$new_item_data = $cart->get_cart_item( $new_cart_item_key );
						$new_quantity = $new_item_data ? $new_item_data['quantity'] - $i['quantity'] : 0;
						if ( 0 < $new_quantity ) {
							$cart->set_quantity( $new_cart_item_key, $new_quantity );
						} else {
							$cart->remove_cart_item( $new_cart_item_key );
						}
					}

					if ( ! $is_added_sucessfully || ! $errors_stack->is_empty() ) {
						$error_key = isset( $i['quantity_field_key'] )
							? $i['quantity_field_key']
							: join( '_', array( 'cart', $product_id, $i['variation_id'] ) );
						$booking_form_validation_errors[ $error_key ] = $errors_stack->is_empty()
							? array( esc_html__( 'An error occured, please contact support', 'adventure-tours' ) )
							: $errors_stack->get_items();
					}
				}
			}
		}

		if ( $errors_stack_filter ) {
			if ( $has_errors_during_add_action || ! $passed_woo_validation ) wc_clear_notices();
			remove_filter( 'woocommerce_add_error', $errors_stack_filter );
		}

		// Discount coupon processing logic.
		if ( ! $this->disable_coupon_field && $coupon_code && $left_add_counter < 1 ) {
			$applied_coupons = WC()->cart->get_applied_coupons();
			if ( ! $applied_coupons || ! in_array( $coupon_code, $applied_coupons ) ) {
				$cart->add_discount( sanitize_text_field( $coupon_code ) );
			}
		}

		return array( $left_add_counter, $added_to_cart, $booking_form_validation_errors );
	}

	/**
	 * Handler used for adding tour to the shopping card.
	 * Used by booking form.
	 *
	 * @param string $url redirect url
	 * @return void
	 */
	public function handler_add_to_cart_handler_tour( $url ) {
		ob_start();

		$is_ajax_reply = !empty( $_REQUEST['is_ajax'] );

		$product_id = $this->get_field_value( 'add-to-cart', null, 0 );
		if ( $product_id ) {
			$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
		}

		// tmp hack to prevent separation of the same product in shopping cart
		/*if ( $product_id && adventure_tours_check('is_wpml_in_use') ) { //TODO remove
			// use 'wpml_object_id' filter instead
			$product_id = apply_filters( 'translate_object_id', $product_id, 'product', true, apply_filters( 'wpml_default_language', '' ) );
		}*/

		if ( 1 > $product_id ) {
			if ( $is_ajax_reply ) {
				wp_send_json( array(
					'success' => false,
				) );
				wp_die();
			}
			return;
		}

		$product = wc_get_product( $product_id );
		list( $left_add_counter, $added_to_cart, $booking_form_validation_errors ) = $this->process_add_to_cart_request(
			$product,
			WC()->cart,
			$this->get_field_value( 'coupon_field', null )
		);

		$is_success = ! empty( $added_to_cart ) && wc_notice_count( 'error' ) == 0 && $left_add_counter < 1;
		if ( $is_success ) {
			$url = apply_filters( 'woocommerce_add_to_cart_redirect', $url );
			if ( ! $url ) {
				$redirect_mode = adventure_tours_get_option( 'tours_booking_redirect', 'checkout_page' );
				if ( 'checkout_page' == $redirect_mode ) {
					$url = wc_get_checkout_url(); // $url = WC()->cart->get_checkout_url();
				} elseif ( 'cart_page' == $redirect_mode || ( 'same_as_product' == $redirect_mode && get_option( 'woocommerce_cart_redirect_after_add' ) == 'yes' ) ) {
					$url = wc_get_cart_url(); // $url = WC()->cart->get_cart_url();
				}
			}
			$this->added_to_cart( $product );
		}

		if ( $is_ajax_reply ) {
			$ajax_response = array(
				'success' => $is_success,
			);

			if ( $is_success ) {
				if ( $url ) {
					$ajax_response['data'] = array(
						'redirect_url' => $url
					);
				} else {
					wc_add_to_cart_message( $added_to_cart );
				}
			} else {
				$full_errors_set = $booking_form_validation_errors;

				// $wc_notices = WC()->session->get( 'wc_notices', array() );
				// $shopping_cart_errors = !empty( $wc_notices['error'] ) ? $wc_notices['error'] : array();
				$shopping_cart_errors = wc_get_notices( 'error' );
				if ( $shopping_cart_errors ) {
					wc_clear_notices();

					$full_errors_set[ $this->get_error_movement_field( 'quantity' ) ] = array_map(
						function($el){
							if (is_array($el) && isset($el['notice'])) {
								return $el['notice'];
							}
							return $el;
						},
						$shopping_cart_errors
					);
				}

				$ajax_response['data'] = array(
					'errors' => $full_errors_set,
				);
			}

			wp_send_json( $ajax_response );
			wp_die();
		} elseif ( $is_success ) {
			// If has custom URL redirect there
			if ( $url ) {
				wp_safe_redirect( $url );
				exit;
			}

			wc_add_to_cart_message( $added_to_cart );
		} else { // is not ajax and not success
			// saves booking form errors into wc notices set
			/*if ( $booking_form_validation_errors ) {
				foreach( $booking_form_validation_errors as $_field_code => $_field_errors ) {
					foreach ( $_field_errors as $_error_message ) {
						wc_add_notice( $_error_message, 'error' );
					}
				}
			}*/
		}
	}

	public function filter_woocommerce_attribute_label( $label, $key ) {
		$list = $this->get_booking_fields( true );
		if ( isset( $list[$key] ) ) {
			return $list[$key];
		} elseif ( $this->booking_data_prefix_in_order_item ) {
			$cleanKey = preg_replace( '/^' . $this->booking_data_prefix_in_order_item . '/', '', $key );
			if ( $cleanKey != $key && isset( $list[ $cleanKey ] ) ) {
				return $list[$cleanKey];
			}
		}
		return $label;
	}

	/**
	 * Loads booking form fileds for each tour added to shopping cart.
	 */
	public function filter_woocommerce_get_cart_item_from_session( $item, $values, $item_key ) {
		$keys = $this->get_booking_fields();
		foreach ( $keys as $key ) {
			if ( array_key_exists( $key, $values ) ) {
				$item[$key] = $values[$key];
			}
		}
		// date specific price calculation
		if ( ! empty( $item['date'] ) && ! empty( $item['data'] )) {
			$date_price = $this->get_product_date_specific_price( $item['data'], $item['date'] );
			if ( version_compare( WC_VERSION, '3.0.0', '<') ) {
				$item['data']->price = $date_price;
			} else {
				$item['data']->set_price( $date_price );
			}
		}


		return $item;
	}

	protected function get_product_date_specific_price( $product, $date ) {
		if ( ! $product ) {
			return '';
		}

		$is_wc_older_than_30 = version_compare( WC_VERSION, '3.0.0', '<');
		if ( $product->is_type( 'variation' ) ) {
			$check_product = $is_wc_older_than_30 
				? $product->parent 
				: wc_get_product( $product->get_parent_id() );

		} else {
			$check_product = $product;
		}

		// hack to avoid issue with WOOCS (WooCommerce Currency Switcher) plugin
		// and 'get_price' method when 'Is multiple allowed' option is enabled
		// price converted 2 times in this case
		$unset_block_price_flag = false;
		if ( isset( $GLOBALS['WOOCS'] ) && ! isset( $_REQUEST['woocs_block_price_hook'] ) ) {
			$_REQUEST['woocs_block_price_hook'] = 1;
			$unset_block_price_flag = true;
		}

		$result = $product->get_price();

		if ( $unset_block_price_flag ) {
			unset($_REQUEST['woocs_block_price_hook']);
		}

		if ( $date && $check_product->is_type( 'tour' ) ) {
			$pid = $is_wc_older_than_30 ? $check_product->id : $check_product->get_id();
			return adventure_tours_di( 'tour_booking_service' )->calculate_date_price( $pid, $date, $result );
		}
		return $result;
	}

	/**
	 * Filter for rendering tour booking attributes on the cart and checkout page.
	 *
	 * @param  array $current_set
	 * @param  assoc $cart_item
	 * @return array
	 */
	public function filter_woocommerce_get_item_data( $current_set, $cart_item ){
		/*$is_tour = $cart_item['data'] && $cart_item['data']->is_type('tour');
		if ( ! $is_tour ) {
			return $current_set;
		}*/

		$keys = $this->get_booking_fields( true );
		foreach ( $keys as $key => $label ) {
			if ( array_key_exists( $key, $cart_item ) && ! empty( $cart_item[$key] ) ) {
				$current_set[] = array(
					'name' => $label,
					'value' => $this->format_booking_field_value( $key, $cart_item[$key] ),
				);
			}
		}

		return $current_set;
	}

	public function format_booking_field_value( $field_key, $raw_value ) {
		if ( 'date' == $field_key ) {
			return $this->convert_date_for_human( $raw_value );
		}
		return $raw_value;
	}

	/**
	 * Saves tour related data assigned to shopping cart item to order item.
	 * Works for WooCommerce < 3.0.0.
	 *
	 * @see init
	 *
	 * @param  string $item_id
	 * @param  assoc  $values  shopping cart item meta
	 * @return void
	 */
	public function filter_woocommerce_add_order_item_meta( $item_id, $values ) {
		$new_order_item_metas = $this->_convert_cart_item_meta_to_order_item_meta( $values );

		if ( $new_order_item_metas ) {
			foreach ( $new_order_item_metas as $_key => $_val ) {
				wc_add_order_item_meta( $item_id, $_key, $_val );
			}
		}
	}

	/**
	 * Saves tour related data assigned to shopping cart item to order item.
	 * Works for WooCommerce >= 3.0.0.
	 *
	 * @see init
	 *
	 * @param  WC_Order_Item_Product    $item
	 * @param  string                   $cart_item_key shpping cart item key
	 * @param  assoc                    $cart_item     shopping cart item
	 * @param  WC_Order                 $order
	 * @return void
	 */
	public function filter_woocommerce_checkout_create_order_line_item( $item, $cart_item_key, $cart_item, $order ) {
		$new_order_item_metas = $this->_convert_cart_item_meta_to_order_item_meta( $cart_item );

		if ( $new_order_item_metas ) {
			foreach ( $new_order_item_metas as $_key => $_val ) {
				$item->add_meta_data( $_key, $_val );
			}
		}
	}

	/**
	 * Converts cart item meta fields related to tour booking to assoc for order item meta fields.
	 *
	 * @param  assoc  $cart_item
	 * @return assoc
	 */
	protected function _convert_cart_item_meta_to_order_item_meta( $cart_item ) {
		$result = array();

		$keys = $this->get_booking_fields();
		foreach ( $keys as $key ) {
			if ( ! empty( $cart_item[$key] ) ) {
				$value = $cart_item[$key];
				if ( 'date' == $key ) {
					if ( $formatted_value = $this->convert_date( $this->get_date_format(), $value ) ) {
						$value = $formatted_value;
					}
				}
				$result[ $this->booking_data_prefix_in_order_item . $key ] = $value;
			}
		}

		return $result;
	}

	/**
	 * Filters notices that should be rendered above the booking form.
	 *
	 * @return assoc
	 */
	public function filter_notices( $notices )
	{
		$all_notices = WC()->session->get( 'wc_notices', array() );
		if ( $all_notices ) {
			$notice_types = apply_filters( 'woocommerce_notice_types', array( 'error', 'success', 'notice' ) );
			foreach ( $all_notices as $notice_type => $set ) {
				if ( ! in_array( $notice_type, $notice_types ) ) {
					continue;
				}
				if ( empty( $notices[ $notice_type ] ) ) {
					$notices[ $notice_type ] = $set;
				} else {
					$notices[ $notice_type ] = array_merge( $notices[ $notice_type ], $set );
				}
			}
			wc_clear_notices();
		}
		return $notices;
	}

	/**
	 * Handler for 'woocommerce_check_cart_items' action.
	 * Checks if tour items in shopping cart is still available for booking.
	 *
	 * @return boolean
	 */
	public function action_woocommerce_check_cart_items() {
		$result = true;

		$cart = WC()->cart;
		$items = $cart->get_cart();
		foreach ( $items as $sc_item_key => $sc_item ) {
			if ( $sc_item['data'] && $sc_item['data']->is_type( 'tour' ) ) {
				//need check if item can be added to the cart
				if ( ! $this->is_still_valid_tour_cart_item( $sc_item ) ) {
					$tour_date = isset( $sc_item['date'] ) ? $sc_item['date'] : null;
					$tour_title = $sc_item['data']->get_title();

					if ( $tour_date ) {
						$notice_text = sprintf(
							__( 'Sorry, "%s" on %s is no longer available, so it has been removed from your cart.', 'adventure-tours' ),
							$tour_title,
							$this->convert_date_for_human( $tour_date )
						);
					} else {
						$notice_text = sprintf(
							__( 'Sorry, "%s" is no longer available, so it has been removed from your cart.', 'adventure-tours' ),
							$tour_title
						);
					}

					$cart->set_quantity( $sc_item_key, 0 );
					wc_add_notice( $notice_text, 'error' );

					$result = false;
				}
			}
		}
		return $result;
	}

	/**
	 * Checks if schopping cart item with tour booking item is still available for booking.
	 *
	 * @param  assoc   $sc_item shopping cart item
	 * @return boolean
	 */
	public function is_still_valid_tour_cart_item( $sc_item ) {
		$tour = isset( $sc_item['data'] ) ? $sc_item['data'] : null;
		$tour_date = ! empty( $sc_item['date'] ) ? $sc_item['date'] : null;
		$quantity = ! empty( $sc_item['quantity'] ) ? $sc_item['quantity'] : 1;

		if ( $tour && $tour_date ) {
			$available_dates_stock = $this->get_booking_dates( $tour );
			$all_available_tickets = ! empty( $available_dates_stock[ $tour_date ] ) ? $available_dates_stock[ $tour_date ] : null;
			if ( $all_available_tickets < 1 ) {
				return false;
			}

			// $all_available_tickets = $this->get_open_tour_tickets( $tour, $tour_date, ! empty( $sc_item['variation_id'] ) ? $sc_item['variation_id'] : 0 );
			$other_for_same_tour_date_in_cart = $this->get_count_tickets_in_cart( $tour, $tour_date ) - $quantity;
			if ( $quantity > ( $all_available_tickets - $other_for_same_tour_date_in_cart ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Determines if the form has few quantity fields for different variations
	 * to allow add few variations during single submission.
	 *
	 * @param  WC_Product  $product
	 * @return boolean
	 */
	protected function is_mutli_quantity_form( $product ) {
		return count( $this->get_quantity_field_expand_values( $product ) ) > 0;
	}

	protected function get_expand_quantity_attribute_name( $product ){
		return $this->expand_quantity_attribute;
	}

	/**
	 * Returns set of variation attribute values that should be used to create quantity fields.
	 *
	 * @param  WP_Product  $product
	 * @param  boolean     $with_labels
	 * @param  boolean     $without_cache
	 * @return array
	 */
	protected function get_quantity_field_expand_values( $product, $with_labels = true, $without_cache = false ) {
		$result = array();

		$expand_attribute = $this->get_expand_quantity_attribute_name( $product );
		if ( $expand_attribute ) {
			$cache_key = $this->make_product_state_key( 'qexpand_attr_' . $expand_attribute, $product );
			$cached_result = $without_cache ? null : $this->get_state( $cache_key );

			if ( $cached_result !== null ) {
				$result = $cached_result;
			} else {
				if ( $product && $product->is_type( 'tour' ) && $product->is_variable_tour() ) {
					$variation_attributes = $product->get_variation_attributes();
					if ( isset( $variation_attributes[ $expand_attribute ] ) ) {
						$set = $variation_attributes[ $expand_attribute ];

						$terms = wc_get_product_terms( $product->get_id(), $expand_attribute, array( 'fields' => 'all' ) );
						foreach ( $terms as $term ) {
							if ( in_array( $term->slug, $set ) ) {
								$result[ $term->slug ] = apply_filters( 'woocommerce_variation_option_name', $term->name );
							}
						}
					}
				}

				$this->set_state( $cache_key, $result );
			}
		}

		return ! $with_labels && $result ? array_keys( $result ) : $result;
	}

	protected function get_product_item_variation_data( $product ) {
		$result = array();

		$request_data = $this->get_field_values( $product );

		$tour_data = array();
		$booking_fields = $this->get_booking_fields();
		if ( $booking_fields ) {
			foreach( $booking_fields as $field_key ) {
				if ( isset( $request_data[ $field_key ] ) ) {
					$tour_data[ $field_key ] = $request_data[ $field_key ];
				}
			}
		}

		$quantity_keys = $this->is_mutli_quantity_form( $product ) ? $this->get_quantity_field_expand_values( $product ) : array();
		if ( $quantity_keys ) {
			$variation_attribute_code = 'attribute_' . $this->expand_quantity_attribute;
			foreach ( $quantity_keys as $_cur_key => $_cur_label ) {
				$q_field = 'quantity_' . $_cur_key;
				$quantity = isset( $request_data[ $q_field ] ) ? $request_data[ $q_field ] : 0;
				if ( $quantity < 1 ) {
					continue;
				}

				$var_id_field = 'variation_id_' . $_cur_key;
				$variation_id = isset( $request_data[ $var_id_field ] ) ? $request_data[ $var_id_field ] : 0;

				$variation_data = $this->get_variation_data_for_product_variation(
					$product,
					$variation_id,
					array_merge(
						$request_data,
						array('attribute_' . $this->expand_quantity_attribute => $_cur_key)
					)
				);

				if ( is_a( $variation_data, 'WP_Error' ) ) {
					// if ( $variation_data->has_errors() ) throw new Exception( join("\n", $variation_data->get_error_messages()) );
					// throw new Exception( 'Incorrect variaion id value.' );
					continue;
				}

				if ( ! $variation_data ) {
					// throw new Exception( 'Incorrect variaion id value.' );
					continue;
				}

				$result[] = array(
					'quantity' => $quantity,
					'tour_data' => $tour_data,
					'variation_id' => $variation_id,
					'variations' => $variation_data,
					'quantity_field_key' => $q_field,
				);
			}
		} else {
			$use_stateless_mode = true;
			if ( $use_stateless_mode ) {
				$variation_id = isset( $request_data['variation_id'] ) ? $request_data['variation_id'] : 0;
				$is_simple_product = empty( $variation_id );
				$variation_data = $is_simple_product
					? array() 
					: $this->get_variation_data_for_product_variation( $product, $variation_id, $request_data );

				if ( $is_simple_product || ( $variation_data && !is_a( $variation_data, 'WP_Error' ) ) ) {
					$result[] = array(
						'quantity' => isset( $request_data[ 'quantity' ] ) ? $request_data[ 'quantity' ] : 0,
						'tour_data' => $tour_data,
						'variation_id' => $variation_id,
						'variations' => $variation_data,
						'quantity_field_key' => 'quantity',
					);
				}
			} else {
				//TODO remove logic that uses values validated during the validation process.
				$variation_settings = $this->get_state(
					$this->make_product_state_key( 'variations_settings', $product ),
					array(
						'variation_id' => 0,
						'variations' => array()
					)
				);
				$result[] = array(
					'quantity' => isset( $request_data[ 'quantity' ] ) ? $request_data[ 'quantity' ] : 0,
					'tour_data' => $tour_data,
					'variation_id' => $variation_settings['variation_id'],
					'variations' => $variation_settings['variations'],
					'quantity_field_key' => 'quantity',
				);
			}
		}
		return $result;
	}

	/**
	 * Get set of the attribute values related to the particular product variation.
	 *
	 * @param  WC_Product     $product       Product that variation belongs to.
	 * @param  string         $variation_id  Id of the product variation.
	 * @param  assoc          $request_data  Booking form field values.
	 * @return assoc|WP_Error
	 */
	protected function get_variation_data_for_product_variation( $product, $variation_id, $request_data ){
		$result_errors = new WP_Error();
		$variations = array();

		$attributes = $product->get_attributes();
		$variation = wc_get_product( $variation_id );

		if ( ! $variation ) { // incorrect variation id
			$result_errors->add('', esc_html__( 'An error occured, please contact support', 'adventure-tours' ) );
		} elseif ( ! $variation->is_in_stock() ) { // variation marked as out of stock
			$result_errors->add('', esc_html__( 'This option is unavailable', 'adventure-tours' ) );
			// esc_html__( 'This option is out of stock', 'adventure-tours' );
		} elseif ( empty( $attributes ) ) {
			$result_errors->add('', esc_html__( 'Please choose product options', 'adventure-tours' ) . '&hellip;' );
		} else {
			$missing_attributes = array();

			// Attributes verification.
			foreach ( $attributes as $attribute ) {
				if ( ! $attribute['is_variation'] ) {
					continue;
				}

				$taxonomy = 'attribute_' . sanitize_title( $attribute['name'] );
				if ( isset( $request_data[ $taxonomy ] ) ) {
					if ( $attribute['is_taxonomy'] ) {
						// Don't use wc_clean as it destroys sanitized characters.
						$variation_value = sanitize_title( stripslashes( $request_data[ $taxonomy ] ) );
					} else {
						$variation_value = wc_clean( stripslashes( $request_data[ $taxonomy ] ) );
					}

					if ( version_compare( WC_VERSION, '3.0.0', '<') ) {
						$variaton_data = $variation->variation_data;
					} else {
						$variaton_data = wc_get_product_variation_attributes( $variation->get_id() );
					}

					// Gets the value of the attribute in the variation.
					$valid_value = $variaton_data[ $taxonomy ];

					if ( '' === $valid_value || $valid_value === $variation_value ) {
						// if ('' === $valid_value) Check if `$variation_value` is allowed for the attribute.
						$variations[ $taxonomy ] = $variation_value;
						continue;
					}

					// Unsupported attribue value has been passed.
				} else {
					$missing_attributes[] = wc_attribute_label( $attribute['name'] );
				}
			}

			if ( $missing_attributes ) {
				$result_errors->add(
					'',
					sprintf( 
						_n( '%s is a required field', '%s are required fields', sizeof( $missing_attributes ), 'adventure-tours' ),
						wc_format_list_of_items( $missing_attributes )
					)
				);
			}
		}
		return $result_errors->has_errors() ? $result_errors : $variations;
	}

	protected function added_to_cart( $product ) {
	}
}
