<?php

class Collection implements Countable,Iterator,ArrayAccess {

	private $records;

	private $position = 0;

	public function __construct($data = null) {
		if ($data !== null) {
			$this->records = $data;
		} else {
			$this->records = [];
		}
	}

	public function add($val) {
		$this->records[] = $val;
	}

	public function first() {
		if (count($this->records) == 0) return null;
		return $this->records[0];
	}

	public function last() {
		if (count($this->records) == 0) return null;
		return $this->records[count($this->records)-1];
	}

	public function pop() {
		if (count($this->records) == 0) return null;
		return array_pop($this->records);
	}

	public function shift() {
		if (count($this->records) == 0) return null;
		return array_shift($this->records);
	}

	public function push($val) {
		return array_push($this->records, $val);
	}

	public function unshift($val) {
		return array_unshift($this->records, $val);
	}

	public function get($n) {
		return isset($this->records[$offset]) ? $this->records[$offset] : null;
	}
	
	public function all() {
		return $this->records;
	}

	// Countable
	public function count() : int {
		return count($this->records);
	}

	// Iterator
	public function rewind() : void {
		$this->position = 0;
	}

	public function current() : mixed {
		return $this->records[$this->position];
	}

	public function key() : mixed {
		return $this->position;
	}

	public function next() : void {
		++$this->position;
	}

	public function valid() : bool {
		return isset($this->records[$this->position]);
	}

	// ArrayAccess
	public function offsetSet($offset, $value) : void {
		if (is_null($offset)) {
			$this->push($value);
		} else {
			$this->records[$offset] = $value;
		}
	}

	public function offsetExists($offset) : bool {
		return isset($this->records[$offset]);
	}

	public function offsetUnset($offset) : void {
		unset($this->records[$offset]);
	}

	public function offsetGet($offset) : mixed {
		return isset($this->records[$offset]) ? $this->records[$offset] : null;
	}

	public function each($f) {
		if ($f instanceof Closure) {
			foreach ($this->records as $r) {
				$f->call($this, $r);
			}
			return;
		}

		if (is_array($f)) {
			if (method_exists($f[0], $f[1])) {
				$cl = $f[0];
				$fun = $f[1];
				foreach ($this->records as $r) {
					$cl::$fun($r);
				}
			}
			return;
		}
	}

	public function sort($field, $ins=false) {
		usort($this->records, function ($a, $b) use ($field,$ins) {

			$aa = $a->$field;
			$bb = $b->$field;

			if ($ins) {
				$aa = strtolower_null($aa);
				$bb = strtolower_null($bb);
			}

			if ($aa > $bb) {
				return 1;
			}

			if ($aa < $bb) {
				return -1;
			}
			return 0;
		});
	}

	public function sort_with_function($func) {
		usort($this->records, $func);
	}


	public function range($start, $len = 0) {

		if ($len == 0) {
			// 0 .. $start
			return array_splice($this->records, 0, $start);
		}

		return array_splice($this->records, $start, $len);
	}

	public function glob($key, $pattern) {
		$nc = new Collection;

		foreach ($this->records as $r) {
			if (fnmatch(strtolower_null($pattern), strtolower_null($r->$key))) {
				$nc->push($r);
			}
		}
		return $nc;
	}

	public function merge($other) {
		$o = new Collection;
		foreach ($this->records as $r) {
			$o->push($r);
		}
		foreach ($other->all() as $r) {
			$o->push($r);
		}
		return $o;
	}

	public static function from_array($arr) {
		$c = new Collection();
		foreach ($arr as $k=>$v) {
			$ob = new stdClass;
			$ob->key = $k;
			$ob->value = $v;
			$c->pusk($ob);
		}
		return $c;
	}
}
