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

/**
 * This class should be called by each units when necessary, to register the event to track
 *
 * @class AEPC_Track
 * @static
 */
class AEPC_Track {

	static $standard_events = array(

		'ViewContent'          => 'value, currency, content_category, content_name, content_type, content_ids',
		'Search'               => 'value, currency, content_category, content_ids, search_string',
		'AddToCart'            => 'value, currency, content_category, content_name, content_type, content_ids',
		'AddToWishlist'        => 'value, currency, content_category, content_name, content_type, content_ids',
		'InitiateCheckout'     => 'value, currency, content_name, content_category, content_type, content_ids, num_items',
		'AddPaymentInfo'       => 'value, currency, content_category, content_ids',
		'Purchase'             => 'value, currency, content_name, content_type, content_ids, num_items',
		'Lead'                 => 'value, currency, content_name, content_category',
		'CompleteRegistration' => 'value, currency, content_name, status',
		'CustomEvent'          => 'value, currency, content_name, content_category, content_type, content_ids',

		'Contact'           => '',
		'CustomizeProduct'  => '',
		'Donate'            => '',
		'FindLocation'      => '',
		'PageView'          => '',
		'Schedule'          => '',
		'StartTrial'        => 'currency, predicted_ltv, value',
		'SubmitApplication' => '',
		'Subscribe'         => 'currency, predicted_ltv, value',

	);

	/**
	 * The list of all events to track, structured as fbq() function requests in javascript code
	 *
	 * @var array
	 */
	static $tracked = array(
		'track' => array(),
		'trackCustom' => array()
	);

	/**
	 * Save the tracking event request, used after to include the code
	 *
	 * @param string $event The event name. If it's not standard one, it automatically will be register among trackCustom
	 * @param array $args Standard parameters, one of registered for each standard events. If event is a custom one, automatically they will be custom parameters
	 * @param array $custom_params Custom additional parameters defined by user, if event is a standard one
	 * @param int|bool $delay Possible delay to postpone pixel firing on frontend
	 *
	 * @return string The track code if necessary
	 */
	public static function track( $event, $args = array(), $custom_params = array(), $delay = false ) {
		$event_params = array();

		// Standard event
		if ( self::is( 'standard', $event ) ) {
			foreach ( self::get_standard_event_fields( $event ) as $field ) {
				if ( isset( $args[ $field ] ) ) {
					$event_params[ $field ] = self::sanitize_field( $field, $args );
				}
			}
		}

		// Custom event
		else {
			if ( empty( $custom_params ) && ! empty( $args ) ) {
				$custom_params = array_filter( $args );
			} else {
				$event_params = array_filter( $args );
			}
		}

		// Manage custom parameters
		if ( ! empty( $custom_params ) ) {
			foreach ( $custom_params as $param_key => $param_value ) {
				if ( ! empty( $param_key ) && ! empty( $param_value ) ) {
					$event_params[ $param_key ] = $param_value;
				}
			}
		}

		// Set or detect delay from event name.
		if ( false === $delay ) {
			$delay = self::detect_delay_firing( $event );
		}

		/**
		 * Define placeholders to set dynamic values
		 *
		 * Save {{placeholder}} in the field text of the conversions/events form. The "placeholder" key you will use
		 * must be defined in a key of this array and then it will be translated in the value you set for that key.
		 */
		$placeholder_format = apply_filters( 'aepc_event_placeholder_format', '{{%s}}' );
		$placeholders = apply_filters( 'aepc_event_placeholders', array(), $event, $event_params );

		// Apply the placeholder format to the keys.
		foreach ( $placeholders as $key => $value ) {
			$placeholders[ sprintf( $placeholder_format, $key ) ] = $value;
			unset( $placeholders[ $key ] );
		}

		// Resolve eventual &amp; or other HTMl entities comes back from database
		$event_params = self::unescape_values( $event_params );

		// Translate all placeholders in the params array.
		$event_params = json_decode( str_replace( array_keys( $placeholders ), array_values( $placeholders ), wp_json_encode( $event_params ) ), true );

		$track_type = self::get_track_type( $event );
		$track_data = apply_filters( 'aepc_track_event_data', array( 'params' => $event_params, 'delay' => $delay ), $event );

		// Register event track
		if ( ! isset( self::$tracked[ $track_type ][ $event ] ) ) {
			self::$tracked[ $track_type ][ $event ] = array( $track_data );
		} else {
			self::$tracked[ $track_type ][ $event ][] = $track_data;
		}

		return self::get_track_code( $event, count( self::$tracked[ $track_type ][ $event ] )-1 );

	}

	/**
	 * Remove a registered event
	 *
	 * @param $event
	 * @param int $index
	 */
	public static function remove_event( $event, $index = null ) {
		if ( is_null( $index ) ) {
			unset( self::$tracked[ self::get_track_type( $event ) ][ $event ] );
		}

		else {
			if ( 'last' === $index ) {
				$index = count( self::$tracked[ self::get_track_type( $event ) ][ $event ] )-1;
			}

			unset( self::$tracked[ self::get_track_type( $event ) ][ $event ][ $index ] );
		}

		// Completely remove event if it remains empty
		if ( empty( self::$tracked[ self::get_track_type( $event ) ][ $event ] ) ) {
			unset( self::$tracked[ self::get_track_type( $event ) ][ $event ] );
		} else {
			self::$tracked[ self::get_track_type( $event ) ][ $event ] = array_values( self::$tracked[ self::get_track_type( $event ) ][ $event ] );
		}
	}

	/**
	 * Return the tracked standard events
	 *
	 * @return array
	 */
	public static function get_standard_events() {
		return array_filter( self::$tracked['track'] );
	}

	/**
	 * Return the tracked standard events
	 *
	 * @return array
	 */
	public static function get_custom_events() {
		return array_filter( self::$tracked['trackCustom'] );
	}

	/**
	 * Clear all event tracked
	 */
	public static function reset_events() {
		self::$tracked = array(
			'track' => array(),
			'trackCustom' => array()
		);
	}

	/**
	 * Return 'track' or 'trackCustom', in base of event passed by parameter
	 *
	 * @param $track_name
	 *
	 * @return string
	 */
	public static function get_track_type( $track_name ) {
		return self::is( 'standard', $track_name ) ? 'track' : 'trackCustom';
	}

	/**
	 * Get track code formatted
	 *
	 * @param string $track_name The event name to track
	 * @param int $index The eventual index if there are many events to track for the same name
	 *
	 * @return string
	 */
	public static function get_track_code( $track_name = '', $index = 0 ) {
		$track_type = self::get_track_type( $track_name );
		$args = "aepc_extend_args(" . wp_json_encode( (object) self::$tracked[ $track_type ][ $track_name ][ $index ]['params'], JSON_PRETTY_PRINT ) . ")";

		return 'fbq(' . implode( ', ', array_filter( array(
			"'{$track_type}'",
			"'{$track_name}'",
			$args
		) ) ) . ');';
	}

	/**
	 * Get track URL
	 *
	 * @param string $track_name The event name to track
	 * @param int $index The eventual index if there are many events to track for the same name
	 *
	 * @return string
	 */
	public static function get_track_url( $track_name = '', $index = 0 ) {
		$track_type = self::get_track_type( $track_name );
		$args = self::$tracked[ $track_type ][ $track_name ][ $index ];
		$cd = '';

		// Structure the arguments list
		if ( ! empty( $args ) ) {
			foreach ( $args as $key => &$value ) {
				if ( is_array( $value ) ) {
					$value = json_encode( $value );
				}

				$value = "cd[{$key}]={$value}";
			}

			$cd = '&' . implode( '&', $args );
		}

		return sprintf( 'https://www.facebook.com/tr?id=%1$s&ev=%2$s%3$s&noscript=1', PixelCaffeine()->get_pixel_id(), $track_name, $cd );
	}

	/**
	 * Detect if the event in parameter is standard or custom one
	 *
	 * @param string $what One of 'standard' or 'custom'
	 * @param string $event Name of event
	 *
	 * @return bool
	 */
	public static function is( $what, $event ) {
		$match = $event != 'CustomEvent' && in_array( $event, array_keys( self::$standard_events ) );
		return 'standard' == $what ? $match : !$match;
	}

	/**
	 * Return a sanitized array with all fields allowed for the standard event
	 *
	 * @param string $track_name The event name
	 *
	 * @return array
	 */
	public static function get_standard_event_fields( $track_name ) {
		return isset( self::$standard_events[ $track_name ] ) ? array_map( 'trim', explode( ',', self::$standard_events[ $track_name ] ) ) : array();
	}

	/**
	 * Sanitize some values, for example force array for content_ids and also add cents to value if a currency is set
	 *
	 * @param string $field The field name to check
	 * @param array $args The list of all fields
	 *
	 * @return mixed
	 */
	public static function sanitize_field( $field, $args ) {
		// Currency
		if ( 'value' == $field && ! empty( $args['currency'] ) ) {
			$args[ $field ] = AEPC_Currency::get_amount( $args[ $field ], $args['currency'] );

			// Force two decimals in order to avoid Facebook error
			$args[ $field ] = apply_filters( 'aepc_event_value_parameter_format', number_format( $args[ $field ], 2, '.', false ) );
		}

		// Content ids
		if ( 'content_ids' == $field && ! empty( $args[ $field ] ) && ! is_array( $args[ $field ] ) ) {
			$args[ $field ] = (array) array_map( 'trim', explode( ',', $args[ $field ] ) );
		}

		return $args[ $field ];
	}

	/**
	 * Sanitize all fields of an array with all parameters to use
	 *
	 * @param $params
	 *
	 * @return mixed
	 */
	public static function sanitize_fields( $params ) {
		foreach ( $params as $param_key => &$param_value ) {
			$param_value = self::sanitize_field( $param_key, $params );
		}

		return $params;
	}

	/**
	 * Remove all escaped values from parameters
	 *
	 * @param string|array $value
	 *
	 * @return string|array
	 */
	public static function unescape_values( $value ) {
		if ( is_array( $value ) ) {
			return array_map( [ __CLASS__, 'unescape_values' ], $value );
		}

		// Remove HTML entities
		if ( is_string( $value ) ) {
			$value = html_entity_decode( $value );
		}

		return $value;
	}

	/**
	 * Get all conversion events saved on DB
	 */
	public static function get_conversions_events() {
		return array_map( [__CLASS__, 'get_default_args'], get_option( 'aepc_conversions_events', array() ) );
	}

	/**
	 * Return the arguments on parameter with the defaults if not found
	 *
	 * @param array $args
	 *
	 * @return array
	 */
	public static function get_default_args($args = []) {
		return wp_parse_args($args, [
			'url_condition' => 'contains',
			'js_event_element' => '',
			'js_event_name' => '',
		]);
	}

	/**
	 * Return if DAP events are active
	 *
	 * @return mixed|void
	 */
	public static function is_dpa_active() {
		return 'yes' === get_option( 'aepc_enable_dpa', 'no' );
	}

	/**
	 * Return if ViewContent events is active
	 *
	 * @return mixed|void
	 */
	public static function is_viewcontent_active() {
		return self::is_dpa_active() && 'yes' === get_option( 'aepc_enable_viewcontent', 'no' );
	}

	/**
	 * Return if AddToCart events is active
	 *
	 * @return mixed|void
	 */
	public static function is_addtocart_active() {
		return self::is_dpa_active() && 'yes' === get_option( 'aepc_enable_addtocart', 'no' );
	}

	/**
	 * Return if AddToWishlist events is active
	 *
	 * @return mixed|void
	 */
	public static function is_addtowishlist_active() {
		return self::is_dpa_active() && 'yes' === get_option( 'aepc_enable_addtowishlist', 'no' );
	}

	/**
	 * Return if InitiateCheckout events is active
	 *
	 * @return mixed|void
	 */
	public static function is_initiatecheckout_active() {
		return self::is_dpa_active() && 'yes' === get_option( 'aepc_enable_initiatecheckout', 'no' );
	}

	/**
	 * Return if AddPaymentInfo events is active
	 *
	 * @return mixed|void
	 */
	public static function is_addpaymentinfo_active() {
		return self::is_dpa_active() && 'yes' === get_option( 'aepc_enable_addpaymentinfo' , 'no');
	}

	/**
	 * Return if Purchase events is active
	 *
	 * @return mixed|void
	 */
	public static function is_purchase_active() {
		return self::is_dpa_active() && 'yes' === get_option( 'aepc_enable_purchase', 'no' );
	}

	/**
	 * Return if Purchase events is active
	 *
	 * @return mixed|void
	 */
	public static function is_completeregistration_active() {
		return self::is_dpa_active() && 'yes' === get_option( 'aepc_enable_completeregistration', 'no' );
	}

	/**
	 * Return if Purchase events is active
	 *
	 * @return mixed|void
	 */
	public static function is_search_active() {
		return 'yes' === get_option( 'aepc_enable_search_event', 'yes' );
	}

	/**
	 * Return if the Custom Audiences events are active
	 *
	 * @return mixed|void
	 */
	public static function is_ca_events_active() {
		return 'yes' === get_option( 'aepc_enable_ca_events', 'yes' );
	}

	/**
	 * Return if the Advanced events are active
	 *
	 * @return mixed|void
	 */
	public static function is_advanced_events_active() {
		return self::is_ca_events_active() && 'yes' === get_option( 'aepc_enable_advanced_events', 'yes' );
	}

	/**
	 * Return if the Taxonomy events are active
	 *
	 * @return mixed|void
	 */
	public static function is_taxonomy_events_active() {
		return self::is_ca_events_active() && 'yes' === get_option( 'aepc_enable_taxonomy_events', 'yes' );
	}

	/**
	 * Get the custom fields to track on advanced events
	 *
	 * @return array|mixed|void
	 */
	public static function get_custom_fields_to_track() {
		return self::is_ca_events_active() ? get_option( 'aepc_custom_fields_event', array() ) : array();
	}

	/**
	 * Check if pixel ID is in the right format
	 *
	 * @return bool
	 */
	public static function validate_pixel_id( $pixel_id ) {
		return (bool) ( empty( $pixel_id ) || preg_match( '/[0-9]{15}/', $pixel_id ) );
	}

	/**
	 * Return the delay to apply to pixel firing
	 *
	 * @param $event
	 *
	 * @return int
	 */
	public static function detect_delay_firing( $event ) {
		if ( 'yes' == get_option( 'aepc_enable_pixel_delay' ) && in_array( $event, array( 'PageView', 'ViewContent' ) ) ) {
			$delay = get_option( 'aepc_general_delay_firing', 0 );

		} elseif ( 'yes' == get_option( 'aepc_enable_advanced_pixel_delay' ) && in_array( $event, array( 'AdvancedEvents', 'CustomFields' ) ) ) {
			$delay = get_option( 'aepc_advanced_pixel_delay_firing', 0 );

		} else {
			$delay = 0;
		}

		return $delay;
	}

	/**
	 * Return if DAP events are active
	 *
	 * @return mixed|void
	 */
	public static function can_init_pixel() {
		return 'no' === get_option( 'aepc_no_pixel_init', 'no' );
	}

	/**
	 * Get if the user wants to track the shipping
	 */
	public static function can_track_shipping_costs() {
		return 'yes' === get_option( 'aepc_track_shipping_costs' );
	}

	/**
	 * Say if we can use 'product_group' as content_type for the variable product
	 */
	public static function can_use_product_group() {
		return 'no' === get_option( 'aepc_conversions_no_product_group', 'no' );
	}

	/**
	 * Say if we can track the variation
	 */
	public static function can_track_variations() {
		return 'no' === get_option( 'aepc_no_variation_tracking', 'no' );
	}

	/**
	 * Say if content_ids should contain IDs or SKUs
	 */
	public static function can_use_sku() {
		return 'no' === get_option( 'aepc_force_ids', 'no' );
	}

	/**
	 * Get the standard parameters
	 *
	 * @param $event
	 *
	 * @return array
	 */
	public static function get_standard_params( $event ) {
		return array_values( array_map( 'trim', explode( ',', self::$standard_events[ $event ] ) ) );
	}

	/**
	 * Get the standard parameters according to the option for the parameters filter
	 *
	 * @param $event
	 *
	 * @return array
	 */
	public static function get_allowed_standard_params( $event ) {
		$params = self::get_standard_params( $event );

		if (
			'yes' === get_option( 'aepc_enable_no_value_parameter', 'no' )
			&& in_array( $event, get_option( 'aepc_no_value_parameter', array() ) )
		) {
			unset( $params[ array_search( 'value', $params ) ] );
			unset( $params[ array_search( 'currency', $params ) ] );
		}

		if (
			'yes' === get_option( 'aepc_enable_no_content_parameters', 'no' )
			&& in_array( $event, get_option( 'aepc_no_content_parameters', array() ) )
		) {
			unset( $params[ array_search( 'content_ids', $params ) ] );
			unset( $params[ array_search( 'content_type', $params ) ] );
			unset( $params[ array_search( 'content_name', $params ) ] );
		}

		return array_values( $params );
	}

	/**
	 * Check the parameters of an event according to the settings of the plugin
	 *
	 * @param string $event
	 * @param array $params
	 *
	 * @return mixed
	 */
	public static function check_event_parameters( $event, $params ) {
		$standard_params = self::get_standard_params( $event );
		$allowed_params = self::get_allowed_standard_params( $event );

		// Exclude parameters
		foreach ( $standard_params as $param ) {
			if ( ! in_array( $param, $allowed_params ) ) {
				unset( $params[ $param ] );
			}
		}

		return apply_filters( 'aepc_allowed_standard_event_params', $params, $event );
	}

}
