<?php

/**
 * Vvveb
 *
 * Copyright (C) 2020  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

/*
//flags
define('SERVICE_CACHE_FLAG_REGENERATE', -2);//wait for cache generation 
if (!defined('SERVICE_CACHE_FLAG_LOCK')) define('SERVICE_CACHE_FLAG_LOCK', -1);//lock set

//time
define('SERVICE_CACHE_EXPIRE_DELAY', 5);//real expiration time +5 seconds 
define('SERVICE_CACHE_WAIT', 3);//wait for cache generation 
define('SERVICE_CACHE_MAX_WAIT_RETRY', 3);//wait for cache generation 
define('SERVICE_CACHE_LOCK_EXPIRE', 20);//lock can not be set more than SERVICE_CACHE_LOCK_EXPIRE seconds
define('COMPONENT_CACHE_EXPIRE', 20);
*/

namespace Vvveb\System;

class Service {
	private static $queue;

	private static $components;

	private static $componentsFile = null;

	private static $loaded = false;

	private static $content = false;

	static function getInstance($regenerate = false, $content = false) {
		self::$componentsFile = view::serviceTemplate() . '.components';
		self::$content        = $content;

		if (! file_exists(self::$componentsFile) || $regenerate) {
			self::generateRequiredComponents();
			self::saveTemplateComponents();
			//	    self :: loadComponents();
		}

		if (self::$loaded) {
			return true;
		}
		self::loadTemplateComponents();
		self::loadComponents();
		self::$loaded = true;
	}

	static function loadComponents() {
		$view = view::getInstance();
		//var_dump(self :: $components);
		//			$comp[$instance] =  self :: $queue[$component][$instance]->results();
		if (isset(self::$components)) {
			self::$components = array_merge([
				'config' => [
					0 => NULL,
				],
			] , self::$components);
		} else {
			self::$components = [
				'config' => [
					0 => NULL,
				],
			];
		}

		$cache = [];
		$objs  = [];

		$notFound404 = false;

		if (is_array(self::$components)) {
			foreach (self::$components as $component => $instances) {
				$class = '\Vvveb\Component\\' . $component;
				$file  = DIR_APP . "/component/$component.php";

				if (file_exists($file)) {
					include_once $file;

					foreach ($instances as $instance => $options) {
						$obj      = new $class($options);
						$cacheKey = $obj->cacheKey();

						if ($cacheKey) {
							$cacheExpireKey                  = 'expire_' . $cacheKey;
							$cache[]                         = $cacheKey;
							$cache[]                         = $cacheExpireKey;
							$obj->component                  = $component;
							$objs[$obj->cacheKey][$instance] = $obj;
						} else {
							$results = $obj->results();

							if ($results !== false || $results !== null) {
								$results['_instance'] = $obj;
							}

							$comp            = &$view->$component;
							$comp[$instance] = $results;

							if (isset($results['404']) && $results['404'] == true) {
								$notFound404 = true;
							}
						}
					}
				}
			}

			//get cached data
			$cacheDriver = Cache::getInstance();
			//$cacheDriver = new Memcached();
			$null = [];
			/*
			if (Session :: $canStart)
			$sessionKey = '_session_' . $_COOKIE['s'];
			{
			//		$cache['_session_' . $_COOKIE['s']] = null;
			}
			*/
			$data = $cacheDriver->getMulti($cache, ID_HOST);
			//$data = $cacheDriver->getMulti($cache, $null );
			//	    if (isset($sessionKey)) {session :: $data = $data[$sessionKey];var_dump($data[$sessionKey]);session_start();}
			foreach ($objs as $cacheKey => $instances) {
				foreach ($instances as $index => $instance) {
					$component                    = $instance->component;
					$data[$cacheKey]['_instance'] = $instance;
					$comp                         = &$view->$component;
					$comp[$index]                 = $data[$cacheKey];

					if (isset($comp[$index]['404']) && $comp[$index]['404'] == true) {
						$notFound404 = true;
					}
				}

				//cache hit, remove from sql regeneration queue
				$cacheExpireKey = 'expire_' . $cacheKey;

				//error_log($data[$cacheExpireKey] . ' -> ' . $data[$cacheExpireKey]);
				//if no lock set (! -1) and cache is expiring then set lock and set for regeneration
				//error_log($data[$cacheExpireKey]);
				if (! isset($data[$cacheExpireKey]) || ! isset($data[$cacheKey]) || ($data[$cacheExpireKey] > 0 && ($data[$cacheExpireKey] + SERVICE_CACHE_EXPIRE_DELAY) < $_SERVER['REQUEST_TIME'])) {
					$cacheDriver->set($cacheExpireKey, SERVICE_CACHE_FLAG_LOCK, SERVICE_CACHE_LOCK_EXPIRE); //set lock
					$data[$cacheExpireKey] = SERVICE_CACHE_FLAG_REGENERATE; //set regeneration flag
					//error_log($data[$cacheExpireKey] . ' regeneration' . $cacheExpireKey);
				}

				if ($data[$cacheExpireKey] > 0) {
					unset($objs[$cacheKey], $cache[$cacheKey], $cache[$cacheExpireKey], $data[$cacheKey], $data[$cacheExpireKey]);
				}
			}

			/*			error_log(print_r($cache, 1));
			 error_log(print_r($data, 1));*/

			$wait  = true;
			$retry = 0;
			//run sql queries for uncached components
			$saveCache = [];

			while ($wait && $retry < SERVICE_CACHE_MAX_WAIT_RETRY) {
				$wait = false;
				$retry++;

				foreach ($objs as $key => $objects) {
					$cacheExpireKey = 'expire_' . $key;
					//error_log($cacheExpireKey . ' --- waiTTTTT ' . $data[$cacheExpireKey]);
					//check lock
					if (! isset($data[$cacheExpireKey]) || ! isset($data[$key]) || $data[$cacheExpireKey] == SERVICE_CACHE_FLAG_REGENERATE) {
						//lock set by this script, regenerate content
						$id      = key($objects);
						$results = $objects[$id]->results();

						if ($results) {
							$saveCache[$key]            = $results;
							$saveCache[$cacheExpireKey] = $_SERVER['REQUEST_TIME'] + $objects[$id]->cacheExpire;
						}

						if (isset($results['404']) && $results['404'] == true) {
							$notFound404 = true;
						}

						$data[$cacheExpireKey] = 0;

						foreach ($objects as $index => $instance) {
							$results['_instance'] = $instance;
							$component            = $instance->component;
							$comp                 = &$view->$component;
							$comp[$index]         = $results;
						}
					} else {
						if ($data[$cacheExpireKey] == SERVICE_CACHE_FLAG_LOCK) {
							//error_log("wait for $cacheExpireKey");
							//item is locked, some other script is generating content
							$wait = true;
						}
					}
				}

				if ($wait) {
					//error_log('wait cache ' . $_SERVER['REQUEST_URI'] . print_r(array_keys($cache),1));
					//get
					sleep(SERVICE_CACHE_WAIT);
					$data = $cacheDriver->getMulti(array_keys($cache));
				}
			}

			if ($retry >= SERVICE_CACHE_MAX_WAIT_RETRY) {
				error_log('error:CACHE max retry reached for ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
			}
		}

		if (! empty($saveCache)) {
			//    var_dump($saveCache);
			$cacheDriver->setMulti($saveCache, $_SERVER['REQUEST_TIME'] + COMPONENT_CACHE_EXPIRE, ID_HOST);
		}

		//call request for each component
		foreach (self::$components as $component => $instances) {
			foreach ($instances as $index => $options) {
				if (isset($view->$component)) {
					$comp    = &$view->$component;
					$results = $comp[$index];
					$object  = $results['_instance'];

					if (method_exists($object, 'request')) {
						$object->request($results);
					}
				}
			}
		}
		/*
		foreach($objs as $key => $objects)
		{
		foreach ($objects as $index => $instance)
		{
		$component = $instance->component;
		
		
		}
		}*/

		unset($data, $cache);

		self::$components = NULL;

		if ($notFound404) {
			front_controller::notFound(false);
		}
	}

	/*	static  function isLoaded($component)
	{
	return isset(self :: $queue[$component]);
	}
	*/
	static function results() {
	}

	static function generateRequiredComponents() {
		//get components from html page
		$document                      = new \DomDocument();
		$document->preserveWhiteSpace  = false;
		$document->recover             = true;
		$document->strictErrorChecking = false;
		$document->formatOutput        = false;
		$document->resolveExternals    = false;
		$document->validateOnParse     = false;
		$document->xmlStandalone       = true;

		$view = view::getInstance();
		libxml_use_internal_errors(true);

		if (self::$content) {
			@$document->loadHTML(self::$content);
		} else {
			@$document->loadHTMLFile(view::$templatePath . $view->template(),
						LIBXML_NOWARNING | LIBXML_NOERROR);
		}

		$xpath = new \DOMXpath($document);

		//include froms in case any component_ is included
		$elements = $xpath->query('//*[ @data-v-copy-from ]');

		if ($elements && $elements->length) {
			$fromDocument                      = new \DomDocument();
			$fromDocument->preserveWhiteSpace  = false;
			$fromDocument->recover             = true;
			$fromDocument->strictErrorChecking = false;
			$fromDocument->formatOutput        = false;
			$fromDocument->resolveExternals    = false;
			$fromDocument->validateOnParse     = false;
			$fromDocument->xmlStandalone       = true;

			foreach ($elements as $element) {
				if (preg_match('/([^\,]+)\,([^\s$,]+)/', $element->getAttribute('data-v-copy-from') , $from)) {
					$file     = $from[1];
					$selector = $from[2];

					$fromDocument->loadHTMLFile(view::$templatePath . $file);

					$fromXpath = new \DOMXpath($fromDocument);

					$fromElements = $fromXpath->query(\Vvveb\cssToXpath($selector));

					foreach ($fromElements as $externalNode) {
						$importedNode = $document->importNode($externalNode, true);
						$element
							->parentNode
							->replaceChild($importedNode, $element);
					}
				}
			}
		}

		//search for elements that have a class starting with component_
		//$elements = $xpath->query('//*[ contains(@class, "component_") ]');
		$elements = $xpath->query('//*[@*[starts-with(name(), "data-v-component-")]]');

		foreach ($elements as $element) {
			$component = '';
			$opts      = [];

			foreach ($element->attributes as $attr) {
				$nodeName = $attr->nodeName;

				if (strpos($nodeName, 'data-v-component-') === 0) {
					$component = str_replace('data-v-component-', '', $nodeName);
				//$classes = explode(' ', trim($attr->nodeValue));
				} else {
					if (strpos($nodeName, 'data-v-') === 0) {
						$option        = str_replace('data-v-', '', $nodeName);
						$opts[$option] = $attr->nodeValue;
					}
				}
			}

			//$component = preg_replace('/.*component_([a-zA-Z_]+).*/',  '\1', $element->getAttribute("class"));
			//get all classes
			//$classes = explode(' ',trim($element->getAttribute("class")));
			//search for options
			$options = null;

			//validate options
			$validOptions   = [];
			$componentClass = '\Vvveb\Component\\' . $component;
			$file           = DIR_APP . "/component/$component.php";

			if (file_exists($file)) {
				include_once $file;
				//$componentClass = new $componentClass;
				//do not add design only components
				if ($componentClass::$designOnly == true) {
					continue;
				}
				$validOptions = array_keys($componentClass::$defaultOptions);
			} else {
				continue;
			}

			//save options
			foreach ($opts as $name => $option) {
				if (in_array($name, $validOptions) && isset($option) !== false) {
					if ((isset($option[0]) && ($option[0] == '{')) || (strpos($option, ',') !== false)) {
						$options[$name] = json_decode($option, 1);
					} else {
						$options[$name] = $option;
					}
				}
			}

			$options['_hash']         = md5(serialize($options));
			$components[$component][] = $options;

			/*
			$options = $xpath->query(".//*[ contains( concat(\" \",@class),
			\" {$component}_\") ]", $element);
			foreach($options as $option)
			{
			$opt = preg_replace("/.*({$component}_[a-zA-Z_]+).+/", '\1',
			$option->getAttribute("class"));
			$comp[] = $opt;
			}*/
		}

		if (isset($components)) {
			self::$components = $components;
		}
		//get fields for component
		//load components and feed fields
	}

	static function saveTemplateComponents() {
		/*
		if (isset($_POST['_component_content']))
		{
			$component = $_REQUEST['_component_ajax'];
			$id = $_REQUEST['_component_id'];
			self::$components = [$component => [self::$components[$component][$id]]];
		}*/

		$php = var_export(self::$components, true);
		$php = preg_replace('/\s+/', ' ', $php);
		//repeating end lines
		$php = preg_replace('/\n+/', '', $php);

		return file_put_contents(self::$componentsFile, '<?php $components=' . $php . ';');
	}

	static function loadTemplateComponents() {
		include_once self::$componentsFile;

		if (isset($components)) {
			self::$components = $components;
		}
		//keep only requested service
		if (isset($_GET['component_ajax'])) {
		}

		return true;
	}
}
