<?php

require_once 'common/Object.php';
require_once 'common/collection/IIterable.php';
require_once 'common/collection/ICollection.php';

/**
 * Provides support functions for different ICollection Classes.
 * 
 * This is mostly (but not exactly) an abstract class, that serves as base 
 * to almost all other Collection classes. It keeps the collection in an 
 * internal and protected <em>elements</em> attribute, not directly exposed 
 * to the outside.
 * 
 * @implements ICollection, IIterable
 * @author  $Author: pruna $
 * @package common/collection
 * @version $Revision: 1.1 $
 * @created 12-Feb-2004
 * @modified $Date: 2005/12/06 02:56:45 $
 */
class Collection extends Object implements IIterable, ICollection {
    
    /**
     * Internal array holder for any Collection's elements
     * 
     * @var      array
     * @access   protected
     */
    protected $elements = array();
    
    /**
     * Required constructor for any Collection. All children classes must have 
     * a constructor that accepts a Collection as parameter.
     * Creates a new Collection with the specified elements.
     *
     * @access    public
     * @param     ICollection    $collection    Initial elements
     */
    public function __construct($collection = null) {
        if ($collection !== null) {
            $this->elements = Collection::castCollection($collection);
        }
    }
    
    /**
     * Gets an ICollection, and cast it to another (by type or name).
     * It receives the <em>ICollection</em> or array to cast, and the type to 
     * cast to. If no second parameter is provided, the default is to return 
     * an array.
     * Currently, it's working with <em>ICollection</em> and arrays.
     * This method is one of the few of this class designed to be called 
     * statically. You can't expect that this method works on <em>this</em>,
     * if you don't pass it as first parameter.
     * 
     * @param   mixed   $collection     ICollection or array to cast.
     * @param   mixed   $typeToCast     ICollection or string to cast to.
     * @access  public
     * @return  mixed
     */
    public function castCollection($collection, $typeToCast = null) {
        // Check if it's already ok
        if (ObjectInspector::isA($collection, $typeToCast)) {
            return $collection;
        }
        // First, cast $collection to array
        if (ObjectInspector::isA($collection, 'ICollection')) {
            $collection = $collection->toArray();
        } elseif (is_array($collection)) {
            // ok
        } else {
            // @todo an option here is to do "array($collection)", but it could 
            // create new problems when $collection is a null or empty value, 
            // because it will then create a collection with one elment instead 
            // of an empty one.
            $collection = array();
        }
        // Then, cast array to $typeToCast
        if (is_string($typeToCast) && class_exists($typeToCast)) {
        //&& ObjectInspector::isA($typeToCast, 'ICollection')) {
            // Can't check this, because isA don't work with 2 strings
            return new $typeToCast($collection);
        } elseif (is_object($typeToCast) 
        && ObjectInspector::isA($typeToCast, 'ICollection')) {
            $typeToCast = ObjectInspector::getClass($typeToCast);
            return new $typeToCast($collection);
        } else {
            return $collection;
        }
    }
    
    /**
     * Removes all of the elements from this Collection.
     *
     * @access    public
     */
    public function clear() {
        $this->elements = array();
    }
    
    /**
     * Concatenates two collections, and returns as selected type.
     *
     * @access    public
     * @param     Collection   $collection1    Collection to concatenate
     * @param     Collection   $collection2    Collection to concatenate
     * @param     Collection   $typeToCast     Type to return
     * @return    Collection
     */
    public function concat($collection1, $collection2, $typeToCast = null) {
        return self::castCollection(array_merge(
            self::castCollection($collection1),
            self::castCollection($collection2)
        ), $typeToCast);
    }
    
    /**
     * Returns true if this Collection maps one or more keys to 
     * the specified element.
     *
     * @access    public
     * @param     mixed    $element
     * @return    boolean
     */
    public function contains($element) {
        return in_array($element, $this->elements);
    }
    
    /**
     * Compares the specified object with this object for equality.
     *
     * @access    public
     * @param     mixed    $object    Object to be compared whit $this
     * @return    boolean
     */
    public function equals($object) {
        return ($this->toArray() === $object->toArray());
    }
    
    /**
     * Get a BaseIterator for the Collection.
     *
     * @access    public
     * @return    BaseIterator
     */
    public function getIterator() {
        return new BaseIterator($this->elements);
    }
    
    /**
     * Returns true if this Collection is empty.
     *
     * @access    public
     * @return    boolean
     */
    public function isEmpty() {
        return $this->size() === 0;
    }
    
    /**
     * Returns the number of elements in this Collection.
     *
     * @access    public
     * @return    integer
     */
    public function size() {
        return sizeof($this->elements);
    }
    
    /**
     * Returns an array containing all of the elements in this Collection in 
     * order.
     *
     * @access    public
     * @return    array
     */
    public function toArray() {
        return $this->elements;
    }
    
}

?>