<?php

namespace Drupal\basket;

use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;

class BasketCart{

    protected $Basket;
    protected $SID = NULL;
    protected $SID_create = FALSE;
    protected $getItem;
    protected $setItem;
    protected $getCount = 0;
    protected $getItemsInBasket = NULL;
    protected $getTotalSum = NULL;
    protected $loadItem = NULL;
    protected $getItemPrice = NULL;
    protected $getItemDiscount = NULL;
    protected $getItemImg = NULL;
    protected $getNodeEntity = NULL;
    protected $getCurrencyName;

    const MAX_INT = 2147483647;

    public function __construct(){
        if(!isset($this->Basket)){
            $this->Basket = \Drupal::service('Basket');
        }
    }
    public function __get($name){
        if(isset($this->{$name})){
            return $this->{$name};
        }
        return NULL;
    }
    /*
    The processing process of adding goods to the cart
    */
    public function add($add){
        // ---
        if(empty($add['nid']))  return FALSE;
        // ---
        $this->SID_create = TRUE;
        $params = $this->encodeParams(!empty($add['params']) ? $add['params'] : []);
        $this->getItem = $this->getItem($add['nid'], $params);
        // ---
        $addCount = !empty($add['count']) && $add['count'] > 0 ? $add['count'] : 1;
        if(!empty($this->getItem->id)){
            $entityAdd = \Drupal::entityTypeManager()->getStorage('node')->load($add['nid']);
            if(!empty($entityAdd)){
                $nodeTypes = $this->Basket->getNodeTypes(FALSE);
                if(!empty($nodeTypes[$entityAdd->bundle()]->extra_fields)){
                    $extraFields = unserialize($nodeTypes[$entityAdd->bundle()]->extra_fields);
                    if(!empty($extraFields['add_count_sum']['on'])){
                        $addCount = $addCount+$this->getItem->count;
                    }
                }
            }
        }

        $this->setItem = (object)[
            'id'        => !empty($this->getItem->id) ? $this->getItem->id : NULL,
            'sid'       => $this->getSid(),
            'nid'       => $add['nid'],
            'count'     => $addCount,
            'add_time'  => time(),
            'all_params'=> $params
        ];
        $update_array = (array)$this->setItem;
        if ($update_array['count'] >= $this::MAX_INT){
            $update_array['count'] = $this::MAX_INT;
        }
        unset($update_array['id']);
        if(empty($this->getItem->id)){
            $this->setItem->id = \Drupal::database()->insert('basket')
                                            ->fields($update_array)
                                            ->execute();
        } else {
            $this->setItem->id = \Drupal::database()->update('basket')
                                            ->fields($update_array)
                                            ->condition('id', $this->getItem->id)
                                            ->execute();
        }
        if(isset($GLOBALS['BasketCartSetItemID'])){
            $GLOBALS['BasketCartSetItemID'] = $this->setItem->id;
        }
        // Remove basket items cache
        $this->getItemsInBasket = NULL;
        // Hook cart add
        $cartItem = !empty($this->getItem->id) ? $this->loadItem($this->getItem->id) : $this->loadItem($this->setItem->id);
        \Drupal::moduleHandler()->invokeAll('basket_cart', [$cartItem, 'add']);
        // ---
    }
    /*
    Search for a product in the cart
    */
    public function getItem($nid, $params){
        return \Drupal::database()->select('basket', 'b')
                            ->fields('b')
                            ->condition('b.sid', $this->getSid())
                            ->condition('b.nid', $nid)
                            ->condition('b.all_params', $params)
                            ->execute()->fetchObject();
    }
    /*
    The process of updating the product to the cart
    */
    public function UpdateCount($update){
        if(empty($update['update_id'])) return FALSE;
        if(empty($update['count'])) return FALSE;
        $this->SID_create = FALSE;
        if ($update['count'] >= $this::MAX_INT){
            $update['count'] = $this::MAX_INT;
        }
        \Drupal::database()->update('basket')
                    ->fields([
                        'count'     => $update['count'] > 0 ? $update['count'] : 1
                    ])
                    ->condition('sid', $this->getSid())
                    ->condition('id', $update['update_id'])
                    ->execute();

        // Remove basket items cache
        $this->getItemsInBasket = NULL;
        // Hook cart updateCount
        $cartItem = $this->loadItem($update['update_id']);
        \Drupal::moduleHandler()->invokeAll('basket_cart', [$cartItem, 'updateCount']);
        // ---
    }
    /**
     * The process of removing goods in the basket
     *
     * @param int | array $delete - item to delete
     *
     * Examples:
     * \Drupal::service('Basket')->Cart()->loadItem(12); // API, recomended
     * \Drupal::service('Basket')->Cart()->loadItem(['delete_item' => 12]); // для зворотньої сумісності
     */
    public function deleteItem($delete){
        if ( is_array($delete) && isset($delete['delete_item']) ){
            $delete = $delete['delete_item'];
        }
        if( !is_numeric($delete) ){
            return FALSE;
        }

        $this->SID_create = FALSE;
        $sid = $this->getSid();

        // Hook cart updateCount
        $cartItem = $this->loadItem($delete);
        \Drupal::moduleHandler()->invokeAll('basket_cart', [$cartItem, 'delete']);
        // ---

        if ( !empty($sid) ){ // Якщо анонім, то SID не створюється і повернеться NULL
            \Drupal::database()->delete('basket')
                        ->condition('sid', $sid)
                        ->condition('id',  $delete)
                        ->execute();

            // Remove basket items cache
            $this->getItemsInBasket = NULL;
        }
    }
    /*
     * Update
     * @param stdClass $item
     */
    public function updateItem($item){
        if( empty($item->nid) )  {
            return FALSE;
        }
        $params = $this->encodeParams(!empty($item->all_params) ? $item->all_params : []);
        $update_array = [
            'sid'       => $this->getSid(),
            'nid'       => $item->nid,
            'count'     => !empty($item->count) ? $item->count : 1,
            'all_params'=> $params
        ];

        $id = \Drupal::database()->update('basket')
                                        ->fields($update_array)
                                        ->condition('id', $item->id)
                                        ->execute();

        // Remove basket items cache
        $this->getItemsInBasket = NULL;

        return $id;
    }
    /*
    The process of returning parameters popup after adding to the basket
    */
    public function getPopupInfo(){
        $info = [
            'popup_title'       => $this->Basket->Translate()->t('Item successfully added'),
            'links'             => [
                'basket'            => [
                    'text'              => $this->Basket->Translate()->t('Checkout'),
                    'attributes'        => new Attribute([
                        'href'              => Url::fromRoute('basket.pages', [
                            'page_type'         => 'view'
                        ])->toString(),
                        'class'             => ['button', 'basket-popup-link']
                    ])
                ],
                'close'             => [
                    'text'              => $this->Basket->Translate()->t('Continue shopping'),
                    'attributes'        => new Attribute([
                        'href'              => 'javascript:void(0);',
                        'class'             => ['button', 'basket-popup-link', 'close-link'],
                        'onclick'           => \Drupal::service('BasketPopup')->getCloseOnclick()
                    ])
                ]
            ],
            'getItem'           => $this->getItem,
            'setItem'           => $this->setItem
        ];
        // Alter
        \Drupal::moduleHandler()->alter('basket_add_popup', $info);
        // ---
        return $info;
    }
    /*
    Quantity in the basket
    */
    public function getCount(){
        if(!empty($this->getSid())){
            $query = \Drupal::database()->select('basket', 'b');
            $query->addExpression('SUM(b.count)', 'count');
            $query->condition('b.sid', $this->getSid());
            // ---
            $getNodeTypes = \Drupal::service('Basket')->getNodeTypes();
            if(!empty($getNodeTypes)){
                $get_types = [];
                foreach ($getNodeTypes as $getNodeType){
                    $get_types[$getNodeType->type] = $getNodeType->type;
                }
                $query->innerJoin('node_field_data', 'n', 'n.nid = b.nid');
                $query->condition('n.type', $get_types, 'in');
                $query->condition('n.status', 1);
                $query->condition('n.default_langcode', 1);
            }
            // ---
            $getCount = $query->execute()->fetchField();
        }
        $this->getCount = !empty($getCount) ? $getCount : 0;
        return $this->getCount;
    }
    /*
    The total amount of orders basket
    */
    public function getTotalSum($notDiscount = FALSE, $notDelivety = FALSE){
        if ( isset($this->getTotalSum[$notDiscount?1:0][$notDelivety?1:0]) ){
            return $this->getTotalSum[$notDiscount?1:0][$notDelivety?1:0];
        }
        $getTotalSum = 0;
        if(!empty($this->getItemsInBasket())){
            $getItemsInBasket = $this->getItemsInBasket();
            foreach ($getItemsInBasket as $row){
                $getItemPrice = $this->getItemPrice($row);
                if(empty($notDiscount)){
                    $getItemDiscount = $this->getItemDiscount($row);
                    if(!empty($getItemDiscount)){
                        $getItemPrice = $getItemPrice-($getItemPrice/100*$getItemDiscount);
                    }
                }
                $getTotalSum += $getItemPrice*$row->count;
            }
            // Add delivery
            if(empty($notDelivety)){
                $deliveryInfo = \Drupal::service('BasketDelivery')->getDeliveryInfo();
                if(!empty($deliveryInfo['sum']) && !empty($deliveryInfo['isPay'])){
                    $getTotalSum += $deliveryInfo['sum'];
                }
            }
            // Alter
            \Drupal::moduleHandler()->alter('basket_getTotalSum', $getTotalSum, $getItemsInBasket, $notDiscount, $notDelivety);
            // End alter
        }
        $this->getTotalSum[$notDiscount?1:0][$notDelivety?1:0] = $getTotalSum;
        return $getTotalSum;
    }
    public function getPayInfo(){
        $info = [
            'price'     => $this->getTotalSum(),
            'currency'  => $this->Basket->Currency()->getPayCurrency()
        ];
        $cur = $this->Basket->Currency()->getCurrent();
        $this->Basket->Currency()->PriceConvert($info['price'], $cur, FALSE, $info['currency']);
        $info['currency'] = $this->Basket->Currency()->load($info['currency']);
        // Alter
        \Drupal::moduleHandler()->alter('basket_getPayInfo', $info);
        // End alter
        return $info;
    }
    /*
    Receipt of item cost
    */
    public function getItemPrice($row, $returnInfo = FALSE, $keyPriceField = 'MIN'){
        if(is_array($row) && !empty($row['id'])){
            $row = $this->loadItem($row['id']);
        }
        if(!empty($row)){
            $key = implode('-', array($row->nid, $row->id, $keyPriceField));
            if(!isset($this->getItemPrice[$key])){
                $price = NULL;
                // Alter
                \Drupal::moduleHandler()->alter('basket_getItemPrice', $price, $row);
                // Is empty price
                if(is_null($price) && !empty($this->getNodeEntity($row->nid))){
                    $getInfo = \Drupal\basket\Query\BasketGetNodePriceQuery::getNodePriceMIN($this->getNodeEntity($row->nid), $keyPriceField);
                    if($returnInfo){
                        return $getInfo;
                    }
                    if(!empty($getInfo->priceConvert)){
                        $price = $getInfo->priceConvert;
                    }
                }
                // ---
                $this->getItemPrice[$key] = $price;
            }
            return round($this->getItemPrice[$key], 2);
        }
        return 0;
    }
    public function loadItem($id){
        if(!isset($this->loadItem[$id])){
            $this->loadItem[$id] = \Drupal::database()->select('basket', 'b')
                                ->fields('b')
                                ->condition('b.id', $id)
                                ->execute()->fetchObject();
            if(!empty($this->loadItem[$id]->all_params)){
                $this->loadItem[$id]->all_params = $this->decodeParams($this->loadItem[$id]->all_params);
            }
        }
        return $this->loadItem[$id];
    }
    /*
    Receipt of item discount
    */
    public function getItemDiscount($row){
        if(is_array($row) && !empty($row['id'])){
            $row = $this->loadItem($row['id']);
        }
        if(!empty($row)){
            if(!isset($this->getItemDiscount[$row->nid.'-'.$row->id])){
                $this->getItemDiscount[$row->nid.'-'.$row->id] = NULL;
                // Alter
                \Drupal::moduleHandler()->alter('basket_getItemDiscount', $this->getItemDiscount[$row->nid.'-'.$row->id], $row);
                // --
                if(is_null($this->getItemDiscount[$row->nid.'-'.$row->id])){
                    $discounts = \Drupal::service('BasketDiscount')->geDiscounts($row);
                    $this->getItemDiscount[$row->nid.'-'.$row->id] = max($discounts);
                }
            }
            return $this->getItemDiscount[$row->nid.'-'.$row->id];
        }
        return 0;
    }
    /*
    Getting a picture of the item
    */
    public function getItemImg($row){
        if(is_array($row) && !empty($row['id'])){
            $row = $this->loadItem($row['id']);
        }
        if(!empty($row)){
            if(!isset($this->getItemImg[$row->nid.'-'.$row->id])){
                $fid = 0;
                // Alter
                \Drupal::moduleHandler()->alter('basket_getItemImg', $fid, $row);
                // Is empty FID
                if(empty($fid) && !empty($this->getNodeEntity($row->nid))){
                    $fid = \Drupal\basket\Query\BasketGetNodeImgQuery::getNodeImgFirst($this->getNodeEntity($row->nid));
                }
                $this->getItemImg[$row->nid.'-'.$row->id] = !empty($fid) ? $fid : 0;
            }
            return $this->getItemImg[$row->nid.'-'.$row->id];
        }
        return 0;
    }
    /*
    List of items added to shopping cart
    */
    public function getItemsInBasket(){
        if(!empty($this->getSid()) && empty($this->getItemsInBasket) ){
            $query = \Drupal::database()->select('basket', 'b');
            $query->fields('b');
            $query->condition('b.sid', $this->getSid());
            // ---
            $getNodeTypes = \Drupal::service('Basket')->getNodeTypes();
            if(!empty($getNodeTypes)){
                $get_types = [];
                foreach ($getNodeTypes as $getNodeType){
                    $get_types[$getNodeType->type] = $getNodeType->type;
                }
                $query->innerJoin('node_field_data', 'n', 'n.nid = b.nid');
                $query->condition('n.type', $get_types, 'in');
                $query->condition('n.status', 1);
                $query->condition('n.default_langcode', 1);
            }
            // ---
            $query->orderBy('b.add_time', 'DESC');
            $this->getItemsInBasket = $query->execute()->fetchAll();
            if(!empty($this->getItemsInBasket)){
                foreach ($this->getItemsInBasket as &$row){
                    $row->all_params = $this->decodeParams($row->all_params);
                }
            }
        }
        if(!empty($GLOBALS['replaceGetItemsInBasket'])){
            $this->getItemsInBasket = $GLOBALS['replaceGetItemsInBasket'];
        }
        return $this->getItemsInBasket;
    }
    /*
    Current currency name
    */
    public function getCurrencyName(){
        if(!isset($this->getCurrencyName)){
            $Currency = \Drupal::service('Basket')->Currency();
            $this->getCurrencyName = $Currency->load($Currency->getCurrent())->name;
        }
        return $this->getCurrencyName;
    }
    /*
    Unique user ID in the cart
    */
    public function getSid(){
        if(!isset($this->SID)){
            if(!\Drupal::currentUser()->isAnonymous()){
                $this->SID = \Drupal::currentUser()->id();
            } else {
                if(empty($_SESSION['basket_user_sid']) && !empty($this->SID_create)) $_SESSION['basket_user_sid'] = \Drupal::service('session')->getId();
                $this->SID = !empty($_SESSION['basket_user_sid']) ? $_SESSION['basket_user_sid'] : NULL;
            }
        }
        return $this->SID;
    }
    public function clearAll(){
        if(!empty($this->getSid())){
            \Drupal::database()->delete('basket')
                        ->condition('sid', $this->getSid())
                        ->execute();
        }
    }
    public function MovingItems($uid){
        if(!empty($_SESSION['basket_user_sid'])){
            \Drupal::database()->update('basket')
                        ->fields([
                            'sid'       => $uid
                        ])
                        ->condition('sid', $_SESSION['basket_user_sid'])
                        ->execute();
        }
    }
    /*
    Conversion to parameter string
    */
    public function encodeParams($params){
        if(!is_array($params)) return $params;
        if(!empty($params)){
            $this->sortParams($params);
        }
        return \Drupal\Component\Serialization\Yaml::encode($params);
    }
    public function decodeParams($params){
        if(is_array($params)) return $params;
        return \Drupal\Component\Serialization\Yaml::decode($params);
    }
    /*
    Sorting parameter keys
    */
    public function sortParams(&$params){
        foreach ($params as &$param){
            if(is_array($param)){
                $this->sortParams($param);
            } else {
                $param = (string)$param;
            }
        }
        ksort($params);
    }
    private function getNodeEntity($nid){
        if(empty($this->getNodeEntity[$nid])){
            $this->getNodeEntity[$nid] = \Drupal\node\Entity\Node::load($nid);
        }
        return $this->getNodeEntity[$nid];
    }
}
