<?php

//================================================================================//
//== Define CLass REAXML_Manager
//================================================================================//
class REAXML_Manager {
	//-- Constants --//
	public const START_MIN_RES1 = 1;
	public const START_MIN_RES2 = 11;
	public const START_MIN_RES3 = 21;
	public const START_MIN_COMM = 31;
	public const START_MIN_LAND = 41;
	public const START_MIN_RENT = 51;
	
	
	//-- Protected variables --//
	protected $sFilePathInput;      //-- Input folder that stores the listing --//
	protected $sFilePathOutput;     //-- Output folder where the inputs are merged in with other from their category --//
	protected $sFilePathArchive;    //-- Old XML Input files get stored here --//
	protected $sFilePathLog;        //-- File path to the Log file --//
	protected $aPropertyTypes       = array( "business", "commercial", "commercialLand", "land", "rental", "holidayRental", "residential", "rural" );
	protected $bDebugging           = false;
	protected $mData                = array();       //-- Data variable for holding d
	protected $mAgents              = array();
	
	//-- Public Variables --//
	public    $aDebugLog            = array();
	public    $mDebugLogSpecial     = array();
	
	
	
	/**
	 * Constructor
	 * @return void
	 */
	public function __construct( $mConfig ) {
		$this->sFilePathInput   = $mConfig['sPathInput'];
		$this->sFilePathOutput  = $mConfig['sPathOutput'];
		$this->sFilePathArchive = $mConfig['sPathArchive'];
		$this->sFilePathLog     = $mConfig['sPathLogFile'];
		$this->mAgents          = $mConfig['mAgents'];
		$this->bDebugging       = $mConfig['bDebugging'];
	}
	

	//================================================================//
	//== ListLookup XML Queue
	//================================================================//
	/**
	 * 
	 * @param string $sParamPropType - The property type to search for. (All if after)
	 * @param bool $bReturnMapByType - If False returns an array / If True returns a map
	 * @throws Exception
	 * @return Array
	 */
	public function LookupListInputQueue( $sParamPropType="All", $bReturnMapByType=false ) {
		//------------------------------------------------------------//
		//-- 1.0 - Validate parameters and Declare variables
		//------------------------------------------------------------//
		
		//--------------------------------------------//
		//-- 1.1 - Validate Parameter
		//--------------------------------------------//
		if( $sParamPropType!=="All" ) {
			$bValidPropType = in_array( $sParamPropType, $this->aPropertyTypes );
			
			if( $bValidPropType===false ) {
				throw new \Exception("Requested property type is not valid.");
			}
		}
		
		//--------------------------------------------//
		//-- 1.3 - Declare Variables and constants
		//--------------------------------------------//
		$iFileIndex     = 0;
		$aResults       = array();

		if( $bReturnMapByType===true ) {
			$aResults = array( "residential"=>array(), "commercial"=>array(), "land"=>array(), "rental"=>array() );
		}

		//------------------------------------------------------------//
		//-- FileList
		//------------------------------------------------------------//
		//-- Lookup File List  -- //
		$aFileListXML = $this->getFileList( $this->sFilePathInput, 'xml', false );

		//-- Foreach File --//
		foreach( $aFileListXML as $sXMLFilePath ) {
			$bXMLEntryFound = false;
			$sXMLFileName   = basename( $sXMLFilePath );
			//------------------------------------------------------------//
			//-- Load the file
			//------------------------------------------------------------//
			$this->LogDebugAdd( "Load File: '".$sXMLFilePath."' " );
			
			//$this->ExtractPropertiesFromFile( $sFileXML, $iFileIndex );
			$oXMLReader = new XMLReader();
			try {
				//var_dump( $sXMLFilePath );
				$file_contents = $this->fixContent( file_get_contents( $sXMLFilePath ) );
				$oXMLReader->XML($file_contents);
			} catch (\Exception $e) {
				throw new Exception( "Failed to parse file at '".$sXMLFilePath."'. " );
			}
			
			//------------------------------------------------------------//
			//-- Parse the XML contents of the file
			//------------------------------------------------------------//
			$iFileIndexSub = 0;
			while( $oXMLReader->read() ) {
				if( $oXMLReader->nodeType == XMLReader::ELEMENT && in_array( $oXMLReader->localName, $this->aPropertyTypes ) ) {
					//-- Create Debug String --//
					$sDebugName       = ($iFileIndex+1)."-".($iFileIndexSub+1);
					
					//--------------------------------------------//
					//-- Extract
					//--------------------------------------------//
					$sPropType = $oXMLReader->localName;
					
					if( $sParamPropType!=="All" && $sPropType!==$sParamPropType ) {
						$this->LogDebugAdd( " - '".$sDebugName."' skipping due to it not matching the desired type!" );
						continue;
					}
					
					$oNodeExtracted   = $oXMLReader->expand();
					$sUniqId          = $oNodeExtracted->getElementsByTagName("uniqueID")->item(0)->nodeValue;

					$oAddressNum      = $oNodeExtracted->getElementsByTagName("streetNumber");
					$oAddressStreet   = $oNodeExtracted->getElementsByTagName("street");
					$oAddressSuburb   = $oNodeExtracted->getElementsByTagName("suburb");

					if( !( $oAddressSuburb->length >= 1 ) ) {
						$this->LogDebugAdd( " - '".$sDebugName."' skipping due to no suburb!" );
						continue;
					} else {
						$sAddressSuburb   = $oAddressSuburb->item(0)->nodeValue;
					}
					if( $oAddressNum->length    >= 1 ) { $sAddressNum      = $oAddressNum->item(0)->nodeValue; }
					if( $oAddressStreet->length >= 1 ) { $sAddressStreet   = $oAddressStreet->item(0)->nodeValue; }



					$sModTime         = $oXMLReader->getAttribute('modTime');
					$oModTime         = DateTime::createFromFormat( "Y-m-d-H:i:s", $sModTime );
					$sModTimeUnixTS   = $oModTime->format("U");
					$sStatus          = $oXMLReader->getAttribute('status');
					
					//-- Create Strings from Base Variables --//
					$sAddress         = $sAddressNum." ".$sAddressStreet.", ".$sAddressSuburb;
					
					//-- Debugging --//
					$this->LogDebugAdd( " - '".$sDebugName."' Found property with the UniqueId '".$sUniqId."' that has a type of '".$sPropType."'. (".$sAddress.")" );
					
					//-- Image Count --//
					$iImageCount = 0;
					$oImages     = $oNodeExtracted->getElementsByTagName("img");
					foreach( $oImages as $oImage ) {
						$sImgURL = $oImage->getAttribute('url');
						if( is_string($sImgURL) ) {
							if( strlen( trim( $sImgURL ) ) >=1 ) {
								$iImageCount++;
							}
						}
					}
					//$iImageCount = $oImages->count();
					
					//--------------------------------------------//
					//-- Store the Property
					//--------------------------------------------//
					if( $bReturnMapByType===false ) {
						$aResults[]       = array( "Type"=>$sPropType, "UniqId"=>$sUniqId, "Address"=>$sAddress, "Status"=>$sStatus, "ModTime"=>$sModTime, "ModTimeUnixTS"=>$sModTimeUnixTS, "ImageCount"=>$iImageCount, "FileName"=>$sXMLFileName );
					} else {
						$sPropTypeLowerCase = strtolower( $sPropType );
						//-- Create the Property Type if not found --//
						if( !isset( $aResults[$sPropTypeLowerCase] ) ) { $aResults[$sPropTypeLowerCase] = array(); }
						$aResults[$sPropTypeLowerCase][] = array( "Type"=>$sPropType, "UniqId"=>$sUniqId, "Address"=>$sAddress, "Status"=>$sStatus, "ModTime"=>$sModTime, "ModTimeUnixTS"=>$sModTimeUnixTS, "ImageCount"=>$iImageCount, "FileName"=>$sXMLFileName );
					}
					
					//--  --//
					$iFileIndexSub++;
					unset( $oImages );
					unset( $oNodeExtracted );
					
				}
			}    //-- EndWhile Reading the Contents of the XML file --//
		}    //-- EndForeach XMLFilePath in List --//
		
		//------------------------------------------------------------//
		//-- Return Results
		//------------------------------------------------------------//
		return $aResults;
	}    //-- EndFunction LookupListInputQueue (public) --//


	//================================================================//
	//== Archive Duplicates
	//================================================================//
	/**
	 * @param bool $bArchive - FALSE=read-only; TRUE=archive duplicates; Defaults to FALSE (ReadOnly)
	 * @return array[]
	 * @throws Exception
	 */
	public function ArchiveDuplicateInputs( $bArchive=false ) {
		//------------------------------------//
		//-- Setup Variables
		//------------------------------------//
		$mData = array( "aUnique"=>array(), "aToArchive"=>array() );
		$mDebugLog = array();
		$bDebugLog = $this->LogDebugEnabled();

		//------------------------------------//
		//-- Lookup File List
		//------------------------------------//
		$aFileListXML = $this->getFileList( $this->sFilePathInput, 'xml', true );


		$iFileIndex  = 0;
		foreach( $aFileListXML as $iKey => $sXMLFilePath ) {
			$bXMLEntryFound = false;
			//------------------------------------------------------------//
			//-- Load the file
			//------------------------------------------------------------//
			$this->LogDebugGroupedAdd( $iKey, "Load File: '".$sXMLFilePath."' " );   //$this->LogDebugAdd( "Load File: '".$sXMLFilePath."' " );

			//$this->ExtractPropertiesFromFile( $sFileXML, $iFileIndex );
			$oXMLReader = new XMLReader();
			try {
				$file_contents = $this->fixContent( file_get_contents( $sXMLFilePath ) );
				$oXMLReader->XML($file_contents);
			} catch (\Exception $e) {
				throw new Exception( "Failed to parse file at '".$sXMLFilePath."'. " );
			}
			//------------------------------------------------------------//
			//-- Parse the XML contents of the file
			//------------------------------------------------------------//
			$iFileIndexSub = 0;
			while( $oXMLReader->read() ) {
				if( $oXMLReader->nodeType == XMLReader::ELEMENT && in_array( $oXMLReader->localName, $this->aPropertyTypes ) ) {
					//-- Create Debug String --//
					$sDebugName       = ($iFileIndex+1)."-".($iFileIndexSub+1);

					//--------------------------------------------//
					//-- Extract
					//--------------------------------------------//
					$sPropType = $oXMLReader->localName;

					$oNodeExtracted   = $oXMLReader->expand();
					$sUniqId          = $oNodeExtracted->getElementsByTagName("uniqueID")->item(0)->nodeValue;
					//$this->LogDebugAdd( " - '".$sDebugName."' Found property with the UniqueId '".$sUniqId."' that has a type of '".$sPropType."'." );
					$this->LogDebugGroupedAdd( $iKey," - '".$sDebugName."' Found property with the UniqueId '".$sUniqId."' that has a type of '".$sPropType."'." );

					//--------------------------------------------//
					//-- Check if Already found
					//--------------------------------------------//
					if( !in_array( $sUniqId, $mData['aUnique'] ) ) {
						//-- Add to the list of found --//
						$mData['aUnique'][] = $sUniqId;
						//$this->LogDebugAdd(  "     - '".$sDebugName."' Found the latest change for '".$sUniqId."' UniqId in the InputQueue. " );
						$this->LogDebugGroupedAdd( $iKey,"     - '".$sDebugName."' Found the latest change for '".$sUniqId."' UniqId in the InputQueue. " );
					} else {
						//$this->LogDebugAdd(  "     - '".$sDebugName."' Found duplicate with the '".$sUniqId."' UniqId in the InputQueue. " );
						$this->LogDebugGroupedAdd( $iKey, "     - '".$sDebugName."' Found duplicate with the '".$sUniqId."' UniqId in the InputQueue. " );
						$mData['aToArchive'][] = array( "sUniqueId"=>$sUniqId, "sFileName"=>$sXMLFilePath, "iDebugKey"=>$iKey, "sDebugKey"=>$sDebugName );
						//continue;
					}

					$bXMLFileFound    = true;
					$bXMLEntryFound   = true;
					$iFileIndexSub++;
					unset( $oNodeExtracted );
				}
			}    //-- XMLReader is reading --//


			//-- Increment the File Index --//
			$iFileIndex++;

			//-- Skip if no valid data is found  --//
			if( $bXMLEntryFound===false ) { continue; }

		}    //-- EndForeach XML File in the List --//


		if( $bArchive===true ) {
			foreach( $mData['aToArchive'] as $mToArchive ) {
				//-- Move File if new location is valid --//
				if( $this->sFilePathArchive===null || empty( $this->sFilePathArchive ) ) {
					//$this->LogDebugAdd( " - Archive Path is not setup." );
					$this->LogDebugGroupedAdd( $mToArchive['iDebugKey'], " - Archive Path is not setup." );
					continue;
				}
				if( !is_writable( $this->sFilePathArchive ) ) {
					//$this->LogDebugAdd( " - Archive Path is not writable." );
					$this->LogDebugGroupedAdd( $mToArchive['iDebugKey'], " - Archive Path is not writable." );
					continue;
				}

				//echo $sFileMove."<br/>\n";
				$sFileName = pathinfo( $mToArchive['sFileName'],  PATHINFO_BASENAME );
				$sFileMove = $this->sFilePathArchive."/".$sFileName;
				if( $sFileMove!==null && strlen($sFileMove)>10 ) {
					rename( $mToArchive['sFileName'], $sFileMove );
					//$this->LogDebugAdd( " - Moved file '".$sFileName."' to xml archive folder." );
					$this->LogDebugGroupedAdd( $mToArchive['iDebugKey'], " - Moved file '".$sFileName."' to xml archive folder." );

					$this->AddToFileLog( "Auto-archive", array( $mToArchive["sUniqueId"], $mToArchive["sFileName"] ) );
				}

			}    //-- EndForeach ToArchive --//
		}


		foreach( $this->mDebugLogSpecial as $sKey => $mLogGroup ) {
			foreach( $mLogGroup as $sLogEntry ) {
				$this->LogDebugAdd( $sLogEntry );
			}
		}
		$this->mDebugLogSpecial = array();

		//------------------------------------------------------------//
		//-- Return Success
		//------------------------------------------------------------//
		return $mData;

	}    //-- EndFunction ArchiveDuplicateInputs (public) --//
	
	//================================================================//
	//== Build XML
	//================================================================//
	/**
	 * Reads through all the files and builds the XML in memory in the mData variable
	 * @param string $sParamPropType -
	 * @return boolean
	 * @throws Exception
	 */
	public function BuildXMLForType( $sParamPropType ) {
		//------------------------------------------------------------//
		//-- 1.0 - Validate parameters and Declare variables
		//------------------------------------------------------------//
		
		//--------------------------------------------//
		//-- 1.1 - Validate Parameter
		//--------------------------------------------//
		$bValidPropType = in_array( $sParamPropType, $this->aPropertyTypes );
		
		if( $bValidPropType===false ) {
			throw new Error("Requested property type is not valid.");
		}
		//--------------------------------------------//
		//-- 1.2 - Declare variables
		//--------------------------------------------//
		//$sValidPropType = false;
		$bFoundValidXML = false;
		$sUniqId        = "Unknown";
		
		//------------------------------------------------------------//
		//-- 
		//------------------------------------------------------------//
		$oXMLDOM = new DomDocument( "1.0", "utf-8" );
		$oXMLDOM->preserveWhiteSpace = false;
		$oXMLDOM->formatOutput       = true;
		$oXMLDOM->loadXML('<propertyList date="'.date("Y-m-d-H:i:s").'" username="" password=""></propertyList>');


		//------------------------------------//
		//-- Lookup File List
		//------------------------------------//
		$aFileListXML = $this->getFileList( $this->sFilePathInput, 'xml', false );


		$iFileIndex     = 0;
		$bXMLFileFound  = false;
		$bXMLEntryFound = false;
		foreach( $aFileListXML as $sXMLFilePath ) {
			$bXMLEntryFound = false;
			//------------------------------------------------------------//
			//-- Load the file
			//------------------------------------------------------------//
			$this->LogDebugAdd(  "Load File: '".$sXMLFilePath."' " );
			
			//$this->ExtractPropertiesFromFile( $sFileXML, $iFileIndex );
			$oXMLReader = new XMLReader();
			try {
				$file_contents = $this->fixContent( file_get_contents( $sXMLFilePath ) );
				$oXMLReader->XML($file_contents);
			} catch (\Exception $e) {
				throw new Exception( "Failed to parse file at '".$sXMLFilePath."'. " );
			}
			//------------------------------------------------------------//
			//-- Parse the XML contents of the file
			//------------------------------------------------------------//
			$iFileIndexSub = 0;
			while( $oXMLReader->read() ) {
				if( $oXMLReader->nodeType == XMLReader::ELEMENT && in_array( $oXMLReader->localName, $this->aPropertyTypes ) ) {
					//-- Create Debug String --//
					$sDebugName       = ($iFileIndex+1)."-".($iFileIndexSub+1);
					
					//--------------------------------------------//
					//-- Extract
					//--------------------------------------------//
					$sPropType = $oXMLReader->localName;
					if( $sPropType!==$sParamPropType ) {
						$this->LogDebugAdd( " - '".$sDebugName."' skipping due to it not matching the desired type!" );
						continue;
					}
					
					$oNodeExtracted   = $oXMLReader->expand();
					$sUniqId          = $oNodeExtracted->getElementsByTagName("uniqueID")->item(0)->nodeValue;
					$this->LogDebugAdd( " - '".$sDebugName."' Found property with the UniqueId '".$sUniqId."' that has a type of '".$sParamPropType."'." );

					//--------------------------------------------//
					//-- Convert XML for Easy Property Listing
					//--------------------------------------------//
					$oNodeNew         = $this->_XMLEasyPropertyListingConvert( $sDebugName, $oNodeExtracted );
					$oNodeNewImported = $oXMLDOM->importNode( $oNodeNew, true );
					$oXMLDOM->documentElement->appendChild( $oNodeNewImported );
					
					
					$bXMLFileFound    = true;
					$bXMLEntryFound   = true;
					$iFileIndexSub++;
					unset( $oNodeExtracted );
				}
			}

			
			//-- Increment the File Index --//
			$iFileIndex++;
			
			//-- Skip if no valid data is found  --//
			if( $bXMLEntryFound===false ) { continue; }


			//------------------------------------------------------------//
			//-- Save the Converted XML File
			//------------------------------------------------------------//
			$sFileOutputPath = $this->sFilePathOutput."/".$sParamPropType.".xml";
			$oXMLDOM->save( $sFileOutputPath );

			//------------------------------------------------------------//
			//-- Archive the file
			//------------------------------------------------------------//
			
			//-- Move File if new location is valid --//
			if( $this->sFilePathArchive!==null && is_writable( $this->sFilePathArchive ) ) {
				//echo $sFileMove."<br/>\n";
				$sFileName = pathinfo( $sXMLFilePath,  PATHINFO_BASENAME );
				$sFileMove = $this->sFilePathArchive."/".$sFileName;
				
				if( $sFileMove!==null && strlen($sFileMove)>10 ) {
					rename( $sXMLFilePath, $sFileMove );
					$this->LogDebugAdd( " - Moved file '".$sFileName."' to xml archive folder." );
					$this->AddToFileLog( "ArchiveNormal", array( $sUniqId, $sXMLFilePath ) );
				}
			}
			
			//------------------------------------------------------------//
			//-- Return Success
			//------------------------------------------------------------//
			return true;
			
		}    //-- EndForeach XML File in the List --//
		
		
		//------------------------------------------------------------//
		//-- Save Blank XML File and Return Failure
		//------------------------------------------------------------//
		$sFileOutputPath = $this->sFilePathOutput."/".$sParamPropType.".xml";
		$oXMLDOM->save( $sFileOutputPath );
		
		return false;

	}    //-- EndFunction BuildXML --//
	
	
	//================================================================//
	//== Build XML All
	//================================================================//
	public function BuildXML_All() {
		//--------------------------------------------//
		//-- Foreach XML File
		//--------------------------------------------//
		$aFileListXML = $this->getFileList( $this->sFilePathInput, 'xml', true );
		foreach( $this->aPropertyTypes as $sPropType ) {
			$this->mData[$sPropType] = array(
				"aParsed" => array(),
				"oDOM"    => new DomDocument( "1.0", "utf-8" )
			);
			//$this->mData[$sPropType]['oDom'] = new DomDocument();
			
			
			$this->mData[$sPropType]['oDOM']->preserveWhiteSpace = false;
			$this->mData[$sPropType]['oDOM']->formatOutput = true;
			$this->mData[$sPropType]['oDOM']->loadXML('<propertyList date="'.date("Y-m-d-H:i:s").'" username="" password=""></propertyList>');
			//$this->mPropertyTypes[$sPropType]->createElement('propertyList');
		}
		
		//--------------------------------------------//
		//-- Build New XML Files
		//--------------------------------------------//
		$iFileIndex = 0;
		foreach( $aFileListXML as $sFileXML ) {
			$this->LogDebugAdd( "Load File: '".$sFileXML."' " );
			$this->BuildXML_All_ExtractFromFile( $sFileXML, $iFileIndex );
			$iFileIndex++;
			
			
			//-- Move File if new location is valid --//
			if( $this->sFilePathArchive!==null && is_writable( $this->sFilePathArchive ) ) {
				//echo $sFileMove."<br/>\n";
				$sFileName = pathinfo( $sFileXML,  PATHINFO_BASENAME );
				$sFileMove = $this->sFilePathArchive."/".$sFileName;
				
				if( $sFileMove!==null && strlen($sFileMove)>10 ) {
					rename( $sFileXML, $sFileMove );
					$this->LogDebugAdd( " - Moved file '".$sFileName."' to xml archive folder." );
				}
			}
		}    //-- EndForeach --//
		
		//var_dump($this->mPropertyTypes);
		
		//--------------------------------------------//
		//-- Save all changed XML files
		//--------------------------------------------//
		foreach( $this->mData as $sPropType => $mData ) {
			$sFileOutputPath = $this->sFilePathOutput."/".$sPropType.".xml";
			$mData['oDOM']->save( $sFileOutputPath );
		}
	}    //-- EndFunction BuildXML_All --//
	
	
	/**
	 * 
	 * @param string $sFilePath 
	 * @param integer $iFileIndex
	 * @throws Exception
	 * @return void
	 */
	public function BuildXML_All_ExtractFromFile( $sFilePath, $iFileIndex=0 ) {
		//$results = [];
		
		$oXMLReader = new XMLReader();
		try {
			$file_contents = $this->fixContent( file_get_contents( $sFilePath ) );
			$oXMLReader->XML($file_contents);
		} catch (\Exception $e) {
			throw new Exception( "Failed to parse file at ".$sFilePath );
		}
		
		$iRealtyIndex = 0;
		while( $oXMLReader->read() ) {
			if( $oXMLReader->nodeType == XMLReader::ELEMENT && in_array( $oXMLReader->localName, $this->aPropertyTypes ) ) {
				
				$sPropType      = $oXMLReader->localName;
				$oNodeExtracted = $oXMLReader->expand();
				
				//--------------------------------------------//
				//-- Check if Already found
				//--------------------------------------------//
				$sDebugName = ($iFileIndex+1)."-".($iRealtyIndex+1);
				$iRealtyIndex++;
				$sUniqId = $oNodeExtracted->getElementsByTagName("uniqueID")->item(0)->nodeValue;
				
				
				if( in_array( $sUniqId, $this->mData[$sPropType]['aParsed'] ) ) {
					//echo "Skipping ".$sUniqId."\n"; var_dump( $sUniqId );
					$this->LogDebugAdd( "     - '".$sDebugName."' Skipping duplicate with the '".$sUniqId."' UniqId on Entry." );
					continue;
				}
				
				//-- Add the Unique Id --//
				$this->mData[$sPropType]['aParsed'][] = $sUniqId;
				
				//--------------------------------------------//
				//-- Convert XML for Easy Property Listing
				//--------------------------------------------//
				$oNodeNew = $this->_XMLEasyPropertyListingConvert( $sDebugName, $oNodeExtracted );
				
				
				$oNodeNewImported = $this->mData[$sPropType]['oDOM']->importNode( $oNodeNew, true );
				$this->mData[$sPropType]['oDOM']->documentElement->appendChild( $oNodeNewImported );
				
				
				//--------------------------------------------//
				//-- Debugging
				//--------------------------------------------//
				//$oSubNode1   = $node->getElementsByTagName("listingAgent")->item(0);
				//$oSubNode2 = $oSubNode1->getElementsByTagName("")->item(0);
				//var_dump( $this->mData[$sPropType]['oDOM'] );
				//var_dump( $this->mData[$sPropType]['oDOM']->saveXML() );
				//die();
				
				unset( $oNodeExtracted );
			}
		}
	}
	
	
	private function _XMLEasyPropertyListingConvert( $sDebugId, $oXMLNodeToConvert ) {
		//--------------------------------------------//
		//-- Add to temp DOM
		//--------------------------------------------//
		$oTempDOM = new DomDocument( "1.0", "utf-8" );
		$oNodeNew = $oTempDOM->importNode( $oXMLNodeToConvert, true );
		$oTempDOM->appendChild( $oNodeNew );
		
		//--------------------------------------------//
		//-- Listing Agent
		//--------------------------------------------//
		$oNodesListAgents = $oNodeNew->getElementsByTagName("listingAgent");
		
		foreach( $oNodesListAgents as $i => $oNodeAgent ) {
			$sAgentName = null;
			$oNodesListAgentsName = $oNodeAgent->getElementsByTagName("name");
			foreach( $oNodesListAgentsName as $oNodeAgentName ) {
				$sAgentName = $oNodeAgentName->nodeValue;
			}
			
			
			if( $sAgentName!==null && $sAgentName!=="" ) {
				//--------------------------------------------//
				//-- If the AgentName is Known
				//--------------------------------------------//
				if( isset( $this->mAgents[$sAgentName] ) ) {
					$oNodeAgentUserName = $oTempDOM->createElement( 'agentUserName', $this->mAgents[$sAgentName] );
					$oNodeAgent->appendChild($oNodeAgentUserName);
					$this->LogDebugAdd( "    - '".$sDebugId."' Added the '".$this->mAgents[$sAgentName]."' AgentUserName for the Agent ".(intval($i)+1)." Entry." );
				
				//--------------------------------------------//
				//-- ElseIf Default agent is available
				//--------------------------------------------//
				} else if( isset( $this->mAgents['Default'] ) ) {
					$oNodeAgentUserName = $oTempDOM->createElement( 'agentUserName', $this->mAgents['Default'] );
					$oNodeAgent->appendChild($oNodeAgentUserName);
					$this->LogDebugAdd(  "    - '".$sDebugId."' Added the Default AgentUserName for the Agent ".(intval($i)+1)." Entry due to not having a known name." );
				//--------------------------------------------//
				//-- ELSE Unrecognised Agent Name
				//--------------------------------------------//
				} else {
					$this->LogDebugAdd( "    - WARNING: '".$sDebugId."' Unrecognised name '".$sAgentName."' for Agent ".(intval($i)+1)." Entry." );
				}
			}
			//var_dump( $sAgentName );  //$oNodeAgent
		}
		
		//--------------------------------------------//
		//-- Remove Videos
		//--------------------------------------------//
		$oNodesListVideos = $oNodeNew->getElementsByTagName("videoLink");
		for( $i=0; $i<$oNodesListVideos->length; $i++ ) {
			$href = $oNodesListVideos->item($i);
			$href->parentNode->removeChild($href);
		}
		$oNodesListVideos = $oNodeNew->getElementsByTagName("externalLink");
		for( $i=0; $i<$oNodesListVideos->length; $i++ ) {
			$href = $oNodesListVideos->item($i);
			$href->parentNode->removeChild($href);
		}
		
		
		//--------------------------------------------//
		//-- Add to the DOM
		//--------------------------------------------//
		$oTempDOM2 = new DomDocument( "1.0", "utf-8" );
		$oTempDOM2->preserveWhiteSpace = false;
		$oTempDOM2->formatOutput = true;
		$oTempDOM2->loadXML( $oTempDOM->saveXML() );
		
		//--------------------------------------------//
		//-- Return Results
		//--------------------------------------------//
		return $oTempDOM2->documentElement;
	}    //-- EndFunction _XMLEasyPropertyListingConvert --//
	
	
	/**
	 * 
	 * @param string $sFolderPath
	 * @param string $sExtension - File extension to look for. Example xml
	 * @param boolean $bDescending - If true sorts FileList in descending order
	 * @return string[]
	 */
	public function getFileList( $sFolderPath, $sExtension, $bDescending=false ) {
		$arrFiles = array();
		$oHandle   = opendir($sFolderPath);
		
		if( $oHandle ) {
			while( ($entry = readdir($oHandle)) !== FALSE) {
				$ext = pathinfo($entry, PATHINFO_EXTENSION);
				if( $ext === $sExtension ) {
					$arrFiles[] = $sFolderPath."/".$entry;
				}
			}
		}
		closedir($oHandle);
		
		//-- Sort the file list by descending/ascending order --//
		if( $bDescending===true ) {
			arsort($arrFiles); 
		} else if( $bDescending===false ) {
			asort($arrFiles); 
		}
		//-- Return Results --//
		return $arrFiles;
	}
	
	
	public function fixContent( $content ) {
		//--------------------------------------------------------------------//
		//-- Declare variables
		//--------------------------------------------------------------------//
		$sModified = $content;
		
		$aRules = array(
			//-- Lowercase LandCategory fix --//
			array(
				'type'     => 'basic',
				'pattern'  => '/(<\/?)landcategory/',
				'replace'  => '$1landCategory'
			),
			
			//-- Fix the issue when seconds not present --//
			array(
				'type'     => 'adv',
				'pattern'  => '/(?:modTime="(\d{4})-(\d{2})-(\d{2})-(\d{2}):(\d{2})")/',
				'function' => function( $aMatches ) { return "modTime=\"".$aMatches[1].'-'.$aMatches[2].'-'.$aMatches[3]."-".$aMatches[4].":".$aMatches[5].":00\""; },
			),
			
			//-- Remove space between date and time --//
			//array(
			//	'type'     => 'adv',
			//	'pattern'  => '/(?:modTime="(\d{4})-(\d{2})-(\d{2})-(\d{2}):(\d{2}):(\d{2})")/',
			//	'function' => function( $aMatches ) { return "modTime=\"".$aMatches[1].'-'.$aMatches[2].'-'.$aMatches[3]." ".$aMatches[4].":".$aMatches[5].":".$aMatches[6]."\""; },
			//),
			
			//-- Remove the following characters: Â Ã Ä Å  --//
			array(
				'type'     => 'basic',
				'pattern'  => '/(&#xC2;)|(&#xC3;)|(&#xC4;)|(&#xC5;)/',
				'replace'  => ''
			)
		);
		
		
		//--------------------------------------------------------------------//
		//-- Define Rules
		//--------------------------------------------------------------------//
		foreach( $aRules as $mRule ) {
			if( $mRule['type']==='basic' ) {
				$sModified = preg_replace( $mRule['pattern'], $mRule['replace'], $sModified );
			} else if( $mRule['type']==='adv' ) {
				$sModified = preg_replace_callback( $mRule['pattern'], $mRule['function'], $sModified );
			}
		}
		
		//--------------------------------------------------------------------//
		//-- Return Results
		//--------------------------------------------------------------------//
		return $sModified;
	}


	/**
	 * Creates a Datetime string for the next minute time slot at a certain minute
	 * @param integer $iStartMinute - The minute time slot to find.
	 * @param integer $iHourMod - How many whole hours to skip to find the minute time slot.
	 * @return string
	 * @throws Exception
	 */
	public static function DateTimeStringFromMinuteAndHourMod( $iStartMinute, $iHourMod ) {
		$oDate          = new DateTime( 'now', new DateTimeZone('Australia/Brisbane') );
		$iMinuteCurrent = $oDate->format( 'i' );
		
		//----------------------------------------//
		//-- Set the Minutes
		//----------------------------------------//
		if( $iMinuteCurrent!==$iStartMinute ) {
			$iDiff = $iStartMinute - $iMinuteCurrent;
			
			if( $iStartMinute<$iMinuteCurrent ) { $iDiff += 60; }
			$oDate->add( new DateInterval('PT'.$iDiff.'M') );
		}
		
		//----------------------------------------//
		//-- Set the Hours
		//----------------------------------------//
		if( $iHourMod>0 ) {
			$oDate->add( new DateInterval('PT'.$iHourMod.'H') );
		}
		
		return $oDate->format('d-m-Y h:ia');
	}

	//================================================================//
	//== LOGS
	//================================================================//
	public function LogDebugAdd( $sMessage ) {
		if( $this->bDebugging===true ) {
			$this->aDebugLog[] = $sMessage;
		}
	}

	public function LogDebugEnabled() {
		return $this->bDebugging;
	}

	public function LogDebugListFetch() {
		if( $this->bDebugging===true ) {
			return $this->aDebugLog;
		}
		return null;
	}


	public function LogDebugGroupedAdd( $sKey, $sMessage ) {
		if( $this->bDebugging===true ) {
			if( !isset( $this->mDebugLogSpecial[$sKey] ) ) {
				$this->mDebugLogSpecial[$sKey] = array();
			}
			$this->mDebugLogSpecial[$sKey][] = $sMessage;
		}
	}


	//================================================================//
	//== List Nested Lookup Archive
	//================================================================//
	/**
	 * Lists archived properties grouped by property type
	 * @param string $sParamPropType - The property type to search for. (All if after)
	 * @throws Exception
	 * @return Array
	 */
	public function ListNestedLookupArchive( $sParamPropType="All" ) {
		//------------------------------------------------------------//
		//-- 1.0 - Validate parameters and Declare variables
		//------------------------------------------------------------//

		//--------------------------------------------//
		//-- 1.2 - Validate Parameter
		//--------------------------------------------//
		if( $sParamPropType!=="All" ) {
			$bValidPropType = in_array( $sParamPropType, $this->aPropertyTypes );

			if( $bValidPropType===false ) {
				throw new \Exception("Requested property type is not valid.");
			}
		}

		//--------------------------------------------//
		//-- 1.3 - Declare Variables and constants
		//--------------------------------------------//
		$iFileIndex     = 0;
		$aResults       = array();

		if( $sParamPropType==="All" ) {
			for( $i=0; $i<count( $this->aPropertyTypes ); $i++ ) {
				$sPropTypeLowerCase = strtolower( $this->aPropertyTypes[$i] );
				$aResults[$sPropTypeLowerCase] = array();
			}
		}

		//------------------------------------------------------------//
		//-- FileList
		//------------------------------------------------------------//
		//------------------------------------//
		//-- Lookup File List
		//------------------------------------//
		$aFileListXML = $this->getFileList( $this->sFilePathArchive, 'xml', false );


		//------------------------------------//
		//-- Foreach File
		//------------------------------------//
		foreach( $aFileListXML as $sXMLFilePath ) {
			$bXMLEntryFound = false;
			$sXMLFileName   = basename( $sXMLFilePath );
			//------------------------------------------------------------//
			//-- Load the file
			//------------------------------------------------------------//
			$this->LogDebugAdd( "Load File: '".$sXMLFilePath."' " );

			//$this->ExtractPropertiesFromFile( $sFileXML, $iFileIndex );
			$oXMLReader = new XMLReader();
			try {
				//var_dump( $sXMLFilePath );
				$file_contents = $this->fixContent( file_get_contents( $sXMLFilePath ) );
				$oXMLReader->XML($file_contents);
				$bXMLEntryFound = true;

			} catch (\Exception $e) {
				throw new Exception( "Failed to parse file at '".$sXMLFilePath."'. " );
			}

			//------------------------------------------------------------//
			//-- Parse the XML contents of the file
			//------------------------------------------------------------//
			$iFileIndexSub = 0;
			while( $oXMLReader->read() ) {
				if( $oXMLReader->nodeType == XMLReader::ELEMENT && in_array( $oXMLReader->localName, $this->aPropertyTypes ) ) {
					//-- Create Debug String --//
					$sDebugName       = ($iFileIndex+1)."-".($iFileIndexSub+1);

					//--------------------------------------------//
					//-- Extract
					//--------------------------------------------//
					$sPropType = $oXMLReader->localName;

					if( $sParamPropType!=="All" && $sPropType!==$sParamPropType ) {
						$this->LogDebugAdd( " - '".$sDebugName."' skipping due to it not matching the desired type!" );
						continue;
					}

					$oNodeExtracted   = $oXMLReader->expand();
					$sUniqId          = $oNodeExtracted->getElementsByTagName("uniqueID")->item(0)->nodeValue;

					if( !($oNodeExtracted->getElementsByTagName("suburb")->length>=1) ) {
						$this->LogDebugAdd( " - '".$sDebugName."' skipping due to no suburb!" );
						continue;
					}

					$sAddressNum      = "";
					$sAddressStreet   = "";
					$sAddressSuburb   = "";
					$oAddressNum      = $oNodeExtracted->getElementsByTagName("streetNumber");
					$oAddressStreet   = $oNodeExtracted->getElementsByTagName("street");
					$oAddressSuburb   = $oNodeExtracted->getElementsByTagName("suburb");
					if( $oAddressNum->length    >= 1 ) { $sAddressNum      = $oAddressNum->item(0)->nodeValue; }
					if( $oAddressStreet->length >= 1 ) { $sAddressStreet   = $oAddressStreet->item(0)->nodeValue; }
					if( $oAddressSuburb->length >= 1 ) { $sAddressSuburb   = $oAddressSuburb->item(0)->nodeValue; }


					$sModTime         = $oXMLReader->getAttribute('modTime');
					$oModTime         = DateTime::createFromFormat( "Y-m-d-H:i:s", $sModTime );
					$sModTimeUnixTS   = $oModTime->format("U");
					$sStatus          = $oXMLReader->getAttribute('status');

					//-- Create Strings from Base Variables --//
					$sAddress         = $sAddressNum." ".$sAddressStreet.", ".$sAddressSuburb;

					//-- Debugging --//
					$this->LogDebugAdd( " - '".$sDebugName."' Found property with the UniqueId '".$sUniqId."' that has a type of '".$sPropType."'. (".$sAddress.")" );

					//-- Image Count --//
					$iImageCount = 0;
					$oImages     = $oNodeExtracted->getElementsByTagName("img");
					foreach( $oImages as $oImage ) {
						$sImgURL = $oImage->getAttribute('url');
						if( is_string($sImgURL) && strlen( trim( $sImgURL ) ) >=1 ) {
							$iImageCount++;
						}
					}
					//$iImageCount = $oImages->count();

					//--------------------------------------------//
					//-- Store the Property
					//--------------------------------------------//
					$sPropTypeLowerCase = strtolower( $sPropType );
					//-- Create the Property Type if not found --//
					if( !isset( $aResults[$sPropTypeLowerCase] ) ) {
						$aResults[$sPropTypeLowerCase] = array();
					}
					if( !isset( $aResults[$sPropTypeLowerCase][$sUniqId] ) ) {
						$aResults[$sPropTypeLowerCase][$sUniqId] = array( "UniqId"=>$sUniqId, "Address"=>$sAddress, "Files"=>array() );
					}
					$aResults[$sPropTypeLowerCase][$sUniqId]["Files"][] = array( "Type"=>$sPropType, "Status"=>$sStatus, "ModTime"=>$sModTime, "ModTimeUnixTS"=>$sModTimeUnixTS, "ImageCount"=>$iImageCount, "FileName"=>$sXMLFileName );

					//--  --//
					$iFileIndexSub++;
					unset( $oImages );
					unset( $oNodeExtracted );

				}
			}    //-- EndWhile Reading the Contents of the XML file --//
		}    //-- EndForeach XMLFilePath in List --//

		//------------------------------------------------------------//
		//-- Return Results
		//------------------------------------------------------------//
		return $aResults;
	}    //-- EndFunction ListNestedLookupArchive (public) --//


	//================================================================//
	//== List Nested Lookup Agents
	//================================================================//
	/**
	 * Lists archived properties grouped by property type
	 * @param string $sParamPropType - The property type to search for. (All if after)
	 * @throws Exception
	 * @return Array
	 */
	public function ListNestedLookupAgents( $sXmlSourceFolder, $sParamPropType="All" ) {
		//------------------------------------------------------------//
		//-- 1.0 - Validate parameters and Declare variables
		//------------------------------------------------------------//

		//--------------------------------------------//
		//-- 1.2 - Validate Parameter
		//--------------------------------------------//
		//-- XML Source Folder --//
		if( $sXmlSourceFolder==="archive" || $sXmlSourceFolder==="Archive" ) {
			$sXmlFolder = $this->sFilePathArchive;
		} else {
			$sXmlFolder = $this->sFilePathInput;
		}

		//-- Property Type --//
		if( $sParamPropType!=="All" ) {
			$bValidPropType = in_array( $sParamPropType, $this->aPropertyTypes );

			if( $bValidPropType===false ) {
				throw new \Exception("Requested property type is not valid.");
			}
		}

		//--------------------------------------------//
		//-- 1.3 - Declare Variables and constants
		//--------------------------------------------//
		$iFileIndex     = 0;
		$aResults       = array();

		if( $sParamPropType==="All" ) {
			for( $i=0; $i<count( $this->aPropertyTypes ); $i++ ) {
				$sPropTypeLowerCase = strtolower( $this->aPropertyTypes[$i] );
				$aResults[$sPropTypeLowerCase] = array();
			}
		}

		//------------------------------------------------------------//
		//-- FileList
		//------------------------------------------------------------//
		//------------------------------------//
		//-- Lookup File List
		//------------------------------------//
		$aFileListXML = $this->getFileList( $sXmlFolder, 'xml', false );

		$iDebugSpecial = 0;
		//------------------------------------//
		//-- Foreach File
		//------------------------------------//
		foreach( $aFileListXML as $sXMLFilePath ) {
			$bXMLEntryFound = false;
			$sXMLFileName   = basename( $sXMLFilePath );
			//------------------------------------------------------------//
			//-- Load the file
			//------------------------------------------------------------//
			$this->LogDebugAdd( "Load File: '".$sXMLFilePath."' " );

			//$this->ExtractPropertiesFromFile( $sFileXML, $iFileIndex );
			$oXMLReader = new XMLReader();
			try {
				//var_dump( $sXMLFilePath );
				$file_contents = $this->fixContent( file_get_contents( $sXMLFilePath ) );
				$oXMLReader->XML($file_contents);
				$bXMLEntryFound = true;

			} catch (\Exception $e) {
				throw new Exception( "Failed to parse file at '".$sXMLFilePath."'. " );
			}

			//------------------------------------------------------------//
			//-- Parse the XML contents of the file
			//------------------------------------------------------------//
			$iFileIndexSub = 0;
			while( $oXMLReader->read() ) {
				if( $oXMLReader->nodeType == XMLReader::ELEMENT && in_array( $oXMLReader->localName, $this->aPropertyTypes ) ) {
					//-- Create Debug String --//
					$sDebugName       = ($iFileIndex+1)."-".($iFileIndexSub+1);

					//--------------------------------------------//
					//-- Extract
					//--------------------------------------------//
					$sPropType = $oXMLReader->localName;
					$sPropTypeLowerCase = strtolower( $sPropType );

					if( $sParamPropType!=="All" && $sPropType!==$sParamPropType ) {
						$this->LogDebugAdd( " - '".$sDebugName."' skipping due to it not matching the desired type!" );
						continue;
					}

					$oNodeExtracted   = $oXMLReader->expand();
					$sUniqId          = $oNodeExtracted->getElementsByTagName("uniqueID")->item(0)->nodeValue;
					$sModTime         = $oXMLReader->getAttribute('modTime');
					$oModTime         = DateTime::createFromFormat( "Y-m-d-H:i:s", $sModTime );
					$sModTimeUnixTS   = $oModTime->format("U");

					//-- Check If Street and other values are present --//
					if( !($oNodeExtracted->getElementsByTagName("suburb")->length>=1) ) {
						$this->LogDebugAdd( " - '".$sDebugName."' skipping due to no suburb!" );
						continue;
					}

					$sAddressNum      = "";
					$sAddressStreet   = "";
					$sAddressSuburb   = "";
					$oAgentList       = $oNodeExtracted->getElementsByTagName("listingAgent");
					$oAddressNum      = $oNodeExtracted->getElementsByTagName("streetNumber");
					$oAddressStreet   = $oNodeExtracted->getElementsByTagName("street");
					$oAddressSuburb   = $oNodeExtracted->getElementsByTagName("suburb");
					if( $oAddressNum->length    >= 1 ) { $sAddressNum      = $oAddressNum->item(0)->nodeValue; }
					if( $oAddressStreet->length >= 1 ) { $sAddressStreet   = $oAddressStreet->item(0)->nodeValue; }
					if( $oAddressSuburb->length >= 1 ) { $sAddressSuburb   = $oAddressSuburb->item(0)->nodeValue; }

					//$sStatus          = $oXMLReader->getAttribute('status');

					//-- Create Strings from Base Variables --//
					$sAddress         = $sAddressNum." ".$sAddressStreet.", ".$sAddressSuburb;

					//-- Debugging --//
					$this->LogDebugAdd( " - '".$sDebugName."' Found property with the UniqueId '".$sUniqId."' that has a type of '".$sPropType."'. (".$sAddress.")" );

					//echo "\n================================================\n== ".$sAddress."\n================================================\n";
					$iAgentListCount = $oAgentList->length;
					for( $i=0; $i<$iAgentListCount; $i++ ) {
						$oAgentDataName = $oAgentList->item( $i )->getElementsByTagName("name");
						$iAgentDataName = $oAgentDataName->length;
						$sAgentName = null;

						if( $iAgentDataName>=1 ) {
							$sAgentName = $oAgentDataName->item(0)->nodeValue;
							$sAgentNameMin = preg_replace("/[^a-zA-Z0-9]+/", "", $sAgentName);
							//var_dump( $sAgentName );
							if( !isset( $aResults[$sPropTypeLowerCase][$sAgentNameMin] ) ) {
								$aResults[$sPropTypeLowerCase][$sAgentNameMin] = array(
									"Name"  => $sAgentName,
									"Count" => 0,
									"Files" => array()
								);
							}    //-- EndIf Agent not already in results --//
							$aResults[$sPropTypeLowerCase][$sAgentNameMin]['Count']++;
							$aResults[$sPropTypeLowerCase][$sAgentNameMin]['Files'][] = $sXMLFileName." - ".($iFileIndexSub+1);
						}
					}

					//--  --//
					$iFileIndexSub++;
					//unset( $oImages );
					unset( $oNodeExtracted );

				}
			}    //-- EndWhile Reading the Contents of the XML file --//
		}    //-- EndForeach XMLFilePath in List --//

		//------------------------------------------------------------//
		//-- Return Results
		//------------------------------------------------------------//
		return $aResults;
	}    //-- EndFunction ListNestedLookupAgents (public) --//
	//private function XmlPropertyExtractValues( &$oNodeExtracted ) {}


	public function AddToFileLog( $sLogEntryType, $aValues ) {
		//var_dump( $this->sFilePathLog );
		$sLogString = "".time().", \"".$sLogEntryType."\"";
		foreach( $aValues as $sValue ) {
			if( is_string( $sValue ) || is_numeric( $sValue ) ) {
				$sLogString .= ", \"".$sValue."\""; //$sLogString .= ", \"".urlencode( $sValue )."\"";
			}
		}
		$sLogString .= " \n";
		$Result = file_put_contents( $this->sFilePathLog, $sLogString, FILE_APPEND | LOCK_EX );
		//var_dump( $Result );
	}    //-- EndFunction ListNestedLookupArchive (public) --//

}    //-- EndClass REAXML_Manager --//
