<?php
/*
* Define class pspCronjobs
* Make sure you skip down to the end of this file, as there are a few
* lines of code that are very important.
*/
!defined('ABSPATH') and exit;
if (class_exists('pspCronjobs') != true) {
	class pspCronjobs
	{
		/*
		* Some required plugin information
		*/
		const VERSION = '1.0';

		/*
		* Store some helpers config
		*/
		public $the_plugin = null;

		private $module_folder = '';
		private $module_folder_path = '';
		private $module = '';
		
		static protected $_instance;
		
		public $is_admin = false;
		
		public $alias = '';
		public $localizationName = '';
		
		static private $cron_config_alias = '';
		public $custom_schedules = array();
		public $config = array();


		/*
		* Required __construct() function that initalizes the AA-Team Framework
		*/
		public function __construct($psp)
		{
			//global $psp;
   
			$this->the_plugin = $psp;
			$this->module_folder = $this->the_plugin->cfg['paths']['plugin_dir_url'] . 'modules/cronjobs/';
			$this->module_folder_path = $this->the_plugin->cfg['paths']['plugin_dir_path'] . 'modules/cronjobs/';
			$this->module = isset($this->the_plugin->cfg['modules']['cronjobs']) ? $this->the_plugin->cfg['modules']['cronjobs'] : array();
			
			$this->alias = $this->the_plugin->alias;
			$this->localizationName = $this->the_plugin->localizationName;
 
			$this->is_admin = $this->the_plugin->is_admin;
			
			self::$cron_config_alias = $this->alias.'_cronjobs';

			$this->build_config();
			if ( empty($this->config) ) return;
			
			$this->init();
		}
		
		/**
		* Singleton pattern
		*
		* @return pspCronjobs Singleton instance
		*/
		static public function getInstance()
		{
			if (!self::$_instance) {
				self::$_instance = new self;
			}

			return self::$_instance;
		}
		
		
		/**
		 * Init...
		 */
		public function init() {
			add_filter('cron_schedules', array($this, 'add_custom_schedules'));
			$this->add_cron_actions();
			add_action('init', array($this, 'add_cron_events'));
		}
		
		public function build_config() {
			// Initialize the wwcAmazonSyncronize class
			require_once( $this->the_plugin->cfg['paths']['plugin_dir_path'] . '/modules/cronjobs/cronjobs.config.php' );
			$cronObj = new pspCronjobsConfig($this->the_plugin);
 
			$this->custom_schedules = $cronObj->get_custom_schedules();
			$this->config = $cronObj->get_config();
 
			foreach ($this->config as $cron_id => $cron) {
				if ( isset($cron["recurrence_wp"])
					&& in_array($cron["recurrence_wp"], array_keys($this->custom_schedules)) ) {

					$this->config["$cron_id"]["recurrence_wp"] = $this->alias . '_' . $cron["recurrence_wp"]; 
				}
			}
		}

		public function get_config($cron_id='all', $include_dynamic=true) {
			$cfg = $this->config;

			if ( empty($cfg) || !is_array($cfg) ) return array();
	 
			if ( $cron_id!=='all' ) {
				if ( !isset($cfg["$cron_id"]) || !is_array($cfg["$cron_id"]) || empty($cfg["$cron_id"]) ) {
					return array();
				} else {
					$cfg = $cfg["$cron_id"];
				}
			}
 
			$ret = $cfg;
			if ( $include_dynamic ) {
				$opt = get_option(self::$cron_config_alias, array());
				if ( $cron_id!=='all' ) {
					if ( !empty($opt) && isset($opt["$cron_id"]) ) {
						$opt = (array) $opt["$cron_id"];
					}
				}
				$ret = array_merge_recursive($ret, (array) $opt);
			}
			
			// keep only those that exists in config too!
			if ( $cron_id!=='all' ) {
				if ( !isset($cfg) || empty($cfg) ) {
					unset( $ret );
				}
			} else {
				foreach ($ret as $__cron_id => $__cron) {
					if ( !isset($cfg["$__cron_id"]) || empty($cfg["$__cron_id"]) ) {
						unset( $ret["$__cron_id"] );
					}
				}
			}
			
			if ( $cron_id!=='all' ) {
				$ret = array_merge_recursive($ret, (array) $this->set_config_extra($ret));
			} else {
				foreach ($ret as $__cron_id => $__cron) {
					if ( empty($__cron) || !is_array($__cron) ) continue 1;
					$ret["$__cron_id"] = array_merge_recursive($ret["$__cron_id"],
						(array) $this->set_config_extra($ret["$__cron_id"])
					);
				}
			}

			return $ret;
		}

		private function set_config_extra( $cron ) {
			$ret = array();
			if ( !isset($cron['status']) || empty($cron['status']) ) {
				$ret['status'] = $cron['status_default'];
			}
			if ( !isset($cron['is_active']) || empty($cron['is_active'])
				|| !in_array($cron['is_active'], array('no', 'yes')) ) {
				$ret['is_active'] = isset($cron['is_active_default']) ? $cron['is_active_default'] : 'no';
			}
			if ( isset($cron['end_time'], $cron['start_time']) ) {
				$ret['run_duration'] = $cron['end_time'] - $cron['start_time'];
				$ret['run_duration'] = number_format($ret['run_duration'], 2);
			}
			return $ret;
		}


		/**
		 * Run cron
		 */
		public function run_core( $cron_id, $cron ) {
			$cfg = isset($this->config) ? $this->config : array();
			if ( empty($cfg) || !is_array($cfg) ) return array();

			$external_func_status = array();
			{
				//@ini_set('memory_limit', '512M');
				@ini_set('max_execution_time', 0);
				@set_time_limit(0); // infinte

				switch ($cron_id) {
					case 'serp':
						// Initialize the serp class
						require_once( $this->the_plugin->cfg['paths']['plugin_dir_path'] . '/modules/serp/init.php' );
						$serp = new pspSERP(true);

						$external_func_status = $serp->cronjob_keyword_update_ranks( $cron, 'return' );
						break;

					case 'facebook':
						// Initialize the facebook class
						require_once( $this->the_plugin->cfg['paths']['plugin_dir_path'] . '/modules/facebook_planner/init.php' );
						$facebook = new pspFacebook_Planner(true);

						$external_func_status = $facebook->facebook_cronjob( $cron, 'return' );
						break;

					// alexa is disabled
					/*case 'alexa_rank':
						// Initialize the alexa rank class
						require_once( $this->the_plugin->cfg['paths']['plugin_dir_path'] . '/modules/Alexa_Rank/init.php' );
						$alexarank = new pspAlexaRank(true);

						$external_func_status = $alexarank->cronjob( $cron, 'return' );
						break;*/

					case 'report':
						// Initialize the pspReport class
						require_once( $this->the_plugin->cfg['paths']['plugin_dir_path'] . '/modules/report/init.php' );
						$pspReport = new pspReport(true);
						
						$external_func_status = $pspReport->cronjob( $cron, 'return' );
						break;
				} // end switch
			}
			return $external_func_status;
		}
		public function run( $cron_id ) {
			// debug
			//update_option($this->alias.'_'.$cron_id.'_cron', array('cron_id' => $cron_id, 'time' => time()));
			
			// unblock crons!
			if ( $cron_id == 'unblock_crons' ) {
				return $this->unblock( $cron_id );
			}

			$cfg = isset($this->config["$cron_id"]) ? $this->config["$cron_id"] : array();
			if ( empty($cfg) || !is_array($cfg) ) return array();

			// default init...
			$cron_status_all = $this->get_cron($cron_id);
			$cron_status = isset($cron_status_all['status']) ? $cron_status_all['status'] : $cfg['status_default'];
			
			// verify if current cron is blocked: status is running or stop
			///*debug
			if ( in_array($cron_status, array('running', 'stop')) ) {
				return true;
			}
			//*/
			$this->set_cron($cron_id, array(
				'status' 		=> 'running',
				'start_time' 	=> time(),
				'end_time' 		=> time()
			)); // current cron status become running!
			
			$ret = array('status' => 'done');
			if ( /*debug 1 ||*/ in_array($cron_status, array('new', 'failed', 'done')) ) {

				// run core...
				$external_func_status = $this->run_core($cron_id, $cron_status_all);

				//var_dump('<pre>external_func_status:',$external_func_status,'</pre>');
				$ret = array_merge($ret, (array) $external_func_status);
			}
			$cron_new_status = isset($ret['status']) ? $ret['status'] : 'done';
			$cron_new_info = array(
				'status' 		=> $cron_new_status,
				'end_time' 		=> time(),
			);
			if ( isset($ret['msg']) && ! empty($ret['msg']) ) {
				$cron_new_info['last_msg'] = $ret['msg'];
			}
			$this->set_cron($cron_id, $cron_new_info); // current cron status become done | failed!
 
			// depedency
			if ( isset($ret['depedency']) && !empty($ret['depedency']) ) {
				foreach ( $ret['depedency'] as $_cronid => $_cronstatus) {
					//$this->set_cron($_cronid, array('status' => $_cronstatus)); // depedency cron new status!
					$this->set_cron($_cronid, $_cronstatus); // depedency cron new status!
				}
			}
			
			//var_dump('<pre>', $cron_id, $ret, '</pre>');
			return $cron_new_status;
		}

		private function unblock( $cron_id ) {
			// debug
			//update_option($this->alias.'_'.$cron_id.'_cron', array('cron_id' => $cron_id, 'time' => time()));
			   
			$cfg = isset($this->config["$cron_id"]) ? $this->config["$cron_id"] : array();
			if ( empty($cfg) || !is_array($cfg) ) return array();

			// default init...
			$cron_status_all = $this->get_cron($cron_id);
			$cron_status = isset($cron_status_all['status']) ? $cron_status_all['status'] : $cfg['status_default'];
			
			// verify if current cron is blocked: status is running or stop
			///*debug
			if ( in_array($cron_status, array('running', 'stop')) ) {
				return true;
			}
			//*/
			$this->set_cron($cron_id, array(
				'status' 		=> 'running',
				'start_time' 	=> time(),
				'end_time' 		=> time()
			)); // current cron status become running!
			
			$ret = array('status' => 'done');
			if ( /*debug 1 ||*/ in_array($cron_status, array('new', 'failed', 'done')) ) {
				$current_time = time();

				$cronsinfo = array( 'total' => 0, 'unblocked' => 0 );
				foreach ($this->config as $__cron_id => $__cron) {
					if ( $__cron_id == 'unblock_crons' ) continue 1;
					$cronsinfo['total']++;

					$__cron_status_all = $this->get_cron($__cron_id);
					$__cron_status = isset($__cron_status_all['status']) ? $__cron_status_all['status'] : $__cron['status_default'];
					if (
						$__cron_status == 'running'
						&& ( $current_time > (int) ( $__cron_status_all['start_time'] + $__cron['max_execution_time'] ) )
					) {
						$__cron_new_status = 'failed';
						$this->set_cron($__cron_id, array(
							'status' 		=> $__cron_new_status,
							'end_time' 		=> time()
						));
						$cronsinfo['unblocked']++;
					}
				} // end foreach

				$ret['msg'] = sprintf( '%s of total %s cronjobs, were unblocked.', $cronsinfo['unblocked'], $cronsinfo['total'] );
			}
			$cron_new_status = isset($ret['status']) ? $ret['status'] : 'done';
			$cron_new_info = array(
				'status' 		=> $cron_new_status,
				'end_time' 		=> time(),
			);
			if ( isset($ret['msg']) && ! empty($ret['msg']) ) {
				$cron_new_info['last_msg'] = $ret['msg'];
			}
			$this->set_cron($cron_id, $cron_new_info); // current cron status become done | failed!
			
			//var_dump('<pre>', $cron_id, $ret, '</pre>');
			return $cron_new_status;
		}


		/**
		 * Get / Set single cron details 
		 */
		public function get_cron( $cron_id, $fields='all' ) {
			$cfg = $this->get_config($cron_id);
			if ( empty($cfg) ) return array();

			if ( $fields === 'all' ) {
				return $cfg;
			} else if ( !is_array($fields) && isset($cfg["$fields"]) ) {
				return $cfg["$fields"];
			} else if ( is_array($fields) ) {
				return array_intersect_key($cfg, array_fill_keys($fields, 1));
			}
			return array();
		}
		public function set_cron( $cron_id, $fields=array() ) {
			$opt = (array) get_option(self::$cron_config_alias, array());
			if ( !isset($opt["$cron_id"]) || !is_array($opt["$cron_id"]) ) {
				$opt["$cron_id"] = array();
			}
			$opt["$cron_id"] = array_merge($opt["$cron_id"], $fields);

			//var_dump('<pre>set_cron:',self::$cron_config_alias, $opt,'</pre>');  
			update_option(self::$cron_config_alias, $opt);
			return $opt;
		}
		public function set_cron_remove( $cron_id ) {
			$opt = (array) get_option(self::$cron_config_alias, array());
			if ( isset($opt["$cron_id"]) ) {
				unset( $opt["$cron_id"] );
			}

			//var_dump('<pre>set_cron:',self::$cron_config_alias, $opt,'</pre>');  
			update_option(self::$cron_config_alias, $opt);
			return $opt;
		}


		/**
		 * Schedule crons
		 */
		public function add_custom_schedules( $schedules ) {
			$new = array();
			foreach ($this->custom_schedules as $key => $val) {
				$_key = $this->alias . '_' . $key;
				$new["$_key"] = $val;
			}

			$schedules = array_merge($schedules, $new);
			return $schedules;
		}

		public function add_cron_events() {
			$cfg = $this->get_config();
			foreach ($cfg as $cron_id => $cron) {
				if ( $cron['is_active'] != 'yes' ) continue 1;
				$this->schedule_cron( $cron_id );
			}
		}
		
		public function schedule_cron( $cron_id ) {
			$cron = $this->get_cron($cron_id);
			$_cron_id = $this->alias . '_' . $cron_id;

			if ( !wp_next_scheduled($_cron_id, array($cron_id)) ) {
				
				$recurrence = $cron['recurrence_wp'];
				if ( $cron['start_hour'] === 'now' ) {
					$start_time = time();
				} else {
					$start_time = strtotime( date('y-m-d') ) + ( 3600 * $cron['start_hour'] );
				}
				//$start_hour += 30; // add a small delay of 30 seconds
 
				if ( $cron['is_active'] == 'yes' ) {
					wp_schedule_event($start_time, $recurrence, $_cron_id, array($cron_id));
				}
			}
		}

		public function add_cron_actions() {
			$cfg = $this->get_config();
			foreach ($cfg as $cron_id => $cron) {
				
				$hook_func = 'run';
				$_cron_id = $this->alias . '_' . $cron_id;
				add_action($_cron_id, array($this, $hook_func), 10, 1);

				if ( $cron['is_active'] != 'yes' ) {
					wp_clear_scheduled_hook( $_cron_id, array($cron_id) );
				}
			}
		}
	}
}

//$pspCronjobs = new pspCronjobs();
//$pspCronjobs = pspCronjobs::getInstance();