<?php

namespace ArrayDigger;

class ArrayDigger {

	/**
	 * The data on which the search is performed.
	 */
	public $data = null;

	/**
	 * Initialize the digger with the data.
	 *
	 * @param	$data	The data to initialize the digger.
	 */
	public function __construct($data) {
		$this->setData($data);
	}

	/**
	 * Set the data to the digger, overwriting the previous data.
	 *
	 * @param	$data	The data to set.
	 *
	 * @return	The supplied data.
	 */
	public function setData($data) {

		# Validate
		if(!is_array($data)) {
			throw new \Exception('ArrayDigger only supports array.');
		}

		return $this->data = $data;
	}

	/**
	 * Extract the data from the supplied path.
	 *
	 * @param	$path the path to
	 *
	 * @return	The value found on the path, or null if not found.
	 */
	public function extract($path) {

		# Parse the path
		$path = $this->parsePath($path);

		# Extract
		return $this->advanceInto($path, $this->data);
	}

	/**
	 * Extract the value by using the supplied map to extract the data.
	 *
	 * @param	$map	The map to use, in 'key' => 'path' format.
	 *
	 * @return	Array containing the extracted data.
	 */
	public function extractFromMap($map) {
		$results = array();

		# Find all
		foreach($map as $key => $path) {
			$results[$key] = $this->extract($path);
		}

		return $results;
	}

	/**
	 * Advance into the path.
	 *
	 * @param	$path	The path to follow.
	 * @param	$data	The data on which to advance.
	 * @param	$depth	The current depth.
	 *
	 * @return	The advanced array.
	 */
	private function advanceInto($path, $data, $depth = 0) {
		$element = array_shift($path);

		# Finish
		if(!$element) {
			return $data;
		}

		# Go deep
		foreach($data as $key => $value) {
			if($next = $element->matches($key, $data)) {
				return $this->advanceInto($path, $next, $depth + 1);
			}
		}
	}

	/**
	 * Parse the supplied path into recongizable elements.
	 *
	 * @param	$path	The path to parse.
	 *
	 * @return	The parsed path as elements.
	 */
	private function parsePath($path) {
		$elements = explode('/', $path);

		# Parse each element
		foreach($elements as $i => $element) {
			$elements[$i] = new Element($element);
		}

		return $elements;
	}

}

/**
 * A single path element for the search.
 */
class Element {

	/**
	 * The name of the element.
	 */
	public $name;

	/**
	 * List of searches to look for.
	 */
	public $search = array();

	/**
	 * List of custom methods to use.
	 */
	public $custom = array();

	/**
	 * Initialize PathElement.
	 *
	 * @param	$element	The raw element which should be parsed.
	 */
	public function __construct($element) {

		# Split
		$split = preg_split('~(?=[\[:])~', $element);
		$this->name = array_shift($split);

		# Extract other sections
		foreach($split as $section) if($section) {
			switch($section[0]) {
				case '[':
					$this->search[] = new SearchSection($section);
					break;

				case ':':
					// TODO
					break;
			}
		}
	}

	/**
	 * Check if the object matches the element.
	 *
	 * @param	$key	The key of the element.
	 * @param	$parent	The parent in which the key is located.
	 *
	 * @return	The next step of data or null if it does not match.
	 */
	public function matches($key, $parent) {

		# Basic check
		if($this->name && $key != $this->name) {
			return null;
		}

		# Search
		if(!empty($this->search)) {

			# Searches are conneceted wiht AND condition
			foreach($this->search as $search) {

				# Find first occurenc
				foreach($parent[$key] as $i => $value) {
					if($search->matches($i, $parent[$key])) {
						return $parent[$key][$i];
					}
				}
			}

			# Search has failed
			return null;
		}

		return $parent[$key];
	}

}

/**
 * A section within the element
 */
class SearchSection {

	/**
	 * On which field to search for match.
	 */
	public $field;

	/**
	 * The search to perform on a field.
	 */
	public $search;

	/**
	 * Create search section from raw section.
	 *
	 * @param	$raw	The raw section to be converted to search.
	 */
	public function __construct($raw) {

		# Validate
		$list = explode('=', trim($raw, '[ ]'), 2);
		if(sizeof($list) !== 2) {
			throw new \Exception("Unable to understand the search element: '{$raw}'. Please use the following format: [fieldToSearch=whatToSearchFor].");
		}

		# Set
		$this->field = $list[0];
		$this->search = $list[1];
	}

	/**
	 * Check if the object matches the search.
	 *
	 * @param	$key	The key of the object.
	 * @param	$parent	The parent in which the key is located.
	 *
	 * @return	True if it is a match, false otherwise.
	 */
	public function matches($key, $parent) {
		return $parent[$key][$this->field] == $this->search;
	}

}
