<?php

class Brizy_Admin_Rule extends Brizy_Admin_Serializable implements Brizy_Admin_RuleInterface {

	const MODE_UNKNOWN = null;
	const MODE_REFERENCE = 'reference';
	const MODE_SPECIFIC = 'specific';

	const TYPE_INCLUDE = 1;
	const TYPE_EXCLUDE = 2;

	const ALL = 0;
	const POSTS = 1;
	const TAXONOMY = 2;
	const ARCHIVE = 4;
	const TEMPLATE = 8; // author/search/home_page...
	const BRIZY_TEMPLATE = 16; // brizy template
	const WOO_SHOP_PAGE = 256;
	const DATE_ARCHIVE = self::ARCHIVE | 512;
	const YEAR_ARCHIVE = self::DATE_ARCHIVE | 1024;
	const MONTH_ARCHIVE = self::DATE_ARCHIVE | 2048;
	const DAY_ARCHIVE = self::DATE_ARCHIVE | 4096;

	/**
	 * @var int
	 */
	protected $id;

	/**
	 * @var int
	 */
	protected $type;

	/**
	 * @var int
	 */
	protected $appliedFor;

	/**
	 * @var string
	 */
	protected $entityType;

	/**
	 * @var string
	 */
	protected $mode;

	/**
	 * If null the rule will be applied on all entities
	 *
	 * @var int[]
	 */
	protected $entityValues = array();

	/**
	 * @return array|mixed
	 */
	public function jsonSerialize() {
		return $this->convertToOptionValue();
	}

	/**
	 * Brizy_Admin_Rule constructor.
	 *
	 * @param int $id
	 * @param int $type
	 * @param int $applied_for
	 * @param int $entity_type
	 * @param array $entities
	 */
	public function __construct( $id, $type, $applied_for, $entity_type, $entities ) {

		if ( ! $id ) {
			$this->setId( $this->generateId( $type, $applied_for, $entity_type, $this->getEntitiesAsString() ) );
		} else {
			$this->setId( $id );
		}

		$this->setType( $type );
		$this->setAppliedFor( $applied_for );
		$this->setEntityType( $entity_type );
		$this->setEntityValues( array_filter( (array) $entities, array( $this, 'filter' ) ) );
	}

	function filter( $v ) {
		return ! empty( $v );
	}

	/**
	 * Return true if the rule matches for the given parameters
	 *
	 * @param $applyFor
	 * @param $entityType
	 * @param $entityValues
	 *
	 * @return bool
	 */
	public function isMatching( $applyFor, $entityType, $entityValues ) {

		$ruleValues = array(
			$this->getType(),
			$this->getAppliedFor(),
			$this->getEntityType(),
			$this->getEntityValues(),
		);

		$checkValues = array(
			self::TYPE_INCLUDE, // include
			$applyFor,
			$entityType,
			$entityValues,
		);


		// return if the rule is for exclude
//		if($ruleValues[0]!=$checkValues[0]) {
//			return false;
//		}

		$entity_values = $this->getEntityValues();

		// check is the rule is matching all
		if ( $ruleValues[0] == self::TYPE_INCLUDE && ! $ruleValues[1] ) {
			return true;
		}

		// exception for home page that has two behaviors.. as page and as a template
		if ( $applyFor == self::TEMPLATE &&
		     $entityType == 'front_page' &&
		     $this->getAppliedFor() == self::POSTS &&
		     $this->getEntityType() == 'page' &&
		     isset( $entity_values[0] ) &&
		     $entity_values[0] == get_option( 'page_on_front' ) ) {
			return true;
		}

		// check post author
		if ( isset( $entity_values[0] ) && isset( $entityValues[0] ) && ( $values = explode( '|', $entity_values[0] ) ) && count( $values ) == 2  && $this->getEntityType() == $entityType ) {
			if ( $values[0] === 'author' ) {
				if($values[1]=='')
					return true;

				return get_post_field( 'post_author', $entityValues[0] ) == $values[1];
			}

			if ( $applyFor == self::POSTS && $this->getAppliedFor() == self::POSTS && $values[0] === 'in' ) {
				$postTerms = wp_get_post_terms($entityValues[0],$values[1]);
				$postTermIds = array_map(function($t){return $t->term_id;},$postTerms);
				$allTerms = array_map(function($t){return $t->term_id;},get_terms(['taxonomy'=>$values[1] ]));;
				return count(array_intersect($postTermIds,$allTerms));
			}
		}

		// check if post is in a term
		if ( isset( $entity_values[0] ) && ( $values = explode( '|', $entity_values[0] ) ) && count( $values ) == 3 &&  isset($entityValues[0]) && $this->getEntityType() == $entityType ) {

			// POSTS
			if ( $applyFor == self::POSTS && $this->getAppliedFor() == self::POSTS && $values[0] === 'in' ) {
				// check if the post is in taxonomy with name $values[0] and with id $values[1]
				return has_term( $values[2], $values[1], $entityValues[0] );
			}

			// check if post is in a term
			if ( $applyFor == self::POSTS && $this->getAppliedFor() == self::POSTS && $values[0] === 'child' ) {
				// check if the post is in taxonomy with name $values[0] and with id $values[1]
				$tax = get_term_children( $values[2], $values[1] );

				foreach ( $tax as $t ) {
					if ( has_term( $t, $values[1], $entityValues[0] ) ) {
						return true;
					}
				}

				return false;
			}

			// TAXONOMY
			if ( $applyFor == self::TAXONOMY && $this->getAppliedFor() == self::TAXONOMY && $values[0] === 'in' ) {
				return $entityValues[0] == $values[2];
			}

			// check if terms is child of a term
			if ( $applyFor == self::TAXONOMY && $this->getAppliedFor() == self::TAXONOMY && $values[0] === 'child' ) {
				$tax = get_term_children( $values[2], $values[1] );

				return in_array( $entityValues[0], $tax );
			}
		}

		// matching archive rules
		if ( isset( $ruleValues[1] ) && isset( $checkValues[1] ) && ( $ruleValues[1] & self::ARCHIVE ) && ( $checkValues[1] & self::ARCHIVE ) ) {

			if ( $checkValues[1] == $ruleValues[1] ||
			     $ruleValues[1] == self::DATE_ARCHIVE ||
			     $ruleValues[1] == self::YEAR_ARCHIVE ||
			     $ruleValues[1] == self::MONTH_ARCHIVE ||
			     $ruleValues[1] == self::DAY_ARCHIVE ||
			     $ruleValues[1] == self::ARCHIVE ) {
				if ( empty( $ruleValues[2] ) || $ruleValues[2] == $entityType ) {
					return true;
				}
			}


		}

		foreach ( $ruleValues as $i => $value ) {

			if ( $i === 0 ) {
				continue;
			}

			if ( is_array( $value ) ) {
				// this means that the rule accept any value
				if ( count( $ruleValues[ $i ] ) == 0 ) {
					break;
				}

				if(count($checkValues[ $i ])==0 && count($ruleValues[ $i ])!=0) {
					return false;
				}

				// check if the value is contained in this rule
				if ( count( array_diff( $checkValues[ $i ], $ruleValues[ $i ] ) ) != 0 ) {
					return false;
				}

			} else {
				if ( $ruleValues[ $i ] != $checkValues[ $i ] ) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * @param Brizy_Admin_Rule $rule
	 *
	 * @return bool
	 */
	public function isEqual( $rule ) {
		return $this->getType() == $rule->getType() &&
		       $this->getAppliedFor() == $rule->getAppliedFor() &&
		       $this->getEntityType() == $rule->getEntityType() &&
		       ( count( $rule->getEntityValues() ) == count( $this->getEntityValues() ) &&
		         count( array_diff( $rule->getEntityValues(), $this->getEntityValues() ) ) == 0 );
	}

	/**
	 * @return int
	 */
	public function getId() {
		return $this->id;
	}

	/**
	 * @param int $id
	 *
	 * @return Brizy_Admin_Rule
	 */
	public function setId( $id ) {

		$this->id = $id;

		return $this;
	}

	/**
	 * @return int
	 */
	public function getType() {
		return (int) $this->type;
	}

	/**
	 * @param int $type
	 *
	 * @return Brizy_Admin_Rule
	 */
	public function setType( $type ) {
		$this->type = (int) $type;

		return $this;
	}

	/**
	 * @return int
	 */
	public function getAppliedFor() {
		return $this->appliedFor;
	}

	/**
	 * @param int $appliedFor
	 *
	 * @return Brizy_Admin_Rule
	 */
	public function setAppliedFor( $appliedFor ) {
		if ( $val = (int) $appliedFor ) {
			$this->appliedFor = $val;
		}

		return $this;
	}

	/**
	 * @return string
	 */
	public function getEntityType() {
		return $this->entityType;
	}

	/**
	 * @param string $entityType
	 *
	 * @return Brizy_Admin_Rule
	 */
	public function setEntityType( $entityType ) {
		$this->entityType = $entityType;

		return $this;
	}

	/**
	 * @return string[]
	 */
	public function getEntityValues() {
		return array_map( function ( $v ) {
			if ( is_numeric( $v ) ) {
				return (int) $v;
			} else {
				return $v;
			}
		}, $this->entityValues );
	}

	/**
	 * @param int[] $entityValues
	 *
	 * @return Brizy_Admin_Rule
	 */
	public function setEntityValues( $entityValues ) {

		if ( ! is_array( $entityValues ) ) {
			throw new InvalidArgumentException();
		}

		$this->entityValues = array_values( $entityValues );

		return $this;
	}

	/**
	 * @return array
	 */
	public function convertToOptionValue() {
		return array(
			'id'           => $this->getId(),
			'type'         => $this->getType(),
			'appliedFor'   => $this->getAppliedFor(),
			'entityType'   => $this->getEntityType(),
			'entityValues' => $this->getEntityValues(),
			'mode'         => $this->getRuleMode()
		);
	}


	public function getRuleWeight( $context ) {

		$weight = 0;

		if ( $this->getAppliedFor() == self::TEMPLATE && $this->getEntityType() == 'front_page' ) {
			$weight += 20;
		}
		if ( $this->getAppliedFor() == self::TEMPLATE ) {
			$weight += 10;
		}

		if ( $this->getEntityType() === 'product' ) {
			$weight += 30;
		}

		$values = array();

		if ( $this->getType() ) {
			$values[] = $weight = $this->getType();
		}
		if ( $this->getAppliedFor() ) {
			$values[] = $this->getAppliedFor();
		}
		if ( $this->getEntityType() ) {
			$values[] = $this->getEntityType();
		}
		if ( count( $this->getEntityValues() ) > 0 ) {
			$values[] = $this->getEntityType();
		}

		$weight += count( $values );

		if ( isset( $context['type'] ) && $this->getAppliedFor() === $context['type'] ) {
			$weight += 1;
		}

		if ( isset( $context['entityType'] ) && $this->getEntityType() === $context['entityType'] ) {
			$weight += 1;
		}

		if ( isset( $context['entityValues'] ) && $intersection = count(
				array_intersect( $context['entityValues'], $this->getEntityValues() )
			) ) {
			$weight += $intersection;
		}

		return $weight;
	}

	/**
	 * @param $data
	 *
	 * @return Brizy_Admin_Rule|void
	 * @throws Exception
	 */
	static public function createFromSerializedData( $data ) {

		if ( is_null( $data ) ) {
			throw new Exception( 'Invalid parameter provided' );
		}

		return new self(
			isset( $data['id'] ) ? $data['id'] : null,
			isset( $data['type'] ) && ! empty( $data['type'] ) ? $data['type'] : null,
			isset( $data['appliedFor'] ) && ! empty( $data['appliedFor'] ) ? $data['appliedFor'] : null,
			isset( $data['entityType'] ) ? $data['entityType'] : null,
			isset( $data['entityValues'] ) ? $data['entityValues'] : null
		);
	}

	/**
	 * @param $data
	 *
	 * @return Brizy_Admin_Rule
	 * @throws Exception
	 */
	static public function createFromRequestData( $data ) {

		if ( is_null( $data ) ) {
			throw new Exception( 'Invalid parameter provided' );
		}

		return new self(
			isset( $data['id'] ) ? $data['id'] : null,
			isset( $data['type'] ) ? $data['type'] : null,
			isset( $data['appliedFor'] ) ? $data['appliedFor'] : null,
			isset( $data['entityType'] ) ? $data['entityType'] : null,
			isset( $data['entityValues'] ) ? $data['entityValues'] : null
		);
	}

	static public function createFromJsonObject( $json ) {

		if ( is_null( $json ) ) {
			throw new Exception( 'Invalid parameter provided' );
		}

		return new self(
			isset( $json->id ) ? $json->id : null,
			isset( $json->type ) ? $json->type : null,
			isset( $json->appliedFor ) ? $json->appliedFor : null,
			isset( $json->entityType ) ? $json->entityType : null,
			isset( $json->entityValues ) ? $json->entityValues : null
		);
	}

	/**
	 * @return string
	 */
	public function serialize() {
		return serialize( $this->convertToOptionValue() );
	}


	/**
	 * @param string $delimited
	 *
	 * @return string
	 */
	public function getEntitiesAsString( $delimited = ',' ) {
		return implode( $delimited, $this->getEntityValues() );
	}

	/**
	 * @return string
	 */
	private function generateId() {
		return md5( implode( '', func_get_args() ) . rand( 1, time() ) );
	}

	private function getRuleMode() {
		foreach ( $this->entityValues as $item ) {
			if ( strpos( $item, "author" ) === 0 ||
			     strpos( $item, "in" ) === 0 ||
			     strpos( $item, "child" ) === 0 ) {
				return self::MODE_REFERENCE;
			} else {
				return self::MODE_SPECIFIC;
			}
		}

		return self::MODE_UNKNOWN;
	}
}
