<?php

namespace ElementsKit_Lite\Libs\Xs_Migration;


abstract class Data_Migration implements Migration_Contract {


	const SUB_ROUTINE_STATUS_INCOMPLETE = '__incomplete';
	const SUB_ROUTINE_STATUS_DONE = '__done';


	const GENERIC_STATUS_YES = 'yes';
	const GENERIC_STATUS_NO = 'no';

	const STATUS_DONE = 'done';
	const STATUS_QUEUED = 'queued';
	const STATUS_RUNNING = 'running';

	const STATUS_FINISHED = 'finished';
	const STATUS_INITIATED = 'initiated';

	const STATUS_METHOD_PAUSED = 'paused';
	const STATUS_METHOD_EXECUTED = 'executed';
	const STATUS_METHOD_EXECUTING = 'executing';

	private $new_text_domain = 'elementskit-lite';
	private $text_domain = 'elementskit-lite';
	private $max_iteration = 10;


	/**
	 * @param $txtDomain
	 * @param $versionFrom
	 * @param $versionTo
	 *
	 * @return mixed
	 */
	public function input($txtDomain, $versionFrom, $versionTo) {

		#$versionFrom = '1.1.9';
		#$versionTo   = '1.2.0';
		$optionKey = 'data_migration_' . $txtDomain . '_log';

		$from = str_replace('.', '_', trim($versionFrom));
		$to   = str_replace('.', '_', trim($versionTo));

		$frm = $this->makeFullVersionKey($from);
		$trm = $this->makeFullVersionKey($to);

		$existingOption = get_option($optionKey);


		if(empty($existingOption)) {

			$log   = [];
			$log[] = 'Migration never has been done for this domain.';
			$log[] = 'Initiating migration for version ' . $versionFrom . ' to ' . $versionTo . ' at ' . date('Y-m-d H:i:s') . ' .';
			$log[] = 'Scanning migration file for conversion methods.';

			$cStack = $this->getCallStacks([], $frm, $trm);

			$fn = [];

			foreach($cStack['stack'] as $item) {

				$fn[$item] = self::STATUS_QUEUED;
			}

			$log[] = 'Execution plan prepared.';
			$log[] = 'Execution plan saved into database.';


			$existingOption['_func']   = $fn;
			$existingOption['_log']    = $log;
			$existingOption['_status'] = self::STATUS_INITIATED;

			update_option($optionKey, $existingOption);

			return [
				'status' => 'success',
				'log'    => $existingOption,
				'log2'   => $cStack,
			];
		}

		/**
		 * Now we have something saved into database
		 * lets check the status first
		 *
		 */

		if($existingOption['_status'] == self::STATUS_FINISHED) {

			$log = $existingOption['_log'];

			/**
			 * Now we have to check up-to which version this migration is done
			 */

			$up_to = $this->makeFullVersionKey($existingOption['_last_version_scanned']);

			if($up_to < $trm) {

				/**
				 * New version released of this plugin
				 * check if anything new need to migrate
				 */

				$cStack = $this->getCallStacks([], $frm, $trm);
				$fn     = $existingOption['_func'];

				$log[] = 'A new version update detected.';
				$log[] = 'Scanning for new migration method.';

				$found = false;

				foreach($cStack['stack'] as $item) {

					if(isset($fn[$item])) {
						continue;
					}

					$fn[$item] = self::STATUS_QUEUED;

					$found = true;
				}

				if($found) {

					$log[] = 'New conversion method detected.';
					$log[] = 'Preparing execution plan.';
					$log[] = 'Execution plan saved into database.';

					$existingOption['_func']   = $fn;
					$existingOption['_log']    = $log;
					$existingOption['_status'] = self::STATUS_INITIATED;

					update_option($optionKey, $existingOption);

					return [
						'status' => 'success',
						'log'    => $existingOption,
						'log2'   => $cStack,
					];

				} else {

					$log[] = 'No new conversion method detected.';
					$log[] = 'Updating the migration plan as finished for version ' . $versionTo . ' at ' . date('Y-m-d H:i:s') . '.';

					$existingOption['_func']                 = $fn;
					$existingOption['_log']                  = $log;
					$existingOption['_status']               = self::STATUS_FINISHED;
					$existingOption['_last_version_scanned'] = $versionTo;
					$existingOption['_plan_up_to']           = $trm;

					update_option($optionKey, $existingOption);

					return [
						'status' => 'success',
						'log'    => $existingOption,
						'log2'   => $cStack,
					];
				}

			}

			/**
			 * As status is finished and last scanned version is same as the current plugin version
			 * code execution should not come here
			 * If any case it come to this point we are updating the settings
			 */

			$log[] = 'In no scenario, execution pointer should not come here [something is wrong...].';

			$existingOption['_log']                  = $log;
			$existingOption['_last_version_scanned'] = $versionTo;
			$existingOption['_plan_up_to']           = $trm;

			update_option($optionKey, $existingOption);

			return [
				'status' => 'success',
				'log'    => $existingOption,
			];
		}

		/**
		 * At this point status of the execution plan is not finished
		 * lets do the work
		 *
		 */
		$curExecMethod = '';
		$mtdStat       = '';

		foreach($existingOption['_func'] as $mtd => $stat) {

			if($stat == self::STATUS_METHOD_EXECUTED) {
				continue;
			}

			$curExecMethod = $mtd;
			$mtdStat       = $stat;

			break;
		}

		if(empty($curExecMethod)) {

			/**
			 * All methods has been executed
			 */

			$log = $existingOption['_log'];

			$log[] = 'All conversion method has been executed.';
			$log[] = 'Setting the migration plan as finished for version ' . $versionTo . ' at ' . date('Y-m-d H:i:s') . '.';


			$existingOption['_log']                  = $log;
			$existingOption['_status']               = self::STATUS_FINISHED;
			$existingOption['_last_version_scanned'] = $versionTo;
			$existingOption['_plan_up_to']           = $trm;

			update_option($optionKey, $existingOption);

			return [
				'status' => 'success',
				'log'    => $existingOption,
			];
		}

		/**
		 * We have a conversion method to run whose status is not executed
		 *
		 */

		if($mtdStat == self::STATUS_QUEUED) {

			$log = $existingOption['_log'];

			$log[] = 'Conversion method ' . $curExecMethod . ' entered into queue at ' . date('Y-m-d H:i:s') . '.';
			$log[] = '- Conversion method ' . $curExecMethod . ' has entered into execution phase at ' . date('Y-m-d H:i:s');

			$fn = $existingOption['_func'];

			$fn[$curExecMethod] = self::STATUS_METHOD_EXECUTING;

			$existingOption['_func'] = $fn;
			$existingOption['_log']  = $log;

			update_option($optionKey, $existingOption);

			return $this->$curExecMethod($optionKey, $existingOption);
		}

		if($mtdStat == self::STATUS_METHOD_EXECUTING) {

			return [
				'status' => 'failed',
				'msg'    => 'Another person already initiated the execution.',
				'log'    => $existingOption['_log'],
			];
		}

		if($mtdStat == self::STATUS_METHOD_PAUSED) {

			$log = $existingOption['_log'];

			$log[] = '- Conversion method ' . $curExecMethod . ' has entered into executing phase at ' . date('Y-m-d H:i:s');

			$fn = $existingOption['_func'];

			$fn[$curExecMethod] = self::STATUS_METHOD_EXECUTING;

			$existingOption['_func'] = $fn;
			$existingOption['_log']  = $log;

			update_option($optionKey, $existingOption);

			return $this->$curExecMethod($optionKey, $existingOption);
		}


		/**
		 * This is the scenario that never ever should occur
		 */
		return [
			'status' => 'failed',
			'msg'    => 'Overflow',
			'log'    => [
				'Exiting...data is corrupted.',
			],
		];
	}


	/**
	 *
	 * @param array $data
	 */
	public function output(array $data) {

		if(!empty($data['option'])) {

			foreach($data['option'] as $opKey => $opVal) {

				update_option($opKey, $opVal);
			}
		}
	}


	/**
	 *
	 * @param $versionMap
	 * @param $frm
	 * @param $trm
	 *
	 * @return array
	 */
	private function getCallStacks($versionMap, $frm, $trm) {

		$callStack         = [];
		$conversionMethods = [];
		$methods           = get_class_methods($this);

		foreach($methods as $method) {

			if(substr($method, 0, 13) === 'convert_from_') {

				$conversionMethods[] = $method;

				$tmp = str_replace('convert_from_', '', $method);
				$tmp = explode('_to_', $tmp);

				$vl = $this->makeFullVersionKey($tmp[0]);
				$vh = $this->makeFullVersionKey($tmp[1]);

				$versionMap[$vl] = $tmp[0];
				$versionMap[$vh] = $tmp[1];
			}
		}


		ksort($versionMap);

		foreach($versionMap as $k => $v) {

			if($k >= $frm && $k < $trm) {

				$fnc = '';

				foreach($conversionMethods as $conversionMethod) {

					if(strpos($conversionMethod, 'convert_from_' . $v) !== false) {

						$fnc = $conversionMethod;

						break;
					}
				}

				if(!empty($fnc)) {
					$callStack[] = $fnc;
				}
			}
		}

		return [
			'map'   => $versionMap,
			'func'  => $conversionMethods,
			'stack' => $callStack,
		];
	}


	/**
	 *
	 * @param $string
	 *
	 * @return string
	 */
	public function makeFullVersionKey($string) {

		$fr = explode('_', $string);

		$frm = array_map(function($item) {
			return str_pad($item, 3, '0', STR_PAD_LEFT);
		}, $fr);

		return implode('', $frm);
	}


	/**
	 * @return string
	 */
	public function getNewTextDomain() {
		return $this->new_text_domain;
	}


	/**
	 * @return string
	 */
	public function getTextDomain() {
		return $this->text_domain;
	}


	/**
	 * @return int
	 */
	public function getMaxIteration() {
		return $this->max_iteration;
	}

}