<?php
/**
 * Calculate and store the recipe rating.
 *
 * @link       https://bootstrapped.ventures
 * @since      1.22.0
 *
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public
 */

/**
 * Calculate and store the recipe rating.
 *
 * @since      1.22.0
 * @package    WP_Recipe_Maker
 * @subpackage WP_Recipe_Maker/includes/public
 * @author     Brecht Vandersmissen <brecht@bootstrapped.ventures>
 */
class WPRM_Rating {

	/**
	 * Update the rating for the recipes affected by a specific comment.
	 *
	 * @since    1.22.0
	 * @param	 int $comment_id Comment ID to update the rating for.
	 */
	public static function update_recipe_rating_for_comment( $comment_id ) {
		$comment = get_comment( $comment_id );
		$post_id = $comment->comment_post_ID;

		$recipe_ids = WPRM_Recipe_Manager::get_recipe_ids_from_post( $post_id );

		if ( $recipe_ids ) {
			foreach ( $recipe_ids as $recipe_id ) {
				self::update_recipe_rating( $recipe_id );
			}
		}
	}

	/**
	 * Update the rating for a specific recipe.
	 *
	 * @since    1.22.0
	 * @param	 int $recipe_id Recipe ID to to update the rating for.
	 */
	public static function update_recipe_rating( $recipe_id ) {
		$recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );

		$recipe_rating = array(
			'count' => 0,
			'total' => 0,
			'average' => 0,
			'type' => array(
				'comment' => 0,
				'no_comment' => 0,
				'user' => 0,
			),
		);

		if ( ! $recipe ) {
			return $recipe_rating;
		}

		// Update comment ratings without text.
		$parent_post_id = $recipe->parent_post_id();
		if ( $parent_post_id ) {
			self::update_comment_ratings_no_text( $parent_post_id );
		}

		// Get ratings.
		$ratings = self::get_ratings_for( $recipe_id );

		foreach ( $ratings['ratings'] as $rating ) {
			$recipe_rating['count']++;
			$recipe_rating['total'] += intval( $rating->rating );

			if ( 0 < intval( $rating->recipe_id ) ) {
				$recipe_rating['type']['user']++;
			} else {
				if ( '1' === $rating->has_comment ) {
					$recipe_rating['type']['comment']++;
				} else {
					$recipe_rating['type']['no_comment']++;
				}
			}
		}

		// Calculate average.
		if ( $recipe_rating['count'] > 0 ) {
			$recipe_rating['average'] = ceil( $recipe_rating['total'] / $recipe_rating['count'] * 100 ) / 100;
		}

		// Update recipe rating and average (to sort by).
		update_post_meta( $recipe_id, 'wprm_rating', $recipe_rating );
		update_post_meta( $recipe_id, 'wprm_rating_average', $recipe_rating['average'] );
		update_post_meta( $recipe_id, 'wprm_rating_count', $recipe_rating['count'] );

		if ( class_exists( 'WPRM_Metadata' ) ) {
			WPRM_Metadata::invalidate_metadata_for_recipe( $recipe_id );
		}

		// Update parent post with rating data (TODO account for multiple recipes in a post).
		if ( $parent_post_id ) {
			update_post_meta( $parent_post_id, 'wprm_rating', $recipe_rating );
			update_post_meta( $parent_post_id, 'wprm_rating_average', $recipe_rating['average'] );
			update_post_meta( $parent_post_id, 'wprm_rating_count', $recipe_rating['count'] );
		}

		// Update SEO checker if this is the first rating.
		if ( 1 === $recipe_rating['count'] ) {
			WPRM_Seo_Checker::update_seo_for( $recipe_id );
		}		

		return $recipe_rating;
	}

	/**
	 * Update the comment ratings without text.
	 *
	 * @since    9.5.0
	 * @param	 int $post_id Post ID to check the comment ratings for.
	 */
	public static function update_comment_ratings_no_text( $post_id ) {
		$post_id = intval( $post_id );

		$ratings = WPRM_Rating_Database::get_ratings( array(
			'where' => 'post_id = ' . $post_id . ' AND approved = 1',
		) );

		$comment_ratings_without_text = array();
		$ratings_need_migration = array();

		foreach ( $ratings['ratings'] as $rating ) {
			// Keep track of comments without text.
			if ( is_null( $rating->has_comment ) && '0' !== $rating->comment_id ) {
				$ratings_need_migration[] = $rating;
			} else {
				if ( '0' === $rating->has_comment ) {
					$comment_ratings_without_text[] = $rating->comment_id;
				}
			}
		}

		// Check if there are comment ratings that haven't been checked for text yet.
		if ( $ratings_need_migration ) {
			$comment_ids = wp_list_pluck( $ratings_need_migration, 'comment_id' );

			$comments = get_comments( array(
				'comment__in' => $comment_ids,
			) );

			// Check which comments have text.
			$comments_status = array();

			foreach ( $comments as $comment ) {
				$comment_text = trim( $comment->comment_content );

				if ( $comment_text ) {
					$comments_status[ $comment->comment_ID ] = '1';
				} else {
					$comments_status[ $comment->comment_ID ] = '0';
				}
			}

			$update_rating_database_has_comment = array();
			$update_rating_database_no_comment = array();
			foreach ( $ratings_need_migration as $rating ) {
				if ( isset( $comments_status[ $rating->comment_id ] ) ) {
					if ( '1' === $comments_status[ $rating->comment_id ] ) {
						$update_rating_database_has_comment[] = $rating->id;
					} else {
						$update_rating_database_no_comment[] = $rating->id;
						$comment_ratings_without_text[] = $rating->comment_id;
					}
				}
			}

			global $wpdb;
			$table_name = WPRM_Rating_Database::get_table_name();

			// Update ratings in database.
			if ( $update_rating_database_has_comment ) {
				$wpdb->query( $wpdb->prepare(
					"UPDATE `%1s`
					SET has_comment = '1'
					WHERE id IN (" . implode( ', ', array_fill( 0, count( $update_rating_database_has_comment ), '%d' ) ) . ")",
					array_merge( array( $table_name ), $update_rating_database_has_comment )
				) );
			}
			if ( $update_rating_database_no_comment ) {
				$wpdb->query( $wpdb->prepare(
					"UPDATE `%1s`
					SET has_comment = '0'
					WHERE id IN (" . implode( ', ', array_fill( 0, count( $update_rating_database_no_comment ), '%d' ) ) . ")",
					array_merge( array( $table_name ), $update_rating_database_no_comment )
				) );
			}
		}

		update_post_meta( $post_id, 'wprm_rating_no_text_comments', $comment_ratings_without_text );

		return $comment_ratings_without_text;
	}

	/**
	 * Get the ratings for a specific recipe.
	 *
	 * @since    2.2.0
	 * @param	 int $recipe_id Recipe ID to to get the ratings for.
	 */
	public static function get_ratings_for( $recipe_id ) {
		$recipe = WPRM_Recipe_Manager::get_recipe( $recipe_id );

		$ratings = array(
			'total' => 0,
			'ratings' => array(),
		);
		$query_where = '';

		// Prevent error when recipe doesn't exist.
		if ( ! $recipe ) {
			return $ratings;
		}

		// Get comment ratings.
		if ( WPRM_Settings::get( 'features_comment_ratings' ) || ( WPRM_Addons::is_active( 'premium' ) && WPRM_Settings::get( 'features_user_ratings' ) ) ) {
			if ( WPRM_Migrations::is_migrated_to( 'ratings_db_post_id' ) ) {
				// Can be comment ratings both to recipe itself and its parent post.
				$post_ids = array();

				$user_ratings_enabled = WPRM_Addons::is_active( 'premium' ) && WPRM_Settings::get( 'features_user_ratings' );
				$include_recipe_post_ratings = WPRM_Settings::get( 'post_type_comments' ) || $user_ratings_enabled;
				if ( 'public' === WPRM_Settings::get( 'post_type_structure' ) && $include_recipe_post_ratings ) {
					$post_ids[] = $recipe_id;
				}

				$parent_post_id = $recipe->parent_post_id();
				if ( $parent_post_id ) {
					$post_ids[] = $parent_post_id;
				}

				if ( $post_ids ) {
					$where_comments = 'approved = 1 AND post_id IN (' . implode( ', ', array_map( 'intval', $post_ids ) ) . ')';
					$query_where .= $query_where ? ' OR ' . $where_comments : $where_comments;
				}
			} else {
				$comments = get_approved_comments( $recipe->parent_post_id() );
				$comment_ids = array_map( 'intval', wp_list_pluck( $comments, 'comment_ID' ) );

				if ( count( $comment_ids ) ) {
					$where_comments = 'comment_id IN (' . implode( ',', $comment_ids ) . ')';
					$query_where .= $query_where ? ' OR ' . $where_comments : $where_comments;
				}
			}
		}

		// Get user ratings.
		if ( WPRM_Addons::is_active( 'premium' ) && WPRM_Settings::get( 'features_user_ratings' ) ) {
			$where_recipe = 'recipe_id = ' . intval( $recipe_id );
			$query_where .= $query_where ? ' OR ' . $where_recipe : $where_recipe;
		}

		if ( $query_where ) {
			$rating_args = array(
				'where' => $query_where,
			);
			$ratings = WPRM_Rating_Database::get_ratings( $rating_args );
		}

		return $ratings;
	}

	/**
	 * Get the ratings summary for a specific recipe.
	 *
	 * @since    5.0.0
	 * @param	 int $recipe_id Recipe ID to to get the ratings summary for.
	 */
	public static function get_ratings_summary_for( $recipe_id ) {
		$ratings = array(
			'count' => get_post_meta( $recipe_id, 'wprm_rating_count', true ),
			'average' => get_post_meta( $recipe_id, 'wprm_rating_average', true ),
			'comment_ratings' => false,
			'user_ratings' => false,
		);

		if ( WPRM_Settings::get( 'features_comment_ratings' ) || ( WPRM_Addons::is_active( 'premium' ) && WPRM_Settings::get( 'features_user_ratings' ) ) ) {
			$count = 0;
			$total = 0;

			$comment_ratings = WPRM_Comment_Rating::get_ratings_for( $recipe_id );
			foreach ( $comment_ratings as $comment_rating ) {
				$count++;
				$total += intval( $comment_rating->rating );
			}

			if ( $count ) {
				$ratings['comment_ratings'] = array(
					'count' => $count,
					'average' => ceil( $total / $count * 100 ) / 100,
				);
			}
		}

		if ( WPRM_Addons::is_active( 'premium' ) && WPRM_Settings::get( 'features_user_ratings' ) ) {
			$count = 0;
			$total = 0;

			$user_ratings = WPRMP_User_Rating::get_ratings_for( $recipe_id );
			foreach ( $user_ratings as $user_rating ) {
				$count++;
				$total += intval( $user_rating->rating );
			}

			if ( $count ) {
				$ratings['user_ratings'] = array(
					'count' => $count,
					'average' => ceil( $total / $count * 100 ) / 100,
				);
			}
		}

		return $ratings;
	}

	/**
	 * Get the formatted rating.
	 *
	 * @since    7.2.0
	 * @param	 mixed 	$rating Rating to display.
	 * @param	 int 	$decimals Decimals to use for the average.
	 */
	public static function get_formatted_rating( $rating, $decimals = 2 ) {
		$formatted = '';

		if ( $rating ) {
			$nbr_ratings = isset( $rating['count'] ) ? intval( $rating['count'] ) : 0;

			if ( 0 === $nbr_ratings ) {
				$text = WPRM_Settings::get( 'rating_details_zero' );
			} elseif ( 1 === $nbr_ratings ) {
				$text = WPRM_Settings::get( 'rating_details_one' );
			} else {
				$text = WPRM_Settings::get( 'rating_details_multiple' );				
			}

			// Show user voted text, if a user has voted.
			$user_vote = isset( $rating['user'] ) ? intval( $rating['user'] ) : 0;
			if ( 0 < $user_vote ) {
				$user_voted_text = WPRM_Settings::get( 'rating_details_user_voted' );
				$text = str_ireplace( '%not_voted%', '', $text );
				$text = str_ireplace( '%voted%', $user_voted_text, $text );
			} else {
				$user_not_voted_text = WPRM_Settings::get( 'rating_details_user_not_voted' );
				$text = str_ireplace( '%voted%', '', $text );
				$text = str_ireplace( '%not_voted%', $user_not_voted_text, $text );
			}

			// Average decimals.
			$formatted_average = WPRM_Recipe_Parser::format_quantity( $rating['average'], $decimals );
			
			// Replace placeholders.
			$text = str_ireplace( '%average%', '<span class="wprm-recipe-rating-average">' . $formatted_average . '</span>', $text );
			$text = str_ireplace( '%votes%', '<span class="wprm-recipe-rating-count">' . $nbr_ratings . '</span>', $text );
			$text = str_ireplace( '%user%', '<span class="wprm-recipe-rating-user">' . $user_vote . '</span>', $text );

			$formatted = trim( $text );
		}

		return $formatted;
	}

	/**
	 * Safely replace string placeholders in a translated format.
	 *
	 * @since    10.7.1
	 * @param	 string $format Translated format.
	 * @param	 string $fallback Fallback format.
	 * @param	 mixed  $replacements Replacements to use.
	 */
	public static function safe_format_i18n( $format, $fallback, $replacements ) {
		$format = is_scalar( $format ) ? (string) $format : '';
		$fallback = is_scalar( $fallback ) ? (string) $fallback : '';
		$replacements = is_array( $replacements ) ? $replacements : array( $replacements );

		$replacements = array_map(
			function( $replacement ) {
				return is_scalar( $replacement ) ? (string) $replacement : '';
			},
			$replacements
		);

		$using_numbered_placeholders = preg_match( '/%\d+\$s/', $format );

		if ( $using_numbered_placeholders ) {
			foreach ( array_keys( $replacements ) as $index ) {
				if ( false === strpos( $format, '%' . ( $index + 1 ) . '$s' ) ) {
					$format = $fallback;
					$using_numbered_placeholders = false;
					break;
				}
			}
		} elseif ( substr_count( $format, '%s' ) < count( $replacements ) ) {
			$format = $fallback;
		}

		if ( $using_numbered_placeholders ) {
			foreach ( $replacements as $index => $replacement ) {
				$format = str_replace( '%' . ( $index + 1 ) . '$s', $replacement, $format );
			}
		} else {
			foreach ( $replacements as $replacement ) {
				$placeholder = strpos( $format, '%s' );

				if ( false === $placeholder ) {
					break;
				}

				$format = substr_replace( $format, $replacement, $placeholder, 2 );
			}
		}

		return $format;
	}
}
