<?php defined('SYSPATH') or die('No direct access allowed.');

/**
 * This class supports Services
 *
 * @package    Service
 * @category   Models
 * @author     Deon George
 * @copyright  (c) 2009-2013 Open Source Billing
 * @license    http://dev.osbill.net/license.html
 *
 * Fields:
 * + queue: PROVISION (to be provisioned)
 */
class Model_Service extends ORM_OSB {
	// Relationships
	protected $_has_one = array(
		'service_billing'=>array('far_key'=>'account_billing_id','foreign_key'=>'id'),
	);
	protected $_has_many = array(
		'charge'=>array('far_key'=>'id'),
		'invoice_item'=>array('far_key'=>'id'),
		'invoice'=>array('through'=>'invoice_item'),
		'service_change'=>array('far_key'=>'id'),
		'service_memo'=>array('far_key'=>'id'),
	);
	protected $_belongs_to = array(
		'product'=>array(),
		'account'=>array(),
	);

	protected $_save_message = TRUE;

	// Validation rules
	public function rules() {
		$x = Arr::merge(parent::rules(), array(
			'product_id' => array(
				array('not_equal', array(':value', array(0))),
			),
		));

		return $x;
	}

	/**
	 * Filters used to format the display of values into friendlier values
	 */
	protected $_display_filters = array(
		'date_last_invoice'=>array(
			array('Config::date',array(':value')),
		),
		'date_next_invoice'=>array(
			array('Config::date',array(':value')),
		),
		'external_billing'=>array(
			array('StaticList_YesNo::get',array(':value',TRUE)),
		),
		'price_override'=>array(
			array('Currency::display',array(':value')),
		),
		'recur_schedule'=>array(
			array('StaticList_RecurSchedule::get',array(':value')),
		),
		'suspend_billing'=>array(
			array('StaticList_YesNo::get',array(':value',TRUE)),
		),
		'status'=>array(
			array('StaticList_YesNo::get',array(':value',TRUE)),
		),
	);

	protected $_nullifempty = array(
		'price_override',
	);

	protected $_form = array('id'=>'id','value'=>'service_name()');

	// Cache our calls to our plugins
	public static $plugin = array();

	/**
	 * Get the additional charges associated with this service
	 */
	public function charges($unprocessed=TRUE,$format=FALSE) {
		$result = 0;

		foreach ($this->charge_list($unprocessed) as $co)
			$result += $co->total();

		return $format ? Currency::display($result) : $result;
	}

	public function charge_list($unprocessed=FALSE) {
		$x = $this->charge;

		if ($unprocessed)
			$x->where_open()
			->where('processed','IS',NULL)
			->or_where('processed','=',FALSE)
			->where_close();

		return $x->and_where('void','IS',NULL)->find_all();
	}

	/**
	 * Display how much is due on this service
	 */
	public function due($format=FALSE) {
		$total = 0;

		foreach ($this->invoice_list(TRUE) as $io)
			$total += $io->due();

		return $format ? Currency::display($total) : $total;
	}

	public function email() {
		return ORM::factory('Email_Log')
			->where('module_id','=',$this->mid())
			->where('module_data','=',$this);
	}

	/**
	 * When does this service expire
	 */
	public function expire($format=FALSE) {
		// For plugins the plugin determins expiry
		$expire = (is_null($plugin=$this->plugin()) ? NULL : $plugin->expire());

		// If $expire is NULL, we'll use the next invoice date
		$expire = is_null($expire) ? $this->paid_to() : $expire;

		return $format ? Config::date($expire) : $expire;
	}

	/**
	 * Determine if a service expires in the next $days.
	 */
	public function expiring($days=0) {
		return time()+$days*86400 > $this->expire();
	}

	/**
	 * Display the service number
	 */
	public function id() {
		return sprintf('%05s',$this->id);
	}

	/**
	 * List invoices for this service
	 */
	public function invoice_list($due=FALSE) {
		$result = array();

		$x = $this->invoice->distinct('id');

		// If we only want due invoices, we can speed things up by only looking for unprocessed
		if ($due)
			$x->where_unprocessed();

		foreach ($x->find_all() as $io)
			if (! $due OR $io->due())
				array_push($result,$io);

		return $result;
	}

	/**
	 * Show the date we are invoiced to
	 */
	public function invoiced_to($format=FALSE) {
		$iio = $this->last_invoice_item()
			->limit(1);

		$iio->find();

		$x = (! $iio->loaded() AND $this->date_next_invoice) ? $this->date_next_invoice-86400 : ($iio->total() < 0 ? $iio->date_start-86400 : $iio->date_stop);

		return $format ? Config::date($x) : $x;
	}

	private function last_invoice_item() {
		return ORM::factory('Invoice_Item')
			->where('item_type','IN',array(0,7))
			->where('service_id','=',$this)
			->where('invoice_item.void','IS',NULL)
			->join('invoice')
				->on('invoice.id','=','invoice_item.invoice_id')
				->on('invoice.status','=',1)
			->order_by('date_stop','DESC');
	}

	/**
	 * Display the service product name
	 */
	public function name() {
		return is_null($plugin=$this->plugin()) ? $this->product->title() : $plugin->name();
	}

	/**
	 * Returns the date that an item has been paid to
	 */
	public function paid_to($format=FALSE) {
		$x = $metric = NULL;

		foreach ($this->last_invoice_item()->order_by('date_orig','DESC')->find_all() as $iio)
			if ($iio->invoice->due() == 0) {
				$x = $iio;
				$metric = ($iio->total() < 0) ? $iio->date_start-86400 : $iio->date_stop;
				break;
			}

		return $format ? ($x ? Config::date($metric) : '&nbsp;') : ($x ? $metric : NULL);
	}

	/**
	 * Returns TRUE of this service has a planned change
	 */
	public function pending_change() {
		return $this->service_change()->loaded() ? TRUE : FALSE;
	}

	/**
	 * Show the product that this service will be changed to
	 */
	public function pending_product() {
		return $this->service_change()->product;
	}

	/**
	 * Return the object of the product plugin
	 */
	public function plugin($type='') {
		if (! $this->product->prod_plugin_file)
			return NULL;

		if (! isset(Model_Service::$plugin[$this->id]))
			Model_Service::$plugin[$this->id] = ORM::factory(Kohana::classname(sprintf('Service_Plugin_%s',$this->product->prod_plugin_file)),array('service_id'=>$this->id));

		return $type ? Model_Service::$plugin[$this->id]->$type : Model_Service::$plugin[$this->id];
	}

	/**
	 * Enable the plugin to store data
	 */
	public function plugin_edit() {
		return (is_null($x = $this->plugin())) ? NULL : $x->render_edit();
	}

	public function revenue($annual=FALSE) {
		$multiple = $annual ? Period::multiple($this->recur_schedule) : 1;

		if ($this->suspend_billing)
			$multiple = 0;

		return $this->price(TRUE)*$multiple;
	}

	public function service_change() {
		return $this->service_change->where_active()->where_open()->and_where('complete','!=',1)->or_where('complete','IS',null)->where_close()->find();
	}

	/**
	 * Return a descriptive name for this service
	 */
	public function service_name() {
		return is_null($x=$this->plugin()) ? $this->name() : $x->service_name();
	}

	/**
	 * Return the service charge
	 */
	public function price($tax=FALSE,$format=FALSE,$original=FALSE) {
		$x = $this->product->keyget('price_group',$this->recur_schedule);

		// @todo This index shouldnt be hard coded.
		$p = ! is_null($this->price) ? $this->price : (isset($x[$this->price_group]['price_base']) ? $x[$this->price_group]['price_base'] : NULL);

		if (! $original AND ! is_null($this->price_override))
			$p = $this->price_override;

		if ($tax)
			$p = Tax::add($p);

		return $format ? Currency::display($p) : $p;
	}

	/**
	 * Render some details for specific calls, eg: invoice
	 */
	public function details($type) {
		$plugin = $this->plugin();

		switch ($type) {
			case 'invoice_detail_items':
				return is_null($plugin) ? array() : $plugin->_details($type);

			default:
				throw new Kohana_Exception('Unkown detail request :type',array(':type'=>$type));
		}
	}

	public function service_view() {
		return ! is_object($x=$this->plugin()) ? HTML::nbsp('') : $x->render_view();
	}

	public function transactions() {
		return $this->invoice_item->order_by('date_start','ASC')->order_by('product_id','ASC')->order_by('date_stop','ASC');
	}

	/** LIST FUNCTIONS **/

	/**
	 * Search for services matching a term
	 */
	public function list_autocomplete($term,$index,$value,array $label,array $limit=array(),array $options=NULL) {
		// We only show service numbers.
		if (! is_numeric($term) AND (! $limit))
			return array();

		$ao = Auth::instance()->get_user();

		$this->clear();
		$this->where_active();

		// Build our where clause
		$this->where_open()
			->where('id','like','%'.$term.'%')
			->where_close();

		// Restrict results to authorised accounts
		array_push($limit,array('account_id','IN',$ao->RTM->customers($ao->RTM)));

		return parent::list_autocomplete($term,$index,$value,$label,$limit,$options);
	}

	/**
	 * List all products by their plugin type
	 */
	public function list_byplugin($plugin) {
		return $this
			->join('product')
			->on($this->table_name().'.site_id','=','product.site_id') // @todo This should be automatic
			->on($this->table_name().'.product_id','=','product.id')
			->where('prod_plugin_file','=',$plugin)
			->and_where('service.status','=',TRUE)
			->find_all();
	}

	public function list_bylistgroup($cat) {
		$result = array();

		foreach ($this->list_active() as $so)
			if ($so->product->prod_plugin_file == $cat)
				array_push($result,$so);

		Sort::MASort($result,'service_name()');
		return $result;
	}

	/**
	 * List services expiring
	 */
	public function list_expiring($days=14) {
		$result = array();

		foreach ($this->list_active() as $so)
			if ($so->expiring($days))
				array_push($result,$so);

		return $result;
	}

	/**
	 * List services that need to be billed.
	 *
	 * @param $days int Additional number of days to add to the query, above the module config.
	 */
	public function list_invoicesoon($days=0) {
		return $this->_where_active()
			->where_open()->where('suspend_billing','IS',NULL)->or_where('suspend_billing','=','0')->where_close()
			->where('date_next_invoice','<',time()+(ORM::factory('Invoice')->config('GEN_DAYS')+$days)*86400)
			->find_all();
	}

	/**
	 * List services that need to be provisioned
	 */
	public function list_provision() {
		return $this->_where_active()->where('queue','=','PROVISION');
	}
}
?>
