<?php namespace System\DB;

abstract class Eloquent {

	/**
	 * Indicates if the model exists in the database.
	 *
	 * @var bool
	 */
	public $exists = false;

	/**
	 * The model attributes.
	 *
	 * @var array
	 */
	public $attributes = array();

	/**
	 * The model's dirty attributes.
	 *
	 * @var array
	 */
	public $dirty = array();

	/**
	 * The model's ignored attributes.
	 *
	 * Ignored attributes will not be saved to the database, and
	 * are primarily used to hold relationships.
	 *
	 * @var array
	 */
	public $ignore = array();

	/**
	 * The relationships that should be eagerly loaded.
	 *
	 * @var array
	 */
	public $includes = array();

	/**
	 * The relationship type the model is currently resolving.
	 *
	 * @var string
	 */
	public $relating;

	/**
	 * The foreign key of the "relating" relationship.
	 *
	 * @var string
	 */
	public $relating_key;

	/**
	 * The table name of the model being resolved. Used during many-to-many eager loading.
	 *
	 * @var string
	 */
	public $relating_table;

	/**
	 * The model query instance.
	 *
	 * @var Query
	 */
	public $query;

	/**
	 * Create a new model instance and set the relationships
	 * that should be eagerly loaded.
	 *
	 * @return mixed
	 */
	public static function with()
	{
		$model = Eloquent\Factory::make(get_called_class());
		$model->includes = func_get_args();

		return $model;
	}

	/**
	 * Get a model by the primary key.
	 *
	 * @param  int  $id
	 * @return mixed
	 */
	public static function find($id)
	{
		return Eloquent\Factory::make(get_called_class())->where('id', '=', $id)->first();
	}

	/**
	 * Get an array of models from the database.
	 *
	 * @return array
	 */
	private function _get()
	{
		return Eloquent\Hydrate::from($this);
	}

	/**
	 * Get the first model result
	 *
	 * @return mixed
	 */
	private function _first()
	{
		$results = Eloquent\Hydrate::from($this->take(1));

		if (count($results) > 0)
		{
			reset($results);

			return current($results);
		}
	}

	/**
	 * Retrieve the query for a 1:1 relationship.
	 *
	 * @param  string  $model
	 * @param  string  $foreign_key
	 * @return mixed
	 */
	public function has_one($model, $foreign_key = null)
	{
		return Eloquent\Relate::has_one($model, $foreign_key, $this);
	}

	/**
	 * Retrieve the query for a 1:* relationship.
	 *
	 * @param  string  $model
	 * @param  string  $foreign_key
	 * @return mixed
	 */
	public function has_many($model, $foreign_key = null)
	{
		return Eloquent\Relate::has_many($model, $foreign_key, $this);
	}

	/**
	 * Retrieve the query for a 1:1 belonging relationship.
	 *
	 * @param  string  $model
	 * @return mixed
	 */
	public function belongs_to($model)
	{
		// -----------------------------------------------------
		// Get the calling function name.
		// -----------------------------------------------------
		list(, $caller) = debug_backtrace(false);

		return Eloquent\Relate::belongs_to($caller, $model, $this);
	}

	/**
	 * Retrieve the query for a *:* relationship.
	 *
	 * @param  string  $model
	 * @param  string  $table
	 * @return mixed
	 */
	public function has_many_and_belongs_to($model, $table = null)
	{
		return Eloquent\Relate::has_many_and_belongs_to($model, $table, $this);
	}

	/**
	 * Save the model to the database.
	 *
	 * @return bool
	 */
	public function save()
	{
		// -----------------------------------------------------
		// If the model doesn't have any dirty attributes, there
		// is no need to save it to the database.
		// -----------------------------------------------------
		if ($this->exists and count($this->dirty) == 0)
		{
			return true;
		}

		$result = Eloquent\Warehouse::put($this);

		// -----------------------------------------------------
		// The dirty attributes can be cleared after each save.
		// -----------------------------------------------------
		$this->dirty = array();

		return $result;
	}

	/**
	 * Delete a model from the database.
	 */
	public function delete($id = null)
	{
		// -----------------------------------------------------
		// If the method is being called from an existing model,
		// only delete that model from the database.
		// -----------------------------------------------------
		if ($this->exists)
		{
			return Eloquent\Warehouse::forget($this);
		}

		return $this->query->delete($id);
	}

	/**
	 * Magic method for retrieving model attributes.
	 */
	public function __get($key)
	{
		// -----------------------------------------------------
		// Check the ignored attributes first.
		// -----------------------------------------------------
		if (array_key_exists($key, $this->ignore))
		{
			return $this->ignore[$key];
		}

		// -----------------------------------------------------
		// Is the attribute actually a relationship?
		// -----------------------------------------------------
		if (method_exists($this, $key))
		{
			// -----------------------------------------------------
			// Get the query / model for the relationship.
			// -----------------------------------------------------
			$model = $this->$key();

			return ($this->relating == 'has_one' or $this->relating == 'belongs_to')
													? $this->ignore[$key] = $model->first()
													: $this->ignore[$key] = $model->get();
		}

		return (array_key_exists($key, $this->attributes)) ? $this->attributes[$key] : null;
	}

	/**
	 * Magic Method for setting model attributes.
	 */
	public function __set($key, $value)
	{
		// -----------------------------------------------------
		// Is the key actually a relationship?
		// -----------------------------------------------------
		if (method_exists($this, $key))
		{
			$this->ignore[$key] = $value;
		}
		else
		{
			$this->attributes[$key] = $value;
			$this->dirty[$key] = $value;
		}
	}

	/**
	 * Magic Method for determining if a model attribute is set.
	 */
	public function __isset($key)
	{
		return (array_key_exists($key, $this->attributes) or array_key_exists($key, $this->ignore));
	}

	/**
	 * Magic Method for unsetting model attributes.
	 */
	public function __unset($key)
	{
		unset($this->attributes[$key]);
		unset($this->ignore[$key]);
		unset($this->dirty[$key]);
	}

	/**
	 * Magic Method for handling dynamic method calls.
	 */
	public function __call($method, $parameters)
	{
		if ($method == 'get')
		{
			return $this->_get();
		}

		if ($method == 'first')
		{
			return $this->_first();
		}

		// -----------------------------------------------------
		// If the method is an aggregate function, just return
		// the aggregate value from the query.
		// -----------------------------------------------------
		if (in_array($method, array('count', 'sum', 'min', 'max', 'avg')))
		{
			return call_user_func_array(array($this->query, $method), $parameters);
		}

		// -----------------------------------------------------
		// Pass the method call to the query instance.
		// -----------------------------------------------------
		call_user_func_array(array($this->query, $method), $parameters);

		return $this;
	}

	/**
	 * Magic Method for handling dynamic static method calls.
	 */
	public static function __callStatic($method, $parameters)
	{
		$model = Eloquent\Factory::make(get_called_class());

		if ($method == 'get')
		{
			return $model->_get();
		}

		if ($method == 'first')
		{
			return $model->_first();
		}

		// -----------------------------------------------------
		// If the method is an aggregate function, just return
		// the aggregate value from the query.
		// -----------------------------------------------------
		if (in_array($method, array('count', 'sum', 'min', 'max', 'avg')))
		{
			return call_user_func_array(array($model->query, $method), $parameters);
		}

		// -----------------------------------------------------
		// Pass the method call to the query instance.
		// -----------------------------------------------------
		call_user_func_array(array($model->query, $method), $parameters);

		return $model;
	}

}