<?php
class AWeberCollection extends AWeberResponse implements ArrayAccess, Iterator, Countable {

	protected $pageSize = 100;
	protected $_entries = array();

	/**
	 * @var array Holds list of keys that are not publicly accessible
	 */
	protected $_privateData = array(
		'entries',
		'start',
		'next_collection_link',
	);

	/**
	 * getById
	 *
	 * Gets an entry object of this collection type with the given id
	 * @param mixed $id     ID of the entry you are requesting
	 * @access public
	 * @return AWeberEntry
	 */
	public function getById( $id ) {
		$data = $this->adapter->request( 'GET', "{$this->url}/{$id}" );
		return $this->_makeEntry( $data, $id, "{$this->url}/{$id}" );
	}


	/**
	 * create
	 *
	 * Invoke the API method to CREATE a new entry resource.
	 *
	 * Note: Not all entry resources are eligible to be created, please
	 *       refer to the AWeber API Reference Documentation at
	 *       https://labs.aweber.com/docs/reference/1.0 for more
	 *       details on which entry resources may be created and what
	 *       attributes are required for creating resources.
	 *
	 * @access public
	 * @param params mixed  associtative array of key/value pairs.
	 * @return AWeberEntry(Resource) The new resource created
	 */
	public function create( $kv_pairs ) {
		# Create Resource
		$params         = array_merge( array( 'ws.op' => 'create' ), $kv_pairs );
		$data           = $this->adapter->request( 'POST', $this->url, $params, array( 'return' => 'headers' ) );
		$this->_entries = array();

		# Return new Resource
		$url           = $data['Location'];
		$resource_data = $this->adapter->request( 'GET', $url );
		return new AWeberEntry( $resource_data, $url, $this->adapter );
	}

	/**
	 * find
	 *
	 * Invoke the API 'find' operation on a collection to return a subset
	 * of that collection.  Not all collections support the 'find' operation.
	 * refer to https://labs.aweber.com/docs/reference/1.0 for more information.
	 *
	 * @param mixed $search_data   Associative array of key/value pairs used as search filters
	 *                             * refer to https://labs.aweber.com/docs/reference/1.0 for a
	 *                               complete list of valid search filters.
	 *                             * filtering on attributes that require additional permissions to
	 *                               display requires an app authorized with those additional permissions.
	 * @access public
	 * @return AWeberCollection
	 */
	public function find( $search_data ) {
		# invoke find operation
		$params = array_merge( $search_data, array( 'ws.op' => 'find' ) );
		$data   = $this->adapter->request( 'GET', $this->url, $params );

		# get total size
		$ts_params          = array_merge( $params, array( 'ws.show' => 'total_size' ) );
		$total_size         = $this->adapter->request( 'GET', $this->url, $ts_params, array( 'return' => 'integer' ) );
		$data['total_size'] = $total_size;

		# return collection
		return $this->readResponse( $data, $this->url );
	}

	/** getParentEntry
	 *
	 * Gets an entry's parent entry
	 * Returns NULL if no parent entry
	 */
	public function getParentEntry() {
		$url_parts = explode( '/', $this->url );
		$size      = count( $url_parts );

		#Remove collection id and slash from end of url
		$url = substr( $this->url, 0, -strlen( $url_parts[ $size - 1 ] ) - 1 );

		try {
			$data = $this->adapter->request( 'GET', $url );
			return new AWeberEntry( $data, $url, $this->adapter );
		} catch ( Exception $e ) {
			return null;
		}
	}

	/**
	 * _getPageParams
	 *
	 * Returns an array of GET params used to set the page of a collection
	 * request
	 * @param int $start    Which entry offset should this page start on?
	 * @param int $size     How many entries should be included in this page?
	 * @access protected
	 * @return void
	 */
	protected function _getPageParams( $start = 0, $size = 20 ) {
		if ( $start > 0 ) {
			$params = array(
				'ws.start' => $start,
				'ws.size'  => $size,
			);
			ksort( $params );
		} else {
			$params = array();
		}
		return $params;
	}

	/**
	 * _type
	 *
	 * Interpret what type of resources are held in this collection by
	 * analyzing the URL
	 *
	 * @access protected
	 * @return void
	 */
	protected function _type() {
		$urlParts = explode( '/', $this->url );
		$type     = array_pop( $urlParts );
		return $type;
	}

	/**
	 * _calculatePageSize
	 *
	 * Calculates the page size of this collection based on the data in the
	 * next and prev links.
	 *
	 * @access protected
	 * @return integer
	 */
	protected function _calculatePageSize() {
		if ( array_key_exists( 'next_collection_link', $this->data ) ) {
			$url      = $this->data['next_collection_link'];
			$urlParts = parse_url( $url );
			if ( empty( $urlParts['query'] ) ) {
				return $this->pageSize;
			}
			$query = array();
			parse_str( $urlParts['query'], $query );
			if ( empty( $query['ws_size'] ) ) {
				return $this->pageSize;
			}
			$this->pageSize = $query['ws_size'];
		}
		return $this->pageSize;
	}

	/**
	 * _loadPageForOffset
	 *
	 * Makes a request for an additional page of entries, based on the given
	 * offset.  Calculates the start / size of the page needed to get that
	 * offset, requests for it, and then merges the data into it internal
	 * collection of entry data.
	 *
	 * @param mixed $offset     The offset requested, 0 to total_size-1
	 * @access protected
	 * @return void
	 */
	protected function _loadPageForOffset( $offset ) {
		$this->_calculatePageSize();
		$start  = round( $offset / $this->pageSize ) * $this->pageSize;
		$params = $this->_getPageParams( $start, $this->pageSize );

		// Loading page
		$data                 = $this->adapter->request( 'GET', $this->url, $params );
		$this->adapter->debug = false;

		$rekeyed = array();
		foreach ( $data['entries'] as $key => $entry ) {
			$rekeyed[ $key + $data['start'] ] = $entry;
		}
		$this->data['entries'] = array_merge( $this->data['entries'], $rekeyed );
	}

	/**
	 * _getEntry
	 *
	 * Makes sure that entry offset's page is loaded, then returns it. Returns
	 * null if the entry can't be loaded, even after requesting the needed
	 * page.
	 *
	 * @param mixed $offset     Offset being requested.
	 * @access protected
	 * @return void
	 */
	protected function _getEntry( $offset ) {
		if ( empty( $this->data['entries'][ $offset ] ) ) {
			$this->_loadPageForOffset( $offset );
		}
		return ( empty( $this->data['entries'][ $offset ] ) ) ? null :
			$this->data['entries'][ $offset ];
	}

	/**
	 * _makeEntry
	 *
	 * Creates an entry object from the given entry data.  Optionally can take
	 * the id AND URL of the entry, though that data can be infered from the
	 * context in which _makeEntry is being called.
	 *
	 * @param mixed $data   Array of data returned from an API request for
	 *      entry, or as part of the entries array in this collection.
	 * @param mixed $id     ID of the entry. (Optional)
	 * @param mixed $url    URL used to retrieve this entry (Optional)
	 * @access protected
	 * @return void
	 */
	protected function _makeEntry( $data, $id = false, $url = false ) {
		if ( ( ! $url ) or ( ! $id ) ) {
			// if either the url or id is omitted, grab the url from the
			// self_link of the resource
			$url = $this->adapter->app->removeBaseUri( $data['self_link'] );
		} else {
			$url = "{$this->url}/{$id}";
		}
		return new AWeberEntry( $data, $url, $this->adapter );
	}

	/**
	 * ArrayAccess interface methods
	 *
	 * Allows this object to be accessed via bracket notation (ie $obj[$x])
	 * http://php.net/manual/en/class.arrayaccess.php
	 */
	public function offsetSet( $offset, $value ) { }
	public function offsetUnset( $offset ) {}
	public function offsetExists( $offset ) {
		if ( $offset >= 0 && $offset < $this->total_size ) {
			return true;
		}
		return false;
	}
	public function offsetGet( $offset ) {
		if ( ! $this->offsetExists( $offset ) ) {
			return null;
		}
		if ( ! empty( $this->_entries[ $offset ] ) ) {
			return $this->_entries[ $offset ];
		}

		$this->_entries[ $offset ] = $this->_makeEntry( $this->_getEntry( $offset ) );
		return $this->_entries[ $offset ];
	}

	/**
	 * Iterator interface methods
	 *
	 * Provides iterator functionality.
	 * http://php.net/manual/en/class.iterator.php
	 */
	protected $_iterationKey = 0;
	public function current() {
		return $this->offsetGet( $this->_iterationKey );
	}

	public function key() {
		return $this->_iterationKey;
	}

	public function next() {
		$this->_iterationKey++;
	}

	public function rewind() {
		$this->_iterationKey = 0;
	}

	public function valid() {
		return $this->offsetExists( $this->key() );
	}

	/**
	 * Countable interface methods
	 *
	 * Allows PHP's count() and sizeOf() functions to act on this object
	 * http://www.php.net/manual/en/class.countable.php
	 */
	public function count() {
		return $this->total_size;
	}

}
