<?php

	function QcubedHandleCodeGenParseError($__exc_errno, $__exc_errstr, $__exc_errfile, $__exc_errline) {
		$strErrorString = str_replace("SimpleXMLElement::__construct() [<a href='function.SimpleXMLElement---construct'>function.SimpleXMLElement---construct</a>]: ", '', $__exc_errstr);
		QCodeGen::$RootErrors .= sprintf("%s\r\n", $strErrorString);
	}

	function GO_BACK($intNumChars) {
		$content_so_far = ob_get_contents();
		ob_end_clean();
		$content_so_far = substr($content_so_far, 0, strlen($content_so_far) - $intNumChars);
		ob_start();
		print $content_so_far;
	}

	// returns true if $str begins with $sub
	function beginsWith( $str, $sub ) {
	    return ( substr( $str, 0, strlen( $sub ) ) == $sub );
	}

	// return tru if $str ends with $sub
	function endsWith( $str, $sub ) {
	    return ( substr( $str, strlen( $str ) - strlen( $sub ) ) == $sub );
	}

	// trims off x chars from the front of a string
	// or the matching string in $off is trimmed off
	function trimOffFront( $off, $str ) {
	    if( is_numeric( $off ) )
	        return substr( $str, $off );
	    else
	        return substr( $str, strlen( $off ) );
	}

	// trims off x chars from the end of a string
	// or the matching string in $off is trimmed off
	function trimOffEnd( $off, $str ) {
	    if( is_numeric( $off ) )
	        return substr( $str, 0, strlen( $str ) - $off );
	    else
	        return substr( $str, 0, strlen( $str ) - strlen( $off ) );
	}

	/**
	 * This is the CodeGen class which performs the code generation
	 * for both the Object-Relational Model (e.g. Data Objects) as well as
	 * the draft Forms, which make up simple HTML/PHP scripts to perform
	 * basic CRUD functionality on each object.
	 * @package Codegen
	 * @property string $Errors List of errors
	 * @property string $Warnings List of warnings
	 */
	abstract class QCodeGenBase extends QBaseClass {
		// Class Name Suffix/Prefix
		/** @var string Class Prefix, as specified in the codegen_settings.xml file */
		protected $strClassPrefix;
		/** @var string Class suffix, as specified in the codegen_settings.xml file */
		protected $strClassSuffix;

		/** @var string Errors and Warnings collected during the process of codegen **/
		protected $strErrors;

		/** @var string Warnings collected during the codegen process. */
		protected $strWarnings;

		/**
		 * PHP Reserved Words.  They make up:
		 * Invalid Type names -- these are reserved words which cannot be Type names in any user type table
		 * Invalid Table names -- these are reserved words which cannot be used as any table name
		 * Please refer to : http://php.net/manual/en/reserved.php
		 */
		const PhpReservedWords = 'new, null, break, return, switch, self, case, const, clone, continue, declare, default, echo, else, elseif, empty, exit, eval, if, try, throw, catch, public, private, protected, function, extends, foreach, for, while, do, var, class, static, abstract, isset, unset, implements, interface, instanceof, include, include_once, require, require_once, abstract, and, or, xor, array, list, false, true, global, parent, print, exception, namespace, goto, final, endif, endswitch, enddeclare, endwhile, use, as, endfor, endforeach, this';

		/**
		 * @var array The list of template base paths to search, in order, when looking for a particular template. Set this
		 * to insert new template paths. If not set, the default will be the project template path, following by the qcubed core path.
		 */
		public static $TemplatePaths;

		/**
		 * DebugMode -- for Template Developers
		 * This will output the current evaluated template/statement to the screen
		 * On "eval" errors, you can click on the "View Rendered Page" to see what currently
		 * is being evaluated, which should hopefully aid in template debugging.
		 */
		const DebugMode = false;

		/**
		 * This static array contains an array of active and executed codegen objects, based
		 * on the XML Configuration passed in to Run()
		 *
		 * @var QCodeGen[] array of active/executed codegen objects
		 */
		public static $CodeGenArray;

		/**
		 * This is the array representation of the parsed SettingsXml
		 * for reportback purposes.
		 *
		 * @var string[] array of config settings
		 */
		protected static $SettingsXmlArray;

		/**
		 * This is the SimpleXML representation of the Settings XML file
		 *
		 * @var SimpleXmlElement the XML representation
		 */
		protected static $SettingsXml;

		public static $SettingsFilePath;

		/**
		 * Application Name (from CodeGen Settings)
		 *
		 * @var string $ApplicationName
		 */
		public static $ApplicationName;

		/**
		 * Preferred Render Method (from CodeGen Settings)
		 *
		 * @var string $PreferredRenderMethod
		 */
		public static $PreferredRenderMethod;

		/**
		 * Create Method (from CodeGen Settings)
		 *
		 * @var string $CreateMethod
		 */
		public static $CreateMethod;

		/**
		 * Default Button Class (from CodeGen Settings)
		 *
		 * @var string $DefaultButtonClass
		 */
		public static $DefaultButtonClass;

		public static $RootErrors = '';

		/**
		 * @var string[] array of directories to be excluded in codegen (lower cased)
		 * @access protected
		 */
		protected static $DirectoriesToExcludeArray = array('.','..','.svn','svn','cvs','.git');

		/**
		 * Gets the settings in codegen_settings.xml file and returns its text without comments
		 * @return string
		 */
		public static function GetSettingsXml() {
			$strCrLf = "\r\n";

			$strToReturn = sprintf('<codegen>%s', $strCrLf);
			$strToReturn .= sprintf('	<name application="%s"/>%s', QCodeGen::$ApplicationName, $strCrLf);
			$strToReturn .= sprintf('	<render preferredRenderMethod="%s"/>%s', QCodeGen::$PreferredRenderMethod, $strCrLf);
			$strToReturn .= sprintf('	<dataSources>%s', $strCrLf);
			foreach (QCodeGen::$CodeGenArray as $objCodeGen)
				$strToReturn .= $strCrLf . $objCodeGen->GetConfigXml();
			$strToReturn .= sprintf('%s	</dataSources>%s', $strCrLf, $strCrLf);
			$strToReturn .= '</codegen>';

			return $strToReturn;
		}

		/**
		 * The function which actually performs the steps for code generation
		 * Code generation begins here.
		 * @param string $strSettingsXmlFilePath Path to the settings file
		 */
		public static function Run($strSettingsXmlFilePath) {
			define ('__CODE_GENERATING__', true);
			QCodeGen::$CodeGenArray = array();
			QCodeGen::$SettingsFilePath = $strSettingsXmlFilePath;

			if (!file_exists($strSettingsXmlFilePath)) {
				QCodeGen::$RootErrors = 'FATAL ERROR: CodeGen Settings XML File (' . $strSettingsXmlFilePath . ') was not found.';
				return;
			}

			if (!is_file($strSettingsXmlFilePath)) {
				QCodeGen::$RootErrors = 'FATAL ERROR: CodeGen Settings XML File (' . $strSettingsXmlFilePath . ') was not found.';
				return;
			}

			// Try Parsing the Xml Settings File
			try {
				QApplication::SetErrorHandler('QcubedHandleCodeGenParseError', E_ALL);
				QCodeGen::$SettingsXml = new SimpleXMLElement(file_get_contents($strSettingsXmlFilePath));
				QApplication::RestoreErrorHandler();
			} catch (Exception $objExc) {
				QCodeGen::$RootErrors .= 'FATAL ERROR: Unable to parse CodeGenSettings XML File: ' . $strSettingsXmlFilePath;
				QCodeGen::$RootErrors .= "\r\n";
				QCodeGen::$RootErrors .= $objExc->getMessage();
				return;
			}

			// Application Name
			QCodeGen::$ApplicationName = QCodeGen::LookupSetting(QCodeGen::$SettingsXml, 'name', 'application');

			// Codegen Defaults
			QCodeGen::$PreferredRenderMethod = QCodeGen::LookupSetting(QCodeGen::$SettingsXml, 'formgen', 'preferredRenderMethod');
			QCodeGen::$CreateMethod = QCodeGen::LookupSetting(QCodeGen::$SettingsXml, 'formgen', 'createMethod');
			QCodeGen::$DefaultButtonClass = QCodeGen::LookupSetting(QCodeGen::$SettingsXml, 'formgen', 'buttonClass');

			if (!QCodeGen::$DefaultButtonClass) {
				QCodeGen::$RootErrors .= "CodeGen Settings XML Fatal Error: buttonClass was not defined\r\n";
				return;
			}

			// Iterate Through DataSources
			if (QCodeGen::$SettingsXml->dataSources->asXML())
				foreach (QCodeGen::$SettingsXml->dataSources->children() as $objChildNode) {
					switch (dom_import_simplexml($objChildNode)->nodeName) {
						case 'database':
							QCodeGen::$CodeGenArray[] = new QDatabaseCodeGen($objChildNode);
							break;
						case 'restService':
							QCodeGen::$CodeGenArray[] = new QRestServiceCodeGen($objChildNode);
							break;
						default:
							QCodeGen::$RootErrors .= sprintf("Invalid Data Source Type in CodeGen Settings XML File (%s): %s\r\n",
								$strSettingsXmlFilePath, dom_import_simplexml($objChildNode)->nodeName);
							break;
					}
				}
		}

		/**
		 * This will lookup either the node value (if no attributename is passed in) or the attribute value
		 * for a given Tag.  Node Searches only apply from the root level of the configuration XML being passed in
		 * (e.g. it will not be able to lookup the tag name of a grandchild of the root node)
		 *
		 * If No Tag Name is passed in, then attribute/value lookup is based on the root node, itself.
		 *
		 * @param SimpleXmlElement $objNode
		 * @param string $strTagName
		 * @param string $strAttributeName
		 * @param string $strType
		 * @return mixed the return type depends on the QType you pass in to $strType
		 */
		static public function LookupSetting($objNode, $strTagName, $strAttributeName = null, $strType = QType::String) {
			if ($strTagName)
				$objNode = $objNode->$strTagName;

			if ($strAttributeName) {
				switch ($strType) {
					case QType::Integer:
						try {
							$intToReturn = QType::Cast($objNode[$strAttributeName], QType::Integer);
							return $intToReturn;
						} catch (Exception $objExc) {
							return null;
						}
					case QType::Boolean:
						try {
							$blnToReturn = QType::Cast($objNode[$strAttributeName], QType::Boolean);
							return $blnToReturn;
						} catch (Exception $objExc) {
							return null;
						}
					default:
						$strToReturn = trim(QType::Cast($objNode[$strAttributeName], QType::String));
						return $strToReturn;
				}
			} else {
				$strToReturn = trim(QType::Cast($objNode, QType::String));
				return $strToReturn;
			}
		}

		/**
		 *
		 * @return array
		 */
		public static function GenerateAggregate() {
			$objDbOrmCodeGen = array();
			$objRestServiceCodeGen = array();

			foreach (QCodeGen::$CodeGenArray as $objCodeGen) {
				if ($objCodeGen instanceof QDatabaseCodeGen)
					array_push($objDbOrmCodeGen, $objCodeGen);
				if ($objCodeGen instanceof QRestServiceCodeGen)
					array_push($objRestServiceCodeGen, $objCodeGen);
			}

			$strToReturn = array();
			array_merge($strToReturn, QDatabaseCodeGen::GenerateAggregateHelper($objDbOrmCodeGen));
//			array_push($strToReturn, QRestServiceCodeGen::GenerateAggregateHelper($objRestServiceCodeGen));

			return $strToReturn;
		}

		/**
		 * Given a template prefix (e.g. db_orm_, db_type_, rest_, soap_, etc.), pull
		 * all the _*.tpl templates from any subfolders of the template prefix
		 * in QCodeGen::TemplatesPath and QCodeGen::TemplatesPathCustom,
		 * and call GenerateFile() on each one.  If there are any template files that reside
		 * in BOTH TemplatesPath AND TemplatesPathCustom, then only use the TemplatesPathCustom one (which
		 * in essence overrides the one in TemplatesPath)
		 *
		 * @param string  $strTemplatePrefix the prefix of the templates you want to generate against
		 * @param mixed[] $mixArgumentArray  array of arguments to send to EvaluateTemplate
		 *
		 * @throws Exception
		 * @throws QCallerException
		 * @return boolean success/failure on whether or not all the files generated successfully
		 */
		public function GenerateFiles($strTemplatePrefix, $mixArgumentArray) {
			// If you are editing core templates, and getting EOF errors only on the travis build, this may be your problem. Scan your files and remove short tags.
			if (QCodeGen::DebugMode && ini_get ('short_open_tag')) _p("Warning: PHP directive short_open_tag is on. Using short tags will cause unexpected EOF on travis build.\n", false);

			// Default the template paths
			if (!static::$TemplatePaths) {
				static::$TemplatePaths = array (
					__QCUBED_CORE__ . '/codegen/templates/',
					__QCUBED__ . '/codegen/templates/'
				);
			}

			// validate the template paths
			foreach (static::$TemplatePaths as $strPath) {
				if (!is_dir($strPath)) {
					throw new Exception(sprintf("Template path: %s does not appear to be a valid directory.", $strPath));
				}
			}

			// Create an array of arrays of standard templates and custom (override) templates to process
			// Index by [module_name][filename] => true/false where
			// module name (e.g. "class_gen", "form_delegates) is name of folder within the prefix (e.g. "db_orm")
			// filename is the template filename itself (in a _*.tpl format)
			// true = override (use custom) and false = do not override (use standard)
			$strTemplateArray = array();

			// Go through standard templates first, then override in order
			foreach (static::$TemplatePaths as $strPath) {
				$this->buildTemplateArray($strPath . $strTemplatePrefix, $strTemplateArray);
			}

			// Finally, iterate through all the TemplateFiles and call GenerateFile to Evaluate/Generate/Save them
			$blnSuccess = true;
			foreach ($strTemplateArray as $strModuleName => $strFileArray) {
				foreach ($strFileArray as $strFilename => $strPath) {
					if (!$this->GenerateFile($strTemplatePrefix . '/' . $strModuleName, $strPath, $mixArgumentArray)) {
						$blnSuccess = false;
					}
				}
			}

			return $blnSuccess;
		}

		protected function buildTemplateArray ($strTemplateFilePath, &$strTemplateArray) {
			if (!$strTemplateFilePath) return;
			if (substr( $strTemplateFilePath, -1 ) != '/') {
				$strTemplateFilePath .= '/';
			}
			if (is_dir($strTemplateFilePath)) {
				$objDirectory = opendir($strTemplateFilePath);
				while ($strModuleName = readdir($objDirectory)) {
					if (!in_array(strtolower($strModuleName), QCodeGen::$DirectoriesToExcludeArray) &&
							is_dir($strTemplateFilePath . $strModuleName)) {
						$objModuleDirectory = opendir($strTemplateFilePath . $strModuleName);
						while ($strFilename = readdir($objModuleDirectory)) {
							if ((QString::FirstCharacter($strFilename) == '_') &&
								(substr($strFilename, strlen($strFilename) - 8) == '.tpl.php')
							) {
								$strTemplateArray[$strModuleName][$strFilename] = $strTemplateFilePath . $strModuleName . '/' . $strFilename;
							}
						}
					}
				}
			}
		}

		/**
		 * Returns the settings of the template file as SimpleXMLElement object
		 *
		 * @param null|string $strTemplateFilePath Path to the file
		 * @param null|string $strTemplate         Text of the template (if $strTemplateFilePath is null, this field must be string)
		 * @deprecated
		 *
		 * @return SimpleXMLElement
		 * @throws Exception
		 */
		protected function getTemplateSettings($strTemplateFilePath, &$strTemplate = null) {
			if ($strTemplate === null)
				$strTemplate = file_get_contents($strTemplateFilePath);
			$strError = 'Template\'s first line must be <template OverwriteFlag="boolean" DocrootFlag="boolean" TargetDirectory="string" DirectorySuffix="string" TargetFileName="string"/>: ' . $strTemplateFilePath;
			// Parse out the first line (which contains path and overwriting information)
			$intPosition = strpos($strTemplate, "\n");
			if ($intPosition === false) {
				throw new Exception($strError);
			}

			$strFirstLine = trim(substr($strTemplate, 0, $intPosition));

			$objTemplateXml = null;
			// Attempt to Parse the First Line as XML
			try {
				@$objTemplateXml = new SimpleXMLElement($strFirstLine);
			} catch (Exception $objExc) {}

			if (is_null($objTemplateXml) || (!($objTemplateXml instanceof SimpleXMLElement)))
				throw new Exception($strError);
			$strTemplate = substr($strTemplate, $intPosition + 1);
			return $objTemplateXml;
		}

		/**
		 * Generates a php code using a template file
		 *
		 * @param string  $strModuleSubPath
		 * @param string  $strTemplateFilePath Path to the template file
		 * @param mixed[] $mixArgumentArray
		 * @param boolean $blnSave             whether or not to actually perform the save
		 *
		 * @throws QCallerException
		 * @throws Exception
		 * @return mixed returns the evaluated template or boolean save success.
		 */
		public function GenerateFile($strModuleSubPath, $strTemplateFilePath, $mixArgumentArray, $blnSave = true) {
			// Setup Debug/Exception Message
			if (QCodeGen::DebugMode) _p("Evaluating $strTemplateFilePath<br/>", false);

			// Check to see if the template file exists, and if it does, Load It
			if (!file_exists($strTemplateFilePath))
				throw new QCallerException('Template File Not Found: ' . $strTemplateFilePath);

			// Evaluate the Template
			// make sure paths are set up to pick up included files from the various directories.
			// Must be the reverse of the buildTemplateArray order
			$a = array();
			foreach (static::$TemplatePaths as $strTemplatePath) {
				array_unshift($a,  $strTemplatePath . $strModuleSubPath);
			}
			$strSearchPath = implode (PATH_SEPARATOR, $a) . PATH_SEPARATOR . get_include_path();
			$strOldIncludePath = set_include_path ($strSearchPath);
			if ($strSearchPath != get_include_path()) {
				throw new QCallerException ('Can\'t override include path. Make sure your apache or server settings allow include paths to be overridden. ' );
			}

			$strTemplate = $this->EvaluatePHP($strTemplateFilePath, $mixArgumentArray, $templateSettings);
			set_include_path($strOldIncludePath);

			$blnOverwriteFlag = QType::Cast($templateSettings['OverwriteFlag'], QType::Boolean);
			$blnDocrootFlag = QType::Cast($templateSettings['DocrootFlag'], QType::Boolean);
			$strTargetDirectory = QType::Cast($templateSettings['TargetDirectory'], QType::String);
			$strDirectorySuffix = QType::Cast($templateSettings['DirectorySuffix'], QType::String);
			$strTargetFileName = QType::Cast($templateSettings['TargetFileName'], QType::String);

			if (is_null($blnOverwriteFlag) || is_null($strTargetFileName) || is_null($strTargetDirectory) || is_null($strDirectorySuffix) || is_null($blnDocrootFlag))  {
				throw new Exception('the template settings cannot be null');
			}

			if ($blnSave && $strTargetDirectory) {
				// Figure out the REAL target directory
				if ($blnDocrootFlag)
					$strTargetDirectory = __DOCROOT__ . $strTargetDirectory . $strDirectorySuffix;
				else
					$strTargetDirectory = $strTargetDirectory . $strDirectorySuffix;

				// Create Directory (if needed)
				if (!is_dir($strTargetDirectory))
					if (!QApplication::MakeDirectory($strTargetDirectory, 0777))
						throw new Exception('Unable to mkdir ' . $strTargetDirectory);

				// Save to Disk
				$strFilePath = sprintf('%s/%s', $strTargetDirectory, $strTargetFileName);
				if ($blnOverwriteFlag || (!file_exists($strFilePath))) {
					$intBytesSaved = file_put_contents($strFilePath, $strTemplate);

					$this->setGeneratedFilePermissions($strFilePath);
					return ($intBytesSaved == strlen($strTemplate));
				} else
					// Because we are not supposed to overwrite, we should return "true" by default
					return true;
			}

			// Why Did We Not Save?
			if ($blnSave) {
				// We WANT to Save, but QCubed Configuration says that this functionality/feature should no longer be generated
				// By definition, we should return "true"
				return true;
			}
			// Running GenerateFile() specifically asking it not to save -- so return the evaluated template instead
			return $strTemplate;
		}

		/**
		 * Sets the file permissions (Linux only) for a file generated by the Code Generator
		 * @param string $strFilePath Path of the generated file
		 *
		 * @throws QCallerException
		 */
		protected function setGeneratedFilePermissions($strFilePath) {
			// CHMOD to full read/write permissions (applicable only to nonwindows)
			// Need to ignore error handling for this call just in case
			if (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
				QApplication::SetErrorHandler(null);
				chmod($strFilePath, 0666);
				QApplication::RestoreErrorHandler();
			}
		}

		/**
		 * Returns the evaluated PHP
		 *
		 * @param $strFilename
		 * @param $mixArgumentArray
		 * @param null $templateSettings
		 * @return mixed|string
		 */
		protected function EvaluatePHP($strFilename, $mixArgumentArray, &$templateSettings = null)  {
			// Get all the arguments and set them locally
			if ($mixArgumentArray) foreach ($mixArgumentArray as $strName=>$mixValue) {
				$$strName = $mixValue;
			}
			global $_TEMPLATE_SETTINGS;
			unset($_TEMPLATE_SETTINGS);
			$_TEMPLATE_SETTINGS = null;

			// Of course, we also need to locally allow "objCodeGen"
			$objCodeGen = $this;

			// Get Database Escape Identifiers
			$strEscapeIdentifierBegin = QApplication::$Database[$this->intDatabaseIndex]->EscapeIdentifierBegin;
			$strEscapeIdentifierEnd = QApplication::$Database[$this->intDatabaseIndex]->EscapeIdentifierEnd;

			// Store the Output Buffer locally
			$strAlreadyRendered = ob_get_contents();

			if (ob_get_level()) ob_clean();
			ob_start();
			include($strFilename);
			$strTemplate = ob_get_contents();
			ob_end_clean();

			$templateSettings = $_TEMPLATE_SETTINGS;
			unset($_TEMPLATE_SETTINGS);

			// Restore the output buffer and return evaluated template
			print($strAlreadyRendered);

			// Remove all \r from the template (for Win/*nix compatibility)
			$strTemplate = str_replace("\r", '', $strTemplate);
			return $strTemplate;
		}

		///////////////////////
		// COMMONLY OVERRIDDEN CONVERSION FUNCTIONS
		///////////////////////

		/**
		 * Given a table name, returns the name of the class for the corresponding model object.
		 *
		 * @param string $strTableName
		 * @return string
		 */
		protected function ModelClassName($strTableName) {
			$strTableName = $this->StripPrefixFromTable($strTableName);
			return sprintf('%s%s%s',
				$this->strClassPrefix,
				QConvertNotation::CamelCaseFromUnderscore($strTableName),
				$this->strClassSuffix);
		}

		/**
		 * Given a table name, returns a variable name that will be used to represent the corresponding model object.
		 * @param string $strTableName
		 * @return string
		 */
		public function ModelVariableName($strTableName) {
			$strTableName = $this->StripPrefixFromTable($strTableName);
			return QConvertNotation::PrefixFromType(QType::Object) .
			QConvertNotation::CamelCaseFromUnderscore($strTableName);
		}

		/**
		 * Given a table name, returns the variable name that will be used to refer to the object in a
		 * reverse reference context (many-to-one).
		 * @param string $strTableName
		 * @return string
		 */
		protected function ModelReverseReferenceVariableName($strTableName) {
			$strTableName = $this->StripPrefixFromTable($strTableName);
			return $this->ModelVariableName($strTableName);
		}

		/**
		 * Given a table name, returns the variable type of the object in a
		 * reverse reference context (many-to-one).
		 * @param $strTableName
		 * @return string
		 */
		protected function ModelReverseReferenceVariableType($strTableName) {
			$strTableName = $this->StripPrefixFromTable($strTableName);
			return $this->ModelClassName($strTableName);
		}


		/**
		 * Given a column, returns the name of the variable used to represent the column's value inside
		 * the model object.
		 *
		 * @param QSqlColumn $objColumn
		 * @return string
		 */
		protected function ModelColumnVariableName(QSqlColumn $objColumn) {
			return QConvertNotation::PrefixFromType($objColumn->VariableType) .
				QConvertNotation::CamelCaseFromUnderscore($objColumn->Name);
		}

		/**
		 * Return the name of the property corresponding to the given column name as used in the getter and setter of
		 * the model object.
		 * @param string $strColumnName
		 * @return string
		 */
		protected function ModelColumnPropertyName($strColumnName) {
			return QConvertNotation::CamelCaseFromUnderscore($strColumnName);
		}

		/**
		 * Return the name of the property corresponding to the given column name as used in the getter and setter of
		 * a Type object.
		 * @param string $strColumnName Column name
		 * @return string
		 */
		protected function TypeColumnPropertyName($strColumnName) {
			return QConvertNotation::CamelCaseFromUnderscore($strColumnName);
		}

		/**
		 * Given the name of a column that is a foreign key to another table, returns a kind of
		 * virtual column name that would refer to the object pointed to. This new name is used to refer to the object
		 * version of the column by json and other encodings, and derivatives
		 * of this name are used to represent a variable and property name that refers to this object that will get stored
		 * in the model.
		 *
		 * @param string $strColumnName
		 * @return string
		 */
		protected function ModelReferenceColumnName($strColumnName) {
			$intNameLength = strlen($strColumnName);

			// Does the column name for this reference column end in "_id"?
			if (($intNameLength > 3) && (strtolower(substr($strColumnName, $intNameLength - 3)) == "_id")) {
				// It ends in "_id" but we don't want to include the "Id" suffix
				// in the Variable Name.  So remove it.
				$strColumnName = substr($strColumnName, 0, $intNameLength - 3);
			} else {
				// Otherwise, let's add "_object" so that we don't confuse this variable name
				// from the variable that was mapped from the physical database
				// E.g., if it's a numeric FK, and the column is defined as "person INT",
				// there will end up being two variables, one for the Person id integer, and
				// one for the Person object itself.  We'll add Object to the name of the Person object
				// to make this deliniation.
				$strColumnName = sprintf("%s_object", $strColumnName);
			}

			return $strColumnName;
		}

		/**
		 * Given a column name to a foreign key, returns the name of the variable that will represent the foreign object
		 * stored in the model.
		 *
		 * @param string $strColumnName
		 * @return string
		 */
		protected function ModelReferenceVariableName($strColumnName) {
			$strColumnName = $this->ModelReferenceColumnName($strColumnName);
			return QConvertNotation::PrefixFromType(QType::Object) .
				QConvertNotation::CamelCaseFromUnderscore($strColumnName);
		}

		/**
		 * Given a column name to a foreign key, returns the name of the property that will be used in the getter and setter
		 * to represent the foreign object stored in the model.
		 *
		 * @param string $strColumnName
		 * @return string
		 */
		protected function ModelReferencePropertyName($strColumnName) {
			$strColumnName = $this->ModelReferenceColumnName($strColumnName);
			return QConvertNotation::CamelCaseFromUnderscore($strColumnName);
		}

		protected function ParameterCleanupFromColumn(QSqlColumn $objColumn, $blnIncludeEquality = false) {
			if ($blnIncludeEquality)
				return sprintf('$%s = $objDatabase->SqlVariable($%s, true);',
					$objColumn->VariableName, $objColumn->VariableName);
			else
				return sprintf('$%s = $objDatabase->SqlVariable($%s);',
					$objColumn->VariableName, $objColumn->VariableName);
		}

		// To be used to list the columns as input parameters, or as parameters for sprintf
		protected function ParameterListFromColumnArray($objColumnArray) {
			return $this->ImplodeObjectArray(', ', '$', '', 'VariableName', $objColumnArray);
		}

		protected function ImplodeObjectArray($strGlue, $strPrefix, $strSuffix, $strProperty, $objArrayToImplode) {
			$strArrayToReturn = array();
			if ($objArrayToImplode) foreach ($objArrayToImplode as $objObject) {
				array_push($strArrayToReturn, sprintf('%s%s%s', $strPrefix, $objObject->__get($strProperty), $strSuffix));
			}

			return implode($strGlue, $strArrayToReturn);
		}

		protected function TypeTokenFromTypeName($strName) {
			$strToReturn = '';
			for($intIndex = 0; $intIndex < strlen($strName); $intIndex++)
				if (((ord($strName[$intIndex]) >= ord('a')) &&
					 (ord($strName[$intIndex]) <= ord('z'))) ||
					((ord($strName[$intIndex]) >= ord('A')) &&
					 (ord($strName[$intIndex]) <= ord('Z'))) ||
					((ord($strName[$intIndex]) >= ord('0')) &&
					 (ord($strName[$intIndex]) <= ord('9'))) ||
					($strName[$intIndex] == '_'))
					$strToReturn .= $strName[$intIndex];

			if (is_numeric(QString::FirstCharacter($strToReturn)))
				$strToReturn = '_' . $strToReturn;
			return $strToReturn;
		}

		/**
		 * Returns the control label name as used in the ModelConnector corresponding to this column or table.
		 *
		 * @param QSqlColumn|QReverseReference|QManyToManyReference $objColumn
		 *
		 * @return string
		 */
		public static function ModelConnectorControlName ($objColumn) {
			if (($o = $objColumn->Options) && isset ($o['Name'])) { // Did developer default?
				return $o['Name'];
			}
			return QConvertNotation::WordsFromCamelCase(QCodeGen::ModelConnectorPropertyName($objColumn));
		}

		/**
		 * The property name used in the ModelConnector for the given column, virtual column or table
		 *
		 * @param QSqlColumn|QReverseReference|QManyToManyReference $objColumn
		 *
		 * @return string
		 * @throws Exception
		 */
		public static function ModelConnectorPropertyName ($objColumn) {
			if ($objColumn instanceof QSqlColumn) {
				if ($objColumn->Reference) {
					return $objColumn->Reference->PropertyName;
				} else {
					return $objColumn->PropertyName;
				}
			}
			elseif ($objColumn instanceof QReverseReference) {
				if ($objColumn->Unique) {
					return ($objColumn->ObjectDescription);
				}
				else {
					return ($objColumn->ObjectDescriptionPlural);
				}
			}
			elseif ($objColumn instanceof QManyToManyReference) {
				return $objColumn->ObjectDescriptionPlural;
			}
			else {
				throw new Exception ('Unknown column type.');
			}
		}

		/**
		 * Return a variable name corresponding to the given column, including virtual columns like
		 * QReverseReference and QManyToMany references.
		 * @param QSqlColumn|QReverseReference|QManyToManyReference $objColumn
		 * @return string
		 */
		public function ModelConnectorVariableName($objColumn) {
			$strPropName = static::ModelConnectorPropertyName($objColumn);
			$objControlHelper = $this->GetControlCodeGenerator($objColumn);
			return $objControlHelper->VarName ($strPropName);
		}

		/**
		 * Returns a variable name for the "label" version of a control, which would be the read-only version
		 * of viewing the data in the column.
		 * @param QSqlColumn|QReverseReference|QManyToManyReference $objColumn
		 * @return string
		 */
		public function ModelConnectorLabelVariableName($objColumn) {
			$strPropName = static::ModelConnectorPropertyName($objColumn);
			return QLabel_CodeGenerator::Instance()->VarName($strPropName);
		}

		/**
		 * Returns the class for the control that will be created to edit the given column,
		 * including the 'virtual' columns of reverse references (many to one) and many-to-many references.
		 *
		 * @param QSqlColumn|QReverseReference|QManyToManyReference $objColumn
		 *
		 * @return string Class name of control which can handle this column's data
		 * @throws Exception
		 */
		protected function ModelConnectorControlClass($objColumn) {

			// Is the class specified by the developer?
			if ($o = $objColumn->Options) {
				if (isset ($o['FormGen']) && $o['FormGen'] == QFormGen::LabelOnly) {
					return 'QLabel';
				}
				if (isset($o['ControlClass'])) {
					return $o['ControlClass'];
				}
			}

			// otherwise, return the default class based on the column
			if ($objColumn instanceof QSqlColumn) {
				if ($objColumn->Identity)
					return 'QLabel';

				if ($objColumn->Timestamp)
					return 'QLabel';

				if ($objColumn->Reference)
					return 'QListBox';

				switch ($objColumn->VariableType) {
					case QType::Boolean:
						return 'QCheckBox';
					case QType::DateTime:
						return 'QDateTimePicker';
					case QType::Integer:
						return 'QIntegerTextBox';
					case QType::Float:
						return 'QFloatTextBox';
					default:
						return 'QTextBox';
				}
			}
			elseif ($objColumn instanceof QReverseReference) {
				if ($objColumn->Unique) {
					return 'QListBox';
				} else {
					return 'QCheckBoxList';	// for multi-selection
				}
			}
			elseif ($objColumn instanceof QManyToManyReference) {
				return 'QCheckBoxList';	// for multi-selection
			}
			throw new Exception('Unknown column type.');
		}


		public function DataListControlClass (QSqlTable $objTable) {
			// Is the class specified by the developer?
			if ($o = $objTable->Options) {
				if (isset($o['ControlClass'])) {
					return $o['ControlClass'];
				}
			}

			// Otherwise, return a default
			return 'QDataGrid';
		}

		/**
		 * Returns the control label name as used in the data list panel corresponding to this column.
		 *
		 * @param QSqlTable $objTable
		 *
		 * @return string
		 */
		public static function DataListControlName (QSqlTable $objTable) {
			if (($o = $objTable->Options) && isset ($o['Name'])) { // Did developer default?
				return $o['Name'];
			}
			return QConvertNotation::WordsFromCamelCase($objTable->ClassNamePlural);
		}

		/**
		 * Returns the name of an item in the data list as will be displayed in the edit panel.
		 *
		 * @param QSqlTable $objTable
		 *
		 * @return string
		 */
		public static function DataListItemName (QSqlTable $objTable) {
			if (($o = $objTable->Options) && isset ($o['ItemName'])) { // Did developer override?
				return $o['ItemName'];
			}
			return QConvertNotation::WordsFromCamelCase($objTable->ClassName);
		}

		public function DataListVarName (QSqlTable $objTable) {
			$strPropName = self::DataListPropertyNamePlural($objTable);
			$objControlHelper = $this->GetDataListCodeGenerator($objTable);
			return $objControlHelper->VarName($strPropName);
		}

		public static function DataListPropertyName (QSqlTable $objTable) {
			return $objTable->ClassName;
		}

		public static function DataListPropertyNamePlural (QSqlTable $objTable) {
			return $objTable->ClassNamePlural;
		}


		/**
		 * Returns the class for the control that will be created to edit the given column,
		 * including the 'virtual' columns of reverse references (many to one) and many-to-many references.
		 *
		 * @param QSqlColumn|QReverseReference|QManyToManyReference $objColumn
		 *
		 * @return AbstractControl_CodeGenerator helper object
		 * @throws Exception
		 */
		public function GetControlCodeGenerator($objColumn) {
			$strControlClass = $this->ModelConnectorControlClass($objColumn);

			if (method_exists($strControlClass, 'GetCodeGenerator')) {
				return call_user_func($strControlClass.'::GetCodeGenerator');
			}

			switch ($strControlClass) {
				case 'QLabel': return QLabel_CodeGenerator::Instance();
				case 'QListBox': return new QListBox_CodeGenerator();
				case 'QCheckBox': return new QCheckBox_CodeGenerator();
				case 'QDateTimePicker': return new QDateTimePicker_CodeGenerator();
				case 'QTextBox': return new QTextBox_CodeGenerator();
				case 'QIntegerTextBox': return new QIntegerTextBox_CodeGenerator();
				case 'QFloatTextBox': return new QFloatTextBox_CodeGenerator();
				case 'QCheckBoxList': return new QCheckBoxList_CodeGenerator();
				default: break;
			}

			$strOrigControlClass = $strControlClass;
			$strControlCodeGeneratorClass = $strControlClass .'_CodeGenerator';
			while (!class_exists($strControlCodeGeneratorClass)) {
				$strControlClass = get_parent_class($strControlClass);
				if ($strControlClass === 'QControl') {
					throw new QCallerException("Cannot find an appropriate subclass of AbstractControl_CodeGenerator for ".$strOrigControlClass);
				}
				$strControlCodeGeneratorClass = $strControlClass .'_CodeGenerator';
			}
			return new $strControlCodeGeneratorClass($strOrigControlClass);
		}

		public function GetDataListCodeGenerator($objTable) {
			$strControlClass = $this->DataListControlClass($objTable);

			if (method_exists($strControlClass, 'GetCodeGenerator')) {
				return call_user_func($strControlClass.'::GetCodeGenerator');
			}

			return new QDataGrid_CodeGenerator();
		}


		protected function CalculateObjectMemberVariable($strTableName, $strColumnName, $strReferencedTableName) {
			return sprintf('%s%s%s%s',
				QConvertNotation::PrefixFromType(QType::Object),
				$this->strAssociatedObjectPrefix,
				$this->CalculateObjectDescription($strTableName, $strColumnName, $strReferencedTableName, false),
				$this->strAssociatedObjectSuffix);
		}

		protected function CalculateObjectPropertyName($strTableName, $strColumnName, $strReferencedTableName) {
			return sprintf('%s%s%s',
				$this->strAssociatedObjectPrefix,
				$this->CalculateObjectDescription($strTableName, $strColumnName, $strReferencedTableName, false),
				$this->strAssociatedObjectSuffix);
		}

		// TODO: These functions need to be documented heavily with information from "lexical analysis on fk names.txt"
		protected function CalculateObjectDescription($strTableName, $strColumnName, $strReferencedTableName, $blnPluralize) {
			// Strip Prefixes (if applicable)
			$strTableName = $this->StripPrefixFromTable($strTableName);
			$strReferencedTableName = $this->StripPrefixFromTable($strReferencedTableName);

			// Starting Point
			$strToReturn = QConvertNotation::CamelCaseFromUnderscore($strTableName);

			if ($blnPluralize)
				$strToReturn = $this->Pluralize($strToReturn);

			if ($strTableName == $strReferencedTableName) {
				// Self-referencing Reference to Describe

				// If Column Name is only the name of the referenced table, or the name of the referenced table with "_id",
				// then the object description is simply based off the table name.
				if (($strColumnName == $strReferencedTableName) ||
					($strColumnName == $strReferencedTableName . '_id'))
					return sprintf('Child%s', $strToReturn);

				// Rip out trailing "_id" if applicable
				$intLength = strlen($strColumnName);
				if (($intLength > 3) && (substr($strColumnName, $intLength - 3) == "_id"))
					$strColumnName = substr($strColumnName, 0, $intLength - 3);

				// Rip out the referenced table name from the column name
				$strColumnName = str_replace($strReferencedTableName, "", $strColumnName);

				// Change any double "_" to single "_"
				$strColumnName = str_replace("__", "_", $strColumnName);
				$strColumnName = str_replace("__", "_", $strColumnName);

				$strColumnName = QConvertNotation::CamelCaseFromUnderscore($strColumnName);

				// Special case for Parent/Child
				if ($strColumnName == 'Parent')
					return sprintf('Child%s', $strToReturn);

				return sprintf("%sAs%s",
					$strToReturn, $strColumnName);

			} else {
				// If Column Name is only the name of the referenced table, or the name of the referenced table with "_id",
				// then the object description is simply based off the table name.
				if (($strColumnName == $strReferencedTableName) ||
					($strColumnName == $strReferencedTableName . '_id'))
					return $strToReturn;

				// Rip out trailing "_id" if applicable
				$intLength = strlen($strColumnName);
				if (($intLength > 3) && (substr($strColumnName, $intLength - 3) == "_id"))
					$strColumnName = substr($strColumnName, 0, $intLength - 3);

				// Rip out the referenced table name from the column name
				$strColumnName = str_replace($strReferencedTableName, "", $strColumnName);

				// Change any double "_" to single "_"
				$strColumnName = str_replace("__", "_", $strColumnName);
				$strColumnName = str_replace("__", "_", $strColumnName);

				return sprintf("%sAs%s",
					$strToReturn,
					QConvertNotation::CamelCaseFromUnderscore($strColumnName));
			}
		}

		// this is called for ReverseReference Object Descriptions for association tables (many-to-many)
		protected function CalculateObjectDescriptionForAssociation($strAssociationTableName, $strTableName, $strReferencedTableName, $blnPluralize) {
			// Strip Prefixes (if applicable)
			$strTableName = $this->StripPrefixFromTable($strTableName);
			$strAssociationTableName = $this->StripPrefixFromTable($strAssociationTableName);
			$strReferencedTableName = $this->StripPrefixFromTable($strReferencedTableName);

			// Starting Point
			$strToReturn = QConvertNotation::CamelCaseFromUnderscore($strReferencedTableName);

			if ($blnPluralize)
				$strToReturn = $this->Pluralize($strToReturn);

			// Let's start with strAssociationTableName

			// Rip out trailing "_assn" if applicable
			$strAssociationTableName = str_replace($this->strAssociationTableSuffix, '', $strAssociationTableName);

			// remove instances of the table names in the association table name
			$strTableName2 = str_replace('_', '', $strTableName); // remove underscores if they are there
			$strReferencedTableName2 = str_replace('_', '', $strReferencedTableName); // remove underscores if they are there

			if (beginsWith ($strAssociationTableName, $strTableName . '_')) {
				$strAssociationTableName = trimOffFront ($strTableName . '_', $strAssociationTableName);
			} elseif (beginsWith ($strAssociationTableName, $strTableName2 . '_')) {
				$strAssociationTableName = trimOffFront ($strTableName2 . '_', $strAssociationTableName);
			} elseif (beginsWith ($strAssociationTableName, $strReferencedTableName . '_')) {
				$strAssociationTableName = trimOffFront ($strReferencedTableName . '_', $strAssociationTableName);
			} elseif (beginsWith ($strAssociationTableName, $strReferencedTableName2 . '_')) {
				$strAssociationTableName = trimOffFront ($strReferencedTableName2 . '_', $strAssociationTableName);
			} elseif ($strAssociationTableName == $strTableName ||
					$strAssociationTableName == $strTableName2 ||
					$strAssociationTableName == $strReferencedTableName ||
					$strAssociationTableName == $strReferencedTableName2) {
				$strAssociationTableName = "";
			}

			if (endsWith ($strAssociationTableName,  '_' . $strTableName)) {
				$strAssociationTableName = trimOffEnd ('_' . $strTableName, $strAssociationTableName);
			} elseif (endsWith ($strAssociationTableName, '_' . $strTableName2)) {
				$strAssociationTableName = trimOffEnd ('_' . $strTableName2, $strAssociationTableName);
			} elseif (endsWith ($strAssociationTableName,  '_' . $strReferencedTableName)) {
				$strAssociationTableName = trimOffEnd ('_' . $strReferencedTableName, $strAssociationTableName);
			} elseif (endsWith ($strAssociationTableName, '_' . $strReferencedTableName2)) {
				$strAssociationTableName = trimOffEnd ('_' . $strReferencedTableName2, $strAssociationTableName);
			} elseif ($strAssociationTableName == $strTableName ||
					$strAssociationTableName == $strTableName2 ||
					$strAssociationTableName == $strReferencedTableName ||
					$strAssociationTableName == $strReferencedTableName2) {
				$strAssociationTableName = "";
			}

			// Change any double "__" to single "_"
			$strAssociationTableName = str_replace("__", "_", $strAssociationTableName);
			$strAssociationTableName = str_replace("__", "_", $strAssociationTableName);
			$strAssociationTableName = str_replace("__", "_", $strAssociationTableName);

			// If we have nothing left or just a single "_" in AssociationTableName, return "Starting Point"
			if (($strAssociationTableName == "_") || ($strAssociationTableName == ""))
				return sprintf("%s%s%s",
					$this->strAssociatedObjectPrefix,
					$strToReturn,
					$this->strAssociatedObjectSuffix);

			// Otherwise, add "As" and the predicate
			return sprintf("%s%sAs%s%s",
				$this->strAssociatedObjectPrefix,
				$strToReturn,
				QConvertNotation::CamelCaseFromUnderscore($strAssociationTableName),
				$this->strAssociatedObjectSuffix);
		}

		// This is called by AnalyzeAssociationTable to calculate the GraphPrefixArray for a self-referencing association table (e.g. directed graph)
		protected function CalculateGraphPrefixArray($objForeignKeyArray) {
			// Analyze Column Names to determine GraphPrefixArray
			if ((strpos(strtolower($objForeignKeyArray[0]->ColumnNameArray[0]), 'parent') !== false) ||
				(strpos(strtolower($objForeignKeyArray[1]->ColumnNameArray[0]), 'child') !== false)) {
				$strGraphPrefixArray[0] = '';
				$strGraphPrefixArray[1] = 'Parent';
			} else if ((strpos(strtolower($objForeignKeyArray[0]->ColumnNameArray[0]), 'child') !== false) ||
						(strpos(strtolower($objForeignKeyArray[1]->ColumnNameArray[0]), 'parent') !== false)) {
				$strGraphPrefixArray[0] = 'Parent';
				$strGraphPrefixArray[1] = '';
			} else {
				// Use Default Prefixing for Graphs
				$strGraphPrefixArray[0] = 'Parent';
				$strGraphPrefixArray[1] = '';
			}

			return $strGraphPrefixArray;
		}

		/**
		 * Returns the variable type corresponding to the database column type
		 * @param string $strDbType
		 * @return string
		 * @throws Exception
		 */
		protected function VariableTypeFromDbType($strDbType) {
			switch ($strDbType) {
				case QDatabaseFieldType::Bit:
					return QType::Boolean;
				case QDatabaseFieldType::Blob:
					return QType::String;
				case QDatabaseFieldType::Char:
					return QType::String;
				case QDatabaseFieldType::Date:
					return QType::DateTime;
				case QDatabaseFieldType::DateTime:
					return QType::DateTime;
				case QDatabaseFieldType::Float:
					return QType::Float;
				case QDatabaseFieldType::Integer:
					return QType::Integer;
				case QDatabaseFieldType::Time:
					return QType::DateTime;
				case QDatabaseFieldType::VarChar:
					return QType::String;
				case QDatabaseFieldType::Json:
					return QType::String;
				default:
					throw new Exception("Invalid Db Type to Convert: $strDbType");
			}
		}

		/**
		 * Return the plural of the given name. Override this and return the plural version of particular names
		 * if this generic version isn't working for you.
		 *
		 * @param string $strName
		 * @return string
		 */
		protected function Pluralize($strName) {
			// Special Rules go Here
			switch (true) {
				case (strtolower($strName) == 'play'):
					return $strName . 's';
			}

			$intLength = strlen($strName);
			if (substr($strName, $intLength - 1) == "y")
				return substr($strName, 0, $intLength - 1) . "ies";
			if (substr($strName, $intLength - 1) == "s")
				return $strName . "es";
			if (substr($strName, $intLength - 1) == "x")
				return $strName . "es";
			if (substr($strName, $intLength - 1) == "z")
				return $strName . "zes";
			if (substr($strName, $intLength - 2) == "sh")
				return $strName . "es";
			if (substr($strName, $intLength - 2) == "ch")
				return $strName . "es";

			return $strName . "s";
		}

		public function ReportError ($strError) {
			$this->strErrors .= $strError . "\r\n";
		}

		////////////////////
		// Public Overriders
		////////////////////

		/**
		 * Override method to perform a property "Get"
		 * This will get the value of $strName
		 *
		 * @param string $strName
		 *
		 * @throws Exception|QCallerException
		 * @return mixed
		 */
		public function __get($strName) {
			switch ($strName) {
				case 'Errors':
					return $this->strErrors;
				case 'Warnings':
					return $this->strWarnings;
				default:
					try {
						return parent::__get($strName);
					} catch (QCallerException $objExc) {
						$objExc->IncrementOffset();
						throw $objExc;
					}
			}
		}

		/**
		 * PHP magic method to set class properties
		 * @param string $strName
		 * @param string $mixValue
		 *
		 * @return mixed|void
		 */
		public function __set($strName, $mixValue) {
			try {
				switch($strName) {
					case 'Errors':
						return ($this->strErrors = QType::Cast($mixValue, QType::String));
					case 'Warnings':
						return ($this->strWarnings = QType::Cast($mixValue, QType::String));
					default:
						return parent::__set($strName, $mixValue);
				}
			} catch (QCallerException $objExc) {
				$objExc->IncrementOffset();
			}
		}
	}
