<?php
/**
 * Simply Schedule Appointments Notices.
 *
 * @since   0.1.0
 * @package Simply_Schedule_Appointments
 */

/**
 * Simply Schedule Appointments Notices.
 *
 * @since 0.1.0
 */
class SSA_Notices {
	/**
	 * Parent plugin class.
	 *
	 * @since 0.1.0
	 *
	 * @var   Simply_Schedule_Appointments
	 */
	protected $plugin = null;

	protected $top_notice_transient_key = 'ssa/notices/one_notice_to_display';


	/**
	 * Constructor.
	 *
	 * @since  0.1.0
	 *
	 * @param  Simply_Schedule_Appointments $plugin Main plugin object.
	 */
	public function __construct( $plugin ) {
		$this->plugin = $plugin;
		$this->hooks();
	}

	/**
	 * Initiate our hooks.
	 *
	 * @since  0.1.0
	 */
	public function hooks() {

	}

	/**
	 * Call & Return cached notice if set, otherwise call for it and cache it 
	 *
	 * @return string
	 */
	public function get_the_one_notice_to_display() {

		$cached_notice = get_transient( $this->top_notice_transient_key );

		if( empty( $cached_notice ) ) {

			$cached_notice = $this->get_appropriate_notice();
	
			set_transient( $this->top_notice_transient_key , $cached_notice, HOUR_IN_SECONDS );

		}

		return $cached_notice === 'none' ? '' : $cached_notice;

	}
	
	/**
	 * Invoke the chain of filters to run
	 * Return the notice's id to display for the current site | 'none'
	 *
	 * @return string
	 */
	public function get_appropriate_notice(){

		$valid_notices = $this->get_appropriate_notices_for_this_site();

		$filtered_by_pinned_notices = $this->filter_by_pinned_notices( $valid_notices );

		$notice_to_display = $this->filter_by_priority( $filtered_by_pinned_notices );

		return $notice_to_display ? $notice_to_display['id'] : 'none';
	}

	/**
	 * Get the max priority from a list of notices then return the first that matches
	 * If two notices have their priority set to 1 (most important)
	 * We would return the first one found and discard all other notices
	 * 
	 * @param array $input
	 * @return array
	 */
	public function filter_by_priority( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$min_priority = min(array_column($input, 'priority'));

		foreach( $input as $notice ) {
			if ( $min_priority === $notice['priority'] ) {
				$output = $notice;
				break;
			}
		}
		return $output;
	}

	/**
	 * Get all notices, then filter out all inapproriate for the current site
	 * Returns an array of all valid notices to display
	 *
	 * @return array
	 */
	public function get_appropriate_notices_for_this_site() {

		$all_notices_list                   = $this->get_all_notices();

		$filtered_by_active_state           = $this->filter_by_active_status( $all_notices_list );

		$filtered_by_dissmissed_notices     = $this->filter_by_dismissed_notices( $filtered_by_active_state );

		$filtered_by_edition                = $this->filter_by_edition( $filtered_by_dissmissed_notices );

		$filtered_by_active_plugin          = $this->filter_by_active_plugin_any( $filtered_by_edition );

		$filtered_by_installed_features     = $this->filter_by_installed_feature_any( $filtered_by_active_plugin );

		$filtered_by_enabled_features       = $this->filter_by_enabled_feature_any( $filtered_by_installed_features );

		$filtered_by_activated_features     = $this->filter_by_activated_feature_any( $filtered_by_enabled_features );

		$filtered_by_not_installed_features = $this->filter_by_not_installed_feature_any( $filtered_by_activated_features );

		$filtered_by_not_enabled_features   = $this->filter_by_not_enabled_feature_any( $filtered_by_not_installed_features );

		$filtered_by_not_activated_features = $this->filter_by_not_activated_feature_any( $filtered_by_not_enabled_features );

		$filtered_by_current_user_can       = $this->filter_by_current_user_can( $filtered_by_not_activated_features );

		$filtered_by_min_activated_days     = $this->filter_by_min_activated_days( $filtered_by_current_user_can );

		$filtered_by_activation_date_after  = $this->filter_by_activation_date_after( $filtered_by_min_activated_days );

		$filtered_by_min_appt_count         = $this->filter_by_min_appt_count( $filtered_by_activation_date_after );

		return $filtered_by_min_appt_count;
	}

	/**
	 * Get ALL_NOTICES_LIST
	 *
	 * @return array
	 */
	public function get_all_notices() {
		return SSA_Notices_Data::get_notices_list();
	}

	/**
	 * Each notice has and active property set to true by default
	 * In case set to false, will allow us to eliminate this notice early
	 * Without the need to remove it from the list entirely
	 *
	 * @return array
	 */
	public function filter_by_active_status( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {
			return $notice['active'] === true;
		});

		return $output;
	}

	/**
	 * Filter out notices that got dissmissed by the user
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_dismissed_notices( $input = array() ) {

		$dismissed_notices = $this->get_dismissed_notices();

		if ( empty( $dismissed_notices ) || empty( $input ) ){
			return $input;
		}

		$output = array_filter( $input, function( $notice ) use ( $dismissed_notices ) {

			return !in_array( $notice['id'], $dismissed_notices);
		});

		return $output;
	}

	/**
	 * Get dismissed notices stored in the database if any
	 * An empty option dissmissed notices is stored: a:0:{}
	 *
	 * @return array
	 */
	public function get_dismissed_notices() {
		$dismissed_notices = get_option( 'ssa_dismissed_notices', array() );

		return $dismissed_notices;
	}

	/**
	 * Get pinned notices stored in the database if any
	 *
	 * @return array
	 */
	public function get_pinned_notices() {
		$pinned_notices = get_option( 'ssa_pinned_notices', array() );

		return $pinned_notices;
	}

	/**
	 * Check if the user's edition matches with the required edition for the notices
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_edition( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$edition_detected = $this->plugin->get_current_edition();

		$output = array_filter( $input, function( $notice ) use ( $edition_detected ) {

			$required_editions = $notice['requires']['current_edition_any'];

			if ( empty( $required_editions ) ) {
				// No specific edition required for this notice -> keep it
				return true;

			} else {
				// If the notice requires specific edition(s); check if the user's edition matches
				return in_array( $edition_detected, $required_editions );
			}
		});
		return $output;
	}


	/**
	 * Filter by ['active_plugin_any'] field
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_active_plugin_any( $input = array() ){

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			$plugins = $notice['requires']['active_plugin_any'];

			if ( empty( $plugins ) ) {
				return true;
			}
			
			static $all_active_plugins;

			foreach( $plugins as $plugin ) {

				if( empty( $all_active_plugins ) ) {
					$all_active_plugins = $this->get_active_plugins();
				}
				if ( in_array( $plugin, $all_active_plugins ) ) {
					return true;
				}
			}
			return false;
		});

		return $output;
	}

	/**
	 * Get all active plugins
	 *
	 * @return array
	 */
	public function get_active_plugins(){

		$active_plugins = get_option( 'active_plugins' );
		$output = array();
		
		foreach ($active_plugins as $active_plugin ) {
			if ( strpos( $active_plugin, '/' ) ) {
				$active_plugin = substr( $active_plugin, 0, strpos( $active_plugin, '/' ) );
			}
			$output[] = $active_plugin;
		}
		return $output;
	}

	/**
	 * Check if any notice requires a specific installed feature
	 * By checking the ['installed_feature_any'] field
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_installed_feature_any( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			// For every notice in the array check the field below
			$features = $notice['requires']['installed_feature_any'];

			if ( empty( $features ) ) {
				// No feature required to be installed for this notice -> Nothing to check -> keep it
				return true;

			} else {
				// Else a feature is required to be installed, so we need to check
				foreach( $features as $feature) {
					if ( $this->check_if_feature_installed( $feature ) ) {
						return true;
					}
				}
				return false;
			}
		});

		return $output;
	}

	/**
	 * Check if any notice requires a NOT installed feature
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_not_installed_feature_any( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			// For every notice check if it requires a NOT installed feature
			$features = $notice['requires']['not_installed_feature_any'];

			if ( empty( $features ) ) {
				// No feature required to be NOT installed for this notice -> Nothing to check -> keep it
				return true;

			} else {
				// Else a feature is required to be NOT installed, so we need to check
				foreach( $features as $feature) {
					if ( ! $this->check_if_feature_installed( $feature ) ) {
						return true;
					}
				}
				return false;
			}
		});

		return $output;
	}

	/**
	 * Custom function to call out the is_installed built in method
	 * Return true if installed, false otherwise
	 *
	 * @param string $feature
	 * @return boolean
	 */
	public function check_if_feature_installed( $feature = '' ) {
		
		if ( empty( $feature ) ){ return false; }
		
		return $this->plugin->settings_installed->is_installed( $feature );
	}



	/**
	 * Check if any notice requires an enabled feature
	 * If no enabled feature is required then let it pass; keep the notice
	 * Otherwise check for the feature:
	 * If it is enabled; keep the notice
	 * If it is not then filter the notice out
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_enabled_feature_any( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			// For every notice in the array check the field below
			$features = $notice['requires']['enabled_feature_any'];

			if ( empty( $features ) ) {
				// No feature required to be enabled for this notice -> Nothing to check -> keep it
				return true;

			} else {
				// Else a feature is required to be enabled, so we need to check
				foreach( $features as $feature) {
					if ( $this->check_if_feature_enabled( $feature ) ) {
						return true;
					}
				}
				return false;
			}
		});

		return $output;
	}

	/**
	 * Check if any notice requires an NOT enabled feature
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_not_enabled_feature_any( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			// For every notice check for required feature to be NOT enabled
			$features = $notice['requires']['not_enabled_feature_any'];

			if ( empty( $features ) ) {
				// No feature required to be NOT enabled for this notice -> Nothing to check -> keep it
				return true;

			} else {
				// Else a feature is required to be NOT enabled, so we need to check
				foreach( $features as $feature) {
					if ( ! $this->check_if_feature_enabled( $feature ) ) {
						return true;
					}
				}
				return false;
			}
		});

		return $output;
	}

	/**
	 * Custom function to call out the is_enabled built in method
	 * Return true if enabled, false otherwise
	 *
	 * @param string $feature
	 * @return boolean
	 */
	public function check_if_feature_enabled( $feature = '' ) {

		if ( empty( $feature ) ){ return false; }
		
		return $this->plugin->settings_installed->is_enabled( $feature );
	}

	/**
	 * Check if any notice requires an activated feature 
	 * If any has been found call out the check_if_feature_activated
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_activated_feature_any( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			$features = $notice['requires']['activated_feature_any'];

			if ( empty( $features ) ) {
				// No feature required to be activated for this notice -> keep it
				return true;

			} else {
				foreach( $features as $feature) {
					if ( $this->check_if_feature_activated( $feature ) ) {
						return true;
					}
				}
				return false;
			}
		});

		return $output;
	}


	/**
	 * Check if any notice requires a NOT activated feature 
	 * If any has been found call out the check_if_feature_activated
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_not_activated_feature_any( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			$features = $notice['requires']['not_activated_feature_any'];

			if ( empty( $features ) ) {
				// No feature required to be not activated for this notice -> keep it
				return true;

			} else {
				// the `!` is needed since it requires the feature to be NOT activated
				foreach( $features as $feature) {
					if ( ! $this->check_if_feature_activated( $feature ) ) {
						return true;
					}
				}
				return false;
			}
		});

		return $output;
	}

	/**
	 * Custom function to call out the is_activated built in method
	 *
	 * @param string $feature
	 * @return boolean
	 */
	public function check_if_feature_activated( $feature = '' ) {
		
		if ( empty( $feature ) ){ return false; }
		
		return $this->plugin->settings_installed->is_activated( $feature );
	}

	public function filter_by_pinned_notices( $input = array() ) {

		$pinned_notices = $this->get_pinned_notices();

		if ( empty( $pinned_notices ) || empty( $input ) ){
			return $input;
		}

		// Assign a priority of 22 for every pinned notices
		$output = array_map( function( $notice ) use ( $pinned_notices ) {
			// Check for pinned notices
			if ( in_array( $notice['id'], $pinned_notices ) ){
				$notice['priority'] = '22';

			}
			return $notice;

		}, $input );

		return $output;
	}

	/**
	 * Filter by ['requires']['current_user_can'] field
	 * Example: ['requires']['current_user_can'] => array( 'ssa_manage_site_settings' ),
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_current_user_can( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			$permissions = $notice['requires']['current_user_can'];

			if ( empty( $permissions ) ) {
				return true;

			} else {
				// Loop over the permissions since it's an array
				foreach( $permissions as $permission ) {
					// If any evaluated to false break & return false to filter the notice out
					if ( ! current_user_can( $permission ) ) {
						return false;
					}
				}
				return true;
			}
		});

		return $output;
	}

	/**
	 * Check and filter notices by min_activated_days
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_min_activated_days( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {
			
			$required_activated_days = $notice['requires']['min_activated_days'];
			
			if ( empty( $required_activated_days ) ) {
				return true;
				
			} else {
				static $ssa_activated_days;

				if( empty( $ssa_activated_days ) && $ssa_activated_days !== 0 ) {
					$ssa_activated_days = $this->ssa_activated_days();
				}

				return (int) $required_activated_days <= (int) $ssa_activated_days;
			}
		});

		return $output;
	}

	/**
	 * Check and filter notices by activation_date_after
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_activation_date_after( $input = array() ) {

		if ( empty( $input ) ) return [];

		$output = array_filter( $input, function( $notice ) {
			
			$required_activation_date_after = $notice['requires']['activation_date_after'];
			
			if ( empty( $required_activation_date_after ) ) {
				return true;
				
			} else {
				static $ssa_activation_date;

				if( empty( $ssa_activation_date ) ) {
					$ssa_activation_date = $this->get_ssa_activation_date();
				}

				$activation_date    = $ssa_activation_date; // DateTimeImmutable
				$min_date_required  = ssa_datetime( $required_activation_date_after );

				return $activation_date > $min_date_required;
			}
		});

		return $output;
	}

	/**
	 * In class-upgrade.php -> record_version() stores the date of each migration running
	 * Hence the first migration date indicates the activation date of the plugin
	 *
	 * @return int
	 */
	public function ssa_activated_days() {

		$ssa_versions = get_option( 'ssa_versions', json_encode( array() ) );
		$ssa_versions = json_decode( $ssa_versions, true );

		if( empty( $ssa_versions ) ) {
			return 0;
		}

		$activation_date	= array_keys($ssa_versions)[0];
		$activation_date	= ssa_datetime( $activation_date );
		$now        		= ssa_datetime();
		$diff       		= $now->diff( $activation_date );

		return $diff->days;
	}

	/**
	 * In class-upgrade.php -> record_version() stores the date of each migration running
	 * Hence the first migration date indicates the activation date of the plugin
	 *
	 * @return int
	 */
	public function get_ssa_activation_date() {

		$ssa_versions = get_option( 'ssa_versions', json_encode( array() ) );
		$ssa_versions = json_decode( $ssa_versions, true );

		if( empty( $ssa_versions ) || ! is_array( $ssa_versions ) ) {
			return ssa_datetime(); // Let's assume it's today in case is empty
		}

		if ( empty( array_keys( $ssa_versions ) ) || empty( array_keys( $ssa_versions )[0] ) ) {
			return ssa_datetime();
		}

		return ssa_datetime( array_keys($ssa_versions)[0] );
	}
  
  	/**
	 * Check and filter notices that require minimum booked appointment count
	 *
	 * @param array $input
	 * @return array
	 */
	public function filter_by_min_appt_count( $input = array() ) {

		if ( empty( $input ) ) { return []; }

		$output = array_filter( $input, function( $notice ) {

			$required_appt_count = $notice['requires']['min_appt_count'];

			if ( empty( $required_appt_count ) ) {
				return true;

			} else {

				static $appointments_count;
				if( empty( $appointments_count ) && $appointments_count !== 0 ) {
					$appointments_count = $this->get_completed_appointments_count();
				}

				return (int) $required_appt_count <= (int) $appointments_count;
			}
		});

		return $output;
	}

	/**
	 * Query COUNT of all booked appointments that have their end_date before today
	 *
	 * @return integer
	 */
	public function get_completed_appointments_count(){

		$appointments_count = $this->plugin->appointment_model->count( array(
			'status'        => array( 'booked' ),
			'end_date_max'	=> gmdate( 'Y-m-d H:i:s' ),
		));
		
		return $appointments_count;
	}

	/**
	 * Delete cached transient
	 *
	 * @return boolean
	 */
	public function delete_top_notice_cached_transient(){
		$transient = $this->top_notice_transient_key;
		return delete_transient( $transient );
	}

}
