<?php

class YARPP_Cache_Postmeta extends YARPP_Cache {

	public $name = "postmeta";

	// variables used for lookup
	private $related_postdata = array();
	private $related_IDs = array();

	/**
	 * SETUP/STATUS
	 */
	function __construct( &$core ) {
		parent::__construct( $core );
	}

	public function is_enabled() {
		return true; // always enabled.
	}

	public function setup() {
	}
	
	public function upgrade( $last_version ) {
		if ( $last_version && version_compare('3.4b1', $last_version) > 0 ) {
			// 3.4 moved _yarpp_body_keywords and _yarpp_title_keywords into the single
			// postmeta _yarpp_keywords, so clear out the old data now.
			delete_post_meta_by_key('_yarpp_title_keywords');
			delete_post_meta_by_key('_yarpp_body_keywords');
		}	
	}

	public function cache_status() {
		global $wpdb;
		return $wpdb->get_var("select (count(p.ID)-sum(m.meta_value IS NULL))/count(p.ID)
			FROM `{$wpdb->posts}` as p
			LEFT JOIN `{$wpdb->postmeta}` as m ON (p.ID = m.post_id and m.meta_key = '" . YARPP_POSTMETA_RELATED_KEY . "')
			WHERE p.post_status = 'publish'");
	}

	public function uncached($limit = 20, $offset = 0) {
		global $wpdb;
		return $wpdb->get_col("select SQL_CALC_FOUND_ROWS p.ID
			FROM `{$wpdb->posts}` as p
			LEFT JOIN `{$wpdb->postmeta}` as m ON (p.ID = m.post_id and m.meta_key = '" . YARPP_POSTMETA_RELATED_KEY . "')
			WHERE p.post_status = 'publish' and m.meta_value IS NULL
			LIMIT $limit OFFSET $offset");
	}

	public function stats() {
		global $wpdb;
		return wp_list_pluck($wpdb->get_results("select num, count(*) as ct from (select 0 + if(meta_value = '" . YARPP_NO_RELATED . "', 0, substring(substring_index(meta_value,':',2),3)) as num from `{$wpdb->postmeta}` where meta_key = '" . YARPP_POSTMETA_RELATED_KEY . "') as t group by num order by num asc", OBJECT_K), 'ct');
	}

	/**
	 * MAGIC FILTERS
	 */
	public function where_filter($arg) {
		global $wpdb;
		// modify the where clause to use the related ID list.
		if (!count($this->related_IDs))
			$this->related_IDs = array(0);
		$arg = preg_replace("!{$wpdb->posts}.ID = \d+!","{$wpdb->posts}.ID in (".join(',',$this->related_IDs).")",$arg);

		// if recent is set, add an additional condition
		$recent = $this->core->get_option('recent');
		if ((bool) $recent) $arg .= " and post_date > date_sub(now(), interval {$recent}) ";
		return $arg;
	}

	public function orderby_filter($arg) {
		global $wpdb;
		// only order by score if the score function is added in fields_filter, which only happens
		// if there are related posts in the postdata
		if ($this->score_override &&
		    is_array($this->related_postdata) && count($this->related_postdata))
			return str_replace("$wpdb->posts.post_date","score",$arg);
		return $arg;
	}

	public function fields_filter($arg) {
		global $wpdb;
		if (is_array($this->related_postdata) && count($this->related_postdata)) {
			$scores = array();
			foreach ($this->related_postdata as $related_entry) {
				$scores[] = " WHEN {$related_entry['ID']} THEN {$related_entry['score']}";
			}
			$arg .= ", CASE {$wpdb->posts}.ID" . join('',$scores) ." END as score";
		}
		return $arg;
	}

	public function limit_filter($arg) {
		if ($this->online_limit)
			return " limit {$this->online_limit} ";
		return $arg;
	}

	/**
	 * RELATEDNESS CACHE CONTROL
	 */
	public function begin_yarpp_time($reference_ID) {
		$this->yarpp_time = true;
		// get the related posts from postmeta, and also construct the relate_IDs array
		$this->related_postdata = get_post_meta($reference_ID,YARPP_POSTMETA_RELATED_KEY,true);
		if (is_array($this->related_postdata) && count($this->related_postdata))
			$this->related_IDs = wp_list_pluck( $this->related_postdata, 'ID' );
		add_filter('posts_where',array(&$this,'where_filter'));
		add_filter('posts_orderby',array(&$this,'orderby_filter'));
		add_filter('posts_fields',array(&$this,'fields_filter'));
		add_filter('post_limits',array(&$this,'limit_filter'));
		add_action('pre_get_posts',array(&$this,'add_signature'));
		// sets the score override flag.
		add_action('parse_query',array(&$this,'set_score_override_flag'));
	}

	public function end_yarpp_time() {
		$this->yarpp_time = false;
		$this->related_IDs = array();
		$this->related_postdata = array();
		remove_filter('posts_where',array(&$this,'where_filter'));
		remove_filter('posts_orderby',array(&$this,'orderby_filter'));
		remove_filter('posts_fields',array(&$this,'fields_filter'));
		remove_filter('post_limits',array(&$this,'limit_filter'));
		remove_action('pre_get_posts',array(&$this,'add_signature'));
		// sets the score override flag.
		remove_action('parse_query',array(&$this,'set_score_override_flag'));
	}

	// @return YARPP_NO_RELATED | YARPP_RELATED | YARPP_NOT_CACHED
	public function is_cached($reference_ID) {
		$related = get_post_meta($reference_ID, YARPP_POSTMETA_RELATED_KEY, true);
		if ( YARPP_NO_RELATED === $related )
			return YARPP_NO_RELATED;			
		if ( '' == $related )
			return YARPP_NOT_CACHED;
		return YARPP_RELATED;
	}

	public function clear( $reference_IDs ) {
		$reference_IDs = wp_parse_id_list( $reference_IDs );
	
		if ( !count($reference_IDs) )
			return;
		
		// clear each cache
		foreach( $reference_IDs as $id ) {
			delete_post_meta( $id, YARPP_POSTMETA_RELATED_KEY );
			delete_post_meta( $id, YARPP_POSTMETA_KEYWORDS_KEY );
		}
	}

	// @return YARPP_NO_RELATED | YARPP_RELATED
	// @used by enforce
	protected function update($reference_ID) {
		global $wpdb;

		$original_related = $this->related($reference_ID);
		$related = $wpdb->get_results($this->sql($reference_ID), ARRAY_A);
		$new_related = wp_list_pluck( $related, 'ID' );

		if ( count($new_related) ) {
			update_post_meta($reference_ID, YARPP_POSTMETA_RELATED_KEY, $related);
			if ($this->core->debug) echo "<!--YARPP just set the cache for post $reference_ID-->";

			// Clear the caches of any items which are no longer related or are newly related.
			if (count($original_related)) {
				$this->clear(array_diff($original_related, $new_related));
				$this->clear(array_diff($new_related, $original_related));
			}

			return YARPP_RELATED;
		} else {
			update_post_meta($reference_ID, YARPP_POSTMETA_RELATED_KEY, YARPP_NO_RELATED);

			// Clear the caches of those which are no longer related.
			if (count($original_related))
				$this->clear($original_related);

			return YARPP_NO_RELATED;
		}
	}

	public function flush() {
		delete_post_meta_by_key( YARPP_POSTMETA_RELATED_KEY );
		delete_post_meta_by_key( YARPP_POSTMETA_KEYWORDS_KEY );
	}

	public function related($reference_ID = null, $related_ID = null) {
		global $wpdb;

		if ( !is_int( $reference_ID ) && !is_int( $related_ID ) ) {
			_doing_it_wrong( __METHOD__, 'reference ID and/or related ID must be set', '3.4' );
			return;
		}

		if (!is_null($reference_ID) && !is_null($related_ID)) {
			$results = get_post_meta($reference_ID,YARPP_POSTMETA_RELATED_KEY,true);
			foreach($results as $result) {
				if ($result['ID'] == $related_ID)
					return true;
			}
			return false;
		}

		// return a list of ID's of "related" entries
		if (!is_null($reference_ID)) {
			$results = get_post_meta($reference_ID,YARPP_POSTMETA_RELATED_KEY,true);
			if (!$results || $results == YARPP_NO_RELATED)
				return array();
			return wp_list_pluck( $results, 'ID' );
		}

		// return a list of entities which list this post as "related"
		if (!is_null($related_ID)) {
			return $wpdb->get_col("select post_id from `{$wpdb->postmeta}` where meta_key = '" . YARPP_POSTMETA_RELATED_KEY . "' and meta_value regexp 's:2:\"ID\";s:\d+:\"{$related_ID}\"'");
		}

		return false;
	}

	/**
	 * KEYWORDS CACHE CONTROL
	 */
	 
	// @return (array) with body and title keywords
	private function cache_keywords($ID) {
		$keywords = array(
			'body' => $this->body_keywords($ID),
			'title' => $this->title_keywords($ID)
		);
		update_post_meta($ID, YARPP_POSTMETA_KEYWORDS_KEY, $keywords);
		return $keywords;
	}

	// @param $ID (int)
	// @param $type (string) body | title | all
	// @return (string|array) depending on whether "all" were requested or not
	public function get_keywords( $ID, $type = 'all' ) {
		if ( !$ID = absint($ID) )
			return false;
	
		$keywords = get_post_meta($ID, YARPP_POSTMETA_KEYWORDS_KEY, true);

		if ( empty($keywords) ) // if empty, try caching them first.
			$keywords = $this->cache_keywords($ID);

		if ( empty($keywords) )
			return false;
		
		if ( 'all' == $type )
			return $keywords;
		return $keywords[$type];
	}
}