<?php
	namespace Mocha\Oms;

	use InvalidArgumentException;
	use Mocha\Core\InstanceKey;
	use Mocha\Core\InstanceReference;
	use Mocha\Core\TenantReference;
	use Phast\Utilities\Stopwatch;
	use Phast\UUID;

	class MySQLDatabaseOms extends DatabaseOms
	{
		private \PDO $PDO;

		private ?InstanceReference $_currentUser = null;
		public function setCurrentUser(?InstanceReference $userInstance)
		{
			$this->_currentUser = $userInstance;
		}
		public function getCurrentUser() : ?InstanceReference
		{
			return $this->_currentUser;
		}

		public function query($query, array $parms)
		{
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute($parms);
			$values = $stmt->fetchAll();
			return $values;
		}

		public function getInstances() : array
		{
			$query = "SELECT * FROM mocha_instances WHERE (" . $this->buildTenantIDQuery() . ")";

			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute();
			$values = $stmt->fetchAll();
			$insts = array();
			foreach ($values as $value)
			{
				$insts[] = $this->getInstanceFromDatabase($value);
			}
			return $insts;
		}

		/**
		 * Raised when the tenant is changed via setTenant.
		 */
		public function onTenantChanged(?TenantReference $oldTenant, ?TenantReference $newTenant)
		{
			$query = "CALL mocha_select_tenant(:tenant_id);";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array
			(
				"tenant_id" => $newTenant->ID
			));
			if ($result === false)
			{
				return false;
			}
			return true;
		}

		/**
		 * 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
		{
			$query = "SELECT * FROM mocha_tenants WHERE tenant_name = :tenant_name;";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array
			(
				"tenant_name" => $tenantName
			));
			$values = $stmt->fetch();
			if ($values !== false)
			{
				$tenant = new TenantReference($values["id"], $values["tenant_name"], $values["global_identifier"]);
				return $tenant;
			}
			return null;
		}
		public function getTenantByID(int $id) : ?TenantReference
		{
			$query = "SELECT * FROM mocha_tenants WHERE id = :tenant_id;";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array
			(
				"tenant_id" => $id
			));
			$values = $stmt->fetch();
			if ($values !== false)
			{
				$tenant = new TenantReference($values["id"], $values["tenant_name"], $values["global_identifier"]);
				return $tenant;
			}
			return null;
		}


		public function __construct($hostname, $port, $databasename, $username, $password)
		{
			parent::__construct();
			$this->PDO = new \PDO("mysql:host=" . $hostname . ";port=" . $port . ";dbname=" . $databasename, $username, $password);
		}

		public function getDbId($instRef)
		{
			if ($instRef == null)
				return 0;

			return $instRef->DatabaseId;
		}

		public function createInstanceOf(InstanceReference $classInstance) : ?InstanceReference
		{
			$assigned_id = null;
			$query = "CALL mocha_create_instance_of(mocha_get_instance_by_key(1, :class_index), :global_identifier, :user_inst_id, :effective_date, @assigned_id);";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array
			(
				"class_index" => $classInstance->InstanceKey->InstanceIndex,
				"global_identifier" => null,
				"user_inst_id" => null,
				"effective_date" => null
			));

			$values = $stmt->fetch();
			$stmt->closeCursor();
			
			$results = current($this->PDO->query("SELECT @assigned_id AS assigned_id")->fetchAll());

			if (isset($results["assigned_id"]))
			{
				return $this->getInstanceByDBID($results["assigned_id"]);
			}
			return null;
		}

		public function getUserByUserName(string $username) : ?InstanceReference
		{
			$query = "SELECT mocha_get_user_by_username(:username);";
			$stmt = $this->PDO->prepare($query);

			try
			{
				$result = $stmt->execute(array
				(
					"username" => $username
				));
				$values = $stmt->fetch();
			}
			catch (\PDOException $ex)
			{
				// this may result in a "String data, right truncated" if we pass in
				// text longer than whatever the stored procedure expects; in this case
				// we should just treat it as the user doesn't exist and return null
				//
				// * Note that if any methods actually allow us to create users with names
				// * this long, this will prevent them from logging in!
				return null;
			}

			if ($values[0] == null)
				return null;
			
			$dbid = $values[0];
			$ir = new InstanceReference($dbid, $this->getInstanceKey($dbid), $this->getGlobalIdentifier($dbid));
			return $ir;
		}

		public function getGlobalIdentifier($dbid) : string
		{
			$query = "SELECT global_identifier FROM mocha_instances WHERE id = :id";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array("id" => $dbid));
			$values = $stmt->fetch();
			return $values[0];
		}

		public function getInstanceKey($dbid) : InstanceKey
		{
			$query = "SELECT class_id, inst_id FROM mocha_instances WHERE id = :id";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array("id" => $dbid));
			$values = $stmt->fetch();
			return new InstanceKey($values["class_id"], $values["inst_id"]);
		}

		public function getInstanceByDBID(int $dbid) : ?InstanceReference
		{
			$tenant = $this->getTenant();
			if ($tenant === null)
			{
				trigger_error("tenant cannot be null", \E_USER_ERROR);
				return null;
			}

			$query = "SELECT * FROM mocha_instances WHERE id = :id";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array
			(
				"id" => $dbid
			));
			$values = $stmt->fetch();

			$ir = new InstanceReference($values["id"], new InstanceKey($values["class_id"], $values["inst_id"]), $values["global_identifier"]);
			return $ir;
		}

		public function getInstanceByKey(InstanceKey $instanceKey) : ?InstanceReference
		{
			$tenant = $this->getTenant();
			if ($tenant === null)
			{
				trigger_error("tenant cannot be null", \E_USER_ERROR);
				return null;
			}

			$query = "CALL mocha_get_instance_by_key(:class_id, :inst_id)";
			// $query = "SELECT * FROM mocha_instances WHERE class_id = :class_id AND inst_id = :inst_id AND class_id IS NOT NULL;";
			$stmt = $this->PDO->prepare($query);
			$parms = array
			(
				"class_id" => $instanceKey->ClassIndex,
				"inst_id" => $instanceKey->InstanceIndex
			);
			$result = $stmt->execute($parms);

			if ($result === false)
			{
				trigger_error("unknown database error, query was:", \E_USER_ERROR);
				trigger_error($query, \E_USER_NOTICE);
				return null;
			}

			$values = $stmt->fetchAll();
			if (count($values) == 0)
			{
				trigger_error("no records found, query was:", \E_USER_NOTICE);
				trigger_error($query, \E_USER_NOTICE);
				foreach ($parms as $key => $value)
				{
					trigger_error($key . ": '" . $value . "'", \E_USER_NOTICE);
				}
				return null;
			}

			$dbid = $values[0]["id"];
			$class_id = $values[0]["class_id"];
			$inst_id = $values[0]["inst_id"];
			$global_id = $values[0]["global_identifier"];
			
			$ir = new InstanceReference($dbid, new InstanceKey($class_id, $inst_id), $global_id);
			return $ir;
		}
		
		private $_instanceCache;
		private static bool $useInstanceCache = true; // makes a 0.2ms difference!

		/*
		render time with stored procedure
		24$3	22.388103961945 ms		without SP
		24$3	10.10122179985  ms		with SP

		plus the SP actually works, and the straight query INTRODUCES bugs... wtf?
		*/

		public function getReferencedTenants(TenantReference $tenantReference) : array
		{
			$retval = [];
			$query = "SELECT target_tenant_id FROM mocha_tenant_references WHERE source_tenant_id = :source_tenant_id";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array
			(
				"source_tenant_id" => $tenantReference->ID
			));
			if ($result === false)
			{
				$this->logDatabaseError($query);
			}
			else
			{
				$values = $stmt->fetchAll();
				$count = count($values);
				for ($i = 0; $i < $count; $i++)
				{
					$target_tenant_id = $values[$i][0];
					$target_tenant = $this->getTenantByID($target_tenant_id);
					if ($target_tenant !== null)
					{
						$retval[] = $target_tenant;
					}
				}
			}
			return $retval;
		}

		private function logDatabaseError(string $query, ?array $parms = null)
		{
			trigger_error("unknown database error, query was:", \E_USER_ERROR);
			trigger_error($query, \E_USER_NOTICE);
			return null;
		}

		private function buildTenantIDQuery(string $tenantIdParmName = "tenant_id")
		{
			$tenant_id_qry = $tenantIdParmName . " = " . $this->getTenant()->ID;
			
			$tenant_references = $this->getReferencedTenants($this->getTenant());
			foreach ($tenant_references as $tenantref)
			{
				$tenant_id_qry .= " OR " . $tenantIdParmName . " = " . $tenantref->ID;
			}
			
			return $tenant_id_qry;
		}

		public function initializeInstanceCache()
		{
			error_log("invalidating instance cache");
			$instanceCache = array();

			$insts = $this->getInstances();
			foreach ($insts as $ir)
			{
				$instanceCache[$ir->GlobalIdentifier->__toStringFormat(false, "", "")] = 
				array
				(
					"dbid" => $ir->DatabaseId,
					"cid" => $ir->InstanceKey->ClassIndex,
					"iid" => $ir->InstanceKey->InstanceIndex,
					"gid" => $ir->GlobalIdentifier->__toStringFormat(false, "", "")
				);
			}

			$_SESSION["instanceCache"] = $instanceCache;
		}


		public function getInstanceByGlobalIdentifier(UUID|string $globalIdentifier) : ?InstanceReference
		{
			$sw = new Stopwatch();
			$sw->start();

			$tenant = $this->getTenant();
			if ($tenant === null)
			{
				debug_print_backtrace();
				trigger_error("tenant cannot be null", \E_USER_ERROR);
				return null;
			}

			if (is_string($globalIdentifier))
			{
				$globalIdentifier = UUID::parse($globalIdentifier);
			}

			if ($_SESSION["instanceCache"] === null)
			{
				$this->initializeInstanceCache();
			}

			$instanceCache = $_SESSION["instanceCache"];
			// if (!array_key_exists($globalIdentifier->__toStringFormat(false, "", ""), $instanceCache))
			// {
			// 	$this->initializeInstanceCache();
			// }

			if (array_key_exists($globalIdentifier->__toStringFormat(false, "", ""), $instanceCache))
			{
				$values = $instanceCache[$globalIdentifier->__toStringFormat(false, "", "")];
				return new InstanceReference($values["dbid"], new InstanceKey(intval($values["cid"]), intval($values["iid"])), UUID::parse($values["gid"]));
			}
			# trigger_error("instance cache: key not found " . $globalIdentifier->__toString(), \E_USER_ERROR);
			return null;
		}

		public function getInstanceByGlobalIdentifier__OLD(UUID|string $globalIdentifier) : ?InstanceReference
		{
			$tenant = $this->getTenant();
			if ($tenant === null)
			{
				trigger_error("tenant cannot be null", \E_USER_ERROR);
				return null;
			}

			// 13 ms with query executed for each line

			if (is_string($globalIdentifier))
			{
				$globalIdentifier = UUID::parse($globalIdentifier);
			}

			if ($this->_instanceCache == null || !MySQLDatabaseOms::$useInstanceCache)
			{
				$this->_instanceCache = array();
			}
			if (array_key_exists($globalIdentifier->__toString(), $this->_instanceCache))
			{
				return $this->_instanceCache[$globalIdentifier->__toString()];
			}

			// echo("calling sql for GetInstanceByGID (" . $globalIdentifier->__toString() . ")");

			// $query = "CALL mocha_get_instance_by_global_identifier(mocha_normalize_uuid(:global_identifier))";
			echo ("query using SQL");
			$query = "SELECT * FROM mocha_instances WHERE (" . $this->buildTenantIDQuery() . ") AND global_identifier = :global_identifier AND class_id IS NOT NULL";
			$stmt = $this->PDO->prepare($query);
			$parms = array
			(
				//"global_identifier" => $globalIdentifier->__toString()
				"global_identifier" => $globalIdentifier->__toStringFormat(false, "", "")
			);
			$result = $stmt->execute($parms);

			if ($result === false)
			{
				trigger_error("unknown database error, query was:", \E_USER_ERROR);
				trigger_error($query, \E_USER_NOTICE);
				return null;
			}

			$values = $stmt->fetchAll();
			if (count($values) == 0)
			{
				trigger_error("no records found, query was:", \E_USER_NOTICE);
				trigger_error($query, \E_USER_NOTICE);
				foreach ($parms as $key => $value)
				{
					trigger_error($key . ": '" . $value . "'", \E_USER_NOTICE);
				}
				return null;
			}

			$dbid = $values[0]["id"];
			$class_id = $values[0]["class_id"];
			$inst_id = $values[0]["inst_id"];
			$global_id = $values[0]["global_identifier"];
			
			$ir = new InstanceReference($dbid, new InstanceKey($class_id, $inst_id), $global_id);
			$this->_instanceCache[$ir->GlobalIdentifier->__toString()] = $ir;
			return $ir;
		}

		/**
		 * !UGLY UGLY HACK
		 * !Remove when we can figure out a solution to the duplicate instance problem
		 * !(DUPLICATEINSTANCES)
		 */
		// public $HACK__IncludeDuplicateInstances = false;
		
		protected function getRelatedInstancesInternal(InstanceReference $sourceInstance, InstanceReference $relationshipInstance, \DateTime $effectiveDate = null) : ?array
		{
			/*
			if ($this->_relatedInstancesCache === null)
			{
				$this->_relatedInstancesCache = array();
			}

			if (!array_key_exists($sourceInstance->DatabaseId, $this->_relatedInstancesCache))
			{
				$this->_relatedInstancesCache[$sourceInstance->DatabaseId] = array();
			}
			if (array_key_exists($relationshipInstance->DatabaseId, $this->_relatedInstancesCache[$sourceInstance->DatabaseId]))
			{
				return $this->_relatedInstancesCache[$sourceInstance->DatabaseId][$relationshipInstance->DatabaseId];
			}
			*/
			$dt = null;// $this->normalizeSqlDateTime($effectiveDate, true);
			/*
			$query = "CALL mocha_get_related_instances(:src_inst_id, :rel_inst_id, :eff_date)";
			$stmt = $this->PDO->prepare($query);
			$parms = array
			(
				"src_inst_id" => $this->getDbId($sourceInstance),
				"rel_inst_id" => $this->getDbId($relationshipInstance),
				"eff_date" => $dt
			);
			*/

			/*
			$query = "SELECT mocha_instances.* FROM mocha_instances INNER JOIN mocha_relationships ON mocha_instances.id = mocha_relationships.destination_inst_id WHERE mocha_relationships.tenant_id = :tenant_id " .
				"AND source_inst_id = :src_inst_id AND relationship_inst_id = :rel_inst_id" ; // AND :eff_date IS NULL OR (effective_date >= :eff_date)";
			*/
			$query = "SELECT destination_inst_id, remove_flag FROM mocha_relationships WHERE (" . $this->buildTenantIDQuery("mocha_relationships.tenant_id") . ") " .
			"AND source_inst_id = :src_inst_id AND relationship_inst_id = :rel_inst_id" ; // AND :eff_date IS NULL OR (effective_date >= :eff_date)";
			$stmt = $this->PDO->prepare($query);
			$parms = array
			(
				"src_inst_id" => $this->getDbId($sourceInstance),
				"rel_inst_id" => $this->getDbId($relationshipInstance) //,
				//"eff_date" => $dt
			);
			$result = $stmt->execute($parms);

			// trigger_error($query, \E_USER_NOTICE);
			// trigger_error("source inst id: " . $sourceInstance->InstanceKey, \E_USER_NOTICE);
			// trigger_error("relationship inst id: " . $relationshipInstance->InstanceKey, \E_USER_NOTICE);
			// foreach ($parms as $key => $value)
			// {
			// 	trigger_error($key . ": '" . $value . "'", \E_USER_NOTICE);
			// }
			
			if ($result === false)
			{
				return null;
			}

			$values = $stmt->fetchAll();
			$ret = array();
			foreach ($values as $value)
			{
				/*
				$retval = $this->getInstanceFromDatabase($value);
				if ($retval !== null)
				{
					if ($value["remove_flag"] == 1)
					{
						unset($ret[$retval->GlobalIdentifier->__toString()]);
					}
					else
					{
						//echo($retval->GlobalIdentifier->__toString() . "\n");
						// $ret[$retval->GlobalIdentifier->__toString()] = $retval;
						$ret[$retval->GlobalIdentifier->__toString()] = $retval;
					}
				}
				*/
				$ir = $this->getInstanceByDBID($value["destination_inst_id"]);
				if ($ir !== null)
				{
					// !HACK: (DUPLICATEINSTANCES)
					// !      We cannot do duplicate instances AND single line removal of instances
					// !      because we would not have a way to determine which *index* of instance
					// !      was removed!
					/*
					if ($this->HACK__IncludeDuplicateInstances)
					{
						$ret[] = $ir;
					}
					else
					{
						if ($value["remove_flag"] == 1)
						{
							unset($ret[$ir->GlobalIdentifier->__toString()]);
						}
						else
						{
							//echo($retval->GlobalIdentifier->__toString() . "\n");
							// $ret[$retval->GlobalIdentifier->__toString()] = $retval;
							$ret[$ir->GlobalIdentifier->__toString()] = $ir;
						}
					}
					*/
					if ($value["remove_flag"] == 1)
					{
						unset($ret[$ir->GlobalIdentifier->__toString()]);
					}
					else
					{
						//echo($retval->GlobalIdentifier->__toString() . "\n");
						// $ret[$retval->GlobalIdentifier->__toString()] = $retval;
						$ret[$ir->GlobalIdentifier->__toString()] = $ir;
					}
				}
			}
			/*
			if ($this->HACK__IncludeDuplicateInstances)
			{
				$this->MetadataExceptions["MX_DUPLICATEINSTANCES_HACK"] = new OmsMetadataException("An element on this page uses the (DUPLICATEINSTANCES) hack", [ "Source" => "getRelatedInstancesInternal" ], null, OmsMetadataExceptionSeverity::Warning);
				return $ret;
			}
			*/
			//$this->_relatedInstancesCache[$sourceInstance->DatabaseId][$relationshipInstance->DatabaseId] = $ret;
			return array_values($ret);
		}

		protected function getAttributeValueInternal(InstanceReference $sourceInstance, InstanceReference $attributeInstance, $defaultValue = null, $effectiveDate = null) : ?string
		{
			$dt = $this->normalizeSqlDateTime($effectiveDate, true);
			// $query = "CALL mocha_get_attribute_value(:src_inst_id, :att_inst_id, :eff_date)";
			//echo ("GAV: " . $sourceInstance->InstanceKey . " : " . $attributeInstance->InstanceKey );
			$query = "SELECT * FROM mocha_attributes WHERE tenant_id = :tenant_id AND src_inst_id = :src_inst_id AND att_inst_id = :att_inst_id AND att_effective_date <= NOW() ORDER BY att_effective_date DESC";
			$stmt = $this->PDO->prepare($query);

			$vars = array
			(
				"tenant_id" => $this->getTenant()->ID,
				"src_inst_id" => $this->getDbId($sourceInstance),
				"att_inst_id" => $this->getDbId($attributeInstance)//,
				//"eff_date" => $dt
			);
			$result = $stmt->execute($vars);
			
			if ($result === false)
			{
				return $defaultValue;
			}

			$values = $stmt->fetchAll();
			if (count($values) > 0)
			{
				$value = $values[0]["att_value"];
				return $value;
			}
			
			// the attribute was not set on the current tenant,
			// so retry the query with our parent tenants
			$parentTenants = $this->getReferencedTenants($this->getTenant());
			foreach ($parentTenants as $parentTenant)
			{
				$vars["tenant_id"] = $parentTenant->ID;
				$result = $stmt->execute($vars);
				
				if ($result === false)
				{
					return $defaultValue;
				}

				$values = $stmt->fetchAll();
				if (count($values) > 0)
				{
					// we have a value!
					$value = $values[0]["att_value"];
					return $value;
				}
			}
			
			// attribute has not been defined anywhere
			/*
			echo ("<!-- NOK - query : " . $query);
			
			print_r($vars);
			echo (" -->");
			*/
			return $defaultValue;
		}
		public function setAttributeValueInternal(InstanceReference $sourceInstance, InstanceReference $attributeInstance, mixed $value, ?\DateTime $effectiveDate = null)
		{
			$dt = $this->normalizeSqlDateTime($effectiveDate, true);
			$query = "CALL mocha_set_attribute_value(:src_inst_id, :attr_inst_id, :attr_value, :user_inst_id, :eff_date)";
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute(array
			(
				"src_inst_id" => $this->getDbId($sourceInstance),
				"attr_inst_id" => $this->getDbId($attributeInstance),
				"attr_value" => $value,
				"user_inst_id" => $this->getCurrentUser()->DatabaseId,
				"eff_date" => $dt
			));
		}

		private function normalizeSqlDateTime(?\DateTime $parm, bool $useNow = false)
		{
			return null;

			$dt = null;
			if ($parm !== null)
			{
				$dt = $parm->format("Y-m-d");
			}
			else if ($useNow)
			{
				$dt = (new \DateTime())->format("Y-m-d");
			}
			return $dt;
		}

		protected function removeRelationshipInstanceInternal(InstanceReference|string|InstanceKey $sourceInstance, InstanceReference|string|InstanceKey $relationshipInstance, InstanceReference|string|InstanceKey|array $targetInstances, ?\DateTime $effectiveDate = null)
		{
			$dt = $this->normalizeSqlDateTime($effectiveDate, true);
			
			if (is_array($targetInstances))
			{
				foreach ($targetInstances as $targetInstance)
				{
					$query = "INSERT INTO mocha_relationships (tenant_id, source_inst_id, relationship_inst_id, destination_inst_id, user_inst_id, effective_date, remove_flag) VALUES (:tenant_id, :src_inst_id, :rel_inst_id, :dest_inst_id, :user_inst_id, NOW(), 1)";
					$stmt = $this->PDO->prepare($query);
					$result = $stmt->execute(array
					(
						"tenant_id" => $this->getTenant()->ID,
						"src_inst_id" => $this->getDbId($sourceInstance),
						"rel_inst_id" => $this->getDbId($relationshipInstance),
						"dest_inst_id" => $this->getDbId($targetInstance),
						"user_inst_id" => $this->getCurrentUser()->DatabaseId #,
						# "eff_date" => $dt
					));
				}
			}
			else
			{
				$query = "INSERT INTO mocha_relationships (tenant_id, source_inst_id, relationship_inst_id, destination_inst_id, user_inst_id, effective_date, remove_flag) VALUES (:tenant_id, :src_inst_id, :rel_inst_id, :dest_inst_id, :user_inst_id, NOW(), 1)";
				$stmt = $this->PDO->prepare($query);
				$result = $stmt->execute(array
				(
					"tenant_id" => $this->getTenant()->ID,
					"src_inst_id" => $this->getDbId($sourceInstance),
					"rel_inst_id" => $this->getDbId($relationshipInstance),
					"dest_inst_id" => $this->getDbId($targetInstances),
					"user_inst_id" => $this->getCurrentUser()->DatabaseId #,
					# "eff_date" => $dt
				));
			}
		}
		
		protected function assignRelationshipInternal(InstanceReference $sourceInstance, InstanceReference $relationshipInstance, InstanceReference|array $targetInstances, ?\DateTime $effectiveDate = null)
		{
			$dt = $this->normalizeSqlDateTime($effectiveDate, true);
			if (is_array($targetInstances))
			{
				foreach ($targetInstances as $targetInstance)
				{
					$query = "CALL mocha_assign_relationship(:src_inst_id, :rel_inst_id, :dest_inst_id, :user_inst_id, :eff_date)";
					$stmt = $this->PDO->prepare($query);
					$result = $stmt->execute(array
					(
						"src_inst_id" => $this->getDbId($sourceInstance),
						"rel_inst_id" => $this->getDbId($relationshipInstance),
						"dest_inst_id" => $this->getDbId($targetInstance),
						"user_inst_id" => $this->getCurrentUser()->DatabaseId,
						"eff_date" => $dt
					));
				}
			}
			else
			{
				$query = "CALL mocha_assign_relationship(:src_inst_id, :rel_inst_id, :dest_inst_id, :user_inst_id, :eff_date)";
				$stmt = $this->PDO->prepare($query);
				$result = $stmt->execute(array
				(
					"src_inst_id" => $this->getDbId($sourceInstance),
					"rel_inst_id" => $this->getDbId($relationshipInstance),
					"dest_inst_id" => $this->getDbId($targetInstances),
					"user_inst_id" => $this->getCurrentUser()->DatabaseId,
					"eff_date" => $dt
				));
			}
		}

		public function getInstancesByAttributes($parms)
		{
			// FIXME: NOT IMPLEMENTED
			//usage:
			// getInstanceByAttributes (array ( getInstanceByGlobalIdentifier(NAME_ATTRIBUTE) => "zq-developer" ))
			$query = "SELECT mocha_instances.* FROM mocha_instances, mocha_attributes WHERE (" . $this->buildTenantIDQuery("mocha_instances.tenant_id") . ") AND mocha_attributes.tenant_id = mocha_instances.tenant_id AND mocha_instances.id = mocha_attributes.src_inst_id";
			if (count($parms) > 0)
			{
				$query .= " AND ";
				$i = 0;
				$queryParms = array();
				foreach ($parms as $key => $value)
				{
					$query .= "(att_value = :att_value$i AND att_inst_id = mocha_get_instance_by_global_identifier(:att_inst_id$i))";
					$queryParms["att_inst_id$i"] = $key;
					$queryParms["att_value$i"] = $value;

					$i++;
					if ($i < count($parms) - 1)
					{
						$query .= " AND ";
					}
				}
			}
			$stmt = $this->PDO->prepare($query);
			$result = $stmt->execute($queryParms);
			$values = $stmt->fetchAll();

			$insts = array();
			if (count($values) > 0)
			{
				foreach ($values as $value)
				{
					$inst = $this->getInstanceFromDatabase($value);
					$insts[] = $inst;
				}
			}
			return $insts;
		}

		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;
		}
	}
?>