<?php
	namespace Mocha\Oms;

	use Mocha\Core\InstanceKey;
	use Mocha\Core\InstanceReference;

	use Mocha\Core\KnownClassGuids;
	use Mocha\Core\KnownInstanceGuids;
	use Mocha\Core\KnownAttributeGuids;
	use Mocha\Core\KnownMethodBindingGuids;
	use Mocha\Core\KnownRelationshipGuids;
	use Mocha\Core\OmsContext;
	use Mocha\Core\TenantReference;

	use Phast\System;
	use Phast\UUID;
	use Phast\WebPageMessage;
	use Phast\WebPageMessageSeverity;

	abstract class Oms
	{
		public function __construct()
		{
			$this->_tenant = null;
			$this->Messages = array();
		}

		/**
		 * @var array 
		 */
		public $Messages;
		
		/**
		 * Gets the instance with the specified global identifier.
		 */
		public function getInstanceByGlobalIdentifier(UUID|string $inst) : ?InstanceReference
		{
			return null;
		}

		/**
		 * Indices are 1-based
		 */
		public function getNthInstanceOf(InstanceReference|string|InstanceKey $sourceInstance, int $index, $defaultValue = null)
		{
			$insts = $this->getRelatedInstances($sourceInstance, KnownRelationshipGuids::Class__has__Instance);
			if (count($insts) > 0)
			{
				return $insts[$index - 1];
			}
			return $defaultValue;
		}

		public function getRelatedInstances(InstanceReference|string|InstanceKey $sourceInstance, InstanceReference|string|InstanceKey|null $relationshipInstance, \DateTime $effectiveDate = null) : ?array
		{
			$tenant = $this->getTenant();
			if ($tenant === null)
			{
				trigger_error("tenant cannot be null", \E_USER_ERROR);
				return null;
			}
			
			$sourceInstance = $this->normalizeInstanceReference($sourceInstance);
			$relationshipInstance = $this->normalizeInstanceReference($relationshipInstance);
			if ($relationshipInstance === null)
				return null;
			
			return $this->getRelatedInstancesInternal($sourceInstance, $relationshipInstance, $effectiveDate);
		}
		protected abstract function getRelatedInstancesInternal(InstanceReference $sourceInstance, InstanceReference $relationshipInstance, \DateTime $effectiveDate = null) : ?array;
		
		public function getRelatedInstance(InstanceReference|string|InstanceKey $sourceInstance, InstanceReference|string|InstanceKey $relationshipInstance, \DateTime $effectiveDate = null) : ?InstanceReference
		{
			$insts = $this->getRelatedInstances($sourceInstance, $relationshipInstance, $effectiveDate);
			if ($insts === null)
				return null;
			if (count($insts) === 0)
				return null;
			return $insts[0];
		}

		/**
		 * Gets the parent class Instance of the specified Instance.
		 */
		public function getParentClass(InstanceReference $inst) : ?InstanceReference
		{
			$gid = $inst->GlobalIdentifier->__toString();
			$forClass = $this->getRelatedInstance($inst, $this->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Instance__for__Class));
			return $forClass;
		}

		/**
		 * Gets the instance representing the default tenant.
		 */
		public function getTenantInstance() : ?InstanceReference
		{
			return $this->getInstanceByGlobalIdentifier(KnownInstanceGuids::DefaultTenant);
		}

		/**
		 * Gets the value of the given attribute on the specified instance.
		 */
		public function getAttributeValue(InstanceReference|string|InstanceKey|null $sourceInstance, InstanceReference|string|InstanceKey|null $attributeInstance, $defaultValue = null, $effectiveDate = null) : ?string
		{
			$tenant = $this->getTenant();
			if ($tenant === null)
			{
				trigger_error("tenant cannot be null", \E_USER_ERROR);
				return null;
			}
			
			$sourceInstance = $this->normalizeInstanceReference($sourceInstance);
			$attributeInstance = $this->normalizeInstanceReference($attributeInstance);
			if ($sourceInstance === null || $attributeInstance === null)
				return $defaultValue;
			
			return $this->getAttributeValueInternal($sourceInstance, $attributeInstance, $defaultValue, $effectiveDate);
		}
		protected abstract function getAttributeValueInternal(InstanceReference $sourceInstance, InstanceReference $attributeInstance, $defaultValue = null, $effectiveDate = null) : ?string;

		public function setAttributeValue(InstanceReference|string|InstanceKey $sourceInstance, InstanceReference|string|InstanceKey $attributeInstance, mixed $value, ?\DateTime $effectiveDate = null)
		{
			$tenant = $this->getTenant();
			if ($tenant === null)
			{
				trigger_error("tenant cannot be null", \E_USER_ERROR);
				return null;
			}

			$sourceInstance = $this->normalizeInstanceReference($sourceInstance);
			$attributeInstance = $this->normalizeInstanceReference($attributeInstance);
			$this->setAttributeValueInternal($sourceInstance, $attributeInstance, $value, $effectiveDate);
		}
		protected abstract function setAttributeValueInternal(InstanceReference $sourceInstance, InstanceReference $attributeInstance, mixed $value, ?\DateTime $effectiveDate = null);

		public function getDefaultTaskUrl(InstanceReference $inst) : ?string
		{
			$instParent = $this->getParentClass($inst);
			if ($instParent === null)
				return null;

			$instDefaultTask = $this->getRelatedInstance($instParent, KnownRelationshipGuids::Class__has_default__Task);
			if ($instDefaultTask != null)
			{
				// !FIXME: check to see if the task is authorized for the current user
				// (e.g. this can be by if `User .has Security Domain` any in `Securable.has Security Domain`)
				// e.g. if User has `Administrator` and Task has `Administrator`, return true
				
				if (true) // ($oms->isTaskAuthorizedForUser($instDefaultTask, $oms->getCurrentUser()))
				{
					return System::ExpandRelativePath("~/d/inst/" . $instParent->InstanceKey . "/" . $inst->InstanceKey . ".htmld");
				}
			}
			return null;
		}
		public function getInstanceText($inst)
		{
			if ($inst === null)
				return "ERR: inst null";

			$parentClass = $this->getParentClass($inst);
			if ($parentClass === null)
			{
				$this->Messages[] = new WebPageMessage("Parent class empty for inst " . $inst->InstanceKey);
				return "ERR: no parent class for inst";
			}
			
			$instancesLabeledByRAMB = $this->getRelatedInstance($parentClass, KnownRelationshipGuids::Class__instances_labeled_by__Executable_returning_Attribute);
			if ($instancesLabeledByRAMB !== null)
			{
				$context = new OmsContext();
				$context->setWorkData($parentClass->GlobalIdentifier->__toString(), $inst);
				$textAttr = $this->execute($context, $instancesLabeledByRAMB);
				$text = $context->getWorkData($textAttr);

				//$this->printWorkData($context->workData);
				return $text;
			}
			else
			{
				//echo ("no RAMB");
			}
			return $this->getAttributeValue($inst, $this->getInstanceByGlobalIdentifier(KnownAttributeGuids::Name));
		}

		public function assignRelationship(InstanceReference|string|InstanceKey $sourceInstance, InstanceReference|string|InstanceKey $relationshipInstance, InstanceReference|string|InstanceKey|array $targetInstances, ?\DateTime $effectiveDate = null)
		{
			$tenant = $this->getTenant();
			if ($tenant === null)
			{
				trigger_error("tenant cannot be null", \E_USER_ERROR);
				return null;
			}

			$sourceInstance = $this->normalizeInstanceReference($sourceInstance);
			$relationshipInstance = $this->normalizeInstanceReference($relationshipInstance);
			$targetInstances = $this->normalizeInstanceReference($targetInstances);
			return $this->assignRelationshipInternal($sourceInstance, $relationshipInstance, $targetInstances, $effectiveDate);
		}
		protected abstract function assignRelationshipInternal(InstanceReference $sourceInstance, InstanceReference $relationshipInstance, InstanceReference|array $targetInstances, ?\DateTime $effectiveDate = null);

		private $_tenant;
		public function getTenant()
		{
			return $this->_tenant;
		}
		public function setTenant(TenantReference $tenant)
		{
			$oldTenant = $this->_tenant;
			$this->_tenant = $tenant;
			$this->onTenantChanged($oldTenant, $tenant);
		}

		public function onTenantChanged(TenantReference $oldTenant, TenantReference $newTenant)
		{
		}

		public function getTenantName() : ?string
		{
			if ($this->_tenant !== null)
			{
				return $this->_tenant->Name;
			}
			return null;
		}

		/**
		 * 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
		{
			return null;
		}

		public function printInstanceKeys(array $instanceReferences, string $separator = ":")
		{
			$ct = count($instanceReferences);
			for($i = 0; $i < $ct; $i++)
			{
				echo($instanceReferences[$i]->InstanceKey);
				if ($i < $ct - 1)
				{
					echo ($separator);
				}
			}
		}

		public function instanceSetContains(array /*<InstanceReference>*/ $haystack, InstanceReference $needle)
		{
			foreach ($haystack as $v)
			{
				if ($v->DatabaseId == $needle->DatabaseId)
				{
					return true;
				}
			}
			return false;
		}

		public function getInstancesOf(InstanceReference $classInstance)
		{
			$instances = $this->getRelatedInstances($classInstance, KnownRelationshipGuids::Class__has__Instance);
			return $instances;
		}

		/**
		 * @deprecated 0.5.1
		 */
		public function executeMethodReturningInstanceSet(InstanceReference $method, array $parms) : ?array
		{
			$parentClass = $this->getParentClass($method);
			if ($parentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ReturnInstanceSetMethodBinding)))
			{
				//return $this->executeMethodReturningInstanceSet($methodBindingHasMethod, $parms);
			}
			else if ($parentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::GetSpecifiedInstancesMethod)))
			{
				$instances = $this->getRelatedInstances($method, KnownRelationshipGuids::Get_Specified_Instances_Method__uses__Instance);
				return $instances;
			}
			else if ($parentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::GetInstancesMethod)))
			{
				$selectsOfClass = $this->getRelatedInstance($method, KnownRelationshipGuids::Get_Instances_Method__selects_instances_of__Class);
				return $this->getInstancesOf($selectsOfClass);
			}
			else
			{
				echo("zq: executeMethod: unknown parent class '" . $parentClass->GlobalIdentifier . "' [" . $parentClass->InstanceKey . "]");
				die();
			}

			return null;
		}

		/**
		 * Given an InstanceReference, GUID (string), or InstanceKey, return the normalized InstanceReference for that representation.
		 * 
		 * @return InstanceReference|array|null
		 */
		public function normalizeInstanceReference(InstanceReference|string|InstanceKey|array|null $instanceReferenceOrGuidOrInstanceKey) : InstanceReference|array|null
		{
			if ($instanceReferenceOrGuidOrInstanceKey === null)
				return null;

			if (is_array($instanceReferenceOrGuidOrInstanceKey))
			{
				$arry = array();
				foreach ($instanceReferenceOrGuidOrInstanceKey as $instanceRef)
				{
					$arry[] = $this->normalizeInstanceReference($instanceRef);
				}
				return $arry;
			}
			else if (is_string($instanceReferenceOrGuidOrInstanceKey) || $instanceReferenceOrGuidOrInstanceKey instanceof UUID)
			{
				// assume GUID
				return $this->getInstanceByGlobalIdentifier($instanceReferenceOrGuidOrInstanceKey);
			}
			else if ($instanceReferenceOrGuidOrInstanceKey instanceof InstanceKey)
			{
				return $this->getInstanceByKey($instanceReferenceOrGuidOrInstanceKey);
			}
			else if ($instanceReferenceOrGuidOrInstanceKey instanceof InstanceReference)
			{
				return $instanceReferenceOrGuidOrInstanceKey;
			}
			echo ("ERR");
			return null;
		}

		public function getTranslationValue($sourceInstance, $relationshipInstance, $defaultValue = null)
		{
			$sourceInstance = $this->normalizeInstanceReference($sourceInstance);
			$relationshipInstance = $this->normalizeInstanceReference($relationshipInstance);

			$ctx = new OmsContext();
			$m_Translatable__get__Translation_Value_for_Relationship_parm = $this->getInstanceByGlobalIdentifier(KnownMethodGuids::Translatable__get__Translation_Value_for_Relationship_parm);

			$ctx->setWorkData(KnownClassGuids::Relationship, $relationshipInstance);

			$mb_Translatable__get__Translation_Value_for_Relationship_parm = $this->executeMethodReturningAttribute($m_Translatable__get__Translation_Value_for_Relationship_parm, $ctx);
			// Translatable@get Translation Value for Relationship parm(GRA)*S(public)[ramb]
			// loop on instance seTranslation Value for Relationship parm(GRA)*S(public)[ramb]
			// loop on instance settTranslation Value for Relationship parm(GRA)*S(public)[ramb]
			// loop on instance set: Translatable@get Translations for Effective User Language(GRS)*S[rsmb]
			// ---- Translatable@get Translations for Language parm(GRS)*S[rsmb]
			// ---- Language => User@get Effective Language(SSC)[rsmb]
			// ---- ---- if User@get Current Language	then	User@get Current Language
			// ---- ---- if not User@get Current Language	then	Tenant@get Default Language
			
			// get attribute: Translation@get Value(GA)[ramb]

		}

		public function getRelatedTasks(InstanceReference $inst)
		{
			$instParent = $this->getParentClass($inst);
			
			$relatedTasks = [];
			
			$instInstance = $this->getInstanceByGlobalIdentifier(KnownClassGuids::Instance);
			$relatedTasks0 = $this->getRelatedInstances($instInstance, KnownRelationshipGuids::Class__has_related__Task);
			foreach ($relatedTasks0 as $task)
			{
				$relatedTasks[] = $task;
			}

			$relatedTasks1 = $this->getRelatedInstances($instParent, KnownRelationshipGuids::Class__has_related__Task);
			foreach ($relatedTasks1 as $task)
			{
				$relatedTasks[] = $task;
			}

			$superclasses = $this->getRelatedInstances($instParent, KnownRelationshipGuids::Class__has_super__Class);
			foreach ($superclasses as $superclass)
			{
				$relatedTasks2 = $this->getRelatedInstances($superclass, KnownRelationshipGuids::Class__has_related__Task);
				foreach ($relatedTasks2 as $task)
				{
					$relatedTasks[] = $task;
				}
			}
			return $relatedTasks;
		}

		public function getNthRelatedInstance(InstanceReference $inst, InstanceReference|string|InstanceKey|array $rel, int $index)
		{
			$rel = $this->normalizeInstanceReference($rel);
			$relInsts = $this->getRelatedInstances($inst, $rel);
			return $relInsts[$index];
		}

		/**
		 * Checks to ensure the given value is of the appropriate type for the specified work data.
		 * 
		 * @return bool true if the given value is of the appropriate type; false otherwise.
		 */
		public function validateWorkDataType(InstanceReference $instWorkData, mixed $value, &$expectedTypeName, &$actualTypeName)
		{
			$actualTypeName = gettype($value);
			if (is_string($value))
			{
				$expectedTypeName = "Text Attribute";
				if ($this->is_a($instWorkData, KnownClassGuids::TextAttribute))
				{
					return true;
				}
			}
			else if (is_int($value) || is_float($value))
			{
				$expectedTypeName = "Numeric Attribute";
				// int and float should only be assigned to Numeric Attribute
				if ($this->is_a($instWorkData, KnownClassGuids::NumericAttribute))
				{
					return true;
				}
			}
			else if ($value instanceof InstanceReference)
			{
				// instance references can be assigned to Work Sets and all sorts of other stuff
				if ($this->is_a($instWorkData, KnownClassGuids::WorkSet))
				{
					$expectedTypeName = "Work Set";
					// but if our Work Set declares a `Work Set.has valid Class` then we should validate that
					$workDataHasValidClasses = $this->getRelatedInstances($instWorkData, KnownRelationshipGuids::Work_Set__has_valid__Class);
					if ($workDataHasValidClasses !== null && count($workDataHasValidClasses) > 0)
					{
						// loop through all valid classes and check them
						foreach ($workDataHasValidClasses as $validClass)
						{
							if ($this->is_a($instWorkData, $validClass))
							{
								$expectedTypeName = $this->getAttributeValue($validClass, KnownAttributeGuids::Name);
								// our work set declares a valid class, and it passes
								return true;
							}
						}
						// none of the valid classes pass validation for $instWorkData
						$actualTypeName = $this->getAttributeValue($this->getParentClass($instWorkData), KnownAttributeGuids::Name);
						$expectedTypeName = $this->getAttributeValue($validClass, KnownAttributeGuids::Name);
						return false;
					}

					// this work set doesn't care whether or not $instWorkData passes validation
					return true;
				}
				
				// we are not a work set, so we can't validate anything
				return true;
			}
			
			// none of our validations pass
			return false;
		}

		public function buildAccessKeyForOmsAttachment($fileInstance, $entropy)
		{
			/*
			if (!entropy.ContainsKey(this.GetInstanceKey(inst)))
			{
				byte[] entropyData = new byte[256];
				random.NextBytes(entropyData);

				entropy[this.GetInstanceKey(inst)] = entropyData;
			}

			string entropy_co = Convert.ToBase64String(entropy[this.GetInstanceKey(inst)]);
			string guid = inst.GlobalIdentifier.ToString();
			long timestamp = DateTime.Now.Ticks;
			string originalFileName = this.GetAttributeValue<string>(inst, KnownAttributeGuids.Text.Name);
			string ft_co = String.Format("{0}?oms-attachments/{1}?{2}??{3}", entropy_co, guid, timestamp, originalFileName);
			string base64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(ft_co));
			string safeBase64 = MBS.Framework.Conversion.Base64ToUrlSafeBase64(base64);
			return safeBase64;
			*/

			$entropy_co = base64_encode("lalalala");
			//$timestamp = (new \DateTime())->
			$timestamp = "112340340";
			$originalFileName = "lalala.png";
			$ft_co = $entropy_co . "?oms-attachments/" . $fileInstance->GlobalIdentifier . "?" . $timestamp . "??" . $originalFileName;
			return base64_encode($ft_co);
		}

		public function getStackTrace(array $arr)
		{
			$sr = "";
			$ct = count($arr);
			for ($i = 0; $i < $ct; $i++)
			{
				if ($arr[$i] !== null)
				{
					$sr .= $arr[$i]->InstanceKey->__toString();
				}
				if ($i < $ct - 1)
				{
					$sr .= ":";
				}
			}
			return $sr;
		}

		private function createHtmlLinkToInstanceReference($val)
		{
			return "<a href=\"" . System::ExpandRelativePath("~/d/inst/" . $val->InstanceKey . ".htmld") . "\">" . $val->InstanceKey->__toString() . "</a>";
		}
		public function getStackTraceWithLinks(array $arr)
		{
			$sr = "";
			$ct = count($arr);
			for ($i = 0; $i < $ct; $i++)
			{
				if ($arr[$i] !== null)
				{
					$sr .= $this->createHtmlLinkToInstanceReference($arr[$i]);
				}
				if ($i < $ct - 1)
				{
					$sr .= ":";
				}
			}
			return $sr;
		}

		public function getAttachmentUrl(InstanceReference $fileInstance)
		{
			$entropy = null;
			$accessKey = $this->buildAccessKeyForOmsAttachment($fileInstance, $entropy);
			return "/" . $this->getTenantName() . "/attachment/" . $fileInstance->InstanceKey . "/" . $accessKey;
		}

		public function executeReturningAttribute(OmsContext $context, InstanceReference $inst)
		{
			$value = $this->execute($context, $inst);
			if ($value !== null)
			{
				return $context->getWorkData($value);
			}
			return null;
		}
		public function execute(OmsContext $context, InstanceReference $inst, $classesLookupWorkData = true)
		{
			if ($this->is_a($inst, UUID::parse(KnownClassGuids::Method))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::MethodBinding)))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::ReturnAttributeMethodBinding)))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::ReturnInstanceSetMethodBinding)))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::BuildAttributeMethod)))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::GetAttributeMethod)))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::GetReferencedAttributeMethod)))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::GetReferencedInstanceSetMethod)))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::EvaluateBooleanExpressionMethod)))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::ConditionalSelectAttributeMethod)))
			)
			{
				$retval = $this->executeMethod($context, $inst);
			}
			else if (
				// $this->is_a($inst, UUID::parse(KnownClassGuids::TextAttribute))
				//|| $this->is_a($inst, UUID::parse(KnownClassGuids::NumericAttribute))
				//|| $this->is_a($inst, UUID::parse(KnownClassGuids::DateAttribute))
				// || $this->is_a($inst, UUID::parse(KnownClassGuids::BooleanAttribute))
				$this->is_a($inst, UUID::parse(KnownClassGuids::WorkSet))
				|| ($this->is_a($inst, UUID::parse(KnownClassGuids::Clasz)) && $classesLookupWorkData)
				)
			{
				//return $context->getWorkData($inst);
				$retval = $context->getWorkData($inst);
			}
			else
			{
				$retval = $inst;
			}
			return $retval;
		}

		public function applyParameterAssignment(OmsContext $context, InstanceReference $parameterAssignment)
		{
			$assignsFromWorkData = $this->getRelatedInstance($parameterAssignment, KnownRelationshipGuids::Parameter_Assignment__assigns_from__Executable_returning_Work_Data);
			$assignsToParm = $this->getRelatedInstance($parameterAssignment, KnownRelationshipGuids::Parameter_Assignment__assigns_to__Work_Data);
			if ($assignsFromWorkData !== null && $assignsToParm !== null)
			{
				$workData = $this->execute($context, $assignsFromWorkData); 
				if ($workData !== null)
				{
					$workDataValue = $context->getWorkData($workData);
					// $context->getWorkData($assignsFromWorkData); //
					if (is_string($workDataValue))
					{
						// !! HACK !!
						$workData = $workDataValue;
					}
					//echo ("update with " . $assignsToParm->InstanceKey . " = " . $workDataValue);
				}
				else
				{
					//echo ("setting null on " . $assignsToParm->InstanceKey);
				}
				$context->setWorkData($assignsToParm, $workData);
			}
		}

		public function evaluateConditions(OmsContext $context, ?array $trueConditions, ?array $falseConditions)
		{
			if ($trueConditions !== null)
			{
				foreach ($trueConditions as $condition)
				{
					$val = $this->executeReturningAttribute($context, $condition);
					if (!$val)
					{
						return false;
					}
				}
			}
			if ($falseConditions !== null)
			{
				foreach ($falseConditions as $condition)
				{
					$val = $this->executeReturningAttribute($context, $condition);
					if ($val)
					{
						return false;
					}
				}
			}
			return true;
		}

		private function printWorkData(array $workData)
		{
			foreach ($workData as $key => $value)
			{
				echo ($key . " == ");
				if ($value instanceof InstanceReference)
				{
					echo ("[" . $this->createHtmlLinkToInstanceReference($value) . "]");
				}
				else
				{
					echo ("'" . $value . "'");
				}
				echo (" ; ");
			}
		}

		private function CompareInstanceSets_ExactMatch(InstanceReference|array|null $left, InstanceReference|array|null $right)
		{
			//echo ("CIS: " . $left->InstanceKey . " ?? " . $right->InstanceKey);
			if ($left === null && $right === null)
				return true;
			if ($left === null || $right === null)
				return false;

			if (is_array($left) && is_array($right))
			{
				// Both are Instance Sets, so we can make some optimizations
				if (count($left) !== count($right))
					return false;

				$is = array_intersect($left, $right);
				return count($is) === count($left);
			}
			else
			{
				// Instance Set and Instance cannot be compared, for now
				if (is_array($left) || is_array($right))
				{
					return false;
				}
			}

			// If we get here, $left and $right should both be single instances
			if ($left->GlobalIdentifier->__is_equal($right->GlobalIdentifier))
			{
				return true;
			}
			return false;
		}

		public function executeMethod(OmsContext $context, InstanceReference $methodInstance)
		{
			array_push($context->CallStack, $methodInstance);

			$methodInstanceClass = $this->getParentClass($methodInstance);
			//$context->setWorkData("{d53d7283-92a2-4a62-b8f2-cf0a0b975634}", "blah blah");

			$retval = null;
			$forClass = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Method__for__Class);
			if ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ReturnAttributeMethodBinding))
				|| ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ReturnInstanceSetMethodBinding))))
			{
				$methodBindingExecutesMethod = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Method_Binding__executes__Method);
				if ($methodBindingExecutesMethod === null)
				{
					echo ("Method Binding.executes Method not found for " . $methodInstance->InstanceKey);die();
				}

				$parameterAssignments = $this->getRelatedInstances($methodInstance, KnownRelationshipGuids::Method_Binding__has__Parameter_Assignment);
				foreach ($parameterAssignments as $parameterAssignment)
				{
					$this->applyParameterAssignment($context, $parameterAssignment);
				}

				// echo ("MB stack: " . $methodInstance->InstanceKey . ":"  . $methodBindingExecutesMethod->InstanceKey);
				$retval = $this->executeMethod($context, $methodBindingExecutesMethod);
			}
			else if ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::GetAttributeMethod)))
			{
				$returnsAttribute = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Get_Attribute_Method__has__Attribute);
				
				$targetInstance = $context->getWorkData($forClass);
				$value = $this->getAttributeValue($targetInstance, $returnsAttribute);
				$context->workData[$returnsAttribute->GlobalIdentifier->__toString()] = $value;

				// echo ("[GA @" . $forClass->InstanceKey . ": inst (" . $targetInstance->InstanceKey . ") ; att " . $returnsAttribute->InstanceKey . " : " . $value . "]");
				$retval = $returnsAttribute;
			}
			else if ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::GetReferencedAttributeMethod)))
			{
				$returnsAttribute = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Get_Referenced_Attribute_Method__returns__Attribute);
				$loopOnInstanceSet = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Get_Referenced_Attribute_Method__uses_reference__Executable_returning_Instance_Set);
				$answer = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Get_Referenced_Attribute_Method__uses_answer__Executable_returning_Attribute);
				$targetInstance = $context->getWorkData($forClass);

				$refISRel = $this->execute($context, $loopOnInstanceSet);
				if ($refISRel !== null)
				{
					$refIS = $context->getWorkData($refISRel);
					$answerVal = $this->execute($context, $answer);
					
					$value = $context->getWorkData($answerVal); // $this->getAttributeValue($refIS, $answer);
					$context->setWorkData($returnsAttribute, $value);
					$retval = $returnsAttribute;
				}
				else
				{
					$this->Messages[] = new WebPageMessage("Relationship unspecified [ from " . $loopOnInstanceSet->InstanceKey . " ] for `Get Referenced Attribute Method.uses reference Executable returning Attribute` [" . $methodInstance->InstanceKey . "]\nMADI call stack trace: " . $this->getStackTraceWithLinks($context->CallStack), "MADI runtime error", WebPageMessageSeverity::Error);
				}
			}
			else if ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::GetReferencedInstanceSetMethod)))
			{
				$returnsWorkSet = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Get_Referenced_Instance_Set_Method__returns__Work_Set);
				$loopOnInstanceSet = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Get_Referenced_Instance_Set_Method__uses_reference__Executable_returning_Instance_Set);
				$relationship = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Get_Referenced_Instance_Set_Method__uses_answer__Executable_returning_Instance_Set);
				
				$targetInstance = $context->getWorkData($forClass);
				if ($targetInstance !== null)
				{
					$value = $this->getRelatedInstance($targetInstance, $relationship);
					$context->setWorkData($returnsWorkSet, $value);
				}
				else
				{
					//echo ("exec GRS for class : " . $forClass->InstanceKey);
					$this->Messages[] = new WebPageMessage("GRS: target instance unspecified [ from " . $forClass->InstanceKey . " ]\nMADI call stack trace: " . $this->getStackTraceWithLinks($context->CallStack), "MADI runtime error", WebPageMessageSeverity::Error);
				}
				$retval = $returnsWorkSet;
			}
			else if ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::BuildAttributeMethod)))
			{
				$returnsAttribute = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Build_Attribute_Method__returns__Attribute);
				$initialValue = $this->getAttributeValue($methodInstance, KnownAttributeGuids::Value);
				$buildsWithAttributes = $this->getRelatedInstances($methodInstance, KnownRelationshipGuids::Build_Attribute_Method__builds_with__Executable_returning_Attribute);
				
				$value = $initialValue;
				foreach ($buildsWithAttributes as $buildsWithAttribute)
				{
					$attr = $this->execute($context, $buildsWithAttribute);
					// echo ("value of `" . $attr->InstanceKey . "` is '" . $context->getWorkData($attr) . "'");
					if ($attr !== null)
					{
						$val = $context->getWorkData($attr);
						if (is_string($val))
						{
							$value .= $val;
						}
						else
						{
							if ($val !== null)
							{
								$value .= "E!". $val->InstanceKey;
							}
						}
					}
					else
					{
						echo ("ERR[" . $buildsWithAttribute->InstanceKey . "]");
						trigger_error("executeMethod did not return properly - called method: " . $buildsWithAttribute->GlobalIdentifier . " [" . $buildsWithAttribute->InstanceKey . "]");
					}
				}
				$context->setWorkData($returnsAttribute, $value);
				// echo ("value of MethodType is '" . $context->getWorkData(KnownAttributeGuids::MethodType) . "'");
				$retval = $returnsAttribute;
			}
			else if ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ProcessRelatedUpdatesMethod)))
			{
				$classForPRU = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Process_Related_Updates_Method__processes_for__Class);
				if ($classForPRU !== null)
				{
				}

				//!HACK!
				$instForPRU = $this->getNthInstanceOf($classForPRU, 1);
				if ($instForPRU !== null)
				{
					//echo("current work data for '" . $instForPRU->GlobalIdentifier . "' = '" . $context->getWorkData($instForPRU->GlobalIdentifier->__toString()) . "'");
				}

				$instExecutablesForPUMB = $this->getRelatedInstances($methodInstance, KnownRelationshipGuids::Process_Related_Updates_Method__uses__Executable_for_PUMB);
				foreach ($instExecutablesForPUMB as $instExecutableForPUMB)
				{
					if ($instExecutableForPUMB !== null)
					{
						$assignsAttribute = $this->getRelatedInstance($instExecutableForPUMB, KnownRelationshipGuids::Assign_Attribute_Method__assigns__Attribute);
						$usesExecutableReturningAttribute = $this->getRelatedInstance($instExecutableForPUMB, KnownRelationshipGuids::Assign_Attribute_Method__uses__Executable_returning_Attribute);

						//$context->setWorkData($assignsAttribute->GlobalIdentifier->__toString(), $usesExecutableReturningAttribute);
						//echo ("assigning attribute '" . $assignsAttribute->GlobalIdentifier . "' the value from executable '" . $usesExecutableReturningAttribute->GlobalIdentifier . "'");

						$value = $context->getWorkData($usesExecutableReturningAttribute->GlobalIdentifier->__toString());
						if ($value instanceof InstanceReference)
						{
							//echo ("the value from work data is ##IS#'" . $value->GlobalIdentifier . "'##");
						}
						else
						{
							$this->setAttributeValue($instForPRU, $assignsAttribute, $value);
						}
					}
					else
					{
						echo ("executable for PUMB is null");
					}
				}
			}
			else if ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::EvaluateBooleanExpressionMethod)))
			{
				$value = false;
				$returnsAttribute = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Evaluate_Boolean_Expression_Method__returns__Boolean_Attribute);
				$sourceInstanceRef = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Evaluate_Boolean_Expression_Method__has_source__Executable_returning_Work_Data);
				$booleanOperator = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::IDR_Evaluate_Boolean_Expression_Method__uses__Boolean_Operator);
				$targetInstanceRef = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Evaluate_Boolean_Expression_Method__has_target__Executable_returning_Work_Data);
				
				$sourceInstance = $this->execute($context, $sourceInstanceRef);
				if ($sourceInstance === null)
				{
					echo ("ERR: source inst [ from " . $sourceInstanceRef->InstanceKey . " ] null");
					die();
				}
				if ($targetInstanceRef !== null)
				{
					$targetInstance = $this->execute($context, $targetInstanceRef, false);

					//$this->printWorkData($context->workData);
					//echo (" @@@@ ");
					
					//echo ("source " . $sourceInstanceRef->InstanceKey . "(" . $sourceInstance->InstanceKey . ") ... operator " . $booleanOperator->InstanceKey . " ... target " . $targetInstanceRef->InstanceKey . "(" . $targetInstance->InstanceKey . ")");

					if ($booleanOperator->GlobalIdentifier == UUID::parse(KnownInstanceGuids::RelationalOperator__ExactMatch))
					{
						if ($this->getParentClass($sourceInstance)->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::WorkSet)))
						{
							$sourceInstanceWorkData = $sourceInstance;
							$sourceInstance = $context->getWorkData($sourceInstance);
						}
						$value = $this->CompareInstanceSets_ExactMatch($sourceInstance, $targetInstance);
					}
				}
				else
				{
					echo ("source " . $sourceInstanceRef->InstanceKey . "(" . $sourceInstance->InstanceKey . ") ... operator " . $booleanOperator->InstanceKey . " ... target NULL!");
				}
				$context->setWorkData($returnsAttribute, $value);
				$retval = $returnsAttribute;
			}
			else if ($methodInstanceClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ConditionalSelectAttributeMethod)))
			{
				$returnsAttribute = $this->getRelatedInstance($methodInstance, KnownRelationshipGuids::Conditional_Select_Attribute_Method__returns__Attribute);
				$cases = $this->getRelatedInstances($methodInstance, KnownRelationshipGuids::Conditional_Select_Attribute_Method__has__Conditional_Select_Attribute_Case);
				foreach ($cases as $case)
				{
					$trueConditions = $this->getRelatedInstances($case, KnownRelationshipGuids::Condition_Group__has_true_condition__Executable_returning_Attribute);
					$falseConditions = $this->getRelatedInstances($case, KnownRelationshipGuids::Condition_Group__has_false_condition__Executable_returning_Attribute);
					if (!$this->evaluateConditions($context, $trueConditions, $falseConditions))
					{
						continue;
					}

					$invokesExecutableReturningAttribute = $this->getRelatedInstance($case, KnownRelationshipGuids::Conditional_Select_Attribute_Case__invokes__Executable_returning_Attribute);
					if ($invokesExecutableReturningAttribute !== null)
					{
						$att = $this->execute($context, $invokesExecutableReturningAttribute);
						return $att;
					}
					else
					{
						$pp = $this->getInstanceByGlobalIdentifier(KnownAttributeGuids::Value);
						$context->setWorkData($pp, "SAC case with NULL attribute");
						$retval = $pp;
					}
				}
				if ($retval === null)
				{
					$pp = $this->getInstanceByGlobalIdentifier(KnownAttributeGuids::Value);
					$context->setWorkData($pp, "SAC returned no case");
					$retval = $pp;
				}
			}
			else
			{
				trigger_error("unknown method class '" . $methodInstanceClass->GlobalIdentifier . "' [" . $methodInstanceClass->InstanceKey . "]");
			}

			array_pop($context->CallStack);
			return $retval;
		}

		public function is_a(InstanceReference $inst, InstanceReference|string|InstanceKey $what)
		{
			$pclass = $this->getParentClass($inst);
			if ($pclass === null)
				return false;
			if ($pclass->__is_equal($this->normalizeInstanceReference($what)))
				return true;
			return false;
		}
	}
?>
