<?php

namespace Simple_History\Loggers;

use Simple_History\Event_Details\Event_Details_Group;
use Simple_History\Event_Details\Event_Details_Group_Diff_Table_Formatter;
use Simple_History\Event_Details\Event_Details_Group_Table_Formatter;
use Simple_History\Event_Details\Event_Details_Item;
use Simple_History\Helpers;

/**
 * Logs cron event management from the WP Crontrol plugin
 * Plugin URL: https://wordpress.org/plugins/wp-crontrol/
 *
 * Requires WP Crontrol 1.9.0 or later.
 */
class Plugin_WP_Crontrol_Logger extends Logger {
	/** @var string Logger slug */
	public $slug = 'PluginWPCrontrolLogger';

	/**
	 * Get array with information about this logger
	 *
	 * @return array
	 */
	public function get_info() {

		return array(
			'name'        => _x( 'Plugin: WP Crontrol Logger', 'PluginWPCrontrolLogger', 'simple-history' ),
			'description' => _x( 'Logs management of cron events', 'PluginWPCrontrolLogger', 'simple-history' ),
			'name_via'    => _x( 'Using plugin WP Crontrol', 'PluginWPCrontrolLogger', 'simple-history' ),
			'capability'  => 'manage_options',
			'messages'    => array(
				'added_new_event'       => _x( 'Added cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history' ),
				'ran_event'             => _x( 'Manually ran cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history' ),
				'deleted_event'         => _x( 'Deleted cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history' ),
				'deleted_all_with_hook' => _x( 'Deleted all "{event_hook}" cron events', 'PluginWPCrontrolLogger', 'simple-history' ),
				'paused_hook'           => _x( 'Paused the "{event_hook}" cron event hook', 'PluginWPCrontrolLogger', 'simple-history' ),
				'resumed_hook'          => _x( 'Resumed the "{event_hook}" cron event hook', 'PluginWPCrontrolLogger', 'simple-history' ),
				'edited_event'          => _x( 'Edited cron event "{event_hook}"', 'PluginWPCrontrolLogger', 'simple-history' ),
				'added_new_schedule'    => _x( 'Added cron schedule "{schedule_name}"', 'PluginWPCrontrolLogger', 'simple-history' ),
				'deleted_schedule'      => _x( 'Deleted cron schedule "{schedule_name}"', 'PluginWPCrontrolLogger', 'simple-history' ),
			),
			'labels'      => array(
				'search' => array(
					'label'     => _x( 'WP Crontrol', 'PluginWPCrontrolLogger: search', 'simple-history' ),
					'label_all' => _x( 'All WP Crontrol activity', 'PluginWPCrontrolLogger: search', 'simple-history' ),
					'options'   => array(
						_x( 'Cron events added', 'PluginWPCrontrolLogger: search', 'simple-history' ) => array(
							'added_new_event',
						),
						_x( 'Cron events ran manually', 'PluginWPCrontrolLogger: search', 'simple-history' ) => array(
							'ran_event',
						),
						_x( 'Cron events modified', 'PluginWPCrontrolLogger: search', 'simple-history' ) => array(
							'edited_event',
							'paused_hook',
							'resumed_hook',
						),
						_x( 'Cron events deleted', 'PluginWPCrontrolLogger: search', 'simple-history' ) => array(
							'deleted_event',
							'deleted_all_with_hook',
						),
						_x( 'Cron schedules', 'PluginWPCrontrolLogger: search', 'simple-history' ) => array(
							'added_new_schedule',
							'deleted_schedule',
						),
					),
				),
			),
		);
	}

	/**
	 * Called when logger is loaded.
	 */
	public function loaded() {
		add_action( 'crontrol/added_new_event', array( $this, 'added_new_event' ) );
		add_action( 'crontrol/added_new_php_event', array( $this, 'added_new_event' ) );
		add_action( 'crontrol/ran_event', array( $this, 'ran_event' ) );
		add_action( 'crontrol/deleted_event', array( $this, 'deleted_event' ) );
		add_action( 'crontrol/deleted_all_with_hook', array( $this, 'deleted_all_with_hook' ), 10, 2 );
		add_action( 'crontrol/paused_hook', array( $this, 'paused_hook' ) );
		add_action( 'crontrol/resumed_hook', array( $this, 'resumed_hook' ) );
		add_action( 'crontrol/edited_event', array( $this, 'edited_event' ), 10, 2 );
		add_action( 'crontrol/edited_php_event', array( $this, 'edited_event' ), 10, 2 );
		add_action( 'crontrol/added_new_schedule', array( $this, 'added_new_schedule' ), 10, 3 );
		add_action( 'crontrol/deleted_schedule', array( $this, 'deleted_schedule' ) );
	}

	/**
	 * Fires after a new cron event is added.
	 *
	 * @param object $event {
	 *     An object containing the event's data.
	 *
	 *     @type string       $hook      Action hook to execute when the event is run.
	 *     @type int          $timestamp Unix timestamp (UTC) for when to next run the event.
	 *     @type string|false $schedule  How often the event should subsequently recur.
	 *     @type array        $args      Array containing each separate argument to pass to the hook's callback function.
	 *     @type int          $interval  The interval time in seconds for the schedule. Only present for recurring events.
	 * }
	 */
	public function added_new_event( $event ) {
		$context = array(
			'event_hook'      => $event->hook,
			'event_timestamp' => $event->timestamp,
			'event_args'      => $event->args,
		);

		if ( $event->schedule ) {
			$context['event_schedule_name'] = $event->schedule;

			if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
				$context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
			}
		} else {
			$context['event_schedule_name'] = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
		}

		$this->info_message(
			'added_new_event',
			$context
		);
	}

	/**
	 * Fires after a cron event is ran manually.
	 *
	 * @param object $event {
	 *     An object containing the event's data.
	 *
	 *     @type string       $hook      Action hook to execute when the event is run.
	 *     @type int          $timestamp Unix timestamp (UTC) for when to next run the event.
	 *     @type string|false $schedule  How often the event should subsequently recur.
	 *     @type array        $args      Array containing each separate argument to pass to the hook's callback function.
	 *     @type int          $interval  The interval time in seconds for the schedule. Only present for recurring events.
	 * }
	 */
	public function ran_event( $event ) {
		$context = array(
			'event_hook' => $event->hook,
			'event_args' => $event->args,
		);

		$this->info_message(
			'ran_event',
			$context
		);
	}

	/**
	 * Fires after a cron event is deleted.
	 *
	 * @param object $event {
	 *     An object containing the event's data.
	 *
	 *     @type string       $hook      Action hook to execute when the event is run.
	 *     @type int          $timestamp Unix timestamp (UTC) for when to next run the event.
	 *     @type string|false $schedule  How often the event should subsequently recur.
	 *     @type array        $args      Array containing each separate argument to pass to the hook's callback function.
	 *     @type int          $interval  The interval time in seconds for the schedule. Only present for recurring events.
	 * }
	 */
	public function deleted_event( $event ) {
		$context = array(
			'event_hook'      => $event->hook,
			'event_timestamp' => $event->timestamp,
			'event_args'      => $event->args,
		);

		if ( $event->schedule ) {
			$context['event_schedule_name'] = $event->schedule;

			if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
				$context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
			}
		} else {
			$context['event_schedule_name'] = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
		}

		$this->info_message(
			'deleted_event',
			$context
		);
	}

	/**
	 * Fires after all cron events with the given hook are deleted.
	 *
	 * @param string $hook    The hook name.
	 * @param int    $deleted The number of events that were deleted.
	 */
	public function deleted_all_with_hook( $hook, $deleted ) {
		$context = array(
			'event_hook'     => $hook,
			'events_deleted' => $deleted,
		);

		$this->info_message(
			'deleted_all_with_hook',
			$context
		);
	}

	/**
	 * Fires after a cron event hook is paused.
	 *
	 * @param string $hook The hook name.
	 */
	public function paused_hook( $hook ) {
		$context = array(
			'event_hook' => $hook,
		);

		$this->info_message(
			'paused_hook',
			$context
		);
	}

	/**
	 * Fires after a cron event hook is resumed (unpaused).
	 *
	 * @param string $hook The hook name.
	 */
	public function resumed_hook( $hook ) {
		$context = array(
			'event_hook' => $hook,
		);

		$this->info_message(
			'resumed_hook',
			$context
		);
	}

	/**
	 * Fires after a cron event is edited.
	 *
	 * @param object $event {
	 *     An object containing the new event's data.
	 *
	 *     @type string       $hook      Action hook to execute when the event is run.
	 *     @type int          $timestamp Unix timestamp (UTC) for when to next run the event.
	 *     @type string|false $schedule  How often the event should subsequently recur.
	 *     @type array        $args      Array containing each separate argument to pass to the hook's callback function.
	 *     @type int          $interval  The interval time in seconds for the schedule. Only present for recurring events.
	 * }
	 * @param object $original {
	 *     An object containing the original event's data.
	 *
	 *     @type string       $hook      Action hook to execute when the event is run.
	 *     @type int          $timestamp Unix timestamp (UTC) for when to next run the event.
	 *     @type string|false $schedule  How often the event should subsequently recur.
	 *     @type array        $args      Array containing each separate argument to pass to the hook's callback function.
	 *     @type int          $interval  The interval time in seconds for the schedule. Only present for recurring events.
	 * }
	 */
	public function edited_event( $event, $original ) {
		$context = array(
			'event_hook'               => $event->hook,
			'event_timestamp'          => $event->timestamp,
			'event_args'               => $event->args,
			'event_original_hook'      => $original->hook,
			'event_original_timestamp' => $original->timestamp,
			'event_original_args'      => $original->args,
		);

		if ( $event->schedule ) {
			$context['event_schedule_name'] = $event->schedule;

			if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
				$context['event_schedule_name'] = \Crontrol\Event\get_schedule_name( $event );
			}
		} else {
			$context['event_schedule_name'] = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
		}

		if ( $original->schedule ) {
			$context['event_original_schedule_name'] = $original->schedule;

			if ( function_exists( '\Crontrol\Event\get_schedule_name' ) ) {
				$context['event_original_schedule_name'] = \Crontrol\Event\get_schedule_name( $original );
			}
		} else {
			$context['event_original_schedule_name'] = _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
		}

		$this->info_message(
			'edited_event',
			$context
		);
	}

	/**
	 * Fires after a new cron schedule is added.
	 *
	 * @param string $name     The internal name of the schedule.
	 * @param int    $interval The interval between executions of the new schedule.
	 * @param string $display  The display name of the schedule.
	 */
	public function added_new_schedule( $name, $interval, $display ) {
		$context = array(
			'schedule_name'     => $name,
			'schedule_interval' => $interval,
			'schedule_display'  => $display,
		);

		$this->info_message(
			'added_new_schedule',
			$context
		);
	}

	/**
	 * Fires after a cron schedule is deleted.
	 *
	 * @param string $name     The internal name of the schedule.
	 */
	public function deleted_schedule( $name ) {
		$context = array(
			'schedule_name' => $name,
		);

		$this->info_message(
			'deleted_schedule',
			$context
		);
	}

	/**
	 * Generate HTML output for the details of a log row.
	 *
	 * @param object $row Log row object.
	 * @return Event_Details_Group|string
	 */
	public function get_log_row_details_output( $row ) {
		switch ( $row->context_message_key ) {
			case 'added_new_event':
			case 'ran_event':
			case 'deleted_event':
			case 'deleted_all_with_hook':
			case 'edited_event':
				return $this->cronEventDetailsOutput( $row );
			case 'added_new_schedule':
			case 'deleted_schedule':
				return $this->cronScheduleDetailsOutput( $row );
		}

		return '';
	}

	/**
	 * Generate HTML output for the details of a log row.
	 *
	 * @param object $row Log row object.
	 * @return Event_Details_Group
	 */
	protected function cronEventDetailsOutput( $row ) {
		$context = $row->context;

		// For edits with original values, use diff formatter.
		$has_diffs = isset( $context['event_original_hook'] ) || isset( $context['event_original_args'] ) || isset( $context['event_original_timestamp'] ) || isset( $context['event_original_schedule_name'] );

		$group = new Event_Details_Group();
		$group->set_formatter( $has_diffs ? new Event_Details_Group_Diff_Table_Formatter() : new Event_Details_Group_Table_Formatter() );

		// Hook.
		if ( isset( $context['event_original_hook'] ) && $context['event_original_hook'] !== $context['event_hook'] ) {
			$group->add_item(
				( new Event_Details_Item( null, _x( 'Hook', 'PluginWPCrontrolLogger', 'simple-history' ) ) )
					->set_values( $context['event_hook'], $context['event_original_hook'] )
			);
		}

		// Arguments.
		if ( isset( $context['event_original_args'] ) && $context['event_original_args'] !== $context['event_args'] ) {
			$group->add_item(
				( new Event_Details_Item( null, _x( 'Arguments', 'PluginWPCrontrolLogger', 'simple-history' ) ) )
					->set_values( $context['event_args'], $context['event_original_args'] )
			);
		} elseif ( isset( $context['event_args'] ) ) {
			$args = $context['event_args'] !== '[]'
				? $context['event_args']
				: _x( 'None', 'PluginWPCrontrolLogger', 'simple-history' );
			$group->add_item(
				( new Event_Details_Item( null, _x( 'Arguments', 'PluginWPCrontrolLogger', 'simple-history' ) ) )
					->set_new_value( $args )
			);
		}

		// Next Run (timestamps need to be formatted as dates).
		if ( isset( $context['event_original_timestamp'] ) && $context['event_original_timestamp'] !== $context['event_timestamp'] ) {
			$group->add_item(
				( new Event_Details_Item( null, _x( 'Next Run', 'PluginWPCrontrolLogger', 'simple-history' ) ) )
					->set_values(
						gmdate( 'Y-m-d H:i:s', $context['event_timestamp'] ),
						gmdate( 'Y-m-d H:i:s', $context['event_original_timestamp'] )
					)
			);
		} elseif ( isset( $context['event_timestamp'] ) ) {
			$group->add_item(
				( new Event_Details_Item( null, _x( 'Next Run', 'PluginWPCrontrolLogger', 'simple-history' ) ) )
					->set_new_value( gmdate( 'Y-m-d H:i:s', $context['event_timestamp'] ) . ' UTC' )
			);
		}

		// Recurrence.
		if ( isset( $context['event_original_schedule_name'] ) && $context['event_original_schedule_name'] !== $context['event_schedule_name'] ) {
			$group->add_item(
				( new Event_Details_Item( null, _x( 'Recurrence', 'PluginWPCrontrolLogger', 'simple-history' ) ) )
					->set_values( $context['event_schedule_name'], $context['event_original_schedule_name'] )
			);
		} elseif ( isset( $context['event_schedule_name'] ) ) {
			$group->add_item(
				( new Event_Details_Item( null, _x( 'Recurrence', 'PluginWPCrontrolLogger', 'simple-history' ) ) )
					->set_new_value( $context['event_schedule_name'] )
			);
		}

		return $group;
	}

	/**
	 * Generate HTML output for the details of a log row.
	 *
	 * @param object $row Log row object.
	 * @return Event_Details_Group
	 */
	protected function cronScheduleDetailsOutput( $row ) {
		$group = new Event_Details_Group();
		$group->set_formatter( new Event_Details_Group_Table_Formatter() );

		$group->add_items( [
			new Event_Details_Item( 'schedule_name', _x( 'Name', 'PluginWPCrontrolLogger', 'simple-history' ) ),
			new Event_Details_Item( 'schedule_interval', _x( 'Interval', 'PluginWPCrontrolLogger', 'simple-history' ) ),
			new Event_Details_Item( 'schedule_display', _x( 'Display Name', 'PluginWPCrontrolLogger', 'simple-history' ) ),
		] );

		return $group;
	}
}
