<?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(
			$wpdb->prepare(
				"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 %d OFFSET %d",
				$limit,
				$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 ) {
		/*
		 * Only order by score if the score function is added in fields_filter,
		 * which only happens if there are related posts in the post-data.
		 * If ordering by score also order by post ID to keep them consistent in cases where the score is the same
		 * for multiple posts.
		 */
		if ( $this->score_override &&
			is_array( $this->related_postdata ) && count( $this->related_postdata ) ) {
			$arg = $this->orderby_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 );
		}
	}

	/**
	 * Caches the related posts in postmeta.
	 *
	 * @param int   $reference_ID post ID to which we want to find related content
	 * @param array $args see YARPP::display_related()
	 *
	 * @return string (YARPP_NO_RELATED | YARPP_RELATED)
	 */
	protected function update( $reference_ID, $args = array() ) {
		$original_related = $this->related( $reference_ID );
		$related          = $this->query_safely(
			'get_results',
			array(
				$this->sql( $reference_ID, $args ),
				ARRAY_A,
			)
		);
		if ( $related instanceof WP_Error ) {
			return YARPP_DONT_RUN;
		}
		$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(
				$wpdb->prepare(
					"select post_id from `{$wpdb->postmeta}` where meta_key = '" . YARPP_POSTMETA_RELATED_KEY . "' and meta_value regexp 's:2:\"ID\";s:\d+:\"%d\"'",
					$reference_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 ];
	}
}
