<?php

/**
 * @category   Shanty
 * @package    Shanty_Mongo
 * @copyright  Shanty Tech Pty Ltd
 * @license    New BSD License
 * @author     Coen Hyde
 */
class Shanty_Mongo_DocumentSet extends Shanty_Mongo_Document
{
	const DYNAMIC_INDEX = '$';
	
	protected static $_requirements = array(
		self::DYNAMIC_INDEX => 'Document'
	);
	
	/**
	 * Get the property keys for this Document Set
	 * 
	 * @return array
	 */
	public function getPropertyKeys()
	{
		$keys = parent::getPropertyKeys();
		sort($keys, SORT_NUMERIC);
		
		return $keys;
	}
	
	/**
	 * Get a property
	 * 
	 * @param mixed $property
	 */
	public function getProperty($index = null)
	{
		$new = is_null($index);
		
		// If property exists and initialised then return it
		if (!$new && array_key_exists($index, $this->_data)) {
			return $this->_data[$index];
		}
		
		// Make sure we are not trying to create a document that is supposed to be saved as a reference
		if ($new && $this->hasRequirement(static::DYNAMIC_INDEX, 'AsReference')) {
			require_once 'Shanty/Mongo/Exception.php';
			throw new Shanty_Mongo_Exception("Can not create a new document from documentset where document must be saved as a reference");
		}

		if (!$new) {
			// Fetch clean data for this property if it exists
			if (array_key_exists($index, $this->_cleanData)) $data = $this->_cleanData[$index];
			else return null;
		}
		else $data = array();
		
		// If property is a reference to another document then fetch the reference document
		if (MongoDBRef::isRef($data)) {
			$collection = $data['$ref'];
			$data = MongoDBRef::get($this->_getMongoDB(false), $data);
			
			// If this is a broken reference then no point keeping it for later
			if (!$data) {
				$this->_data[$index] = null;
				return $this->_data[$index];
			}
			
			$reference = true;
		}
		else {
			$reference = false;
			$collection = $this->getConfigAttribute('collection');
		}
		
		$config = array ();
		$config['new'] = $new;
		$config['requirementModifiers'] = $this->getRequirements(self::DYNAMIC_INDEX.'.');
		$config['parentIsDocumentSet'] = true;
		$config['connectionGroup'] = $this->getConfigAttribute('connectionGroup');
		$config['db'] = $this->getConfigAttribute('db');
		$config['collection'] = $collection;
		
		if (!$reference) {
			// If this is a new array element. We will $push to the array when saving
			if ($new) $path = $this->getPathToDocument();
			else $path = $this->getPathToProperty($index);
			
			$config['pathToDocument'] = $path;
			$config['criteria'] = $this->getCriteria();
			$config['hasId'] = $this->hasRequirement(self::DYNAMIC_INDEX, 'hasId');
		}
		
		// get the document class
		$documentClass = $this->hasRequirement(self::DYNAMIC_INDEX, 'Document');
		if (isset($data['_type']) && !empty($data['_type'][0])) {
			$documentClass = $data['_type'][0];
		}
		$document = new $documentClass($data, $config);
		
		// if this document was a reference then remember that
		if ($reference) {
			$this->_references->attach($document);
		}
		
		// If this is not a new document cache it
		if (!$new) {
			$this->_data[$index] = $document;
		}
		
		return $document;
	}
	
	/**
	 * Set property
	 * 
	 * @param $index
	 * @param $document
	 */
	public function setProperty($index, $document)
	{
		$new = is_null($index);
		
		// Make sure index is numeric
		if (!$new && !is_numeric($index)) {
			require_once 'Shanty/Mongo/Exception.php';
			throw new Shanty_Mongo_Exception("Index must be numeric '{$index}' given");
		}
		
		// Unset element
		if (!$new && is_null($document)) {
			$this->_data[$index] = null;
			return;
		}
		
		// Make sure we are not keeping a copy of the old document in reference memory
		if (!$new && isset($this->_data[$index]) && !is_null($this->_data[$index])) {
			$this->_references->detach($this->_data[$index]);
		}
		
		// Throw exception if value is not valid
		$validators = $this->getValidators(self::DYNAMIC_INDEX);
		
		if (!$validators->isValid($document)) {
			require_once 'Shanty/Mongo/Exception.php';
			throw new Shanty_Mongo_Exception(implode($validators->getMessages(), "\n"));
		}
		
		if ($new) {
			$keys = $this->getPropertyKeys();
			$index = empty($keys) ? 0 : max($keys)+1;
		}

		// Filter value
//		$value = $this->getFilters(self::DYNAMIC_INDEX)->filter($document);

		if (!$this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) {
			// Make a new document if it has been saved somewhere else
			if (!$document->isNewDocument()) {
				$documentClass = get_class($document);
				$document = new $documentClass($document->export(), array('new' => false, 'pathToDocument' => $this->getPathToProperty($index)));
			}
			else {
				$document->setPathToDocument($this->getPathToProperty($index));
			}
			
			// Inform the document of it's surroundings
			$document->setConfigAttribute('connectionGroup', $this->getConfigAttribute('connectionGroup'));
			$document->setConfigAttribute('db', $this->getConfigAttribute('db'));
			$document->setConfigAttribute('collection', $this->getConfigAttribute('collection'));
			$document->setConfigAttribute('criteria', $this->getCriteria());
			$document->applyRequirements($this->getRequirements(self::DYNAMIC_INDEX.'.'));
		}
		
		$this->_data[$index] = $document;
	}
	
	/**
	 * Export all data
	 * 
	 * @return array
	 */
	public function export($skipRequired = false)
	{
		// Since this is an array, fill in empty index's with null
		$exportData = parent::export($skipRequired);
		
		// Fix PHP "max(): Array must contain at least one element" bug
        // if DocumentSet has no data
        if (count($exportData) > 0) {
    		$maxKey = max(array_keys($exportData));
    		
    		for ($i = 0; $i<$maxKey; $i++) {
    			if (array_key_exists($i, $exportData)) {
    				continue;
    			}
    			
    			$exportData[$i] = null;
    		}
    		
    		ksort($exportData);
        }
		
		return $exportData;
	}
	
	/**
	 * Add a document to this set
	 * 
	 * @param Shanty_Mongo_Document $document
	 */
	public function addDocument(Shanty_Mongo_Document $document)
	{
		return $this->setProperty(null, $document);
	}
	
	/**
	 * Add a document to the push queue
	 * 
	 * @param Shanty_Mongo_Document $document
	 */
	public function pushDocument(Shanty_Mongo_Document $document)
	{
		$this->push(null, $document);
	}
	
	/**
	 * Get all operations
	 * 
	 * @param Boolean $includingChildren Get operations from children as well
	 */
	public function getOperations($includingChildren = false)
	{
		if ($this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) $includingChildren = false;
		
		return parent::getOperations($includingChildren);
	}
	
	/**
	 * Remove all operations
	 * 
	 * @param Boolean $includingChildren Remove operations from children as wells
	 */
	public function purgeOperations($includingChildren = false)
	{
		if ($this->hasRequirement(self::DYNAMIC_INDEX, 'AsReference')) $includingChildren = false;
		
		return parent::purgeOperations($includingChildren);
	}
	
	public function __call($name, $arguments = array())
	{
		switch ($name) {
			case 'new':
				return $this->getProperty();
		}
		
		require_once 'Shanty/Mongo/Exception.php';
		throw new Shanty_Mongo_Exception("Captured in __call. Method $name does not exist.");
	}
}