<?php

class HappyForms_Message_Controller {

	/**
	 * The singleton instance.
	 *
	 * @since 1.0
	 *
	 * @var HappyForms_Message_Controller
	 */
	private static $instance;

	/**
	 * The message post type slug.
	 *
	 * @since 1.0
	 *
	 * @var string
	 */
	public $post_type = 'happyforms-message';

	/**
	 * The parameter name used to identify a
	 * submission form
	 *
	 * @since 1.0
	 *
	 * @var string
	 */
	public $form_parameter = 'happyforms_form_id';

	/**
	 * The parameter name used to identify a
	 * submission form
	 *
	 * @var string
	 */
	public $form_step_parameter = 'happyforms_step';

	/**
	 * The action name used to identify a
	 * message submission request.
	 *
	 * @since 1.0
	 *
	 * @var string
	 */
	public $submit_action = 'happyforms_message';

	/**
	 * The nonce prefix used in forms.
	 *
	 * @since 1.0
	 *
	 * @var string
	 */
	public $nonce_prefix = 'happyforms_message_nonce_';

	/**
	 * The nonce name used in forms.
	 *
	 * @since 1.0
	 *
	 * @var string
	 */
	public $nonce_name = 'happyforms_message_nonce';

	/**
	 * The transient key used to store
	 * the counter of unread messages.
	 *
	 * @since 1.1
	 *
	 * @var string
	 */
	public $counters_transient = 'happyforms_response_counters';

	/**
	 * The reply-and-mark-as-read action name.
	 */
	public $reply_and_mark_action = 'happyforms_reply_and_mark';

	/**
	 * The send-user-email action name.
	 */
	public $send_email_action = 'happyforms_send_user_email';

	public $mark_action = 'happyforms_mark_response';

	/**
	 * The singleton constructor.
	 *
	 * @since 1.0
	 *
	 * @return HappyForms_Message_Controller
	 */
	public static function instance() {
		if ( is_null( self::$instance ) ) {
			self::$instance = new self();
		}

		self::$instance->hook();

		return self::$instance;
	}

	/**
	 * Register hooks.
	 *
	 * @since 1.0
	 *
	 * @return void
	 */
	public function hook() {
		add_action( 'init', array( $this, 'register_post_type' ) );
		add_filter( 'happyforms_responses_page_url', array( $this, 'page_url' ) );
		add_action( 'parse_request', array( $this, 'admin_post' ) );
		add_action( 'admin_init', array( $this, 'admin_post' ) );
		add_action( 'before_delete_post', array( $this, 'before_delete_post' ) );
		add_action( 'delete_post', array( $this, 'delete_post' ) );
		add_action( 'trashed_post', array( $this, 'trashed_post' ) );
		add_action( 'untrashed_post', array( $this, 'untrashed_post' ) );
		add_action( 'happyforms_form_deleted', array( $this, 'form_deleted' ) );
		add_filter( 'happyforms_email_part_visible', array( $this, 'email_part_visible' ), 10, 4 );
		add_filter( 'admin_title', array( $this, 'admin_title' ), 10, 2 );

		// Core multi-step hooks
		add_action( 'happyforms_step', array( $this, 'default_submission_step' ) );
		// Submission preview and review
		add_action( 'happyforms_step', array( $this, 'preview_submission_step' ) );
		add_action( 'happyforms_step', array( $this, 'review_submission_step' ) );
		// Unique IDs
		add_action( 'happyforms_response_created', array( $this, 'response_stamp_unique_id' ), 10, 2 );
		add_action( 'happyforms_submission_success', array( $this, 'notice_append_unique_id' ), 10, 3 );
		// Resend user email link
		add_action( 'wp_ajax_' . $this->send_email_action, array( $this, 'send_user_email' ) );
	}

	public function get_post_fields() {
		$fields = array(
			'post_title' => '',
			'post_type' => $this->post_type,
			'post_status' => 'publish',
		);

		return $fields;
	}

	public function get_meta_fields() {
		$fields = array(
			'form_id' => 0,
			'read' => false,
			'tracking_id' => '',
			'request' => array(),
		);

		return $fields;
	}

	/**
	 * Get the default values of the message post object fields.
	 *
	 * @since 1.0
	 *
	 * @return array
	 */
	public function get_defaults( $group = '' ) {
		$fields = array();

		switch ( $group ) {
			case 'post':
				$fields = $this->get_post_fields();
				break;
			case 'meta':
				$fields = $this->get_meta_fields();
				break;
			default:
				$fields = array_merge(
					$this->get_post_fields(),
					$this->get_meta_fields()
				);
				break;
		}

		return $fields;
	}

	/**
	 * Action: register the message custom post type.
	 *
	 * @hooked action init
	 *
	 * @since 1.0
	 *
	 * @return void
	 */
	public function register_post_type() {
		$labels = array(
			'name' => __( 'Activity', 'happyforms' ),
			'singular_name' => __( 'Activity', 'happyforms' ),
			'edit_item' => __( 'Edit Activity', 'happyforms' ),
			'view_item' => __( 'View Activity', 'happyforms' ),
			'view_items' => __( 'View Activity', 'happyforms' ),
			'search_items' => __( 'Search Activity', 'happyforms' ),
			'not_found' => __( 'No activity found.', 'happyforms' ),
			'not_found_in_trash' => __( 'No activity found in Trash', 'happyforms' ),
			'all_items' => __( 'All Activity', 'happyforms' ),
			'menu_name' => __( 'All Activity', 'happyforms' ),
		);

		$args = array(
			'labels' => $labels,
			'public' => false,
			'publicly_queryable' => false,
			'exclude_from_search' => true,
			'show_ui' => true,
			'show_in_menu' => false,
			'show_in_admin_bar' => false,
			'query_var' => true,
			'capability_type' => 'page',
			'has_archive' => false,
			'hierarchical' => false,
			'supports' => array( 'custom-fields' ),
		);

		register_post_type( $this->post_type, $args );
	}

	public function page_url( $url ) {
		$url = "edit.php?post_type={$this->post_type}";

		return $url;
	}

	public function get_session_reset_callback( $session, $form ) {
		$session_reset_callback = apply_filters(
			'happyforms_session_reset_callback',
			array(
				'function' => array( $session, 'reset_step' ),
				'args'   => array(),
			),
			$session,
			$form
		);

		return $session_reset_callback;
	}

	/**
	 * Action: handle a form submission.
	 *
	 * @hooked action parse_request
	 *
	 * @since 1.0
	 *
	 * @return void
	 */
	public function admin_post() {
		// Exit early if we're not submitting any form
		if ( ! isset ( $_REQUEST['action'] ) || $this->submit_action != $_REQUEST['action'] ) {
			return;
		}

		// Check form_id parameter
		if ( ! isset ( $_REQUEST[$this->form_parameter] ) ) {
			wp_send_json_error();
		}

		$form_id = intval( $_REQUEST[$this->form_parameter] );

		// Validate nonce
		if ( ! isset( $_REQUEST[$this->nonce_name] )
			|| ! $this->verify_nonce( $_REQUEST[$this->nonce_name], $form_id ) ) {

			wp_send_json_error();
		}

		$form_controller = happyforms_get_form_controller();
		$form = $form_controller->get( $form_id );

		// Check if form found
		if ( ! $form || is_wp_error( $form ) ) {
			wp_send_json_error();
		}

		// Set form step
		$step = isset( $_REQUEST[$this->form_step_parameter] ) ?
			$_REQUEST[$this->form_step_parameter] : '';

		happyforms_get_session()->set_step( $step );

		// Validate honeypot
		if ( happyforms_get_form_controller()->has_spam_protection( $form ) ) {
			if ( ! $this->validate_honeypot( $form ) ) {
				wp_send_json_error();
			}
		}

		define( 'HAPPYFORMS_STEPPING', true );
		do_action( 'happyforms_step', $form );
	}

	public function default_submission_step( $form ) {
		if ( 'submit' !== happyforms_get_current_step( $form ) ) {
			return;
		}

		$form_id = $form['ID'];
		$form_controller = happyforms_get_form_controller();
		$session = happyforms_get_session();

		// Validate submission
		$antispam = happyforms_get_antispam_integration();
		$antispam_result = '';

		if ( 1 == $form['captcha'] && $antispam->get_active_service() ) {
			$antispam_result = $antispam->validate_submission( $form );

			if ( is_wp_error( $antispam_result ) ) {
				$antispam_error = new WP_Error( 'error', happyforms_get_validation_message( 'field_empty' ) );

				$session->add_error( happyforms_get_recaptcha_part_name( $form ), $antispam_error->get_error_message() );
			}
		}

		$submission = $this->validate_submission( $form, $_REQUEST );
		$response = array();
		$session_reset_callback = $this->get_session_reset_callback( $session, $form );

		if ( false === $submission || is_wp_error( $antispam_result ) ) {
			// Add a general error notice at the top
			$session->add_error( $form_id, html_entity_decode( $form['error_message'] ) );

			// Reset steps
			call_user_func( $session_reset_callback['function'], $session_reset_callback['args'] );

			/**
			 * This action fires upon an invalid submission.
			 *
			 * @since 1.4
			 *
			 * @param WP_Error $submission Error data.
			 * @param array    $form   Current form data.
			 *
			 * @return void
			 */
			do_action( 'happyforms_submission_error', $submission, $form );

			// Render the form
			$response['html'] = $form_controller->render( $form );

			// Send error response
			wp_send_json_error( $response );
		} else {
			if ( 'redirect' !== $form['confirm_submission'] ) {
				// Add a general success notice at the top
				$session->add_notice( $form_id, html_entity_decode( $form['confirmation_message'] ) );
			}

			// Reset steps
			call_user_func( $session_reset_callback['function'], $session_reset_callback['args'] );

			// Empty submitted values
			$session->clear_values();

			$form = happyforms_get_conditional_controller()->get( $form, $_REQUEST );

			// Create message post
			$message_id = $this->create( $form, $submission );

			if ( ! is_wp_error( $message_id ) ) {
				// Update the unread badge
				$this->update_counters();

				$redirect_url = happyforms_get_form_property( $form, 'redirect_url' );

				if ( ! empty( $redirect_url ) && 'redirect' === happyforms_get_form_property( $form, 'confirm_submission' ) ) {
					$response['redirect'] = $form['redirect_url'];
					$response['redirect_to_blank'] = intval( happyforms_get_form_property( $form, 'redirect_blank' ) );
				}

				$message = $this->get( $message_id );
				$message_is_spam = happyforms_get_message_blocklist()->check_message( $message, $form, $_REQUEST );

				/**
				 * This action fires once a message is succesfully submitted.
				 *
				 * @since 1.4
				 *
				 * @param array $submission Submission data.
				 * @param array $form   Current form data.
				 *
				 * @return void
				 */
				do_action( 'happyforms_submission_success', $submission, $form, $message );

				if( 'success_message_hide_form' === happyforms_get_form_property( $form, 'confirm_submission' ) && ! empty( $submission ) ) {
					$response['hide_steps'] = true;
				}

				if ( ! $message_is_spam && ( 1 === intval( $form['receive_email_alerts'] ) ) ) {
					happyforms_get_task_controller()->add( 'HappyForms_Task_Email_Owner', $message_id );
				}

				if ( ! $message_is_spam && ( 1 === intval( $form['send_confirmation_email'] ) ) ) {
					happyforms_get_task_controller()->add( 'HappyForms_Task_Email_User', $message_id );
				}

				$save_entries = apply_filters( 'happyforms_save_entries', true );

				if ( ! $save_entries ) {
					wp_delete_post( $message_id );
				}

				// Render the form
				$response['html'] = $form_controller->render( $form );

				// Send success response
				$this->send_json_success( $response, $submission, $form );
			}
		}
	}

	public function preview_submission_step( $form ) {
		if ( 'preview' !== happyforms_get_current_step( $form ) ) {
			return;
		}

		$form_id = $form['ID'];
		$form_controller = happyforms_get_form_controller();
		$session = happyforms_get_session();

		// Validate ReCaptcha
		$antispam = happyforms_get_antispam_integration();
		$antispam_result = '';

		if ( 1 == $form['captcha'] && $antispam->get_active_service() ) {
			$antispam_result = $antispam->validate_submission( $form );

			if ( is_wp_error( $antispam_result ) ) {
				$antispam_error = new WP_Error( 'error', happyforms_get_validation_message( 'field_empty' ) );

				$session->add_error( happyforms_get_recaptcha_part_name( $form ), $antispam_error->get_error_message() );
			}
		}

		$submission = $this->validate_submission( $form, $_REQUEST );
		$response = array();
		$session_reset_callback = $this->get_session_reset_callback( $session, $form );

		if ( false === $submission || is_wp_error( $antispam_result ) ) {
			// Add a general error notice at the top
			$session->add_error( $form_id, html_entity_decode( $form['error_message'] ) );

			// Reset steps
			call_user_func( $session_reset_callback['function'], $session_reset_callback['args'] );

			// Render the form
			$response['html'] = $form_controller->render( $form );

			// Send error response
			wp_send_json_error( $response );
		} else {
			// Advance step
			$session->next_step();

			$form = happyforms_get_conditional_controller()->get( $form, $_REQUEST );

			// Render the form
			$response['html'] = $form_controller->render( $form );

			// Send success response
			$this->send_json_success( $response, $submission, $form );
		}
	}

	public function review_submission_step( $form ) {
		if ( 'review' !== happyforms_get_current_step( $form ) ) {
			return;
		}

		$form_id = $form['ID'];
		$form_controller = happyforms_get_form_controller();
		$session = happyforms_get_session();
		$submission = $this->validate_submission( $form, $_REQUEST );
		$response = array();
		$session_reset_callback = $this->get_session_reset_callback( $session, $form );

		if ( false === $submission ) {
			// Add a general error notice at the top
			$session->add_error( $form_id, html_entity_decode( $form['error_message'] ) );
		}

		// Reset steps
		call_user_func( $session_reset_callback['function'], $session_reset_callback['args'] );

		// Render the form
		$response['html'] = $form_controller->render( $form );

		if ( false === $submission ) {
			// Send error response
			wp_send_json_error( $response );
		}

		// Send success response
		$this->send_json_success( $response, $submission, $form );
	}

	public function send_json_success( $response = array(), $submission = array(), $form = array() ) {
		$response = apply_filters( 'happyforms_json_response', $response, $submission, $form );

		wp_send_json_success( $response );
	}

	public function trashed_post( $post_id ) {
		$post = get_post( $post_id );

		if ( $this->post_type !== $post->post_type ) {
			return;
		}

		$this->update_counters();
	}

	public function untrashed_post( $post_id ) {
		$post = get_post( $post_id );

		if ( $this->post_type !== $post->post_type ) {
			return;
		}

		$this->update_counters();
	}

	public function before_delete_post( $post_id ) {
		$post = get_post( $post_id );

		if ( $this->post_type !== $post->post_type ) {
			return;
		}

		do_action( 'happyforms_before_delete_response', $post_id );
	}

	/**
	 * Action: update the unread badge upon message deletion.
	 *
	 * @since 1.1
	 *
	 * @hooked action delete_post
	 *
	 * @param int|string $post_id The ID of the message object.
	 *
	 * @return void
	 */
	public function delete_post( $post_id ) {
		$post = get_post( $post_id );

		if ( $this->post_type !== $post->post_type ) {
			return;
		}

		do_action( 'happyforms_response_deleted', $post_id );

		$this->update_counters();
	}

	public function form_deleted( $form_id ) {
		$responses = $this->get_by_form( $form_id );

		foreach ( $responses as $response ) {
			wp_delete_post( $response['ID'], true );
		}

		$this->update_counters();
	}

	/**
	 * Verify a message nonce.
	 *
	 * @since 1.0
	 *
	 * @param string $nonce   The submitted value.
	 * @param string $form_id The ID of the form being submitted.
	 *
	 * @return boolean
	 */
	public function verify_nonce( $nonce, $form_id ) {
		return wp_verify_nonce( $nonce, $this->nonce_prefix . $form_id );
	}

	/**
	 * Verify honeypot data.
	 *
	 * @since 1.3
	 *
	 * @param array $form Current form data.
	 *
	 * @return boolean
	 */
	private function validate_honeypot( $form ) {
		$honeypot_name = $form['ID'] . 'single_line_text_-1';
		$validated = ! isset( $_REQUEST[$honeypot_name] );

		return $validated;
	}

	public function validate_part( $form, $part, $request ) {
		$part_class = happyforms_get_part_library()->get_part( $part['type'] );

		if ( false !== $part_class ) {
			$part_id = $part['id'];
			$part_name = happyforms_get_part_name( $part, $form );
			$sanitized_value = $part_class->sanitize_value( $part, $form, $request );
			$validated_value = $part_class->validate_value( $sanitized_value, $part, $form );
			$validated_value = apply_filters( 'happyforms_validate_part_submission', $validated_value, $part, $form );

			$session = happyforms_get_session();
			$session->add_value( $part_name, $sanitized_value );

			if ( ! is_wp_error( $validated_value ) ) {
				return $validated_value;
			} else {
				do_action( 'happyforms_validation_error', $form, $part );

				$part_field = $part_name;
				$error_data = $validated_value->get_error_data();

				if ( ! empty( $error_data ) && isset( $error_data['components'] ) ) {
					foreach ( $error_data['components'] as $component ) {
						$session->add_error( $part_field, $validated_value->get_error_message(), $component );
					}
				} else {
					$session->add_error( $part_field, $validated_value->get_error_message() );
				}
			}
		}

		return false;
	}

	public function validate_submission( $form, $request = array() ) {
		$submission = array();
		$is_valid = true;

		// Apply conditional logic
		$form = happyforms_get_conditional_controller()->get( $form, $request );

		// Hide option-limited parts
		$form['parts'] = apply_filters( 'happyforms_get_form_parts', $form['parts'], $form );

		foreach( $form['parts'] as $part ) {
			$part_id = $part['id'];
			$validated_value = $this->validate_part( $form, $part, $request );

			if ( false !== $validated_value ) {
				$string_value = happyforms_stringify_part_value( $validated_value, $part, $form );
				$submission[$part_id] = $string_value;
			} else {
				$is_valid = false;
			}
		}

		$is_valid = apply_filters( 'happyforms_validate_submission', $is_valid, $request, $form );

		return $is_valid ? $submission : false;
	}

	public function get_raw_request( $form, $submission ) {
		$request = array();

		foreach( $form['parts'] as $part_id => $part ) {
			$part_name = happyforms_get_part_name( $part, $form );

			if ( ! isset( $_REQUEST[$part_name] ) ) {
				continue;
			}

			$part_class = happyforms_get_part_library()->get_part( $part['type'] );
			$value = $part_class->sanitize_value( $part, $form, $_REQUEST );
			$request[$part_name] = $value;
		}

		return $request;
	}

	public function get_insert_post_data( $form, $submission ) {
		$defaults = $this->get_post_fields();
		$defaults_meta = $this->get_meta_fields();
		$raw_request = $this->get_raw_request( $form, $submission );
		$message_meta = wp_parse_args( array(
			'form_id' => $form['ID'],
			'request' => $raw_request,
		), $defaults_meta );
		$message_meta = array_merge( $message_meta, $submission );
		$message_meta = happyforms_prefix_meta( $message_meta );
		$post_data = array_merge( $defaults, array(
			'meta_input' => $message_meta
		) );

		return $post_data;
	}

	/**
	 * Create a new message post object.
	 *
	 * @since 1.0
	 *
	 * @param array $form       The message form data.
	 * @param array $submission The message form data.
	 *
	 * @return int|boolean
	 */
	public function create( $form, $submission ) {
		$post_data = $this->get_insert_post_data( $form, $submission );
		$message_id = wp_insert_post( wp_slash( $post_data ), true );

		wp_update_post( array(
			'ID' => $message_id,
			'post_title' => happyforms_get_message_title( $message_id ),
		) );

		do_action( 'happyforms_response_created', $message_id, $form );

		return $message_id;
	}

	public function response_stamp_unique_id( $response_id, $form ) {
		if ( intval( $form['unique_id'] ) ) {
			$increment = $form['unique_id_start_from'];
			$prefix = $form['unique_id_prefix'];
			$suffix = $form['unique_id_suffix'];
			$tracking_id = "{$prefix}{$increment}{$suffix}";

			happyforms_update_meta( $response_id, 'tracking_id', $tracking_id );
		}
	}

	public function notice_append_unique_id( $submission, $form, $message ) {
		if ( intval( $form['unique_id'] ) ) {
			$tracking_id = $message['tracking_id'];
			$notice = $form['confirmation_message'];
			$label = __( 'Tracking number', 'happyforms' );
			$notice = "{$notice}<span>{$label}: {$tracking_id}</span>";
			$notice = html_entity_decode( $notice );

			happyforms_get_session()->add_notice( $form['ID'], $notice );
		}
	}

	/**
	 * Get one or more message post objects.
	 *
	 * @since 1.0
	 *
	 * @param string $post_ids The IDs of the messages to retrieve.
	 *
	 * @return array
	 */
	public function do_get( $post_ids = '' ) {
		$query_params = array(
			'post_type' => $this->post_type,
			'post_status' => array( 'publish', 'draft', 'trash' ),
			'posts_per_page' => -1,
		);

		if ( ! empty( $post_ids ) ) {
			if ( is_numeric( $post_ids ) ) {
				$query_params['p'] = $post_ids;
			} else if ( is_array( $post_ids ) )  {
				$query_params['post__in'] = $post_ids;
			}
		}

		$messages = get_posts( $query_params );
		$message_entries = array_map( array( $this, 'to_array'), $messages );

		if ( is_numeric( $post_ids ) ) {
			if ( count( $message_entries ) > 0 ) {
				return $message_entries[0];
			} else {
				return false;
			}
		}

		return $message_entries;
	}

	public function get( $post_ids ) {
		$args = md5( serialize( func_get_args() ) );
		$key = "_happyforms_cache_responses_get_{$args}";
		$found = false;
		$result = happyforms_cache_get( $key, $found );

		if ( false === $found ) {
			$result = $this->do_get( $post_ids );
			happyforms_cache_set( $key, $result );
		}

		return $result;
	}

	/**
	 * Get all messages relative to a form.
	 *
	 * @since 1.0
	 *
	 * @param string $form_id The ID of the form.
	 *
	 * @return array
	 */
	public function get_by_form( $form_id, $ids_only = false, $count = -1 ) {
		if ( $ids_only ) {
			global $wpdb;

			$query = $wpdb->prepare( "
				SELECT p.ID FROM $wpdb->posts p
				JOIN $wpdb->postmeta m ON p.ID = m.post_id
				WHERE p.post_type = 'happyforms-message'
				AND m.meta_key = '_happyforms_form_id'
				AND m.meta_value = %d;
			", $form_id );

			$results = $wpdb->get_col( $query );

			return $results;
		}

		$query_params = array(
			'post_type'   => $this->post_type,
			'post_status' => 'any',
			'posts_per_page' => $count,
			'meta_query' => array( array(
				'key' => '_happyforms_form_id',
				'value' => $form_id,
			) )
		);

		$messages = get_posts( $query_params );
		$message_entries = array_map( array( $this, 'to_array'), $messages );

		return $message_entries;
	}

	public function count_by_form( $form_id ) {
		global $wpdb;

		$query = $wpdb->prepare( "
			SELECT COUNT(p.ID) FROM $wpdb->posts p
			JOIN $wpdb->postmeta m ON p.ID = m.post_id
			WHERE p.post_type = 'happyforms-message'
			AND m.meta_key = '_happyforms_form_id'
			AND m.meta_value = %d
			GROUP BY m.meta_value;
		", $form_id );

		$results = $wpdb->get_col( $query );
		$count = empty( $results ) ? 0 : $results[0];

		return $count;
	}

	/**
	 * Get messages by a list of meta fields.
	 *
	 * @param string $metas An array of meta fields.
	 *
	 * @return array
	 */
	public function get_by_metas( $metas ) {
		$metas = happyforms_prefix_meta( $metas );
		$meta_query = array();

		foreach ( $metas as $field => $value ) {
			$meta_query[] = array(
				'field' => $field,
				'value' => $value,
			);
		}

		$query_params = array(
			'post_type'   => $this->post_type,
			'post_status' => 'any',
			'posts_per_page' => -1,
			'meta_query' => $meta_query,
		);

		$messages = get_posts( $query_params );
		$message_entries = array_map( array( $this, 'to_array'), $messages );

		return $message_entries;
	}

	public function count_by_option( $form_id, $option_id, $limit ) {
		global $wpdb;

		$meta_key = happyforms_get_option_counter_meta_key( $form_id, $option_id );

		$query = $wpdb->prepare( "
			SELECT COUNT(*) FROM (
				SELECT p.ID FROM $wpdb->posts p
				JOIN $wpdb->postmeta s ON p.ID = s.post_id AND s.meta_key = '_happyforms_read' AND s.meta_value != 2
				JOIN $wpdb->postmeta c ON p.ID = c.post_id AND c.meta_key = %s
				WHERE p.post_type = 'happyforms-message'
				AND p.post_status = 'publish'
				LIMIT 0, %d
			) AS r;
		", $meta_key, $limit );

		$count = $wpdb->get_col( $query );
		$count = empty( $count ) ? 0 : intval( $count[0] );

		return $count;
	}

	/**
	 * Turn a message post object into an array.
	 *
	 * @since 1.0
	 *
	 * @param WP_Post $message The message post object.
	 *
	 * @return array
	 */
	public function to_array( $message ) {
		$message_array = $message->to_array();
		$message_meta = happyforms_unprefix_meta( get_post_meta( $message->ID ) );
		$form_id = $message_meta['form_id'];
		$form = happyforms_get_form_controller()->get( $form_id );
		$meta_defaults = $this->get_meta_fields();
		$message_array = array_merge( $message_array, wp_parse_args( $message_meta, $meta_defaults ) );
		$message_array['parts'] = array();

		if ( $form ) {
			foreach ( $form['parts'] as $part_data ) {
				$part = happyforms_get_part_library()->get_part( $part_data['type'] );

				if ( $part ) {
					$part_id = $part_data['id'];
					$part_value = $part->get_default_value( $part_data );

					if ( isset( $message_meta[$part_id] ) ) {
						$part_value = $message_meta[$part_id];
					}

					$message_array['parts'][$part_id] = $part_value;
					unset( $message_array[$part_id] );
				}
			}
		}

		return $message_array;
	}

	public function email_part_visible( $visible, $part, $form, $response ) {
		$required = happyforms_is_truthy( $part['required'] );
		$value = happyforms_get_email_part_value( $response, $part, $form );

		if ( false === $required && empty( $value ) ) {
			$visible = false;
		}

		if ( isset( $part['use_as_subject'] ) && $part['use_as_subject'] ) {
			$visible = false;
		}

		return $visible;
	}

	public function get_default_counters() {
		$counters = array(
			'read' => 0,
			'unread' => 0,
			'spam' => 0,
			'total' => 0,
		);

		return $counters;
	}

	public function update_counters() {
		global $wpdb;

		$results = $wpdb->get_results( "
			SELECT f.meta_value AS form_id,
			CASE r.meta_value
			WHEN 1 THEN 'read'
			WHEN 2 THEN 'spam'
			ELSE 'unread'
			END AS status,
			COUNT(r.meta_id) AS amount
			FROM $wpdb->posts p, $wpdb->postmeta f, $wpdb->postmeta r
			WHERE f.meta_key = '_happyforms_form_id'
			AND r.meta_key = '_happyforms_read'
			AND f.post_id = r.post_id
			AND p.ID = f.post_id
			AND p.post_status != 'trash'
			GROUP BY form_id, status
		", ARRAY_A );

		$form_ids = happyforms_get_form_controller()->get( array(), true );
		$counters = array_fill_keys( $form_ids, $this->get_default_counters() );
		$total_counter = $this->get_default_counters();

		foreach( $results as $result ) {
			$form_id = $result['form_id'];
			$status = $result['status'];
			$amount = $result['amount'];

			if ( ! isset( $counters[$form_id] ) ) {
				$counters[$form_id] = $this->get_default_counters();
			}

			$counters[$form_id][$status] = $amount;
			$total_counter[$status] += $amount;

			if ( 'spam' !== $status ) {
				$counters[$form_id]['total'] += $amount;
				$total_counter['total'] += $amount;
			}
		}

		$counters[''] = $total_counter;

		set_transient( $this->counters_transient, $counters, 0 );
	}

	public function get_counters( $form_id = '' ) {
		$counters = get_transient( $this->counters_transient );

		if ( ! $counters ) {
			$this->update_counters();
			$counters = get_transient( $this->counters_transient );
		}

		if ( ! isset( $counters[$form_id] ) ) {
			return $this->get_default_counters();
		}

		$counters = $counters[$form_id];

		return $counters;
	}

	public function do_search_metas( $term ) {
		global $wpdb;

		$sql = "
		SELECT m.post_id, m.meta_key, m.meta_value
		FROM $wpdb->postmeta m JOIN $wpdb->posts p ON m.post_id = p.ID
		WHERE p.post_type = %s AND m.meta_value LIKE %s
		GROUP BY m.post_id;
		";

		$term = '%' . $wpdb->esc_like( $term ) . '%';
		$post_type = happyforms_get_message_controller()->post_type;
		$query = $wpdb->prepare( $sql, $post_type, $term );
		$metas = $wpdb->get_results( $query );

		return $metas;
	}

	public function search_metas( $term ) {
		$args = md5( serialize( func_get_args() ) );
		$key = "__happyforms_cache_responses_metas_search_{$args}";
		$found = false;
		$result = happyforms_cache_get( $key, $found );

		if ( false === $found ) {
			$result = $this->do_search_metas( $term );
			happyforms_cache_set( $key, $result );
		}

		return $result;
	}

	public function send_user_email() {
		if ( ! check_ajax_referer( $this->send_email_action ) ) {
			wp_send_json_error();
		}

		if ( ! isset( $_REQUEST['response_id'] ) || ! isset( $_REQUEST['email'] ) ) {
			wp_send_json_error();
		}

		$error_message = __( 'Invalid email.', 'happyforms' );
		$success_message = __( 'Email sent.', 'happyforms' );

		$emails = explode( ',', $_REQUEST['email'] );
		$emails = array_map( 'trim', $emails );
		$emails = array_filter( $emails );

		foreach( $emails as $email ) {
			if ( ! happyforms_is_email( $email ) ) {
				wp_send_json_error( array(
					'message' => $error_message,
				) );
			}
		}

		$emails = array_filter( $emails );
		$emails = implode( ',', $emails );

		if ( empty( $emails ) ) {
			wp_send_json_error( array(
				'message' => $error_message,
			) );
		}

		$response_id = $_REQUEST['response_id'];
		$response = $this->get( $response_id );

		if ( ! $response ) {
			wp_send_json_error( array(
				'message' => $error_message,
			) );
		}

		add_filter( 'happyforms_email_confirmation', function( $email_message ) use( $emails ) {
			$email_message->set_to( $emails );

			return $email_message;
		} );

		happyforms_get_task_controller()->add( 'HappyForms_Task_Email_User', $response_id );

		wp_send_json_success( array(
			'message' => $success_message,
		) );
	}

	public function admin_title( $admin_title, $title ) {
		$screen = get_current_screen();
		$after_title = sprintf( __( '&lsaquo; %s &#8212; WordPress' ), get_bloginfo( 'name' ) );

		if ( $this->post_type === $screen->post_type && 'edit' === $screen->base ) {
			$counter = $this->get_counters();
			$unread_messages = $counter['unread'];

			if ( ! empty( $unread_messages ) ) {
				$title = __( 'Activity', 'happyforms' ) . " ({$unread_messages}) {$after_title}";
				$admin_title = $title;
			}
		}

		return $admin_title;
	}

}

if ( ! function_exists( 'happyforms_get_message_controller' ) ):
/**
 * Get the HappyForms_Message_Controller class instance.
 *
 * @since 1.0
 *
 * @return HappyForms_Message_Controller
 */
function happyforms_get_message_controller() {
	return HappyForms_Message_Controller::instance();
}

endif;

/**
 * Initialize the HappyForms_Message_Controller class immediately.
 */
happyforms_get_message_controller();
