<?php
	namespace Mocha\UI\Renderers\HTML;

	use Mocha\Core\InstanceKey;
	use Mocha\Core\InstanceReference;
	use Mocha\Core\KnownAttributeGuids;
	use Mocha\Core\KnownClassGuids;
	use Mocha\Core\KnownInstanceGuids;
	use Mocha\Core\KnownMethodBindingGuids;
	use Mocha\Core\KnownRelationshipGuids;
	use Mocha\Core\OmsContext;
	
	use Mocha\Oms\Oms;
	use Mocha\Oms\MySQLDatabaseOms;

	use Mocha\Oms\OmsMetadataException;
	use Mocha\Oms\OmsMetadataExceptionSeverity;
	use Mocha\UI\Controls\InstanceBrowser;
	use Mocha\UI\ValidationResult;

	use Phast\HTMLControls\Input;
	use Phast\HTMLControls\InputType;
	use Phast\HTMLControls\RichTextBox;
	use Phast\QuickSort;
	use Phast\System;
	use Phast\Utilities\Stopwatch;
	use Phast\UUID;
	use Phast\WebControlAttribute;

	use Phast\WebControls\BinarySwitch;
	use Phast\WebControls\BinarySwitchEndianness;
	
	use Phast\WebControls\RotarySwitch;
	use Phast\WebControls\RotarySwitchOption;
	
	use Phast\WebControls\Button;
	use Phast\WebControls\TabContainer;
	use Phast\WebControls\TabPage;
	
	class HTMLRenderer
	{
		/**
		 * @var OmsContext
		 */
		public $Context;

		/**
		 * Run in debug mode (prints log to screen and then dies at end).
		 */
		public $DebugMode;

		public array $Editors;

		public $TargetInstance;

		/**
		 * User-defined function to process entire postback (including validations).
		 */
		public $ProcessPostbackFunction;
		/**
		 * User-defined function to process updates (not including validations). Validations will still
		 * be processed by Mocha before this function is called. This function will NOT be called if the
		 * validations fail.
		 */
		public $ProcessUpdatesFunction;

		public $FailedValidations;

		public $FailedValidationElements;

		public $Scripts;
		public $StyleClasses;
		public bool $IsPostback;
		public string $SubmitButtonText;

		public bool $IncludeTopNavigationBar;

		public ?Oms $OMS;

		public function __construct(OmsContext $context, bool $withoutOms = false)
		{
			$this->DebugMode = false;
			$this->Context = $context;
			$this->Editors = array();
			$this->FailedValidations = [];
			$this->FailedValidationElements = [];
			$this->IsPostback = false;
			$this->Scripts = [];
			$this->StyleClasses = [];
			$this->SubmitButtonText = "OK"; // "Save Changes";
			$this->IncludeTopNavigationBar = true;
			$this->__renderingElement = null;
			$this->__processingElement = null;
			$this->OMS = null;

			if (!$withoutOms)
			{
				$this->OMS = mocha_get_oms();
			}
			$this->registerEditors();
		}

		public function registerEditors()
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = $this->OMS;
			if ($oms == null) return;

			$genericViewFunc = function($oms, $parentElementContents, $elementContent, $ecInst, $value)
			{
				echo ($value);
			};
			$booleanAttributeViewFunc = function($oms, $parentElementContents, $elementContent, $ecInst, $value)
			{
				if ($value == "1")
				{
					echo ("Yes");
				}
				else
				{
					// if display option "Show No When False"
					// echo ("No");
				}
			};

			$this->registerEditor($oms->getInstanceByGlobalIdentifier(KnownClassGuids::BooleanAttribute), $booleanAttributeViewFunc, function($oms, $parentElementContents, $elementContent, $ecInst, $value)
			{
				$displayOptions = $oms->getRelatedInstances($elementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
				$this->renderBooleanAttributeEC($parentElementContents, $elementContent, $ecInst, $value);
			});
			$this->registerEditor($oms->getInstanceByGlobalIdentifier(KnownClassGuids::TextAttribute), $genericViewFunc, function($oms, $parentElementContents, $elementContent, $ecInst, $value)
			{
				$displayOptions = $oms->getRelatedInstances($elementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
				$this->renderTextAttributeEC($parentElementContents, $elementContent, $ecInst, $oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__ObscuredText)), $value);
			});
			$this->registerEditor($oms->getInstanceByGlobalIdentifier(KnownClassGuids::NumericAttribute), $genericViewFunc, function($oms, $parentElementContents, $elementContent, $ecInst, $value)
			{
				$displayOptions = $oms->getRelatedInstances($elementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
				$this->renderNumericAttributeEC($parentElementContents, $elementContent, $ecInst, $oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__ObscuredText)), $value);
			});
			$this->registerEditor($oms->getInstanceByGlobalIdentifier(KnownClassGuids::RichTextAttribute), $genericViewFunc, function($oms, $parentElementContents, $elementContent, $ecInst, $value)
			{
				$displayOptions = $oms->getRelatedInstances($elementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
				$this->renderRichTextAttributeEC($parentElementContents, $elementContent, $ecInst, $value);
			});

			$this->registerEditor($oms->getInstanceByGlobalIdentifier(KnownClassGuids::MethodBinding), $genericViewFunc, function($oms, $parentElementContents, $elementContent, $ecInst, $value)
			{
			});
		}

		/**
		 * Thanks https://stackoverflow.com/a/31107425
		 * 
		 * Generate a random string, using a cryptographically secure 
		 * pseudorandom number generator (random_int)
		 *
		 * This function uses type hints now (PHP 7+ only), but it was originally
		 * written for PHP 5 as well.
		 * 
		 * For PHP 7, random_int is a PHP core function
		 * For PHP 5.x, depends on https://github.com/paragonie/random_compat
		 * 
		 * @param int $length      How many characters do we want?
		 * @param string $keyspace A string of all possible characters
		 *                         to select from
		 * @return string
		 */
		private function random_str(int $length = 64, string $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ) : string
		{
			if ($length < 1)
			{
				throw new \RangeException("Length must be a positive integer");
			}
			$pieces = [];
			$max = \mb_strlen($keyspace, '8bit') - 1;
			for ($i = 0; $i < $length; ++$i)
			{
				$pieces[] = $keyspace[\random_int(0, $max)];
			}
			return \implode('', $pieces);
		}

		public function getElementContentId(array $parentElementContents, ?InstanceReference $elementContent, $index = null)
		{
			$fullyQualifiedECID = "";
			$count = count($parentElementContents);
			for ($i = 0; $i < $count; $i++)
			{
				$ec = $parentElementContents[$i];

				if ($ec instanceof InstanceReference)
				{
					$fullyQualifiedECID .= $ec->InstanceKey->__toString();
				}
				else if (is_int($ec))
				{
					$fullyQualifiedECID .= $ec;
				}
				if ($i < $count - 1)
				{
					$fullyQualifiedECID .= ":";
				}
			}
			if ($elementContent != null)
			{
				if ($count > 0)
				{
					$fullyQualifiedECID = $fullyQualifiedECID . ":";
				}
				$idx = "";
				if (is_int($index))
				{
					$idx = $index . ":";
				}
				$fullyQualifiedECID .= $idx . $elementContent->InstanceKey->__toString();
			}
			return $fullyQualifiedECID;
		}

		public function renderBeginContent()
		{
			echo ("<div class=\"uwt-page-content\">");
		}
		public function renderEndContent()
		{
			echo ("</div>");
		}

		public function renderBeginErrorPage(string $title, string $heading)
		{
			$this->renderBeginPage($title);
			$this->renderBeginContent();
			echo ("<div style=\"text-align: center; background-color: #fff; border: 1px solid #e7eaec; padding: 32px; width: 600px; margin-left: auto; margin-right: auto; margin-top: 64px;\">");
			echo ("<img style=\"display: block; width: 256px; margin: 32px; margin-left: auto; margin-right: auto;\" src=\"/images/logo-error.svg\" alt=\"Error\" />");
			echo ("<h1>" . $heading . "</h1>");
		}
		public function renderEndErrorPage()
		{
			echo ("</div>");
			$this->renderSystemFooter();
			$this->renderEndContent();
			$this->renderEndPage();
		}
		public function renderSystemFooter()
		{
			echo ("<div style=\"font-size: 9pt; color: #aaa; margin: 64px; text-align: center;\">Powered by Mocha<br />&copy; 2024 MBS Business Solutions</div>");
		}
		
		public function renderPageHeader(InstanceReference|string $title, InstanceReference|string|null $subtitle)
		{
			echo ("<div class=\"uwt-page-header\">");
			echo ("<h1>");
			if (is_string($title))
			{
				echo ($title);
			}
			else if ($title instanceof InstanceReference)
			{
				$adw = new InstanceBrowser(array($title));
				$adw->Render();
			}
			echo ("</h1>");

			echo ("<h2>");
			if ($subtitle !== null)
			{
				if (is_string($subtitle))
				{
					echo ($subtitle);
				}
				else if ($subtitle instanceof InstanceReference)
				{
					$adw = new InstanceBrowser(array($subtitle));
					$adw->Render();
				}
			}
			echo ("</h2>");
			echo ("</div>");
		}

		private function renderTopNavigationBar()
		{
			$this->renderBeginTag("div", [ ], [ "uwt-header" ]);

			$this->renderBeginTag("div", [ ], [ "uwt-header-item", "uwt-applicationmenu" ]);

			$button = new Button();
			$button->Render();

			$this->renderEndTag("div");

			$this->renderBeginTag("div", [ ], [ "uwt-header-item", "uwt-searchbar" ]);

			$this->renderBeginTag("form", [ "method" => "GET", "action" => System::ExpandRelativePath("~/d/search.htmld") ]);
			$searchText = new Input();
			$searchText->Type = InputType::Text;
			$searchText->ClientID = "mcx-primary-search";
			$searchText->Attributes[] = new WebControlAttribute("autocomplete", "off");
			$searchText->Name = "q";
			$searchText->Render();
			$this->renderEndTag("form");

			$this->renderBeginTag("div", [ ], [ "uwt-popup", "mcx-primary-search-results" ]);
			$this->renderBeginTag("ul", [ ], [ "uwt-menu" ]);
			$this->renderEndTag("ul");
			$this->renderEndTag("div");

			$this->renderEndTag("div");

			$this->renderEndTag("div");
		}

		public function renderTaskStep2(&$parentElementContents, InstanceReference $usesElement)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$title = $oms->getInstanceText($usesElement);
			$label = $oms->getAttributeValue($usesElement, KnownAttributeGuids::Label);
			if ($label !== null)
			{
				$title = $label;
			}
			$workDataRelatedInstance = $this->Context->getWorkData(KnownInstanceGuids::WorkSet__RelatedInstance);
			if ($workDataRelatedInstance !== null)
			{
				$this->TargetInstance = $oms->getInstanceByKey(InstanceKey::Parse($workDataRelatedInstance));
			}

			$this->renderBeginPage();
			echo ("<input type=\"hidden\" name=\"task_step\" value=\"2\" />");
			echo ("<input type=\"hidden\" name=\"target_inst_id\" value=\"" . $this->TargetInstance->InstanceKey . "\" />");
			$this->renderPageHeader($title, $this->TargetInstance);

			$this->renderBeginTag("div", [ ], [ "uwt-page-content" ]);
			$this->renderElement($parentElementContents, $usesElement, [ $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Singular), $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__ShowSubelementsVertically) ]);
			$this->renderEndTag("div");
			$this->renderEndForm();
			$this->renderEndPage();
		}

		public function renderTask(InstanceReference $task, ?InstanceReference $relatedInstance = null)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();
			$taskName = $oms->getAttributeValue($task, KnownAttributeGuids::Name);
			
			array_push($this->Context->CallStack, $task);
			$this->Context->setWorkData(KnownInstanceGuids::WorkSet__TaskRelatedInstance, $relatedInstance);

			$this->renderBeginPage($taskName);

			$this->renderPageHeader($taskName, $relatedInstance);
			echo ("<div class=\"uwt-page-content\">");
			if ($relatedInstance !== null)
			{
				// we skipped over the selection step by passing in a related inst
				echo ("<input type=\"hidden\" name=\"task_step\" value=\"2\" />");
			}
			else
			{
				echo ("<input type=\"hidden\" name=\"task_step\" value=\"1\" />");
			}
			echo ("<input type=\"hidden\" name=\"target_inst_id\" value=\"" . $relatedInstance->InstanceKey . "\" />");
						
			if ($relatedInstance !== null)
			{
				$riParentClass = $oms->getParentClass($relatedInstance);
				if ($riParentClass !== null)
				{
					$this->Context->setWorkData($riParentClass, $relatedInstance);
				}
			}
			echo ("<!-- task: " . $task->InstanceKey . " " . $task->GlobalIdentifier . "\ncall stack trace: " . $oms->getStackTrace($this->Context->CallStack) . " -->");

			$parentClass = $oms->getParentClass($task);
			$allElementsAreReadonly = true;

			if ($parentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::SequenceTask)))
			{
				$DO_Singular = $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Singular);
				$DO_NotEnterable  = $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable);
				$DO_Required  = $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Required);

				$initiatingElement = $oms->getRelatedInstance($task, KnownRelationshipGuids::Task__has_initiating__Element);
				if ($initiatingElement === null)
				{
					$oms->MetadataExceptions[] = new OmsMetadataException("Initiating Element for task is empty", array
					(
						"Task" => $task
					));
				}
				else
				{
					$processedByControlTransaction = $oms->getRelatedInstance($initiatingElement, KnownRelationshipGuids::Element__processed_by__Control_Transaction_Method);
					if ($processedByControlTransaction !== null && $relatedInstance !== null)
					{
						$usesBRMB = $oms->getRelatedInstance($processedByControlTransaction, KnownRelationshipGuids::Control_Transaction_Method__uses__Build_Response_Method_Binding);
						$executesMethod = $oms->getRelatedInstance($usesBRMB, KnownRelationshipGuids::Method_Binding__executes__Method);
						$initiatingElement = $oms->getRelatedInstance($executesMethod, KnownRelationshipGuids::Build_UI_Response_Method__uses__Executable_returning_Element);
					}

					$elementContents = $oms->getRelatedInstances($initiatingElement, KnownRelationshipGuids::Element__has__Element_Content);
					foreach ($elementContents as $elementContent)
					{
						$ecDisplayOptions = $oms->getRelatedInstances($elementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
						$ecInst = $oms->getRelatedInstance($elementContent, KnownRelationshipGuids::Element_Content__has__Instance);

						if (!$oms->instanceSetContains($ecDisplayOptions, $DO_NotEnterable))
						{
							$allElementsAreReadonly = false;
						}
					}
					
					$dummy = [ ];
					$this->renderElement($dummy, $initiatingElement, [ $DO_Singular ]);
				}
			}
			else if ($parentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::HardcodedTask)))
			{
				$className = $oms->getAttributeValue($task, KnownAttributeGuids::ClassName);
				$taskClass = null;
				try
				{
					$taskClass = new $className($task, $relatedInstance);
				}
				catch (\Error $ex)
				{
				}

				if ($taskClass !== null)
				{
					$value = $taskClass->Render();
					if ($value instanceof InstanceReference)
					{
						$pc = $oms->getParentClass($value);
						if ($pc->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::Element)))
						{
							$DO_Singular = $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Singular);
				
							$dummy = [ ];
							$this->renderElement($dummy, $value, [ $DO_Singular ]);
						}
					}
					else if (is_string($value))
					{
						echo($value);
					}
				}
				else
				{
					echo("could not find hardcoded task class '" . $className . "'");
				}
			}
			else
			{
				echo ("could not find task executor for task type `" . $oms->getInstanceText($parentClass) . "`, " . $parentClass->InstanceKey . " " . $parentClass->GlobalIdentifier);
			}
			echo ("</div>");
			$this->renderEndForm($allElementsAreReadonly);

			if (false) // (!$allElementsAreReadonly)
			{
				echo("<script type=\"text/javascript\">");
				echo(<<<EOF
window.onbeforeunload = function(event)
{
	var message = "Are you sure you want to exit without saving your changes?";
	var e = event || window.event;
	if (e)
	{
		e.returnValue = message;
	}
	return message;
};
EOF
				);
				echo("</script>");
			}

			$this->renderEndPage();
		}

		private function updateWorkDataWithElementContents(array $parentElementContents, InstanceReference $element, $singular = true, $i = 0)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			//print_r($_POST);die();

			if ($element !== null)
			{
				$elementContents = $oms->getRelatedInstances($element, KnownRelationshipGuids::Element__has__Element_Content);
				foreach ($elementContents as $ec)
				{
					$ecInst = $oms->getRelatedInstance($ec, KnownRelationshipGuids::Element_Content__has__Instance);
					if ($ecInst != null)
					{
						if ($oms->is_a($ecInst, KnownClassGuids::Element))
						{
							array_push($parentElementContents, $ec);
							$this->updateWorkDataWithElementContents($parentElementContents, $ecInst);
							array_pop($parentElementContents);
						}
						else
						{
							$ecid = $this->getElementContentId($parentElementContents, $ec, ($singular ? null : $i));
							if (isset($_POST["ec_" . $ecid]))
							{
								$value = $_POST["ec_" . $ecid];
								$value_str = $value;
								//!FIXME: refactor out the casting of POST values to actual instances or primitive literals
								if ($oms->is_a($ecInst, KnownClassGuids::Clasz)
								|| $oms->is_a($ecInst, KnownClassGuids::WorkSet)
								|| $oms->is_a($ecInst, KnownClassGuids::Relationship))
								{
									if ($value == "")
									{
										$value = null;
									}
									else
									{
										$ik = InstanceKey::Parse($value);
										if ($ik === null)
										{
											echo ("invalid ik: " . $value);
										}
										$value = $oms->getInstanceByKey($ik);
										$value_str = "`" . $ik . "`";
									}
								}

								echo ("updateWorkDataWithElementContents: setting work data `" . $ecInst->InstanceKey . "` from EC `" . $ecid . "` to value '" . $value_str . "'<br />");
								$this->Context->setWorkData($ecInst, $value);
							}
							else
							{
								echo ("updateWorkDataWithElementContents: not setting work data `" . $ecInst->InstanceKey . "` from EC `" . $ecid . "`, not present in POST<br />");
								
							}
						}
					}
				}
			}
		}

		public function updateElementContentInstance(array &$parentElementContents, $targetInstance, $relatedECInst, $value)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$relatedEC = array_pop($parentElementContents);
			array_push($parentElementContents, $relatedEC);

			$displayOptions = $oms->getRelatedInstances($relatedEC, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable)))
			{
				// do not update
				return;
			}

			if ($relatedECInst === null)
			{
				$oms->MetadataExceptions[] = new OmsMetadataException("update EC: ec inst is null", [ "Element Content" => $relatedEC ], [ ], OmsMetadataExceptionSeverity::Warning);
				return;
			}

			if ($oms->is_a($relatedECInst, KnownClassGuids::ExecutableReturningAttribute))
			{
				// echo ("update ec on " . $targetInstance->InstanceKey . " : " . $relatedECInst->InstanceKey . " = '" . $value . "' <br/>");
				if ($oms->is_a($relatedECInst, KnownClassGuids::BooleanAttribute))
				{
					if ($value === "on")
					{
						$oms->setAttributeValue($targetInstance, $relatedECInst, true);
					}
					else
					{
						$oms->setAttributeValue($targetInstance, $relatedECInst, false);
					}
				}
				else
				{
					if ($oms->is_a($relatedECInst, KnownClassGuids::NumericAttribute))
					{
						if (is_numeric($value))
						{
							$oms->setAttributeValue($targetInstance, $relatedECInst, $value);
						}
						else if ($value == "")
						{
							$oms->setAttributeValue($targetInstance, $relatedECInst, null);
						}
					}
					else
					{
						$oms->setAttributeValue($targetInstance, $relatedECInst, $value);
					}
				}
			}
			else if ($oms->is_a($relatedECInst, KnownClassGuids::Relationship))
			{
				echo ("update ec on " . $targetInstance->InstanceKey . " : " . $relatedECInst->InstanceKey . " = '" . $value . "' <br/>");
				$oms->removeRelationshipInstances($targetInstance, $relatedECInst);

				$insts = array();
				if ($value != "")
				{
					$instanceKeys = explode(",", $value);
					if (count($instanceKeys) > 1)
					{
						//echo ("multi instance saving");die();
					}
					else
					{
						//echo($value);
					}
					foreach ($instanceKeys as $instanceKey)
					{
						$insts[] = $oms->getInstanceByKey(InstanceKey::parse($instanceKey));
					}
				}
				$oms->assignRelationship($targetInstance, $relatedECInst, $insts);
				echo ("set relationship " . $targetInstance->InstanceKey . " : " . $relatedECInst->InstanceKey . " = " . $value . "<br/>");
			}
			else if ($oms->is_a($relatedECInst, KnownClassGuids::Element))
			{
				// update sub element
				// echo ("update sub element : " . $relatedECInst->InstanceKey);
				$this->updateElement($parentElementContents, $targetInstance, $relatedECInst);
			}
		}

		private function updateElement(&$parentElementContents, $targetInstance, $element)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$parentElementContent = null;
			if (count($parentElementContents) > 0)
			{
				$parentElementContent = array_pop($parentElementContents);
				array_push($parentElementContents, $parentElementContent);
			}
			if ($parentElementContent !== null)
			{
				echo ("parent EC ok " . $parentElementContent . "<br/>");
				$parentECDisplayOptions = $oms->getRelatedInstances($parentElementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
				if ($oms->instanceSetContains($parentECDisplayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable)))
				{
					echo ("not enterable");
					// do not update
					return true;
				}
			}
			else
			{
				echo ("parent EC null<br/>");
			}

			if ($this->DebugMode)
			{
				echo ("post is: ");
				print_r($_POST);
				echo ("<br />");
			}

			$pru = $oms->getRelatedInstance($element, KnownRelationshipGuids::Element__processed_by__Process_Related_Updates_Method);
			if ($pru !== null)
			{
				//? BEGIN: stolen from default implementation of PRU

				//!FIXME: in a sequence task, the Initiating Element may just be a stepping stone
				//!       on the way to the next step. In this case we don't really want to save
				//!       any data, just store it in memory (work data) to be used by the next step...
				$elementProcessedByCT = $oms->getRelatedInstance($element, KnownRelationshipGuids::Element__processed_by__Control_Transaction_Method);
				if ($elementProcessedByCT !== null)
				{
				}

				$relatedECs = $oms->getRelatedInstances($element, KnownRelationshipGuids::Element__has__Element_Content);

				$classParent = null;
				if (count($relatedECs) > 0)
				{
					$classParent = $oms->getRelatedInstance($relatedECs[0], KnownRelationshipGuids::Element_Content__has__Instance);
					echo ("class parent : " . $classParent->InstanceKey);
				}

				if ($parentElementContent !== null)
				{
					$parentECFullyQualifiedECID = $this->getElementContentId($parentElementContents, null);
					$ecid_count = "ec_"  . $parentECFullyQualifiedECID . "_count";
					
					if (isset($_POST[$ecid_count]))
					{
						// nonsingular
						if ($this->DebugMode)
						{
							echo ("paren ec: " . $parentElementContent);
						}
						$subelementInstances = $this->getSubelementInstances($parentElementContent);
						$count = $_POST[$ecid_count];

						echo ("check count : " . $count . " vs existing : " . count($subelementInstances));
						if ($count != count($subelementInstances))
						{
							echo ("WARN: count mismatch (got " . $count . "; expected " . count($subelementInstances) . ") - add / remove sub elements not implemented"); //die();
						}

						for ($i = 0; $i < $count; $i++)
						{
							$targetRelatedInstance = null;
							if ($i < count($subelementInstances))
							{
								$targetRelatedInstance = $subelementInstances[$i];
							}
							
							echo ("target rel inst: " . $targetRelatedInstance->InstanceKey);

							$this->updateWorkDataWithElementContents($parentElementContents, $element, false, $i);							
							echo ("class parent: " . $classParent->InstanceKey . " = " . $targetRelatedInstance->InstanceKey . "<br />");
							$this->Context->setWorkData($classParent, $targetRelatedInstance);

							echo ("executing pru '" . $pru->GlobalIdentifier . "' for " . $element->InstanceKey);
							$oms->execute($this->Context, $pru);
						}
					}
				}
				//? END: stolen from default implementation of PRU
				return true;
			}
			else	
			{
				echo ("<!-- pru is null for " . $element->InstanceKey . " -->");
				// use default implementation of PRU

				//!FIXME: in a sequence task, the Initiating Element may just be a stepping stone
				//!       on the way to the next step. In this case we don't really want to save
				//!       any data, just store it in memory (work data) to be used by the next step...
				$elementProcessedByCT = $oms->getRelatedInstance($element, KnownRelationshipGuids::Element__processed_by__Control_Transaction_Method);
				if ($elementProcessedByCT !== null)
				{
				}

				$relatedECs = $oms->getRelatedInstances($element, KnownRelationshipGuids::Element__has__Element_Content);

				$classParent = null;
				if (count($relatedECs) > 0)
				{
					$classParent = $oms->getRelatedInstance($relatedECs[0], KnownRelationshipGuids::Element_Content__has__Instance);
					echo ("class parent : " . $classParent->InstanceKey);
				}
				
				if ($parentElementContent !== null)
				{
					$parentECFullyQualifiedECID = $this->getElementContentId($parentElementContents, null);
					$ecid_count = "ec_"  . $parentECFullyQualifiedECID . "_count";
					// $ecid_count = "ec_"  . $parentElementContent->InstanceKey . "_count";

					if (isset($_POST[$ecid_count]))
					{
						// nonsingular
						if ($this->DebugMode)
						{
							echo ("paren ec: " . $parentElementContent);
						}
						$subelementInstances = $this->getSubelementInstances($parentElementContent);
						$count = $_POST[$ecid_count];
						
						if ($count != count($subelementInstances))
						{
							echo ("WARN: count mismatch (got " . $count . "; expected " . count($subelementInstances) . ") - add / remove sub elements not implemented"); //die();
						}

						for ($i = 0; $i < $count; $i++)
						{
							$targetRelatedInstance = $subelementInstances[$i];
							if ($i >= count($subelementInstances))
							{
								// we are creating a new instance of the class of the subelement (element content #0)
								echo ("creating instance of " . $classParent->InstanceKey);
								$targetRelatedInstance = $oms->createInstanceOf($classParent);
							}
							else
							{
							}
							echo ("target rel inst: " . $targetRelatedInstance->InstanceKey);

							array_push($parentElementContents, $i);
							$relatedECCount = count($relatedECs);
							for ($j = 0; $j < $relatedECCount; $j++)
							{
								$relatedEC = $relatedECs[$j];
								$displayOptions = $oms->getRelatedInstances($relatedEC, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
								if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable)))
								{
									// do not update
									continue;
								}

								$fullyQualifiedECID = $this->getElementContentId($parentElementContents, $relatedEC);
								$value = $_POST["ec_" . $fullyQualifiedECID];
								
								$instParameterAssignments = $oms->getRelatedInstances($relatedEC, KnownRelationshipGuids::Element_Content__has__Parameter_Assignment);
								foreach ($instParameterAssignments as $parmAssignment)
								{
									$assignsToParm = $oms->getRelatedInstance($parmAssignment, KnownRelationshipGuids::Parameter_Assignment__assigns_to__Parameter);
								}

								$relatedECInst = $oms->getRelatedInstance($relatedEC, KnownRelationshipGuids::Element_Content__has__Instance);


								array_push($parentElementContents, $relatedEC);

								if ($this->DebugMode)
								{
									echo ("update EC `" . $fullyQualifiedECID . "`: " . $relatedECInst->InstanceKey . " = '" . $value . "'<br/>");
								}
								$this->updateElementContentInstance($parentElementContents, $targetRelatedInstance, $relatedECInst, $value);
								array_pop($parentElementContents);
								
								$this->Context->setElementParm($relatedEC, $relatedEC->GlobalIdentifier->__toString(), $value);
							}

							array_pop($parentElementContents);
						}
						return;
					}
				}
				
				// singular
				foreach ($relatedECs as $relatedEC)
				{
					$fullyQualifiedECID = $this->getElementContentId($parentElementContents, $relatedEC);
					$value = null;
					if (isset($_POST["ec_" . $fullyQualifiedECID]))
					{
						$value = $_POST["ec_" . $fullyQualifiedECID];
					}
					else
					{
						// is ok, this might be an element
					}
					
					// echo ("OK update sub element " . $fullyQualifiedECID);
					$instParameterAssignments = $oms->getRelatedInstances($relatedEC, KnownRelationshipGuids::Element_Content__has__Parameter_Assignment);
					foreach ($instParameterAssignments as $parmAssignment)
					{
						$assignsToParm = $oms->getRelatedInstance($parmAssignment, KnownRelationshipGuids::Parameter_Assignment__assigns_to__Parameter);
					}

					$relatedECInst = $oms->getRelatedInstance($relatedEC, KnownRelationshipGuids::Element_Content__has__Instance);

					array_push($parentElementContents, $relatedEC);
					$this->updateElementContentInstance($parentElementContents, $targetInstance, $relatedECInst, $value);
					array_pop($parentElementContents);
					
					$this->Context->setElementParm($relatedEC, $relatedEC->GlobalIdentifier->__toString(), $value);
				}
			}
			
			/*
			foreach ($relatedECs as $content)
			{
				$contentInstance = $oms->getRelatedInstance($content, KnownRelationshipGuids::Element_Content__has__Instance);
				if ($contentInstance == null)
				{
					echo ("<!-- contentInstance is null for " . $content->InstanceKey . " -->");
					continue;
				}

				$pc = $oms->getParentClass($contentInstance);
				if ($pc->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::Element)))
				{
					echo("<!-- executing sub PRU -->");
					array_push($parentElementContents, $content);
					if (!$this->processRelatedPostback($parentElementContents, $contentInstance))
					{
						$hasFailedValidations = true;
					}
					array_pop($parentElementContents);
				}
			}
			*/
		}

		private function checkValidationsForElement(array $parentElementContents, $element)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$relatedECs = $oms->getRelatedInstances($element, KnownRelationshipGuids::Element__has__Element_Content);
			foreach ($relatedECs as $relatedEC)
			{
				$ecid = "ec_" . $this->getElementContentId($parentElementContents, $relatedEC);
				$value = null;
				if (isset($_POST[$ecid]))
				{
					$value = $_POST[$ecid];
				}
				if (!$this->checkValidationsForElementContent($parentElementContents, $relatedEC, $value))
				{
					return false;
				}
			}
			return true;
		}

		private function processRelatedPostback(array &$parentElementContents, InstanceReference $element)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$this->__processingElement = $element;
			if ($this->DebugMode)
			{
				echo ("processing element: " . $element->InstanceKey);
			}
			
			$relatedInstance = $element;
			$relatedECs = $oms->getRelatedInstances($element, KnownRelationshipGuids::Element__has__Element_Content);

			$targetInstanceType = $oms->getRelatedInstance($relatedECs[0], KnownRelationshipGuids::Element_Content__has__Instance);
			$targetInstance = $this->TargetInstance; //  = $this->Context->getWorkData($targetInstanceType);
			if ($targetInstance === null)
			{
				$targetInstance = $this->Context->getWorkData($targetInstanceType);
			}

			$processedByControlTransaction = $oms->getRelatedInstance($element, KnownRelationshipGuids::Element__processed_by__Control_Transaction_Method);
			if ($processedByControlTransaction !== null)
			{
				echo ("procesed by CT: " . $processedByControlTransaction->InstanceKey);
				if ($_POST["task_step"] == "1")
				{
					// selection step
					$usesBRMB = $oms->getRelatedInstance($processedByControlTransaction, KnownRelationshipGuids::Control_Transaction_Method__uses__Build_Response_Method_Binding);
					//if (!$oms->is_a($usesBRMB, KnownClassGuids::BuildResponseMethodBinding))
					{

					}
					$executesMethod = $oms->getRelatedInstance($usesBRMB, KnownRelationshipGuids::Method_Binding__executes__Method);
					if ($executesMethod === null)
					{
						trigger_error("task_step: 1; uses BRMB " . $usesBRMB . "; executes method is null");
					}
					$usesElement = $oms->getRelatedInstance($executesMethod, KnownRelationshipGuids::Build_UI_Response_Method__uses__Executable_returning_Element);

					$this->updateWorkDataWithElementContents($parentElementContents, $element);

					$usesElementContents = $oms->getRelatedInstances($usesElement, KnownRelationshipGuids::Element__has__Element_Content);
					if (count($usesElementContents) > 0)
					{
						$ecInst0 = $oms->getRelatedInstance($usesElementContents[0], KnownRelationshipGuids::Element_Content__has__Instance);
						$this->TargetInstance = $this->Context->getWorkData($ecInst0);

						$this->renderTaskStep2($parentElementContents, $usesElement);
					}
					exit();
				}
				else if ($_POST["task_step"] == "2")
				{
					echo ("task step 2<br />");
					// edit step
					$usesBRMB = $oms->getRelatedInstance($processedByControlTransaction, KnownRelationshipGuids::Control_Transaction_Method__uses__Build_Response_Method_Binding);
					$executesMethod = $oms->getRelatedInstance($usesBRMB, KnownRelationshipGuids::Method_Binding__executes__Method);
					$usesElement = $oms->getRelatedInstance($executesMethod, KnownRelationshipGuids::Build_UI_Response_Method__uses__Executable_returning_Element);

					$this->updateWorkDataWithElementContents($parentElementContents, $element);

					$usesElementContents = $oms->getRelatedInstances($usesElement, KnownRelationshipGuids::Element__has__Element_Content);
					if (count($usesElementContents) > 0)
					{
						$ecInst0 = $oms->getRelatedInstance($usesElementContents[0], KnownRelationshipGuids::Element_Content__has__Instance);
						$this->TargetInstance = $this->Context->getWorkData($ecInst0);
						$targetInstance = $this->TargetInstance;
					}
					$element = $usesElement;

					if ($targetInstance === null && $this->TargetInstance === null)
					{
						if (isset($_POST["target_inst_id"]))
						{
							$targetInstance = $oms->getInstanceByKey(InstanceKey::Parse($_POST["target_inst_id"]));
							$this->TargetInstance = $targetInstance;
						}
					}
				}
			}

			if ($this->DebugMode)
			{
				echo ("process element: " . $element->InstanceKey . "  @ ");
				echo ("targ inst type: " . $targetInstanceType->InstanceKey . "  @ ");
				echo("target: " . $targetInstance->InstanceKey . " @ ");
			}

			$hasFailedValidations = false;
			if (!$this->checkValidationsForElement($parentElementContents, $element))
			{
				$hasFailedValidations = true;
			}
			
			if ($hasFailedValidations)
				return false;

			$this->updateWorkDataWithElementContents($parentElementContents, $element);

			if (is_callable($this->ProcessUpdatesFunction))
			{
				// call user-defined ProcessUpdatesFunction
				return call_user_func($this->ProcessUpdatesFunction, $this, $element);
			}
			else
			{
				// $parentElementContents = [ ];
				// update initial element
				echo ("update element: target inst : " . $targetInstance ?? "(null)");
				$this->updateElement($parentElementContents, $targetInstance, $element);
			}
			return true;
		}

		public function processPostback(InstanceReference $element)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			if (is_callable($this->ProcessPostbackFunction))
			{
				// call user-defined ProcessPostbackFunction
				return call_user_func($this->ProcessPostbackFunction, $this, $element);
			}
			else
			{
				$dummy = [ ];
				if (!$this->processRelatedPostback($dummy, $element))
				{
					return false;
				}
			}

			if (count($this->FailedValidations) > 0)
				return false;

			if ($this->DebugMode)
			{
				echo ("debug mode done");
				die();
			}

			if (isset($_POST["ReturnToURL"]))
			{
				System::Redirect($_POST["ReturnToURL"]);
				return true;
			}
			return true;
		}

		/**
		 * @return true if the validation passes; false otherwise
		 */
		private function checkValidationForElementContent(InstanceReference $elementContent, string $value, InstanceReference $validation)
		{
			return true;
		}

		/**
		 * @return true if the validations pass (or there are no validations); false otherwise.
		 */
		private function checkValidationsForElementContent(array $parentElementContents, InstanceReference $elementContent, ?string $value)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();


			$ecInst = $oms->getRelatedInstance($elementContent, KnownRelationshipGuids::Element_Content__has__Instance);
			if ($ecInst === null)
			{
				$oms->MetadataExceptions[] = new OmsMetadataException("check EC validation: ec inst is null", [ "Element Content" => $elementContent ], [ ], OmsMetadataExceptionSeverity::Warning);
				return true;
			}

			$title = $this->get__EC_Override_Label_or_default_Instance_Title(array
			(
				KnownClassGuids::Instance => $ecInst,
				KnownClassGuids::ElementContent => $elementContent
			));
			if ($oms->is_a($ecInst, KnownClassGuids::Element))
			{
				$v = true;
				array_push($parentElementContents, $elementContent);
				if (!$this->checkValidationsForElement($parentElementContents, element: $ecInst))
				{
					$v = false;
				}
				array_pop($parentElementContents);
				if ($v == false)
					return false;
			}

			$displayOptions = $oms->getRelatedInstances($elementContent, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option));
			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Required)))
			{
				// this is a required parm
				if ($value == null)
				{
					$validationResult = new ValidationResult(null, "Missing required parameter", $title);
					$this->FailedValidations[] = $validationResult;
					$this->FailedValidationElements[$elementContent->DatabaseId] = $validationResult;
					return false;
				}
			}

			if ($oms->is_a($ecInst, KnownClassGuids::NumericAttribute))
			{
				if (!(is_numeric($value) || $value == ""))
				{
					$validationResult = new ValidationResult(null, "Non-numeric value specified for numeric attribute", $title);
					$this->FailedValidations[] = $validationResult;
					$this->FailedValidationElements[$elementContent->DatabaseId] = $validationResult;
					return false;
				}
			}

			$validations = $oms->getRelatedInstances($elementContent, KnownRelationshipGuids::Element_Content__has__Validation);
			if (is_array($validations))
			{
				foreach ($validations as $validation)
				{
					if (!$this->checkValidationForElementContent($elementContent, $value, $validation))
					{
						$validationResult = new ValidationResult($validation, null, null);
						$this->FailedValidations[] = $validationResult;
						$this->FailedValidationElements[$elementContent->DatabaseId] = $validationResult;
						return false;
					}
				}
			}
			return true;
		}

		public function elementContentIsEmpty(InstanceReference $elementContent) : bool
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();
			/**
			 * @var InstanceReference
			 */
			$relatedInstance = $this->Context->getWorkData(KnownInstanceGuids::WorkSet__RelatedInstance);
			//if ($relatedInstance === null) return false; //? this should really return true but we don't want to show spurious '(empty)' on forms

			$ecInst = $oms->getRelatedInstance($elementContent, KnownRelationshipGuids::Element_Content__has__Instance);
			if ($ecInst === null) return true;

			if ($oms->is_a($ecInst, KnownClassGuids::Element))
			{
				//$this->renderElement($ecInst, $displayOptions, $renderNotEnterable);
				return false;
			}
			else if ($relatedInstance !== null && $oms->is_a($ecInst, KnownClassGuids::Relationship))
			{
				$value = $oms->getRelatedInstances($relatedInstance, $ecInst);
				return $value === null || count($value) === 0;
			}
			// Executable returning Attribute
			else if ($oms->is_a($ecInst, KnownClassGuids::ExecutableReturningAttribute))
			{
				if (!$oms->is_a($ecInst, KnownClassGuids::Attribute))
				{
					$attr = $oms->executeReturningAttribute($this->Context, $ecInst);
					if ($attr !== null)
					{
						// $value = $this->Context->getWorkData($attr);
						return ($attr == "");
					}
					return true;
				}
				return $oms->getAttributeValue($relatedInstance, $ecInst) == "" && $oms->getAttributeValue($ecInst, KnownAttributeGuids::Value) == "";
			}
			// Executable returning Attribute
			else if ($oms->is_a($ecInst, KnownClassGuids::ReturnAttributeMethodBinding))
			{
				// I *THINK* Return Attribute Method Bindings aren't supposed to return
				// just strings, they're supposed to actually return *attributes* that
				// can then be edited in the Element Content.
				$rambAttribute = $oms->execute($this->Context, $ecInst);
				if ($rambAttribute !== null)
				{
					return $this->Context->getWorkData($rambAttribute) === null;
				}
			}
			else if ($oms->is_a($ecInst, KnownClassGuids::Clasz))
			{
				$targetInstance = $ecInst;
				if ($this->Context !== null)
				{
					$targetInstance = $this->Context->getWorkData($ecInst);
				}
				return $targetInstance === null;
			}
			else
			{
				return $ecInst === null;
			}
			return true;
		}

		public function shouldRenderElementContentLabel(InstanceReference $elementContent)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$displayOptions = $oms->getRelatedInstances($elementContent, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option));
			
			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__WideText)))
			{
				return false;
			}
			$ecLayout = $oms->getRelatedInstance($elementContent, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Layout));
			if ($ecLayout !== null)
			{
				$ecLayoutParentClass = $oms->getParentClass($ecLayout);
				if ($ecLayoutParentClass !== null)
				{
					if (
						($ecLayoutParentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ButtonLayout)))
						|| ($ecLayoutParentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ImageLayout)))
						|| ($ecLayoutParentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::GroupLayout)))
					)
					{
						return false;
					}
				}
			}
			return true;
		}
		
		public function shouldRenderElementContent(InstanceReference $elementContent)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();
			if ($this->Context->getElementParm($elementContent, "visible", false))
			{
				return true;
			}

			$displayOptions = $oms->getRelatedInstances($elementContent, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option));
			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__DoNotShow)))
				return false;

			if (!$this->Context->getElementParm($elementContent, "visible", true))
			{
				return false;
			}

			return true;
		}

		public function buildStyleAttribute(bool $isBlockElement, array $constantStyles, array $stylesFromInstanceSet)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();
			
			$str = "";
			foreach ($constantStyles as $key => $value)
			{
				$str .= $key . ": " . $value . ";";
			}
			foreach ($stylesFromInstanceSet as $ir)
			{
				# $styleHasStyleRule = $oms->getRelatedInstances($ir, KnownRelationshipGuids::Style__has__Style_Rule);
				$styleHasWidthAttr = $oms->getRelatedInstance($ir, KnownRelationshipGuids::Style__has_width__Measurement);
				if ($styleHasWidthAttr !== null)
				{
					$value = $oms->getAttributeValue($styleHasWidthAttr, KnownAttributeGuids::Value);
					$unit = $oms->getRelatedInstance($styleHasWidthAttr, KnownRelationshipGuids::Measurement__has__Measurement_Unit);
					$unitValue = $oms->getAttributeValue($unit, KnownAttributeGuids::CSSValue);
					$str .= "width: " . $value . $unitValue . ";";
				}
				$styleHasHeightAttr = $oms->getRelatedInstance($ir, KnownRelationshipGuids::Style__has_height__Measurement);
				if ($styleHasHeightAttr !== null)
				{
					$value = $oms->getAttributeValue($styleHasHeightAttr, KnownAttributeGuids::Value);
					$unit = $oms->getRelatedInstance($styleHasHeightAttr, KnownRelationshipGuids::Measurement__has__Measurement_Unit);
					$unitValue = $oms->getAttributeValue($unit, KnownAttributeGuids::CSSValue);
					$str .= "height: " . $value . $unitValue . ";";
				}
				$styleHasHorizontalAlignment = $oms->getRelatedInstance($ir, KnownRelationshipGuids::Style__has_horizontal__Alignment);
				if ($styleHasHorizontalAlignment !== null)
				{
					if ($isBlockElement)
					{
						if ($styleHasHorizontalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Near)))
						{
							$str .= "margin-right: auto;";
						}
						else if ($styleHasHorizontalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Center)))
						{
							$str .= "margin-left: auto; margin-right: auto;";
						}
						else if ($styleHasHorizontalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Far)))
						{
							$str .= "margin-left: auto;";
						}
					}
					else
					{
						if ($styleHasHorizontalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Near)))
						{
							$value = "left";
						}
						else if ($styleHasHorizontalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Center)))
						{
							$value = "center";
						}
						else if ($styleHasHorizontalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Far)))
						{
							$value = "right";
						}
						$str .= "text-align: " . $value . ";";
					}
				}
				$styleHasVerticalAlignment = $oms->getRelatedInstance($ir, KnownRelationshipGuids::Style__has_vertical__Alignment);
				if ($styleHasVerticalAlignment !== null)
				{
					if ($styleHasVerticalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Near)))
					{
						$value = "top";
					}
					else if ($styleHasVerticalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Center)))
					{
						$value = "center";
					}
					else if ($styleHasVerticalAlignment->GlobalIdentifier->__is_equal(UUID::parse(KnownInstanceGuids::Alignment__Far)))
					{
						$value = "bottom";
					}
					$str .= "vertical-align: " . $value . ";";
				}
			}
			return $str;
		}

		public function renderElementContent(array &$parentElementContents, InstanceReference $elementContent, $renderNotEnterable = false)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();
			
			$ecInst = $oms->getRelatedInstance($elementContent, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Instance));
			$title = $this->get__EC_Override_Label_or_default_Instance_Title(array
			(
				KnownClassGuids::Instance => $ecInst,
				KnownClassGuids::ElementContent => $elementContent
			));

			$relatedInstance = $this->Context->getWorkData(KnownInstanceGuids::WorkSet__RelatedInstance);
			if ($relatedInstance === null)
			{
				$relatedInstance = $this->TargetInstance;
			}
			
			$fullyQualifiedECID = $this->getElementContentId($parentElementContents, $elementContent);
			echo ("<!-- " . $fullyQualifiedECID . " ; ");
			if ($ecInst !== null)
			{
				echo ($ecInst->InstanceKey);
			 }
			 echo (" -->");

			$ecLayout = $oms->getRelatedInstance($elementContent, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Layout));
			if ($ecLayout !== null)
			{
				$ecStyles = $oms->getRelatedInstances($ecLayout, KnownRelationshipGuids::Layout__has__Style);

				$ecLayoutParentClass = $oms->getParentClass($ecLayout);
				if ($ecLayoutParentClass !== null)
				{
					if ($ecLayoutParentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ButtonLayout)))
					{
						// override layout : button
						echo ("<a class=\"uwt-button\" href=\"");
						$executesTaskFromIS = $oms->getRelatedInstance($ecLayout, KnownRelationshipGuids::Button_Layout__executes_task_from__Instance_Set);
						$getsRelatedInstanceFromIS = $oms->getRelatedInstance($ecLayout, KnownRelationshipGuids::Button_Layout__gets_related_instance_from__Instance_Set);
						if ($executesTaskFromIS !== null)
						{
							if ($getsRelatedInstanceFromIS !== null)
							{
								$getsRelatedInstanceFromIS = $oms->executeAndEvaluateWorkData($this->Context, $getsRelatedInstanceFromIS);
								echo(System::ExpandRelativePath("~/d/inst/" . $getsRelatedInstanceFromIS->InstanceKey . "/rel-task/" . $executesTaskFromIS->InstanceKey . ".htmld"));
							}
							else
							{
								echo(System::ExpandRelativePath("~/d/task/" . $executesTaskFromIS->InstanceKey . ".htmld"));
							}
						}
						echo("\">");
						//
						// 3$7872		Button Layout.uses for label Text Attribute 
						// 3$7873		Text Attribute.label for Button Layout 
						// 
						// TODO: get Translatable label text from EC
						// $attrLabel = $oms->getRelatedInstance($ecLayout, KnownRelationshipGuids::Button_Layout__uses_for_label__Text_Attribute);
						// if ($attrLabel !== null)
						// {
						// }
						$attrLabel = $oms->getAttributeValue($elementContent, KnownAttributeGuids::Label, "NO_LABEL_" . $elementContent->InstanceKey);
						echo ($attrLabel);
						echo ("</a>");
					}
					else if ($ecLayoutParentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::ImageLayout)))
					{
						// override layout : image
						echo ("<div class=\"uwt-image\" style=\"");

						// EC instance should be of type `File`
						$attrContentType = $oms->getAttributeValue($ecInst, KnownAttributeGuids::ContentType);
						$attrContentEncoding = $oms->getAttributeValue($ecInst, KnownAttributeGuids::Encoding);
						$attrValue = $oms->getAttributeValue($ecInst, KnownAttributeGuids::Value);

						echo ($this->buildStyleAttribute(true, [
							"background-image" => "url('" . $oms->getAttachmentUrl($ecInst) . "')"
							/*"url('data:" . $attrContentType . ";" . $attrContentEncoding . "," . $attrValue . "')"*/
						], $ecStyles));
						echo("\">&nbsp;</div>");
					}
					else if ($ecLayoutParentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::GroupLayout)))
					{
						array_push($parentElementContents, $elementContent);

						$tabContainer = new TabContainer();
						$tabContainer->ClassList[] = "mcx-element";
						
						$ecs = $oms->getRelatedInstances($ecInst, KnownRelationshipGuids::Element__has__Element_Content);
						foreach ($ecs as $ec)
						{
							$tp = new TabPage();
							$tp->Title = $oms->getAttributeValue($ec, KnownAttributeGuids::Label);
							$tp->ExtraData = array($ec, $parentElementContents, $renderNotEnterable);
							$tp->Content = function($sender, $extraData)
							{
								$pec = $extraData[1];
								$ec = $extraData[0];
								if ($ec !== null)
								{
									$this->renderElementContent($pec, $ec, $extraData[2]);
								}
								else{
									echo( "ec for this tab is null");
								}
							};
							$tabContainer->TabPages[] = $tp;
						}
						$tabContainer->Render();

						array_pop($parentElementContents);
					}
					else if ($ecLayoutParentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::BinarySwitchLayout)))
					{
						$bs = new BinarySwitch();
						$bs->Name = "ec_" . $fullyQualifiedECID;
						$bs->Endianness = BinarySwitchEndianness::LittleEndian;


						// Executable returning Attribute
						$value = 0;
						if ($oms->is_a($ecInst, KnownClassGuids::ExecutableReturningAttribute))
						{
							$value = $oms->getAttributeValue($relatedInstance, $ecInst);
						}
						$bs->Value = $value;

						if ($renderNotEnterable)
						{
							$bs->Enabled = false;
						}
						else
						{
							$bs->Enabled = true;
						}
						
						// $bs->Value = $value;
						$bs->Render();
					}
					else if ($ecLayoutParentClass->GlobalIdentifier->__is_equal(UUID::parse(KnownClassGuids::RotarySwitchLayout)))
					{
						$bs = new RotarySwitch();
						$bs->Name = "ec_" . $fullyQualifiedECID;

						$themeName = $oms->getAttributeValue($ecLayout, KnownAttributeGuids::ThemeName);
						if ($themeName !== null)
						{
							$bs->ClassList[] = "uwt-color-" . $themeName;
						}

						$rotarySwitchOptions = $oms->getRelatedInstances($ecLayout, KnownRelationshipGuids::Rotary_Switch_Layout__has__Rotary_Switch_Layout_Option);
						foreach ($rotarySwitchOptions as $rotarySwitchOption)
						{
							$value = $oms->getAttributeValue($rotarySwitchOption, KnownAttributeGuids::Value);
							$bs->Options[] = new RotarySwitchOption($value);
						}
						
						// $bs->Value = $value;
						$bs->Render();
					}
				}
			}
			else
			{
				// default layout
				if ($ecInst === null)
				{
				}
				else
				{
					if ($this->shouldRenderElementContent($elementContent))
					{
						$displayOptions = $oms->getRelatedInstances($elementContent, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option));
						
						if ($oms->is_a($ecInst, KnownClassGuids::Element))
						{
							array_push($parentElementContents, $elementContent);
							$this->renderElement($parentElementContents, $ecInst, $displayOptions, $renderNotEnterable);
							array_pop($parentElementContents);
						}
						else if ($oms->is_a($ecInst, KnownClassGuids::Relationship))
						{
							if (isset($_POST["ec_" . $fullyQualifiedECID]))
							{
								$value = $oms->instanceKeysToInstances($_POST["ec_" . $fullyQualifiedECID]);
							}
							else
							{
								if ($this->DebugMode)
								{
									echo ("related instance: " . $relatedInstance->InstanceKey);
									echo ("<br />ec inst: " . $ecInst->InstanceKey);
								}
								$value = $oms->getRelatedInstances($relatedInstance, $ecInst);
								if ($this->DebugMode)
								{
									echo ("<br />value: " . $oms->getInstanceKeysString($value));
								}
							}
							
							$validClasses = [ $oms->getRelatedInstance($ecInst, KnownRelationshipGuids::Relationship__has_destination__Class) ];
							
							$singular = $oms->getAttributeValue($ecInst, KnownAttributeGuids::Singular, true);
							
							$adw = new InstanceBrowser();
							if ($title !== null)
							{
								$adw->Title = $title;
							}
							$adw->Name = "ec_" . $fullyQualifiedECID;
							$adw->Attributes[] = new WebControlAttribute("data-ecid", $elementContent->InstanceKey);
							$adw->SelectedInstances = $value;
							$adw->AutoCompleteUrl = "~/prompt/c0/" . $ecInst->InstanceKey;
							$adw->ValidClasses = $validClasses;
							if (!$singular)
							{
								$adw->MultiSelect = true;
							}
							else
							{
								$adw->MultiSelect = false;
							}

							if ($renderNotEnterable || $oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable)))
							{
								$adw->Editable = false;
							}
							else
							{
								$adw->Editable = true;
							}
							$adw->Render();
						}
						// Executable returning Attribute
						else if ($oms->is_a($ecInst, KnownClassGuids::ExecutableReturningAttribute))
						{
							echo ("<!-- is a Executable returning Attribute -->");
							$this->renderExecutableReturningAttribute($parentElementContents, $elementContent, $ecInst, $relatedInstance, $renderNotEnterable);
						}
						// Executable returning Attribute
						else if ($oms->is_a($ecInst, KnownClassGuids::ReturnAttributeMethodBinding))
						{
							echo ("<!-- is a RAMB -->");
							// I *THINK* Return Attribute Method Bindings aren't supposed to return
							// just strings, they're supposed to actually return *attributes* that
							// can then be edited in the Element Content.
							$rambAttribute = $oms->execute($this->Context, $ecInst);
							if ($rambAttribute !== null)
							{
								$this->renderTextAttribute($this->Context, $rambAttribute);
							}
							else
							{
								echo ("ERR: attribute ( from method " . $ecInst->InstanceKey . " ) is null");
							}
						}
						else if ($oms->is_a($ecInst, KnownClassGuids::WorkSet))
						{
							$targetInstance = $ecInst;
							if ($this->Context !== null)
							{
								$targetInstance = $this->Context->getWorkData($ecInst);
							}

							$adw = new InstanceBrowser();
							if ($title !== null)
							{
								$adw->Title = $title;
							}
							$adw->Name = "ec_" . $fullyQualifiedECID;
							$adw->Attributes[] = new WebControlAttribute("data-ecid", $elementContent->InstanceKey);
							$adw->Editable = !($renderNotEnterable || $oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable)));
							//$adw->ValidClasses[] = $;

							$insts = $this->Context->getWorkData($ecInst);
							$singular = !is_array($insts); // $oms->getAttributeValue($ecInst, KnownAttributeGuids::Singular, false);
							if ($insts !== null)
							{
								if ($singular)
								{
									$adw->SelectedInstances = [ $insts ];
								}
								else
								{
									$adw->SelectedInstances = $insts;
								}
							}
							$adw->Title = $title;
							$adw->Render();
						}
						else if ($oms->is_a($ecInst, KnownClassGuids::Clasz))
						{
							$targetInstance = $ecInst;
							if ($this->Context !== null)
							{
								$targetInstance = $this->Context->getWorkData($ecInst);
							}

							$adw = new InstanceBrowser();
							if ($title !== null)
							{
								$adw->Title = $title;
							}
							$adw->Name = "ec_" . $fullyQualifiedECID;
							$adw->Attributes[] = new WebControlAttribute("data-ecid", $elementContent->InstanceKey);
							$adw->Editable = !($renderNotEnterable || $oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable)));
							$adw->ValidClasses[] = $ecInst;
							if ($targetInstance !== null)
							{
								$adw->SelectedInstances[] = $targetInstance;
							}
							$adw->Title = $title;
							$adw->Render();
						}
						else if ($oms->is_a($ecInst, KnownClassGuids::ExecutableReturningInstanceSet))
						{
							$inst_wd = $oms->execute($this->Context, $ecInst);
							$insts = null;
							if ($oms->is_a($inst_wd, KnownClassGuids::WorkSet))
							{
								$insts = $this->Context->getWorkData($inst_wd);
							}
							if (!is_array($insts) && $insts instanceof InstanceReference)
							{
								$insts = array($insts);
							}
							$adw = new InstanceBrowser($insts);
							$adw->Name = "ec_" . $fullyQualifiedECID;
							$adw->Attributes[] = new WebControlAttribute("data-ecid", $elementContent->InstanceKey);
							$adw->Title = $title;
							$adw->Render();
							// echo ("<span class=\"mcx-value\">unknown pclass ident " . $oms->getParentClass($ecInst)->GlobalIdentifier . "</span>");
						}
						else
						{
							$adw = new InstanceBrowser(array($ecInst));
							$adw->Name = "ec_" . $fullyQualifiedECID;
							$adw->Attributes[] = new WebControlAttribute("data-ecid", $elementContent->InstanceKey);
							$adw->Title = $title;
							$adw->Render();
							// echo ("<span class=\"mcx-value\">unknown pclass ident " . $oms->getParentClass($ecInst)->GlobalIdentifier . "</span>");
						}
						//echo ("</td>");
					}
				}
			}
		}

		public function getElementContentValue(InstanceReference $elementContent, $defaultValue = null)
		{
			print_r($_POST);
			echo ("get post val " . $elementContent->InstanceKey); die();
			if (isset($_POST["ec_" . $elementContent->InstanceKey]))
			{
				return $_POST["ec_" . $elementContent->InstanceKey];
			}
			return $defaultValue;
		}

		public function registerEditor(InstanceReference $forClass, callable $viewFunction, callable $editFunction)
		{
			$this->Editors[] = new Editor($forClass, $viewFunction, $editFunction);
		}
		public function getEditor(InstanceReference $ecInst)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			foreach ($this->Editors as $editor)
			{
				if ($oms->is_a($ecInst, $editor->ForClass))
				{
					return $editor;
				}
			}
			return null;
		}

		public function renderExecutableReturningAttribute(array &$parentElementContents, InstanceReference $elementContent, InstanceReference $ecInst, ?InstanceReference $relatedInstance, $renderNotEnterable = false)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$displayOptions = $oms->getRelatedInstances($elementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
			
			// fill in the value from the target instance
			$value = null;
			if (!$oms->is_a($ecInst, KnownClassGuids::Attribute))
			{
				$attr = $oms->executeReturningAttribute($this->Context, $ecInst);
				if ($attr !== null)
				{
					echo ("<!-- attr is '" . $attr . "' -->");
					// $value = $this->Context->getWorkData($attr);
					$value = $attr;
				}
				else
				{
					echo ("<!-- executeReturningAttribute returned NULL attribute -->");
				}
			}

			if ($value === null)
			{
				$targetInstance = $relatedInstance;
				if ($targetInstance === null)
				{
					$targetInstance = $this->TargetInstance;

					$firstElementContent = $oms->getNthRelatedInstance($this->__renderingElement, KnownRelationshipGuids::Element__has__Element_Content, 0);
					$firstElementContentInst = $oms->getRelatedInstance($firstElementContent, KnownRelationshipGuids::Element_Content__has__Instance);

					$targetInstance = $oms->getNthInstanceOf($firstElementContentInst, 1);
				}
				if ($targetInstance !== null)
				{
					$value = $oms->getAttributeValue($targetInstance, $ecInst);
				}
				
				if ($value === null)
				{
					$value = $oms->getAttributeValue($ecInst, $oms->getInstanceByGlobalIdentifier(KnownAttributeGuids::Value));
				}

				$ecid = "ec_" . $this->getElementContentId($parentElementContents, $elementContent);
				if ($this->IsPostback && isset($_POST[$ecid]))
				{
					// fill in the value from the postback
					$value = $_POST[$ecid];
				}
			}

			$editor = $this->getEditor($ecInst);
			
			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable)) || $renderNotEnterable)
			{
				if ($editor !== null)
				{
					call_user_func($editor->ViewFunction, $oms, $parentElementContents, $elementContent, $ecInst, $value);
				}
			}
			else
			{
				if ($editor !== null)
				{
					call_user_func($editor->EditFunction, $oms, $parentElementContents, $elementContent, $ecInst, $value);
				}
			}
		}

		public function renderTextAttribute(OmsContext $context, InstanceReference $attr)
		{
			// $adw = new InstanceBrowser(array($rambAttribute));
			// $adw->Render();
			if (is_string($attr))
			{
				echo($attr);
			}
			else
			{
				echo($context->getWorkData($attr));
			}
		}

		public function renderTextAttributeEC(array $parentElementContents, InstanceReference $ec, InstanceReference $inst, $password = false, $value = null)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$displayOptions = $oms->getRelatedInstances($ec, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option));
			
			$fullyQualifiedECID = $this->getElementContentId($parentElementContents, $ec);
			$ecid = "ec_" . $fullyQualifiedECID;

			$p = new Input();
			$p->Type = InputType::Text;
			$p->ClientID = $ecid;
			$p->Name = $ecid;
			$p->Attributes[] = new WebControlAttribute("data-ecid", $ecid);
			$p->Value = $value;

			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Required)))
			{
				$p->ClassList[] = "uwt-required";
			}
			if ($this->IsPostback && array_key_exists($ec->DatabaseId, $this->FailedValidationElements))
			{
				$p->ClassList[] = "uwt-failed-validation";
			}

			$maxLength = $oms->getAttributeValue($inst, KnownAttributeGuids::MaximumLength);
			if ($maxLength !== null)
			{
				$p->Attributes[] = new \Phast\WebControlAttribute("maxlength", $maxLength);
			}
			if ($password)
			{
				$p->Type = InputType::Password;
			}
			$p->Render();
		}
		
		public function renderBooleanAttributeEC(array $parentElementContents, InstanceReference $ec, InstanceReference $inst, $value = null)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$displayOptions = $oms->getRelatedInstances($ec, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option));

			$fullyQualifiedECID = $this->getElementContentId($parentElementContents, $ec);
			$ecid = "ec_" . $fullyQualifiedECID;

			$p = new Input();
			$p->Type = InputType::CheckBox;
			$p->ClientID = $ecid;
			$p->Name = $ecid;
			$p->Attributes[] = new WebControlAttribute("data-ecid", $ecid);
			if ($value == 1)
			{
				$p->Attributes[] = new WebControlAttribute("checked", "checked");
			}
			
			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Required)))
			{
				$p->ClassList[] = "uwt-required";
			}
			if ($this->IsPostback && array_key_exists($ec->DatabaseId, $this->FailedValidationElements))
			{
				$p->ClassList[] = "uwt-failed-validation";
			}

			$maxLength = $oms->getAttributeValue($inst, KnownAttributeGuids::MaximumLength);
			if ($maxLength !== null)
			{
				$p->Attributes[] = new WebControlAttribute("maxlength", $maxLength);
			}
			$p->HasContent = false;
			$p->Render();
		}

		public function renderNumericAttributeEC(array $parentElementContents, InstanceReference $ec, InstanceReference $inst, $password = false, $value = null)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$displayOptions = $oms->getRelatedInstances($ec, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option));

			$fullyQualifiedECID = $this->getElementContentId($parentElementContents, $ec);
			$ecid = "ec_" . $fullyQualifiedECID;

			$p = new Input();
			$p->Type = InputType::Number;
			$p->ClientID = $ecid;
			$p->Name = $ecid;
			$p->Attributes[] = new WebControlAttribute("data-ecid", $ecid);
			$p->Value = $value;

			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Required)))
			{
				$p->ClassList[] = "uwt-required";
			}
			if ($this->IsPostback && array_key_exists($ec->DatabaseId, $this->FailedValidationElements))
			{
				$p->ClassList[] = "uwt-failed-validation";
			}

			$p->Render();
		}

		public function renderRichTextAttributeEC(array $parentElementContents, InstanceReference $ec, InstanceReference $inst, $value = null)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$displayOptions = $oms->getRelatedInstances($ec, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option));
			
			$fullyQualifiedECID = $this->getElementContentId($parentElementContents, $ec);
			$ecid = "ec_" . $fullyQualifiedECID;

			$p = new RichTextBox();
			$p->ClientID = $ecid;
			$p->Name = $ecid;
			$p->Attributes[] = new WebControlAttribute("data-ecid", $ecid);
			$p->Value = $value;

			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Required)))
			{
				$p->ClassList[] = "uwt-required";
			}
			if ($this->IsPostback && array_key_exists($ec->DatabaseId, $this->FailedValidationElements))
			{
				$p->ClassList[] = "uwt-failed-validation";
			}

			$maxLength = $oms->getAttributeValue($inst, KnownAttributeGuids::MaximumLength);
			if ($maxLength !== null)
			{
				$p->Attributes[] = new WebControlAttribute("maxlength", $maxLength);
			}
			$p->Render();
		}
		
		
		public function renderArray(?array $array, string $separator, string $prefix = "", string $suffix = "")
		{
			if ($array === null)
				return "";
			if (\count($array) > 0)
			{
				return $prefix . \implode($separator, $array) . $suffix;
			}
			return "";
		}
		
		public function renderKeyedArray(?array $array, string $separator, string $prefix = "", string $suffix = "", string $partSeparator = "", string $keyPrefix = "", string $keySuffix = "", string $valuePrefix = "", string $valueSuffix = "")
		{
			if ($array === null)
				return "";

			if (\count($array) > 0)
			{
				$str = $prefix;
				foreach ($array as $key => $value)
				{
					$str .= $keyPrefix . $key . $keySuffix;
					$str .= $partSeparator;
					$str .= $valuePrefix . $value . $valueSuffix;
					$str .= $separator;
				}
				$str .= $suffix;
				return $str;
			}
			return "";
		}

		private Stopwatch $_stopwatch;

		public function renderBeginPage($title = "")
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = $this->OMS;
			if ($oms !== null)
			{
				$oms->MetadataExceptions = array();
			}

			$this->_stopwatch = new Stopwatch();
			$this->_stopwatch->start();

			$metaCharset = "UTF-8";
			$metaAttributes = [];
			$metaAttributes["viewport"] = "width=device-width, initial-scale=1.0";

			echo ("<!DOCTYPE html>");
			$this->renderBeginTag("html", array("xmlns" => "http://www.w3.org/1999/xhtml"));
			$this->renderBeginTag("head");
			echo ("<meta charset=\"" . $metaCharset . "\" />");
			foreach ($metaAttributes as $key => $value)
			{
				echo ("<meta name=\"" . $key . "\" content=\"" . $value . "\" />");
			}

			$this->renderBeginTag("title");
			echo($title);
			$this->renderEndTag("title");

			$this->renderTag("link", array
			(
				"rel" => "stylesheet",
				"type" => "text/css",
				"href" => System::ExpandRelativePath("~/themes/" . System::GetConfigurationValue("Application.ThemeName") . "/theme.css", false, true)
			));
			
			$this->renderTag("link", array
			(
				"rel" => "stylesheet",
				"type" => "text/css",
				"href" => System::ExpandRelativePath("~/themes/mocha/theme.css", false, true)
			));

			$this->renderTag("script", array("type" => "text/javascript", "src" => System::ExpandRelativePath("~/scripts/phast/ckeditor/ckeditor.js", false, true)));
			$this->renderTag("script", array("type" => "text/javascript", "src" => System::ExpandRelativePath("~/scripts/phast/System.js.php", false, true)));
			$this->renderTag("script", array("type" => "text/javascript", "src" => System::ExpandRelativePath("~/scripts/mocha.js.php", false, true)));
			$this->renderBeginTag("script", array("type" => "text/javascript"));
			echo("System.BasePath = '" . System::GetConfigurationValue("Application.BasePath") . "';");
			echo("System.IsTenantedHostingEnabled = function() { return false; };");

			$tenantName = null;
			if ($oms !== null)
			{
				$tenantName = $oms->getTenantName();
			}
			echo("System.TenantName = '" . $tenantName . "';");
			
			$this->renderEndTag("script");
			foreach ($this->Scripts as $script)
			{
				if ($script->FileName !== null)
				{
					$this->renderBeginTag("script", array("type" => $script->ContentType, "src" => $script->FileName));
					$this->renderEndTag("script");
				}
				else if ($script->Content !== null)
				{
					$this->renderBeginTag("script", array("type" => $script->ContentType));
					echo($script->Content);
					$this->renderEndTag("script");
				}
			}
			$this->renderEndTag("head");
			$bodyClasses = [ "uwt-header-visible", "uwt-header-inverse", "uwt-javascript-disabled" ];
			foreach ($this->StyleClasses as $className)
			{
				$bodyClasses[] = $className;
			}
			if ($this->IsPostback)
			{
				$bodyClasses[] = "uwt-postback";
			}
			$this->renderBeginTag("body", null, $bodyClasses);

			$this->renderBeginTag("script", [ "type" => "text/javascript" ]);

			include("HTMLRenderer.js.php");

			$this->renderEndTag("script");

			$showSpotTimer = false;
			if ($showSpotTimer)
			{
				$spotTimerContent = ""; $spotTimerScript = "";
				\mocha_get_spot_timer_script($spotTimerContent, $spotTimerScript);
				echo(<<<MSG_EOF
				<div class="mocha-ams-spot-popup" id="spot_popup"><h1>SPOT:</h1><h3>Stop in: <label id="spot_timer">$spotTimerContent</label></h3></div>
	MSG_EOF);
				$this->renderTag("script", [ "type" => "text/javascript" ], [ ], $spotTimerScript);
			}

			if ($this->IncludeTopNavigationBar)
			{
				$this->renderTopNavigationBar();
			}

			echo("<form method=\"POST\">");
		}

		private function renderOmsMetadataExceptions(array $array, string $title, string $singularName, string $pluralName, string $colorClass)
		{
			echo("<div class=\"uwt-alert uwt-alert-sticky " . $colorClass . " uwt-collapsible uwt-collapsed uwt-disabled\">");
			echo("<div class=\"uwt-icon\" title=\"" . count($array) . " " . ($array == 1 ? $singularName : $pluralName) . "\">&nbsp;</div>");
			echo("<div class=\"uwt-title\">" . $title . "</div>");
			echo("<div class=\"uwt-badge\">" . count($array) . "</div>");
			echo("<div class=\"uwt-content\">");
			foreach ($array as $exception)
			{
				echo("<div class=\"uwt-disclosure\">");
				echo("<div class=\"uwt-title\">" . str_replace("\n", "<br />", $exception->Message) . "</div>");
				echo("<div class=\"uwt-content\">");
				if ($exception->Parameters !== null)
				{
					echo ("<div class=\"uwt-formview\">");
					foreach ($exception->Parameters as $key => $value)
					{
						echo("<div class=\"uwt-formview-item\"><div class=\"uwt-formview-item-label\"><label>" . $key . "</label></div><div class=\"uwt-formview-item-content\">");
						if ($value instanceof InstanceReference)
						{
							$ib = new InstanceBrowser();
							$ib->SelectedInstances[] = $value;
							$ib->Render();
						}
						else
						{
							echo($value);
						}
						echo("</div></div>");
					}
					echo("</div>");
				}
				if ($exception->CallStack !== null)
				{
					echo ("<h2> Call Stack Trace </h2>");
					$count = count($exception->CallStack);
					for ($i = 0; $i < $count; $i++)
					{
						$inst = $exception->CallStack[$i];

						$ib = new InstanceBrowser();
						$ib->DisplayInline = true;
						$ib->SelectedInstances[] = $inst;
						$ib->Text = $inst->InstanceKey;
						$ib->Render();

						if ($i < $count - 1)
						{
							echo ("<i class=\"fa fa-arrow-right\" style=\"font-weight: lighter; padding-right: 16px;\"></i>");
						}
					}
				}
				echo("</div>");
				echo("</div>");
			}
			echo("</div>");
			echo("</div>");
		}

		public function renderEndPage()
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = $this->OMS;

			echo("<div class=\"uwt-alert-container\">");

			if ($oms !== null)
			{
				if ($this->IsPostback)
				{
					if (count($this->FailedValidations) > 0)
					{
						foreach ($this->FailedValidations as $validation)
						{
							$oms->MetadataExceptions[] = new OmsMetadataException
							(
								$validation->Title . " : " . $validation->Message
							);
							/*
							$ik = "";
							echo("<div class=\"uwt-alert uwt-color-danger\" data-instance-id=\"" . $ik . "\">");
							echo("<div class=\"uwt-title\">" . $validation->Title . "</div>");
							echo("<div class=\"uwt-content\">" . str_replace("\n", "<br />", $validation->Message) . "</div>");
							echo("</div>");
							*/
						}
					}
					/*
					else
					{
						echo("<div class=\"uwt-alert uwt-color-success\">");
						echo("<div class=\"uwt-title\">Saved</div>");
						echo("</div>");
					}
					*/
				}
				
				$errors = [ ];
				$warnings = [ ];
				$infos = [ ];
				foreach ($oms->MetadataExceptions as $exc)
				{
					if ($exc->Severity == OmsMetadataExceptionSeverity::Error)
					{
						$errors[] = $exc;
					}
					else if ($exc->Severity == OmsMetadataExceptionSeverity::Warning)
					{
						$warnings[] = $exc;
					}
					else //if ($exc->Severity == OmsMetadataExceptionSeverity::Information)
					{
						$infos[] = $exc;
					}
				}
				if (count($errors) > 0)
				{
					$this->renderOmsMetadataExceptions($errors, "Errors", "error", "errors", "uwt-color-danger");
				}
				if (count($warnings) > 0)
				{
					$this->renderOmsMetadataExceptions($warnings, "Warnings", "warning", "warnings", "uwt-color-warning");
				}
				if (count($infos) > 0)
				{
					$this->renderOmsMetadataExceptions($infos, "Messages", "message", "messages", "uwt-color-info");
				}
			}
			
			echo("</div>");

			$this->renderEndTag("body");
			$this->renderEndTag("html");

			$this->_stopwatch->stop();
			$this->logRequestSuccess($this->_stopwatch->getElapsedTime());
		}

		public function renderInitialElement(InstanceReference $element)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();
			
			$applicationTitle = "Mocha"; // TOOD: get tenant application title
			$pageTitle = null;
			$pageTitle = $oms->getAttributeValue($element, KnownAttributeGuids::Label);

			if ($pageTitle !== null && $applicationTitle !== null)
			{
				$title = $pageTitle . " - " . $applicationTitle;
			}
			else if ($pageTitle !== null)
			{
				$title = $pageTitle;
			}
			else if ($applicationTitle !== null)
			{
				$title = $applicationTitle;
			}
			$this->renderBeginPage($title);
			
			echo ("<div class=\"uwt-page-content\">");
			$DO_Singular = $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Singular);
			$parentElementContents = [ ];
			$this->renderElement($parentElementContents, $element, [ $DO_Singular ]);
			echo ("</div>");
			$this->renderEndForm();

			$this->renderEndPage();
		}

		private function renderEndForm($allElementsAreReadonly = false)
		{
			if (!$allElementsAreReadonly)
			{
				echo("<div class=\"uwt-page-footer\">");
				// echo ("<a class=\"uwt-button\" href=\"#\" onclick=\"history.back(); return false;\">Cancel</a>");

				$backUrl = $_SERVER['HTTP_REFERER']; // System::ExpandRelativePath("~~/" . System::GetTenantName())
				echo("<input type=\"hidden\" name=\"ReturnToURL\" value=\"" . $backUrl . "\" />");
				echo ("<a class=\"uwt-button\" href=\"" . $backUrl . "\" " . /* onclick=\"history.back(); return false;\" */ ">Cancel</a>");
				echo ("<input class=\"uwt-button uwt-color-primary\" type=\"submit\" value=\"" . $this->SubmitButtonText . "\" />");
				echo("</div>");
			}
			echo ("</form>");
		}

		public function logRequestSuccess(float $elapsedTime)
		{
			echo("\n<!--\nSUCCESS!\n\n" .
			"request: " . $_SERVER["REQUEST_METHOD"] . " " . $_SERVER["REQUEST_URI"] . "\n" .
			"ip address: " . $_SERVER["REMOTE_ADDR"] . "\n" .
			"render time: " . $elapsedTime . " ms\n" .
			"\n-->");
		}

		public function renderTag($tagName, ?array $attributeList = null, ?array $classList = null, $content = null)
		{
			$canBeEmpty = true;
			if ($tagName == "script")
			{
				$canBeEmpty = false;
			}

			if ($canBeEmpty && $content === null)
			{
				echo ("<" . $tagName . $this->renderArray($classList, " ", " class=\"", "\"") . $this->renderKeyedArray($attributeList, " ", " ", "", "=", "", "", "\"", "\"") . " />");
			}
			else
			{
				$this->renderBeginTag($tagName, $attributeList, $classList);
				if ($content !== null)
				{
					echo($content);
				}
				$this->renderEndTag($tagName);
			}
		}

		public function renderBeginTag($tagName, ?array $attributeList = null, ?array $classList = null)
		{
			echo ("<" . $tagName . $this->renderArray($classList, " ", " class=\"", "\"") . $this->renderKeyedArray($attributeList, " ", " ", "", "=", "", "", "\"", "\"") . ">");
		}
		public function renderEndTag($tagName)
		{
			echo ("</" . $tagName . ">");
		}

		private function get__EC_Override_Label_or_default_Instance_Title(array $parms)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$ecInst = $parms[KnownClassGuids::Instance];
			$elementContent = $parms[KnownClassGuids::ElementContent];
			
			$title = $oms->getAttributeValue($ecInst, $oms->getInstanceByGlobalIdentifier(KnownAttributeGuids::Name));
			$label = $oms->getAttributeValue($elementContent, $oms->getInstanceByGlobalIdentifier(KnownAttributeGuids::Label));
			$labelOverride = $oms->getAttributeValue($elementContent, $oms->getInstanceByGlobalIdentifier(KnownAttributeGuids::LabelOverride));
			
			//!HACK
			if ($ecInst->InstanceKey->ClassIndex == 6)
			{
				// ELements should not have a title
				$title = "";
			}
			if ($label != "")
			{
				$title = $label;
			}
			if ($labelOverride != "")
			{
				$title = $labelOverride;
			}
			//$title = "TODO:call Instance@get Title [ramb]";
			return $title;
		}

		private ?InstanceReference $__renderingElement;
		private ?InstanceReference $__processingElement;

		private function getSubelementInstances(InstanceReference $parentElementContent)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$instances = null;
			$bemProcess = $oms->getRelatedInstance($parentElementContent, KnownRelationshipGuids::Element_Content__built_from__BEM_Process);
			if ($bemProcess != null)
			{
				// execute the `Executable returning Instance Set` to get the rows to display (Primary Instances)
				$instanceExecutable = $oms->getRelatedInstance($bemProcess, KnownRelationshipGuids::BEM_Process__uses_loop__Executable_returning_Instance_Set);
				if ($instanceExecutable !== null)
				{
					//! FIXME: this array should be put into teh specified `returns Work Set` variable
					// ... BUT this works the way it's supposed to , for right now!
					//* actually, now, no it doesn't, we have to change it...
					echo ("<!-- execute method: " . $instanceExecutable->InstanceKey . " -->");
					$instancesWorkSet = $oms->execute($this->Context, $instanceExecutable);
					$instances = $this->Context->getWorkData($instancesWorkSet);
					if ($instances !== null && !is_array($instances))
					{
						$instances = [ $instances ];
					}
				}
				if ($instances === null)
				{
					$oms->MetadataExceptions[] = new OmsMetadataException("Selected instances for FlexGrid are empty", array
					(
						"Executable returning Instance Set" => $instanceExecutable,
						"Returned Work Data" => $instancesWorkSet
					));
					$instances = [];
				}
			}
			return $instances;
		}

		public function renderElementAsString(array &$parentElementContents, InstanceReference $element, array $displayOptions, $renderNotEnterable = false)
		{
			ob_start();
			$this->renderElement($parentElementContents, $element, $displayOptions, $renderNotEnterable);
			$contents = ob_get_contents();
			ob_end_clean();
			return $contents;
		}

		public function renderElement(array &$parentElementContents, InstanceReference $element, array $displayOptions, $renderNotEnterable = false)
		{
			/**
			 * @var MySQLDatabaseOms
			 */
			$oms = mocha_get_oms();

			$this->__renderingElement = $element;
			
			$renderingNotEnterable =  $renderNotEnterable || $oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable));
			$contents = $oms->getRelatedInstances($element, $oms->getInstanceByGlobalIdentifier(KnownRelationshipGuids::Element__has__Element_Content));
			if (count($contents) > 0)
			{
				$ecInst0 = $oms->getRelatedInstance($contents[0], KnownRelationshipGuids::Element_Content__has__Instance);
				if ($ecInst0 !== null)
				{
					$contents0DOs = $oms->getRelatedInstances($contents[0], KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
					if ($oms->instanceSetContains($contents0DOs, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__InitializeForEntry)))
					{
						if ($this->DebugMode)
						{
							echo ("have inittialse for entry .. ");
						}
						// initialize for entry - create a new instance of the class for the first EC
						if ($oms->is_a($ecInst0, KnownClassGuids::Clasz))
						{
							//! FIXME: we don't really want to do this until the page is saved!
							// what we should do is abstract all OMS operations out into PHP classes
							// e.g. Instance, ConcreteInstance which inherits from Instance, etc.
							// 
							// then ... create a PHP class called TemporaryInstance
							// assign all attribute and relationship to that
							// if we successfully submit the task, THEN apply TemporaryInstance -> ConcreteInstance
							$newInst = $oms->createInstanceOf($ecInst0);
							echo ("create new ins " . $newInst->InstanceKey); 
							$this->Context->setWorkData($ecInst0, $newInst);
							if ($this->TargetInstance === null)
							{
								echo ("hack updating targ inst");
								$this->TargetInstance = $newInst;
							}
						}
					}
				}
			}

			if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__ShowSubelementsVertically)))
			{
				echo("<!-- show subelements vertically -->");
				$elementLabel = $oms->getAttributeValue($element, KnownAttributeGuids::Label);
				if ($elementLabel !== null)
				{
					echo("<h2>" . $elementLabel . "</h2>");
				}
				
				# $this->renderBeginTag("table", [ "data-instance-id" => $element->InstanceKey ], [ "uwt-formview", "mcx-element" ]);
				$this->renderBeginTag("div", [ "data-instance-id" => $element->InstanceKey ], [ "uwt-formview", "mcx-element" ]);

				if (count($contents) > 0)
				{
					$ecInst0 = $oms->getRelatedInstance($contents[0], KnownRelationshipGuids::Element_Content__has__Instance);
					if ($ecInst0 !== null)
					{
						$wd = $this->Context->getWorkData($ecInst0);
						if ($wd !== null)
						{
							$this->Context->setWorkData(KnownInstanceGuids::WorkSet__RelatedInstance, $wd);
						}
					}
				}

				$qs = new QuickSort();
				$sortedElementContents = $qs->Sort_By($contents, function($item)
				{
					/**
					 * @var MySQLDatabaseOms
					 */
					$oms = mocha_get_oms();

					$order = $oms->getAttributeValue($item, KnownAttributeGuids::Order);
					return $order;
				});
				
				foreach ($sortedElementContents as $elementContent)
				{
					if ($elementContent === null)
					{
						continue;
					}

					if (!$this->shouldRenderElementContent($elementContent))
					{
						continue;
					}
					
					$ecDisplayOptions = $oms->getRelatedInstances($elementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
					$ecInst = $oms->getRelatedInstance($elementContent, KnownRelationshipGuids::Element_Content__has__Instance);
					$mcx_classes = [ "mcx-elementcontent", "uwt-formview-item" ];
					if ($ecInst->InstanceKey->ClassIndex == 6)
					{
						$mcx_classes = [ "mcx-element" ];
					}
					if ($oms->instanceSetContains($ecDisplayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Required)))
					{
						$mcx_classes[] = "mcx-required";
					}
					if ($this->IsPostback && array_key_exists($elementContent->DatabaseId, $this->FailedValidationElements))
					{
						$mcx_classes[] = "mcx-failed-validation";
					}
					if (!$this->shouldRenderElementContentLabel($elementContent))
					{
						$mcx_classes[] = "uwt-hide-label";
					}
					if ($this->elementContentIsEmpty($elementContent))
					{
						$mcx_classes[] = "mcx-empty";
					}
					if (!($renderingNotEnterable || $renderNotEnterable || $oms->instanceSetContains($ecDisplayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__NotEnterable))))
					{
						$mcx_classes[] = "mcx-editable";
					}

					$fullyQualifiedECID = $this->getElementContentId($parentElementContents, $elementContent);

					// $this->renderBeginTag("tr", [ "data-instance-id" => $ecInst->InstanceKey, "data-ecid" => $elementContent->InstanceKey ], $mcx_classes);
					$this->renderBeginTag("div", [ "data-instance-id" => $ecInst->InstanceKey, "data-ecid" => $elementContent->InstanceKey, "data-fqecid" => $fullyQualifiedECID ], $mcx_classes);

					$title = $this->get__EC_Override_Label_or_default_Instance_Title(array
					(
						KnownClassGuids::Instance => $ecInst,
						KnownClassGuids::ElementContent => $elementContent
					));

					if ($this->shouldRenderElementContentLabel($elementContent))
					{
						// $this->renderBeginTag("td");
						// $this->renderEndTag("td");
						// $this->renderBeginTag("td");
					}
					else
					{
						// $this->renderBeginTag("td", [ "colspan" => "2" ]);
					}
					
					$this->renderBeginTag("div", [ ], [ "uwt-formview-item-label "]);

					echo ("<label for=\"ec_" . $this->getElementContentId($parentElementContents, $elementContent) . "\">" . $title . "</label>");

					$this->renderEndTag("div");
					
					$this->renderBeginTag("div", [ ], [ "uwt-formview-item-content" ]);

					$this->renderElementContent($parentElementContents, $elementContent, $renderingNotEnterable);
					$this->renderTag("span", [ ], [ "class" => "mcx-empty-text" ], "(empty)");

					if (array_key_exists($elementContent->DatabaseId, $this->FailedValidationElements))
					{
						$validationResult = $this->FailedValidationElements[$elementContent->DatabaseId];
						$this->renderBeginTag("div", [ ], [ "mcx-validation-message uwt-color-danger" ]);
						echo($validationResult->Title . " : " . $validationResult->Message);
						$this->renderEndTag("div");
					}

					$this->renderEndTag("div");

					// $this->renderEndTag("td");
					
					// $this->renderEndTag("tr");
					$this->renderEndTag("div");
				}
				// $this->renderEndTag("table");
				$this->renderEndTag("div");
			}
			else
			{
				if ($oms->instanceSetContains($displayOptions, $oms->getInstanceByGlobalIdentifier(KnownInstanceGuids::DisplayOption__Singular)))
				{
					echo("<!-- singular -->");
					echo ("<!-- element " . $element->InstanceKey . " has " . count($contents) . " ECs -->");
					foreach ($contents as $content)
					{
						$this->renderElementContent($parentElementContents, $content);
					}
				}
				else
				{
					$title = $oms->getAttributeValue($element, KnownAttributeGuids::Name);
					$label = $oms->getAttributeValue($element, KnownAttributeGuids::Label);
					
					if ($label !== null)
					{
						$title = $label;
					}

					$instances = [];
					// $bem = $oms->getRelatedInstance($element, KnownRelationshipGuids::Element__returned_by__Build_Element_Method);
					//$bemProcess = $oms->getRelatedInstance($bem, KnownRelationshipGuids::Build_Element_Method__has__BEM_Process);
					$parentElementContent = array_pop($parentElementContents);
					array_push($parentElementContents, $parentElementContent);

					$parentECDisplayOptions = $oms->getRelatedInstances($parentElementContent, KnownRelationshipGuids::Element_Content__has__Element_Content_Display_Option);
					
					$listviewClasses = [ "uwt-listview", "mcx-element", "mcx-elementcontent" ];
					if (!($renderingNotEnterable || $renderNotEnterable || $oms->instanceSetContains($parentECDisplayOptions, $oms->normalizeInstanceReference(KnownInstanceGuids::DisplayOption__NotEnterable)) || $oms->instanceSetContains($parentECDisplayOptions, $oms->normalizeInstanceReference(KnownInstanceGuids::DisplayOption__RowsNotEditable))))
					{
						$listviewClasses[] = "mcx-editable";
					}

					$rowCount = 0;
					$instances = $this->getSubelementInstances($parentElementContent);
					if ($instances !== null)
					{
						$rowCount = count($instances);
						
						echo("<!-- nonsingular -->");
						echo ("<input type=\"hidden\" name=\"ec_" . $this->getElementContentId($parentElementContents, null) . "_count\" value=\"" . $rowCount . "\" />");
					}

					$mcx_classes = [ "mcx-element" ];
					if ($rowCount === 0)
					{
						$mcx_classes[] = "mcx-empty";
					}
					$this->renderBeginTag("div", [ "data-instance-id" => $element->InstanceKey ], $mcx_classes);
					$this->renderBeginTag("table", [ "data-ecid" => $parentElementContent->InstanceKey, "data-fqecid" => $this->getElementContentId($parentElementContents, null), "data-instance-id" => $element->InstanceKey ], $listviewClasses);

					// $this->renderBeginTag("div", [ "data-instance-id" => $element->InstanceKey ], [ "uwt-listview", "mcx-element", "uwt-listview-view-details", "uwt-gridlines", "uwt-hottracking" ]);

					// $this->renderBeginTag("div", [ ], [ "uwt-listview-caption" ]);
					$this->renderBeginTag("caption", [ ], [ ]);
					
					$this->renderBeginTag("span", [ ], [ "uwt-listview-title" ]);
					echo ($title);
					$this->renderEndTag("span");

					$this->renderTag("span", [ ], [ "uwt-listview-item-count" ], $rowCount);
					$this->renderTag("span", [ ], [ "uwt-listview-item-count-label" ], "&nbsp;items");
					$this->renderBeginTag("div", [ ], [ "uwt-toolbar", "uwt-listview-controlbox" ]);
					
					// export
					$this->renderBeginTag("a", [ "href" => "#", "title" => "Download" ], [ "uwt-button" ]);
					$this->renderTag("i", [ ], [ "fa", "fa-download"] , "");
					$this->renderEndTag("a");

					// filter
					$this->renderBeginTag("a", [ "href" => "#", "title" => "Filter" ], [ "uwt-button", "uwt-toggle" ]);
					$this->renderTag("i", [ ], [ "fa", "fa-filter"] , "");
					$this->renderEndTag("a");

					// chart
					$this->renderBeginTag("a", [ "href" => "#", "title" => "Chart" ], [ "uwt-button", "uwt-toggle" ]);
					$this->renderTag("i", [ ], [ "fa", "fa-line-chart"] , "");
					$this->renderEndTag("a");

					// columns
					$this->renderBeginTag("a", [ "href" => "#", "title" => "Columns" ], [ "uwt-button", "uwt-toggle" ]);
					$this->renderTag("i", [ ], [ "fa", "fa-columns"] , "");
					$this->renderEndTag("a");

					// expand
					$this->renderBeginTag("a", [ "href" => "#", "title" => "Expand" ], [ "uwt-button", "uwt-toggle" ]);
					$this->renderTag("i", [ ], [ "fa", "fa-expand"] , "");
					$this->renderEndTag("a");

					$this->renderEndTag("div");
					// $this->renderEndTag("div");
					$this->renderEndTag("caption");

					//$this->renderBeginTag("div", [], ["uwt-content"]);

					//$this->renderBeginTag("div", [ ], [ "uwt-listview-column-headers" ]);
					$this->renderBeginTag("thead");
					$this->renderBeginTag("tr");

					$this->renderBeginTag("th", [ ], [ "uwt-listview-row-buttons" ]);
					$this->renderBeginTag("a", [ "href" => "#" ], [ "uwt-listview-row-add" ]);
					$this->renderEndTag("a");
					$this->renderEndTag("th");

					foreach ($contents as $content)
					{
						if ($this->shouldRenderElementContent($content))
						{
							$inst = $oms->getRelatedInstance($content, KnownRelationshipGuids::Element_Content__has__Instance);
							$this->renderBeginTag("th", [ "data-ecid" => $content->InstanceKey ]);
							//$this->renderBeginTag("div", [], [ "uwt-listview-column-header"]);
							echo ($this->get__EC_Override_Label_or_default_Instance_Title(array
							(
								KnownClassGuids::Instance => $inst,
								KnownClassGuids::ElementContent => $content
							)));
							// $this->renderEndTag("div");
							$this->renderEndTag("th");
						}
					}

					$this->renderEndTag("tr");
					$this->renderEndTag("thead");
					//$this->renderEndTag("div");
	
					//! TBODY *must* be rendered for Add Row button to work in JS
					$this->renderBeginTag("tbody");
					if ($instances !== null)
					{
						//$this->renderBeginTag("div", [], [ "uwt-listview-items" ]);
						for ($i = 0; $i < $rowCount; $i++)
						{
							$rowInst = $instances[$i];
							
							$this->Context->setWorkData($oms->normalizeInstanceReference(KnownInstanceGuids::WorkSet__RelatedInstance), $rowInst);
							$this->Context->setWorkData($oms->getParentClass($rowInst), $rowInst);
							
							$this->renderBeginTag("tr", [ ], [ "uwt-listview-item" ]);
							//$this->renderBeginTag("div", [ ], [ "uwt-listview-item" ]);
							echo("<!-- " . $rowInst . " -->");

							$this->renderBeginTag("td", [ ], [ "uwt-listview-row-buttons" ]);
							$this->renderBeginTag("a", [ "href" => "#" ], [ "uwt-listview-row-add" ]);
							$this->renderEndTag("a");
							$this->renderBeginTag("a", [ "href" => "#" ], [ "uwt-listview-row-remove" ]);
							$this->renderEndTag("a");
							$this->renderEndTag("td");

							array_push($parentElementContents, $i);
							foreach ($contents as $content)
							{
								if ($this->shouldRenderElementContent($content))
								{
									$this->renderBeginTag("td");
									//$this->renderBeginTag("div", [], [ "mcx-elementcontent", "uwt-listview-item-column" ]);

									$this->renderElementContent($parentElementContents, $content, $renderingNotEnterable);

									//$this->renderEndTag("div");
									$this->renderEndTag("td");
								}
							}
							array_pop($parentElementContents);

							$this->renderEndTag("tr");
							//$this->renderEndTag("div");

						}

						//$this->renderEndTag("div");

						//$this->renderEndTag("div");
					}
					$this->renderEndTag("tbody");

					$this->renderEndTag("table");
					
					$this->renderBeginTag("div", [ ], [ "mcx-empty-text" ]);
					echo("No items");
					$this->renderEndTag("div");
					
					$this->renderEndTag("div");
				}
			}
		}


	}
?>