<?php

	namespace Mocha\Oms;

	use Mocha\Core\InstanceKey;
	use Mocha\Core\InstanceReference;
	use Mocha\Core\Oms;
	use Mocha\Core\TenantReference;
	use Phast\UUID;

	class HttpRequestMethod
	{
		const GET = 1;
		const POST = 2;
	}

	class HttpPostDataFormat
	{
		const URLENCODED = 1;
		const JSON = 2;
	}

	function http_build_query2(array $ary)
	{
		$retval = "";
		foreach ($ary as $key => $value)
		{
			$retval .= urlencode($key) . "=" . urlencode($value) . "&";
		}
		return substr($retval, 0, strlen($retval) - 1);
	}

	class HttpOmsClient extends Oms
	{
		public string $ServerName;
		public int $PortNumber;

		public function __construct(string $serverName, int $portNumber)
		{
			parent::__construct();

			$this->ServerName = $serverName;
			$this->PortNumber = $portNumber;
		}

		private function curl_new(string $relativeUrl) : \CurlHandle | null
		{
			$curl = \curl_init("http://" . $this->ServerName . ":" . $this->PortNumber . $relativeUrl);
			if ($curl instanceof \CurlHandle)
			{
				return $curl;
			}
			return null;
		}
		private function curl_request($relativeUrl, $method = HttpRequestMethod::GET, string | array | null $data = null, $dataFormat = HttpPostDataFormat::URLENCODED) : string | null
		{
			//trigger_error("curl_request for url " . $relativeUrl);
			$curl = $this->curl_new($relativeUrl);
			\curl_setopt($curl, CURLOPT_HTTPHEADER, array("X-Forwarded-For:" . $_SERVER["REMOTE_ADDR"]));
			if ($method == HttpRequestMethod::POST)
			{
				//trigger_error("POST " . $relativeUrl);
				\curl_setopt($curl, CURLOPT_POST, true);
				if ($data !== null)
				{
					if (is_string($data))
					{
						\curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
					}
					else if (is_array($data))
					{
						if ($dataFormat == HttpPostDataFormat::URLENCODED)
						{
							//trigger_error("POST(" . count($data) . ") " . http_build_query2($data));
							\curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query2($data));
						}
						else
						{
							\curl_setopt($curl, CURLOPT_POSTFIELDS, \json_encode($data));
						}
					}
				}
			}
			\curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);

			$response = \curl_exec($curl);
			\curl_close($curl);

			return $response;
		}
		private function curl_request_json($relativeUrl, $method = HttpRequestMethod::GET, string | array | null $data = null, $dataFormat = HttpPostDataFormat::URLENCODED) : array | null
		{
			$result = $this->curl_request($relativeUrl, $method, $data, $dataFormat);
			if ($result == null)
			{
				return null;
			}

			$json = json_decode($result, true);
			return $json;
		}

		protected function assignRelationshipInternal(InstanceReference $sourceInstance, InstanceReference $relationshipInstance, array|InstanceReference $targetInstances, \DateTime|null $effectiveDate = null)
		{
			if (!is_array($targetInstances))
			{
				$targetInstances = [ $targetInstances ];
			}

			// ? valid actions for 'relationship' are: add, remove, update
			// ? ---- add: adds the specified instances to the existing relationship
			// ? ---- remove: removes the specified instances from the existing relationship
			// ? ---- update: clears the existing relationship and replaces it completely with the specified instances
			$count = count($targetInstances);
			$data = array
			(
				"count" => $count
			);
			for ($i = 0; $i < $count; $i++)
			{
				$data["item" . $i] = $targetInstances[$i]->GlobalIdentifier->__toString();
			}
			$json = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $sourceInstance->GlobalIdentifier . "/relationships/" . $relationshipInstance->GlobalIdentifier . "/update", HttpRequestMethod::POST, $data);
			
			if ($json["result"] == "success")
			{
				return true;
			}
			return false;

		}
		protected function getAttributeValueInternal(InstanceReference $sourceInstance, InstanceReference $attributeInstance, $defaultValue = null, $effectiveDate = null): string|null
		{
			$result = $this->curl_request("/tenants/" . $this->getTenantName() . "/instances/" . $sourceInstance->GlobalIdentifier . "/attributes/" . $attributeInstance->GlobalIdentifier . "/current");
			return $result;
			// print_r($json);
		}
		protected function getRelatedInstancesInternal(InstanceReference $sourceInstance, InstanceReference $relationshipInstance, \DateTime|null $effectiveDate = null): array|null
		{
			$json = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $sourceInstance->GlobalIdentifier . "/relationships/" . $relationshipInstance->GlobalIdentifier);
			if ($json["result"] == "success")
			{
				$ary = $json["value"];
				$retval = array();
				foreach ($ary as $inst)
				{
					$retval[] = new InstanceReference(null, InstanceKey::parse($inst["iid"]), UUID::parse($inst["globalIdentifier"]));
				}
				return $retval;
			}
			return [ ];
		}
		public function isConnected() : bool
		{
			$json = $this->curl_request_json("/ping");
			if (is_array($json))
			{
				if ($json["result"] == "success")
				{
					return true;
				}
			}
			return false;
		}
		protected function removeRelationshipInstanceInternal(string|InstanceKey|InstanceReference $sourceInstance, string|InstanceKey|InstanceReference $relationshipInstance, array|string|InstanceKey|InstanceReference $targetInstances, \DateTime|null $effectiveDate = null)
		{

		}
		protected function setAttributeValueInternal(InstanceReference $sourceInstance, InstanceReference $attributeInstance, mixed $value, \DateTime|null $effectiveDate = null)
		{
			$json = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $sourceInstance->GlobalIdentifier . "/attributes/" . $attributeInstance->GlobalIdentifier . "/update", HttpRequestMethod::POST, array
			(
				"value" => $value
			));

			if ($json["result"] == "success")
			{
				return true;
			}
			return false;
		}
		public function getInstancesByAttributes($parms)
		{
			// FIXME: NOT IMPLEMENTED
			//usage:
			// getInstanceByAttributes (array ( getInstanceByGlobalIdentifier(NAME_ATTRIBUTE) => "zq-developer" ))
			$insts = $this->getInstances();
			$insts2 = [ ];
			foreach ($insts as $inst)
			{
				foreach ($parms as $key => $value)
				{
					$parmInst = $this->normalizeInstanceReference($key);
					if ($this->getAttributeValue($inst, $parmInst) == $value)
					{
						$insts2[] = $inst;
					}
				}
			}
			
			return $insts2;
		}
		public function search(string $query, array | InstanceReference | null $parentClasses = null, string | null $m = null) : array | bool
		{
			$uri = "/tenants/super/instances?";
			
			if ($m != null)
			{
				$uri .= "m=" . urlencode($m) . "&";
			}
			$uri .= "q=" . urlencode($query);
			if (is_array($parentClasses))
			{
				for ($i = 0; $i < count($parentClasses); $i++)
				{
					$uri .= "&parentClassIid=" . $parentClasses[$i]->InstanceKey->__toString();
				}
			}
			else if ($parentClasses instanceof InstanceReference)
			{
				$uri .= "&parentClassIid=" . $parentClasses->InstanceKey->__toString();
			}
			
			$json = $this->curl_request_json($uri, HttpRequestMethod::GET);
			return $json;
		}
		public function getInstancesOfByAttributes(array|InstanceKey|InstanceReference|string|null $parentClass, array $parms)
		{
			$parentClass = $this->normalizeInstanceReference($parentClass);
			$data = array();
			foreach ($parms as $k => $v)
			{
				$data[$this->normalizeInstanceReference($k)->GlobalIdentifier->__toString()] = $v;
			}

			$insts2 = array();
			$url = "/tenants/" . $this->getTenantName() . "/instances";
			if ($parentClass !== null)
			{
				$url .= "?parentClassIid=" . $parentClass->GlobalIdentifier;
			}
			$json = $this->curl_request_json($url, HttpRequestMethod::POST, $data);
			foreach ($json["instances"] as $inst)
			{
				$insts2[] = new InstanceReference(null, InstanceKey::parse($inst["iid"]), UUID::parse($inst["globalIdentifier"]));
			}
			return $insts2;

			/*

			// FIXME: NOT IMPLEMENTED
			//usage:
			// getInstanceByAttributes (array ( getInstanceByGlobalIdentifier(NAME_ATTRIBUTE) => "zq-developer" ))
			if ($parentClass !== null)
			{
				$insts = $this->getInstancesOf($parentClass);
			}
			else
			{
				$insts = $this->getInstances();
			}

			$insts2 = [ ];
			foreach ($insts as $inst)
			{
				foreach ($parms as $key => $value)
				{
					// echo ($key . " = " . $value . " ? ");
					$parmInst = $this->normalizeInstanceReference($key);
					$actualValue = $this->getAttributeValue($inst, $parmInst);
					if ($actualValue == $value)
					{
						$insts2[] = $inst;
					}
				}
			}
			
			return $insts2;
			*/
		}

		public function getInstanceData(array|InstanceKey|InstanceReference|string|null $inst)
		{
			$inst = $this->normalizeInstanceReference($inst);

			$json = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $inst->GlobalIdentifier);
			if (array_key_exists("result", $json))
			{
				// V2 should have result
				if ($json["result"] == "success")
				{
					return $json["value"];
				}
			}
			else
			{
				// HACK
				return $json;
			}
			return null;
		}

		protected function getInstancesInternal() : array
		{
			$json = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances");
			if ($json["result"] == "success")
			{
				$ary = $json["instances"];
				$retval = array();
				foreach ($ary as $inst)
				{
					$retval[] = new InstanceReference(null, InstanceKey::parse($inst["iid"]), UUID::parse($inst["globalIdentifier"]));
				}
				return $retval;
			}
			return [ ];	
		}

		public function getInstanceFromDatabase($value)
		{
			if ($value["class_id"] === null)
				return null;
			
			$retval = new InstanceReference($value["id"], new InstanceKey($value["class_id"], $value["inst_id"]), $value["global_identifier"]);
			return $retval;
		}

		/**
		 * Gets the tenant with the specified name, or NULl if no tenant with the specified name exists on the server.
		 * 
		 * @param string $tenantName the name of the desired tenant
		 * 
		 * @return ?TenantReference
		 */
		public function getTenantByName(string $tenantName) : ?TenantReference
		{
			$json = $this->curl_request_json("/tenants");
			$i = 0;
			if ($json["result"] == "success")
			{
				foreach ($json["tenants"] as $tenant)
				{
					if ($tenant == $tenantName)
					{
						return new TenantReference($i, $tenant, null);
					}
					$i++;
				}
			}
			return null;
		}

		public function getInstancePreview(InstanceReference $inst) : array
		{
			$json = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $inst->GlobalIdentifier->__toString() . "/preview");
			return $json;
		}

		public function getResponse($element)
		{
			$json = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $element->InstanceKey . "/element");
			return $json;
		}
		
		/**
		 * Gets the instance with the specified instance key.
		 */
		public function getInstanceByKey(InstanceKey $key) : ?InstanceReference
		{
			$result = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $key);
			return new InstanceReference(null, InstanceKey::parse($result["iid"]), UUID::parse($result["globalIdentifier"]));
		}
		
		/**
		 * Gets the instance with the specified global identifier.
		 */
		public function getInstanceByGlobalIdentifier(UUID|string $inst) : ?InstanceReference
		{
			$result = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $inst);
			return new InstanceReference(null, InstanceKey::parse($result["iid"]), UUID::parse($result["globalIdentifier"]));
		}

		public function createInstanceOf(InstanceReference $classInstance) : ?InstanceReference
		{
			$result = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/create", HttpRequestMethod::POST, array(
				"parentClassIid" => $classInstance->GlobalIdentifier
			), HttpPostDataFormat::URLENCODED);
			
			if ($result["result"] == "success")
			{
				return new InstanceReference(null, InstanceKey::parse($result["value"]["iid"]), UUID::parse($result["value"]["globalIdentifier"]));
			}
			return null;
		}

		public function processElement(InstanceReference $element, array $values) : array
		{
			$result = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $element->GlobalIdentifier . "/element", HttpRequestMethod::POST, $values);
			return $result;
		}

		public function getRelatedTasks(InstanceReference $inst)
		{
			$result = $this->curl_request_json("/tenants/" . $this->getTenantName() . "/instances/" . $inst->GlobalIdentifier . "/relatedTasks", HttpRequestMethod::GET);
			if ($result["result"] == "success")
			{
				return $result;
			}
			return [ ];
		}

		public function getGlobal(UUID $inst, $defaultValue = null)
		{
			$json = $this->curl_request_json("/globals/" . $inst->__toString(), HttpRequestMethod::GET);
			if ($json["result"] == "success")
			{
				return $json["value"];
			}
			return $defaultValue;
		}

		public function setGlobal(UUID $inst, mixed $value)
		{
			$this->curl_request_json("/globals/" . $inst->__toString(), HttpRequestMethod::POST, array
			(
				"value" => $value
			));
		}

	}

?>