<?php
/*************************************************************************************/
/*      This file is part of the Thelia package.                                     */
/*                                                                                   */
/*      Copyright (c) OpenStudio                                                     */
/*      email : dev@thelia.net                                                       */
/*      web : http://www.thelia.net                                                  */
/*                                                                                   */
/*      For the full copyright and license information, please view the LICENSE.txt  */
/*      file that was distributed with this source code.                             */
/*************************************************************************************/

namespace Thelia\Action;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Condition\ConditionFactory;
use Thelia\Condition\Implementation\ConditionInterface;
use Thelia\Core\Event\Coupon\CouponConsumeEvent;
use Thelia\Core\Event\Coupon\CouponCreateOrUpdateEvent;
use Thelia\Core\Event\Order\OrderEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Core\HttpFoundation\Request;
use Thelia\Coupon\CouponFactory;
use Thelia\Coupon\CouponManager;
use Thelia\Condition\ConditionCollection;
use Thelia\Coupon\Type\CouponInterface;
use Thelia\Model\Coupon as CouponModel;
use Thelia\Model\CouponQuery;
use Thelia\Model\OrderCoupon;

/**
 * Process Coupon Events
 *
 * @package Coupon
 * @author  Guillaume MOREL <gmorel@openstudio.fr>
 *
 */
class Coupon extends BaseAction implements EventSubscriberInterface
{
    /**
     * @var \Thelia\Core\HttpFoundation\Request
     */
    protected $request;

    /** @var CouponFactory $couponFactory */
    protected $couponFactory;

    /** @var CouponManager $couponManager */
    protected $couponManager;

    /** @var ConditionInterface $noConditionRule */
    protected $noConditionRule;

    /** @var ConditionFactory $conditionFactory */
    protected $conditionFactory;

    public function __construct(Request $request,
        CouponFactory $couponFactory, CouponManager $couponManager,
        ConditionInterface $noConditionRule, ConditionFactory $conditionFactory)
    {
        $this->request = $request;
        $this->couponFactory = $couponFactory;
        $this->couponManager = $couponManager;
        $this->noConditionRule = $noConditionRule;
        $this->conditionFactory = $conditionFactory;
    }

     /**
     * Occurring when a Coupon is about to be created
     *
     * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon
     */
    public function create(CouponCreateOrUpdateEvent $event)
    {
        $coupon = new CouponModel();

        $this->createOrUpdate($coupon, $event);
    }

    /**
     * Occurring when a Coupon is about to be updated
     *
     * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon
     */
    public function update(CouponCreateOrUpdateEvent $event)
    {
        $coupon = $event->getCouponModel();

        $this->createOrUpdate($coupon, $event);
    }

    /**
     * Occurring when a Coupon condition is about to be updated
     *
     * @param CouponCreateOrUpdateEvent $event Event creation or update Coupon condition
     */
    public function updateCondition(CouponCreateOrUpdateEvent $event)
    {
        $modelCoupon = $event->getCouponModel();

        $this->createOrUpdateCondition($modelCoupon, $event);
    }

    /**
     * Occurring when a Coupon condition is about to be consumed
     *
     * @param CouponConsumeEvent $event Event consuming Coupon
     */
    public function consume(CouponConsumeEvent $event)
    {
        $totalDiscount = 0;
        $isValid = false;

        /** @var CouponInterface $coupon */
        $coupon = $this->couponFactory->buildCouponFromCode($event->getCode());

        if ($coupon) {
            $isValid = $coupon->isMatching();
            if ($isValid) {
                $consumedCoupons = $this->request->getSession()->getConsumedCoupons();

                if (!isset($consumedCoupons) || !$consumedCoupons) {
                    $consumedCoupons = array();
                }

                if (!isset($consumedCoupons[$event->getCode()])) {
                    // Prevent accumulation of the same Coupon on a Checkout
                    $consumedCoupons[$event->getCode()] = $event->getCode();

                    $this->request->getSession()->setConsumedCoupons($consumedCoupons);

                    $totalDiscount = $this->couponManager->getDiscount();

                    $this->request
                        ->getSession()
                        ->getCart()
                        ->setDiscount($totalDiscount)
                        ->save();
                    $this->request
                        ->getSession()
                        ->getOrder()
                        ->setDiscount($totalDiscount)
                        // ->save()
                    ;
                }
            }
        }

        $event->setIsValid($isValid);
        $event->setDiscount($totalDiscount);
    }

    public function updateOrderDiscount($event)
    {

        $discount = $this->couponManager->getDiscount();

        $this->request
            ->getSession()
            ->getCart()
            ->setDiscount($discount)
            ->save();
        $this->request
            ->getSession()
            ->getOrder()
            ->setDiscount($discount);
    }

    /**
     * Call the Model and delegate the create or delete action
     * Feed the Event with the updated model
     *
     * @param CouponModel               $coupon Model to save
     * @param CouponCreateOrUpdateEvent $event  Event containing data
     */
    protected function createOrUpdate(CouponModel $coupon, CouponCreateOrUpdateEvent $event)
    {
        $coupon->setDispatcher($event->getDispatcher());

        // Set default condition if none found
        /** @var ConditionInterface $noConditionRule */
        $noConditionRule = $this->noConditionRule;
        /** @var ConditionFactory $conditionFactory */
        $conditionFactory = $this->conditionFactory;
        $couponRuleCollection = new ConditionCollection();
        $couponRuleCollection[] = $noConditionRule;
        $defaultSerializedRule = $conditionFactory->serializeConditionCollection(
            $couponRuleCollection
        );

        $coupon->createOrUpdate(
            $event->getCode(),
            $event->getTitle(),
            $event->getEffects(),
            $event->getServiceId(),
            $event->isRemovingPostage(),
            $event->getShortDescription(),
            $event->getDescription(),
            $event->isEnabled(),
            $event->getExpirationDate(),
            $event->isAvailableOnSpecialOffers(),
            $event->isCumulative(),
            $event->getMaxUsage(),
            $defaultSerializedRule,
            $event->getLocale()
        );

        $event->setCouponModel($coupon);
    }

    /**
     * Call the Model and delegate the create or delete action
     * Feed the Event with the updated model
     *
     * @param CouponModel               $coupon Model to save
     * @param CouponCreateOrUpdateEvent $event  Event containing data
     */
    protected function createOrUpdateCondition(CouponModel $coupon, CouponCreateOrUpdateEvent $event)
    {
        $coupon->setDispatcher($event->getDispatcher());

        /** @var ConditionFactory $conditionFactory */
        $conditionFactory = $this->conditionFactory;

        $coupon->createOrUpdateConditions(
            $conditionFactory->serializeConditionCollection($event->getConditions()),
            $event->getLocale()
        );

        $event->setCouponModel($coupon);
    }

    /**
     * @param \Thelia\Core\Event\Order\OrderEvent $event
     */
    public function testFreePostage(OrderEvent $event)
    {
        if ($this->couponManager->isCouponRemovingPostage()) {
            $order = $event->getOrder();

            $order->setPostage(0);

            $event->setOrder($order);

            $event->stopPropagation();
        }
    }

    /**
     * @param \Thelia\Core\Event\Order\OrderEvent $event
     */
    public function afterOrder(OrderEvent $event)
    {
        $consumedCoupons = $this->request->getSession()->getConsumedCoupons();

        if (is_array($consumedCoupons)) {
            foreach ($consumedCoupons as $couponCode) {
                $couponQuery = CouponQuery::create();
                $couponModel = $couponQuery->findOneByCode($couponCode);
                $couponModel->setLocale($this->request->getSession()->getLang()->getLocale());

                /* decrease coupon quantity */
                $this->couponManager->decrementQuantity($couponModel);

                /* memorize coupon */
                $orderCoupon = new OrderCoupon();
                $orderCoupon->setOrder($event->getOrder())
                    ->setCode($couponModel->getCode())
                    ->setType($couponModel->getType())
                    ->setAmount($couponModel->getAmount())

                    ->setTitle($couponModel->getTitle())
                    ->setShortDescription($couponModel->getShortDescription())
                    ->setDescription($couponModel->getDescription())

                    ->setExpirationDate($couponModel->getExpirationDate())
                    ->setIsCumulative($couponModel->getIsCumulative())
                    ->setIsRemovingPostage($couponModel->getIsRemovingPostage())
                    ->setIsAvailableOnSpecialOffers($couponModel->getIsAvailableOnSpecialOffers())
                    ->setSerializedConditions($couponModel->getSerializedConditions())
                ;
                $orderCoupon->save();
            }
        }

        $this->request->getSession()->setConsumedCoupons(array());
    }

    /**
     * Returns an array of event names this subscriber listens to.
     *
     * The array keys are event names and the value can be:
     *
     *  * The method name to call (priority defaults to 0)
     *  * An array composed of the method name to call and the priority
     *  * An array of arrays composed of the method names to call and respective
     *    priorities, or 0 if unset
     *
     * For instance:
     *
     *  * array('eventName' => 'methodName')
     *  * array('eventName' => array('methodName', $priority))
     *  * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
     *
     * @return array The event names to listen to
     *
     * @api
     */
    public static function getSubscribedEvents()
    {
        return array(
            TheliaEvents::COUPON_CREATE => array("create", 128),
            TheliaEvents::COUPON_UPDATE => array("update", 128),
            TheliaEvents::COUPON_CONSUME => array("consume", 128),
            TheliaEvents::COUPON_CONDITION_UPDATE => array("updateCondition", 128),
            TheliaEvents::ORDER_SET_POSTAGE => array("testFreePostage", 256),
            TheliaEvents::ORDER_BEFORE_PAYMENT => array("afterOrder", 128),
            TheliaEvents::CART_ADDITEM => array("updateOrderDiscount", 10),
            TheliaEvents::CART_UPDATEITEM => array("updateOrderDiscount", 10),
            TheliaEvents::CART_DELETEITEM => array("updateOrderDiscount", 10),
        );
    }
}
