<?php

/**
 * Represents a has_many list linked against a polymorphic relationship
 *
 * @package framework
 * @subpackage model
 */
class PolymorphicHasManyList extends HasManyList {

	/**
	 * Name of foreign key field that references the class name of the relation
	 *
	 * @var string
	 */
	protected $classForeignKey;

	/**
	 * Retrieve the name of the class this relation is filtered by
	 *
	 * @return string
	 */
	public function getForeignClass() {
		return $this->dataQuery->getQueryParam('Foreign.Class');
	}

	/**
	 * Create a new PolymorphicHasManyList relation list.
	 *
	 * @param string $dataClass The class of the DataObjects that this will list.
	 * @param string $foreignField The name of the composite foreign relation field. Used
	 * to generate the ID and Class foreign keys.
	 * @param string $foreignClass Name of the class filter this relation is filtered against
	 */
	function __construct($dataClass, $foreignField, $foreignClass) {

		// Set both id foreign key (as in HasManyList) and the class foreign key
		parent::__construct($dataClass, "{$foreignField}ID");
		$this->classForeignKey = "{$foreignField}Class";

		// Ensure underlying DataQuery globally references the class filter
		$this->dataQuery->setQueryParam('Foreign.Class', $foreignClass);

		// For queries with multiple foreign IDs (such as that generated by
		// DataList::relation) the filter must be generalised to filter by subclasses
		$classNames = Convert::raw2sql(ClassInfo::subclassesFor($foreignClass));
		$this->dataQuery->where(sprintf(
			"\"{$this->classForeignKey}\" IN ('%s')",
			implode("', '", $classNames)
		));
	}

	/**
	 * Adds the item to this relation.
	 *
	 * It does so by setting the relationFilters.
	 *
	 * @param $item The DataObject to be added, or its ID
	 */
	public function add($item) {
		if(is_numeric($item)) {
			$item = DataObject::get_by_id($this->dataClass, $item);
		} else if(!($item instanceof $this->dataClass)) {
			user_error(
				"PolymorphicHasManyList::add() expecting a $this->dataClass object, or ID value",
				E_USER_ERROR
			);
		}

		$foreignID = $this->getForeignID();

		// Validate foreignID
		if(!$foreignID) {
			user_error(
				"PolymorphicHasManyList::add() can't be called until a foreign ID is set",
				E_USER_WARNING
			);
			return;
		}
		if(is_array($foreignID)) {
			user_error(
				"PolymorphicHasManyList::add() can't be called on a list linked to mulitple foreign IDs",
				E_USER_WARNING
			);
			return;
		}

		$foreignKey = $this->foreignKey;
		$classForeignKey = $this->classForeignKey;
		$item->$foreignKey = $foreignID;
		$item->$classForeignKey = $this->getForeignClass();

		$item->write();
	}

	/**
	 * Remove an item from this relation.
	 * Doesn't actually remove the item, it just clears the foreign key value.
	 *
	 * @param $item The DataObject to be removed
	 * @todo Maybe we should delete the object instead?
	 */
	public function remove($item) {
		if(!($item instanceof $this->dataClass)) {
			throw new InvalidArgumentException("HasManyList::remove() expecting a $this->dataClass object, or ID",
				E_USER_ERROR);
		}

		// Don't remove item with unrelated class key
		$foreignClass = $this->getForeignClass();
		$classNames = ClassInfo::subclassesFor($foreignClass);
		$classForeignKey = $this->classForeignKey;
		if(!in_array($item->$classForeignKey, $classNames)) return;

		// Don't remove item which doesn't belong to this list
		$foreignID = $this->getForeignID();
		$foreignKey = $this->foreignKey;

		if(	empty($foreignID)
			|| (is_array($foreignID) && in_array($item->$foreignKey, $foreignID))
			|| $foreignID == $item->$foreignKey
		) {
			$item->$foreignKey = null;
			$item->$classForeignKey = null;
			$item->write();
		}

	}
}