<?php

if ( ! defined( 'ABSPATH' ) ) {
	die( 'You are not allowed to call this page directly.' );
}

/**
 * @since 3.0
 */
class FrmProFieldTime extends FrmFieldType {

	/**
	 * @var string
	 * @since 3.0
	 */
	protected $type = 'time';

	/**
	 * Fix WCAG errors when multiple dropdowns for the time field.
	 *
	 * @var bool
	 * @since 3.06.01
	 */
	protected $has_for_label = false;

	/**
	 * @var bool
	 */
	protected $array_allowed = false;

	public function show_on_form_builder( $name = '' ) {
		$field = FrmFieldsHelper::setup_edit_vars( $this->field );
		$field['value'] = $field['default_value'];

		$field_name = $this->html_name( $name );
		$html_id = $this->html_id();

		$this->show_time_field( compact( 'field', 'html_id', 'field_name' ) );
	}

	protected function field_settings_for_type() {
		$settings = array(
			'autopopulate'  => true,
			'size'          => true,
			'unique'        => true,
			'read_only'     => true,
			'invalid'       => true,
		);

		FrmProFieldsHelper::fill_default_field_display( $settings );
		return $settings;
	}

	protected function extra_field_opts() {
		return array(
			'start_time'  => '00:00',
			'end_time'    => '23:30',
			'clock'       => 12,
			'single_time' => 0,
			'step'        => 30,
		);
	}

	/**
	 * @since 4.0
	 * @param array $args - Includes 'field', 'display', and 'values'
	 */
	public function show_primary_options( $args ) {
		$field = $args['field'];
		include( FrmProAppHelper::plugin_path() . '/classes/views/frmpro-fields/back-end/clock-settings.php' );

		$this->auto_width_setting( $args );

		parent::show_primary_options( $args );
	}

	/**
	 * @since 4.0
	 */
	public function default_value_to_string( &$default_value ) {
		if ( is_array( $default_value ) ) {
			$this->time_array_to_string( $default_value );
		}
	}

	protected function fill_default_atts( &$atts ) {
		$defaults = array(
			'format' => $this->get_time_format_for_field(),
		);

		$atts = wp_parse_args( $atts, $defaults );
	}

	public function prepare_front_field( $values, $atts ) {
		$values['options'] = $this->get_options( $values );
		$values['value'] = $this->prepare_field_value( $values['value'], $atts );

		return $values;
	}

	public function prepare_field_value( $value, $atts ) {
		return $this->get_display_value( $value, $atts );
	}

	public function get_options( $values ) {
		if ( empty( $values ) ) {
			// use a text field for conditional logic
			return parent::get_options( $values );
		}

		$this->prepare_time_settings( $values );

		$options = array();
		$this->get_single_time_field_options( $values, $options );

		$use_single_dropdown = FrmField::is_option_true( $values, 'single_time' );
		if ( ! $use_single_dropdown ) {
			$this->get_multiple_time_field_options( $values, $options );
		}

		return $options;
	}

	public function front_field_input( $args, $shortcode_atts ) {
		ob_start();

		$this->show_time_field(
			array(
				'html_id'    => $args['html_id'],
				'field_name' => $args['field_name'],
			)
		);
		$input_html = ob_get_contents();
		ob_end_clean();

		return $input_html;
	}

	private function show_time_field( $values ) {
		if ( isset( $values['field'] ) ) {
			$field = $values['field'];
		} else {
			$field           = $this->field;
			$values['field'] = $field;
		}

		$values['field_value'] = $field['value'];
		$this->set_field_column( 'options', $field['options'] );

		$hidden = $this->maybe_include_hidden_values( $values );
		$this->maybe_format_time( $values['field_value'] );

		if ( isset( $field['options']['H'] ) ) {
			$this->time_string_to_array( $values['field_value'] );
			$this->time_string_to_array( $values['field']['default_value'] );

			$html  = '<div class="frm_time_wrap">';
			$html .= '<span dir="ltr">' . "\r\n";

			$values['combo_name'] = 'H';
			$html                .= $this->get_time_component_html( $values, $field['name'], __( 'hour', 'formidable-pro' ) );

			// Use aria-hidden so a screen reader doesn't read the colon out loud.
			$html .= '<span class="frm_time_sep" aria-hidden="true">:</span>' . "\r\n";

			$values['combo_name'] = 'm';
			$html                .= $this->get_time_component_html( $values, $field['name'], __( 'minute', 'formidable-pro' ) );

			$html .= '</span>' . "\r\n";

			if ( isset( $field['options']['A'] ) ) {
				$values['combo_name'] = 'A';
				$html                .= $this->get_time_component_html( $values, $field['name'] );
			}

			$html .= '</div>';
		} else {
			$labeled_by = 'aria-labelledby="' . esc_attr( $values['html_id'] ) . '_label" ';
			$this->time_array_to_string( $values['field_value'] );
			$html = $this->get_select_box( $values );
			$html = str_replace( '<select ', '<select ' . $labeled_by, $html );
		}

		echo $hidden . $html;
	}

	/**
	 * Composes the html for a single time field, like hour or minute.
	 *
	 * @since 6.1.2
	 *
	 * @param array $values
	 * @param string $field_name
	 * @param string $time_string
	 *
	 * @return string
	 */
	private function get_time_component_html( $values, $field_name, $time_string = '' ) {
		$select_box = $this->get_select_box( $values ) . "\r\n";
		$aria_label = 'aria-label="' . esc_attr( $field_name );
		if ( $time_string ) {
			$aria_label .= ' ... ' . esc_attr( $time_string );
		}
		$aria_label .= '" ';
		return str_replace( '<select ', '<select ' . $aria_label, $select_box );
	}

	/**
	 * If the value was in a hidden field on a previous page,
	 * it may still be in the database format
	 *
	 * @since 3.02.01
	 */
	private function maybe_format_time( &$time ) {
		if ( ! is_array( $time ) && ! strpos( $time, ' ' ) ) {
			$time = $this->get_display_value(
				$time,
				array(
					'format' => $this->get_time_format_for_field(),
				)
			);
		}
	}

	/**
	 * Add extra classes on front-end input
	 *
	 * @since 3.01.04
	 */
	protected function get_input_class() {
		$class = '';
		$is_separate = $this->get_field_column( 'options' );
		$combo_name = FrmField::get_option( $this->field, 'combo_name' );
		if ( isset( $is_separate['H'] ) || ! empty( $combo_name ) ) {
			$class = 'auto_width frm_time_select';
		}

		return $class;
	}

	protected function show_readonly_hidden() {
		return true;
	}

	public function validate( $args ) {
		$errors = array();

		if ( is_array( $args['value'] ) ) {
			$this->time_array_to_string( $args['value'] );
			FrmEntriesHelper::set_posted_value( $this->field, $args['value'], $args );
		}

		$is_required = FrmField::is_required( (array) $this->field );
		$is_empty = ! is_array( $args['value'] ) && trim( $args['value'] ) == '';
		if ( $is_required && $is_empty ) {
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $this->field, 'blank' );
		} elseif ( ! $is_empty && ! $this->in_time_range( $args['value'] ) ) {
			$errors[ 'field' . $args['id'] ] = FrmFieldsHelper::get_error_msg( $this->field, 'invalid' );
		}

		return $errors;
	}

	private function in_time_range( $time ) {
		$values = $this->field->field_options;
		$this->fill_start_end_times( $values );

		$time = FrmProAppHelper::format_time( $time );
		return $time >= $values['start_time'] && $time <= $values['end_time'];
	}

	private function prepare_time_settings( &$values ) {
		$this->fill_start_end_times( $values );

		$values['start_time_str'] = $values['start_time'];
		$values['end_time_str'] = $values['end_time'];

		$this->split_time_setting( $values['start_time'] );
		$this->split_time_setting( $values['end_time'] );

		$this->step_in_minutes( $values['step'] );

		$this->set_step( $values['step'] );
		$values['hour_step'] = floor( $values['step'] / 60 );
		if ( ! $values['hour_step'] ) {
			$values['hour_step'] = 1;
		}

		if ( $values['end_time'][0] < $values['start_time'][0] ) {
			$values['end_time'][0] += 12;
		}
	}

	private function fill_start_end_times( &$values ) {
		$values['clock'] = isset( $values['clock'] ) ? $values['clock'] : 12;
		$values['start_time'] = isset( $values['start_time'] ) ? $values['start_time'] : '';
		$values['end_time'] = isset( $values['end_time'] ) ? $values['end_time'] : '';
		$this->format_time( '00:00', $values['start_time'] );
		$this->format_time( '23:59', $values['end_time'] );
	}

	public function is_not_unique( $value, $entry_id ) {
		$used = false;
		$value = FrmProAppHelper::format_time( $value );

		if ( FrmProEntryMetaHelper::value_exists( $this->get_field_column('id'), $value, false ) ) {

			$first_date_field = FrmProFormsHelper::has_field( 'date', $this->get_field_column('form_id') );

			if ( $first_date_field ) {

				$values = array(
					'time_field' => 'field_' . $this->field->field_key,
					'date_field' => 'field_' . $first_date_field->field_key,
					'time_key'   => $this->field->id,
					'date_key'   => $first_date_field->id,
					'date'       => sanitize_text_field( $_POST['item_meta'][ $first_date_field->id ] ), //TODO: repeat name
					'time'       => $value,
					'entry_id'   => $entry_id,
				);

				$not_allowed = array();
				$this->get_disallowed_times( $values, $not_allowed );
				if ( ! empty( $not_allowed ) ) {
					$used = true;
				}
			} else {
				$used = true;
			}
		}

		return $used;
	}

	/**
	 * Prepare the global time field JS information
	 *
	 * @since 3.0
	 *
	 * @param array $values
	 */
	protected function load_field_scripts( $values ) {
		if ( $this->field['unique'] && $this->field['single_time'] && isset( $values['html_id'] ) ) {
			global $frm_vars;

			if ( ! isset( $frm_vars['timepicker_loaded'] ) || ! is_array( $frm_vars['timepicker_loaded'] ) ) {
				$frm_vars['timepicker_loaded'] = array();
			}

			if ( ! isset( $frm_vars['timepicker_loaded'][ $values['html_id'] ] ) ) {
				$frm_vars['timepicker_loaded'][ $values['html_id'] ] = true;
			}
		}

	}

	public function get_disallowed_times( $values, &$remove ) {
		$values['date'] = FrmProAppHelper::maybe_convert_to_db_date( $values['date'], 'Y-m-d' );

		$remove = apply_filters( 'frm_allowed_times', $remove, $values );
		array_walk_recursive( $remove, 'FrmProAppHelper::format_time_by_reference' );

		$values['date_entries'] = $this->get_entry_ids_for_date( $values );
		if ( empty( $values['date_entries'] ) ) {
			return;
		}

		$used_times = $this->get_used_times_for_entries( $values );
		if ( empty( $used_times ) ) {
			return;
		}

		$number_allowed = apply_filters( 'frm_allowed_time_count', 1, $values['time_key'], $values['date_key'] );
		$count = array();
		foreach ( $used_times as $used ) {
			if ( isset( $remove[ $used ] ) ) {
				continue;
			}

			if ( ! isset( $count[ $used ] ) ) {
				$count[ $used ] = 0;
			}
			$count[ $used ]++;

			if ( (int) $count[ $used ] >= $number_allowed ) {
				$remove[ $used ] = $used;
			}
		}
	}

	private function get_entry_ids_for_date( $values ) {
		$query = array( 'meta_value' => $values['date'] );
		FrmProEntryMeta::add_field_to_query( $values['date_key'], $query );

		return FrmEntryMeta::getEntryIds( $query );
	}

	private function get_used_times_for_entries( $values ) {
		$query = array( 'it.item_id' => $values['date_entries'] );
		FrmProEntryMeta::add_field_to_query( $values['time_key'], $query );

		if ( $values['entry_id'] ) {
			$query['it.item_id !'] = $values['entry_id'];
		}
		if ( isset( $values['time'] ) && ! empty( $values['time'] ) ) {
			$query['meta_value'] = $values['time'];
		}

		global $wpdb;
		$select = $wpdb->prefix . 'frm_item_metas it';
		if ( ! is_numeric( $values['time_key'] ) ) {
			$select .= ' LEFT JOIN ' . $wpdb->prefix . 'frm_fields fi ON (it.field_id = fi.id)';
		}

		$used_times = FrmDb::get_col( $select, $query, 'meta_value' );
		return $used_times;
	}

	private function split_time_setting( &$time ) {
		$separator = ':';

		$time = FrmProAppHelper::format_time( $time );
		$time = explode( $separator, $time );
	}

	private function step_in_minutes( &$step ) {
		$separator = ':';
		$step = explode( $separator, $step );
		$step = ( isset( $step[1] ) ) ? ( ( $step[0] * 60 ) + $step[1] ) : ( $step[0] );
		if ( empty( $step ) ) {
			// force an hour step if none was defined to prevent infinite loop
			$step = 60;
		}
	}

	private function get_single_time_field_options( $values, &$options ) {
		$time = strtotime( $values['start_time_str'] );
		$end_time = strtotime( $values['end_time_str'] );
		$format = ( $values['clock'] == 24 ) ? 'H:i' : 'g:i A';

		$this->set_step( $values['step'] );
		$values['step'] = max( $values['step'] * 60, 60 ); //switch minutes to seconds

		$options[] = '';
		while ( $time <= $end_time ) {
			$options[] = gmdate( $format, $time );
			$time += $values['step'];
		}
	}

	/**
	 * @since 4.04.04
	 */
	private function set_step( &$step ) {
		if ( ! is_numeric( $step ) ) {
			$step = 30;
		}
	}

	private function get_multiple_time_field_options( $values, &$options ) {
		$all_times = $options;

		$options['H'] = array( '' );
		$options['m'] = array( '' );

		$this->get_hours( $all_times, $options );
		$this->get_minutes( $all_times, $options );

		if ( $values['clock'] != 24 ) {
			$options['A'] = array( 'AM', 'PM' );
		}
	}

	/**
	 * Get the hour options for a three-dropdown time field
	 *
	 * @since 3.0
	 *
	 * @param array $all_times
	 * @param array $options
	 */
	private function get_hours( $all_times, &$options ) {
		foreach ( $all_times as $time ) {
			if ( $time == '' ) {
				$options['H'][] = '';
				continue;
			}

			$colon_position = strpos( $time, ':' );
			if ( $colon_position !== false ) {
				$hour = substr( $time, 0, $colon_position );
				$options['H'][] = $hour;
			}
		}
		unset( $time );

		$options['H'] = array_unique( $options['H'] );
	}

	/**
	 * Get the minute options for a three-dropdown time field
	 *
	 * @since 3.0
	 *
	 * @param array $all_times
	 * @param array $options
	 */
	private function get_minutes( $all_times, &$options ) {

		foreach ( $all_times as $time ) {

			if ( $time == '' ) {
				$options['m'][] = '';
				continue;
			}

			$colon_position = strpos( $time, ':' );
			if ( $colon_position !== false ) {

				$minute = substr( $time, $colon_position + 1 );
				if ( strpos( $minute, 'M' ) ) {
					// AM/PM is included, so strip it off
					$minute = str_replace( array( ' AM', ' PM' ), '', $minute );
				}

				$options['m'][] = $minute;
			}
		}
		unset( $time );

		$options['m'] = array_unique( $options['m'] );
		sort( $options['m'] );
	}

	/**
	 * Format the start and end time
	 *
	 * @since 3.0
	 *
	 * @param string $default
	 * @param string $time
	 */
	private function format_time( $default, &$time ) {
		if ( strlen( $time ) === 4 && substr( $time, 1, 1 ) === ':' ) {
			$time = '0' . $time;
		} elseif ( ! preg_match( '/^(?:2[0-3]|[01][0-9]):[0-5][0-9]$/', $time ) || strlen( $time ) !== 5 || $time === '' ) {
			$time = $default;
		}
	}

	public function set_value_before_save( $value ) {
		$this->default_value_to_string( $value );

		return FrmProAppHelper::format_time( $value, 'H:i' );
	}

	protected function prepare_display_value( $value, $atts ) {
		if ( empty( $value ) ) {
			return $value;
		}

		if ( is_array( $value ) && isset( $value['H'] ) ) {
			$this->time_array_to_string( $value );
		} elseif ( ! is_array( $value ) && strpos( $value, ',' ) ) {
			$value = explode( ',', $value );
		}

		return FrmProFieldsHelper::format_values_in_array( $value, $atts['format'], array( 'FrmProAppHelper', 'format_time' ) );
	}

	public function time_array_to_string( &$value ) {
		if ( $this->is_time_empty( $value ) ) {
			$value = '';
		} elseif ( is_array( $value ) ) {
			$new_value = $value['H'] . ':' . $value['m'];
			$new_value .= ( isset( $value['A'] ) ? ' ' . $value['A'] : '' );
			$value = $new_value;
		}
	}

	private function time_string_to_array( &$value ) {
		$defaults = array( 'H' => '', 'm' => '', 'A' => '' );
		if ( is_array( $value ) ) {
			$value = wp_parse_args( $value, $defaults );
		} elseif ( is_string( $value ) && strpos( $value, ':' ) !== false ) {
			$h = explode( ':', $value );
			$m = explode( ' ', $h[1] );

			$value = array(
				'H' => reset( $h ),
				'm' => reset( $m ),
				'A' => isset( $m[1] ) ? $m[1] : '',
			);
		} else {
			$value = $defaults;
		}
	}

	public function is_time_empty( $value ) {
		$empty_string = ! is_array( $value ) && $value == '';
		$empty_array = is_array( $value ) && ( $value['H'] == '' || $value['m'] == '' );
		return $empty_string || $empty_array;
	}

	protected function prepare_import_value( $value, $atts ) {
		return FrmProAppHelper::format_time( $value );
	}

	/**
	 * @since 3.02.01
	 */
	public function get_time_format_for_field( $field = array() ) {
		if ( empty( $field ) ) {
			$field = $this->field;
		}
		$time_format = FrmField::get_option( $field, 'clock' );
		return $this->get_time_format_for_setting( $time_format );
	}

	/**
	 * @since 3.02.01
	 */
	public function get_time_format_for_setting( $time_format ) {
		return ( $time_format == 12 ) ? 'g:i A' : 'H:i';
	}

	/**
	 * @since 4.0.04
	 */
	public function sanitize_value( &$value ) {
		FrmAppHelper::sanitize_value( 'sanitize_text_field', $value );
	}
}
