<?php

/**
 * Class WPML_404_Guess
 *
 * @package    wpml-core
 * @subpackage post-translation
 *
 * @since      3.2.3
 */
class WPML_404_Guess extends WPML_Slug_Resolution {

	/** @var  WPML_Query_Filter $query_filter */
	private $query_filter;

	/**
	 * @param wpdb              $wpdb
	 * @param SitePress         $sitepress
	 * @param WPML_Query_Filter $query_filter
	 */
	public function __construct( &$wpdb, &$sitepress, &$query_filter ) {
		parent::__construct( $wpdb, $sitepress );
		$this->query_filter = &$query_filter;
	}

	/**
	 * Attempts to guess the correct URL based on query vars
	 *
	 * @since 3.2.3
	 *
	 * @param string   $name
	 * @param WP_Query $query
	 *
	 * @return array<string|bool> containing most likely name, type and whether or not a match was found
	 */
	public function guess_cpt_by_name( $name, $query ) {
		$type  = $query->get( 'post_type' );
		$ret   = array( $name, $type, false );
		$types = (bool) $type === false
			? $this->sitepress->get_wp_api()->get_post_types( array( 'public' => true ) )
			: (array) $type;
		if ( (bool) $types === true ) {

			$date_snippet = $this->by_date_snippet( $query );
			$page_first = (bool) $query->get( 'pagename' );

			$cache     = new WPML_WP_Cache( 'WPML_404_Guess' );

			$can_read_private = current_user_can( 'read_private_posts' ) ? '1' : '0';
			$cache_key        = 'guess_cpt' . $name . wp_json_encode( $types ) . $page_first . $date_snippet . $can_read_private;
			$found            = false;
			$ret       = $cache->get( $cache_key, $found );

			if ( ! $found ) {
				$ret = $this->find_post_type( $name, $type, $types, $date_snippet, $page_first );
				$cache->set( $cache_key, $ret );
			}
		}

		return $ret;
	}

	/**
	 * Query the database to find the post type
	 *
	 * @param string $name
	 * @param string $type
	 * @param array  $types
	 * @param string $date_snippet
	 * @param bool $page_first
	 *
	 * @return array
	 */
	private function find_post_type( $name, $type, $types, $date_snippet, $page_first ) {
		$ret    = array( $name, $type, false );
		$where  = $this->wpdb->prepare( 'post_name = %s ', $name );
		$where .= " AND post_type IN ('" . implode( "', '", $types ) . "')";
		$where .= $date_snippet;

		$private_status_clause = current_user_can( 'read_private_posts' )
			? " OR post_status = 'private' "
			: '';

		/** @var \stdClass $res */
		$res    = $this->wpdb->get_row(
			"
										 SELECT post_type, post_name
										 FROM {$this->wpdb->posts} p
										 LEFT JOIN {$this->wpdb->prefix}icl_translations wpml_translations
											ON wpml_translations.element_id = p.ID
											    AND CONCAT('post_', p.post_type) = wpml_translations.element_type
										        AND " . $this->query_filter->in_translated_types_snippet( false, 'p' ) . "
										 WHERE $where
										    AND ( post_status = 'publish' {$private_status_clause}
										        OR ( post_type = 'attachment'
										             AND post_status = 'inherit' ) )
										    " . $this->order_by_type_and_language_snippet( (bool) $date_snippet, $page_first ) . '
									     LIMIT 1'
		);
		if ( (bool) $res === true ) {
			$ret = array( $res->post_name, $res->post_type, true );
		}

		return $ret;
	}


	/**
	 * Retrieves year, month and day parameters from the query if they are set and builds the appropriate sql
	 * snippet to filter for them.
	 *
	 * @param WP_Query $query
	 *
	 * @return string
	 */
	private function by_date_snippet( $query ) {
		$snippet = '';
		foreach ( array(
			'year'     => 'YEAR',
			'monthnum' => 'MONTH',
			'day'      => 'DAY',
		) as $index => $time_unit ) {
			if ( (bool) ( $value = $query->get( $index ) ) === true ) {
				$snippet .= $this->wpdb->prepare( " AND {$time_unit}(post_date) = %d ", $value );
			}
		}

		return $snippet;
	}

	/**
	 * @param bool $has_date
	 * @param bool $page_first
	 *
	 * @return string
	 */
	private function order_by_type_and_language_snippet( $has_date, $page_first ) {
		$lang_order   = $this->get_ordered_langs();
		$current_lang = array_shift( $lang_order );
		$best_score   = count( $lang_order ) + 2;
		$order_by     = sprintf( "ORDER BY post_type = 'page' %s", $page_first ? 'DESC' : 'ASC' );
		if ( $best_score > 2 ) {
			$order_by .= $this->wpdb->prepare(
				', CASE wpml_translations.language_code WHEN %s THEN %d ',
				$current_lang,
				$best_score
			);
			$score     = $best_score - 2;
			foreach ( $lang_order as $lang_code ) {
				$order_by .= $this->wpdb->prepare( ' WHEN %s THEN %d ', $lang_code, $score );
				$score    -= 1;
			}
			$order_by .= ' ELSE 0 END DESC ';
			if ( $has_date ) {
				$order_by .= ", CASE p.post_type WHEN 'post' THEN 0 ELSE 1 END ";
			}
			$order_by .= ' , ' . $this->order_by_post_type_snippet();
		}

		return $order_by;
	}

	/**
	 *
	 * @return string
	 */
	private function order_by_post_type_snippet() {
		$post_types = array(
			'page' => 2,
			'post' => 1,
		);
		$order_by   = ' CASE p.post_type ';
		foreach ( $post_types as $type => $score ) {
			$order_by .= $this->wpdb->prepare( ' WHEN %s THEN %d ', $type, $score );
		}
		$order_by .= ' ELSE 0 END DESC ';

		return $order_by;
	}
}
