<?php
/**
 * Item line in the Invoice table, relates to invoice_item table
 * 
 * {autogenerated}
 * @property int $invoice_item_id 
 * @property int $invoice_id 
 * @property int $item_id 
 * @property string $item_type 
 * @property string $item_title 
 * @property string $item_description 
 * @property int $qty 
 * @property double $first_price 
 * @property double $first_discount 
 * @property double $first_tax 
 * @property double $first_total 
 * @property double $first_shipping 
 * @property string $first_period 
 * @property int $rebill_times 
 * @property double $second_price 
 * @property double $second_discount 
 * @property double $second_tax 
 * @property double $second_total 
 * @property double $second_shipping 
 * @property string $second_period 
 * @property string $currency 
 * @property string $tax_group
 * @property int $is_countable 
 * @property int $variable_qty
 * @property int $is_tangible 
 * @property int $billing_plan_id 
 * @property string $billing_plan_data 
 * @property string $option1 
 * @property string $option2 
 * @property string $option3 
 * @see Am_Table
 * @package Am_Invoice
 */
class InvoiceItem extends Am_Record 
{
    /** @var IProduct */
    protected $_product;
    /**
     * Constant array of fields to copy from product to invoice on add
     * @var array
     */
    protected static $_productInvoiceFields = array(
        'productId'     => 'item_id',
        'title'         => 'item_title',
        'type'          => 'item_type',
        'description'   => 'item_description',

        'firstPrice'         => 'first_price',
        'firstPeriod'   => 'first_period',

        'rebillTimes'   => 'rebill_times',

        'secondPrice'   => 'second_price',
        'secondPeriod' => 'second_period',

        'currencyCode'    => 'currency',

        'taxGroup'         => 'tax_group',
        'isTangible'    => 'is_tangible',
        'isCountable'   => 'is_countable',
        'isVariableQty' => 'variable_qty',
        'billingPlanId' => 'billing_plan_id',
    );
    /**
     * Set fields from a product record
     * @return InvoiceItem provides fluent interface
     */
    public function copyProductSettings(IProduct $p) {
        foreach (self::$_productInvoiceFields as $kp => $ki) {
            $this->$ki = call_user_func(array($p, 'get'.ucfirst($kp)));
            if (strpos($kp, 'is')===0)
                $this->$ki = (bool)$this->$ki;
        }
        if (! $p->getRebillTimes()) {
            $this->second_price  = null;
            $this->second_period = null;
        }
        
        $options = $p->getOptions();
        if ($options) $this->option1 = array_shift($options);
        if ($options) $this->option2 = array_shift($options);
        if ($options) $this->option3 = array_shift($options);

        $this->setBillingPlanData($p->getBillingPlanData());

        $this->_product = $p;
        
        return $this;
    }
    /**
     * Add given number to qty
     * if !is_countable, always set to 1
     */
    function add($qty) {
        $qty = intval($qty);
        if ($qty < 0)
            throw new Am_Exception_InvalidRequest("_qty_is_negative_in_InvoiceItem_add #". (int)$qty);
        $item = $this->tryLoadProduct();
        if (empty($this->qty))
            $this->qty = 0;
        $this->qty = $item->addQty($qty, $this->qty);
    }
    /**
     * try to load related product
     * @return IProduct|null
     */
    public function tryLoadProduct(){
        if (empty($this->_product))
        {
            $this->_product = $this->getTable()->loadItem($this->item_type, $this->item_id, false, @$this->billing_plan_id);
        }
        return $this->_product;
    }
    
    /**
     * Do arifmetic operations to calculate subtotal and total
     */
    public function _calculateTotal(){
        $this->first_total = moneyRound($this->first_price * $this->qty)
                         - $this->first_discount
                         + $this->first_shipping
                         + $this->first_tax;
        $this->second_total = moneyRound($this->second_price * $this->qty)
                         - $this->second_discount
                         + $this->second_shipping
                         + $this->second_tax;
    }

    /**
     * Add access period for current product based on information from incoming paysystem transaction
     * @throws Am_Exception_Db_NotUnique
     */
    public function addAccessPeriod($isFirstPayment, Invoice $invoice, Am_Paysystem_Transaction_Interface $transaction, $beginDate, $invoicePaymentId){
        if ($this->item_type != 'product')
            return; // if that is not a product then no access
        $a = $this->getDi()->accessRecord;
        $a->setDisableHooks(true);
        $a->begin_date = $beginDate;
        $p = new Am_Period($isFirstPayment ? $this->first_period : $this->second_period);
        $a->invoice_id = $this->invoice_id;
        $recurringType = $transaction->getRecurringType();
        if (in_array($recurringType, array(Am_Paysystem_Abstract::REPORTS_EOT, Am_Paysystem_Abstract::REPORTS_NOTHING)))
            $a->expire_date = Am_Period::RECURRING_SQL_DATE;
        else
            $a->expire_date = $p->addTo($a->begin_date);
        $a->product_id = $this->item_id;
        $a->user_id = $invoice->user_id;
        $a->transaction_id = $transaction->getUniqId();
        $a->invoice_payment_id = $invoicePaymentId;
        $a->insert();
    }
    function getFirstSubtotal()
    {
        return moneyRound($this->first_price * $this->qty);
    }
    function getFirstTotal()
    {
        return $this->getFirstSubtotal() - $this->first_discount;
    }
    function getSecondSubtotal()
    {
        return moneyRound($this->second_price * $this->qty);
    }
    function getSecondTotal()
    {
        return $this->getSecondSubtotal() - $this->second_discount;
    }
    function setBillingPlanData(array $data)
    {
        $this->billing_plan_data = serialize((array)$data);
    }
    /**
     * @return array|mixed entire array or requested $key value
     */
    function getBillingPlanData($key = null)
    {
        $arr = empty($this->billing_plan_data) ? array() :
            unserialize($this->billing_plan_data);
        if ($key === null) return $arr;
        return empty($arr[$key]) ? null : $arr[$key];
    }
    /**
     * Replace product in already existing invoice
     * throw exception in case of error
     */
    function replaceProduct($productId, $billingPlanId)
    {
        $old_product_id = $this->item_id;
        
        $p = $this->getDi()->productTable->load((int)$productId);
        $this->item_id = $p->pk();
        $this->item_description = $p->description;
        $this->item_title = $p->title;
        $this->billing_plan_id = (int)$billingPlanId;
        $this->update();
        // replace access records
        $accessRecords = $this->getDi()->accessTable->findBy(array(
            'invoice_id' => $this->invoice_id,
            'product_id' => $old_product_id, 
        ));
        foreach ($accessRecords as $access)
        {
            $access->product_id = $productId;
            $access->update();
        }
        $this->getDi()->invoiceTable->load($this->invoice_id)->getUser()->checkSubscriptions();
    }
}

class InvoiceItemTable extends Am_Table {
    protected $_key = 'invoice_item_id';
    protected $_table = '?_invoice_item';
    protected $_recordClass = 'InvoiceItem';
    
    protected $itemTypes = array(
        'product' => "productTable",
    );
    
    function getItemLoaders()
    {
        return $this->itemTypes;
    }
    function registerItemLoader($item_type, $tableObject)
    {
        $this->itemTypes[$item_type] = $class;
    }
    /**
     * Load reference to class 
     */
    function loadItem($item_type, $item_id, $throwExceptionIfNotFound = false, $billing_plan_id = null)
    {
        if (empty($this->itemTypes[$item_type]))
            throw new Am_Exception_InternalError("No loader defined for invoice item type: [$item_type]");
        $ret = $this->getDi()->getService($this->itemTypes[$item_type])->load($item_id, $throwExceptionIfNotFound);
        if ($billing_plan_id > 0)
        {
           $ret->setBillingPlan($this->getDi()->billingPlanTable->load($billing_plan_id));
        }
        return $ret;
    }
}