<?php
class ModelTotalPromotionsTotal extends Model
{
    private $module = array();
    private $_cart = array();

    public function __construct($registry)
    {
        parent::__construct($registry);

        $this->config->load('isenselabs/promotions');
        $this->module = $this->config->get('promotions');

        $this->load->model('setting/setting');
        $this->load->model($this->module['path']);
        $this->module['model'] = $this->{$this->module['model']};

        // Module setting
        $setting = $this->model_setting_setting->getSetting($this->module['code'], $this->config->get('config_store_id'));
        $this->module['setting'] = array_replace_recursive(
            $this->module['setting'],
            !empty($setting[$this->module['code'] . '_setting']) ? $setting[$this->module['code'] . '_setting'] : array()
        );

        $this->load->language($this->module['path'], $this->module['name']);

        // Misc
        $this->module['page_seo_url'] = $this->module['setting']['page']['seo_url'][0];
        if ($this->config->get('config_seo_url') && !empty($this->module['setting']['page']['seo_url'][$this->config->get('config_language_id')])) {
            $this->module['page_seo_url'] = $this->module['setting']['page']['seo_url'][$this->config->get('config_language_id')];
        }
        $this->module['notification_param'] = array(
            '{promo_name}'         => '',
            '{promo_url}'          => '',
            '{promo_items}'        => '',
            '{condition_required}' => '',
            '{condition_match}'    => '',
            '{condition_remain}'   => ''
        );
    }

   /**
    * OC 2.2+    : getTotal($totals)
    * OC 2.0-2.1 : getTotal(&$total_data, &$total, &$taxes)
    */
    public function getTotal(&$total_data, &$total = null, &$taxes = null) {
				/* OC 2.3 compat shim */
				if (is_array($total_data) && array_key_exists('totals', $total_data)) {
					$__ref_totals = &$total_data['totals'];
					$__ref_total  = &$total_data['total'];
					$__ref_taxes  = &$total_data['taxes'];
					$this->getTotal($__ref_totals, $__ref_total, $__ref_taxes);
					return;
				}
				if ($total === null) { $total = 0; }
				if ($taxes === null) { $taxes = array(); }


        if (!$this->checkAccess()) {
            return;
        }

        // Cart for continues usage across promotions
        $this->_cart = array(
            'sub-total'     => $this->cart->getSubTotal(),
            'promotion'     => 0,
            'taxes'         => $taxes,
            'tax_sum'       => array_sum($taxes),
            'total'         => $this->cart->getTotal() + array_sum($taxes),
            'products'      => $this->cart->getProducts(),
        );

        // Standardize interface
        $this->session->data['isl_promotions'] = array(
            'applied'       => array(),     // Applied promotion
            'promotions'    => array(),     // All available promotions
            'notifications' => array()      // Congratulations, Eligible, Upsell
        );

        $promotions = $this->module['model']->getPromotions();
        $this->session->data['isl_promotions']['promotions'] = $promotions;

        foreach ($promotions as $promotion) {
            $method = $promotion['rule_group'] . '_' . $promotion['rule_type'];

            // Precaution
            if (!method_exists($this, $method)) {
                continue;
            }

            // Blocker
            if ($promotion['apply_coupon'] && (!isset($this->session->data['coupon_promotions']) || !$promotion['coupon_code'] || $promotion['coupon_code'] != $this->session->data['coupon_promotions'])) {
                continue;
            }

            $promotion['valid'] = false; // Congratulations and Eligible considered as valid TRUE
            $promotion['cart_discount']  = array();

            $promo = $this->{$method}($promotion);
            $this->session->data['isl_promotions']['evaluated_promo'][] = $method;

            if (!$promo || !$promo['valid']) {
                continue;
            }

            if ($promo['cart_discount']) {
                $this->session->data['isl_promotions']['applied'][] = $promo;
                $promo_amount = $promo['cart_discount']['amount'];

                $total += $promo_amount;
                $this->_cart['promotion'] += $promo_amount;
                $this->_cart['total']     += $promo_amount; // Always reduce total when discounts applied

                if ($promotion['apply_coupon'] && $promotion['coupon_code']) {
                    $promo['title'] = 'Promo Coupon: ' . $promotion['coupon_code'] . '<br>' . $promo['title'];
                }

                $total_data[] = array(
                    'code'       => $this->module['total_code'],
                    'title'      => $promo['title'],
                    'value'      => $promo_amount,
                    'sort_order' => $this->config->get($this->module['total_code'] .'_sort_order')
                );

                // Taxes modifier
                $taxes = $this->_cart['taxes'];
                $this->_cart['tax_sum'] = array_sum($taxes);
                $this->_cart['total']   = $this->_cart['total'] + $this->_cart['tax_sum'];
            }

            if ($promo['stop']) {
                break;
            }
        }

        // Used to log applied promotions on success checkout
        $this->session->data['isl_promotions_checkout'] = array();
        if (isset($this->request->get['route']) && $this->request->get['route'] == 'checkout/confirm' && $this->session->data['isl_promotions']['applied']) {
            $this->session->data['isl_promotions_checkout'] = $this->session->data['isl_promotions']['applied'];
        }
    }

    protected function shipping_buyxgetfreeshipping($promo)
    {
        // Block early
        if (!$promo['condition_min_quantity'] || !$promo['condition_product_ids'] || !$promo['zone_ids']) {
            return $promo;
        }

        if (isset($this->session->data['shipping_method'])) {
            $shipping = $this->session->data['shipping_method'];
            $products = $this->_cart['products'];
            $product_quantity = 0;

            // Check if not 'All Geo Zones'
            if (isset($this->session->data['shipping_address']) && $promo['zone_ids'] && !in_array(0, $promo['zone_ids'])) {
                $address = $this->session->data['shipping_address'];
                $result  = $this->db->query("SELECT * FROM " . DB_PREFIX . "zone_to_geo_zone WHERE geo_zone_id IN (" . implode(',', $promo['zone_ids']) . ") AND country_id = '" . (int)$address['country_id'] . "' AND (zone_id = '" . (int)$address['zone_id'] . "' OR zone_id = '0')");

                if (!$result->num_rows) {
                    return $promo;
                }
            }

            foreach ($products as $product) {
                // Skip product with special price
                if ($product['islpr_is_special'] && !$promo['apply_special']) {
                    continue;
                }

                if (in_array($product['product_id'], $promo['condition_product_ids'])) {
                    $product_quantity += $product['quantity'];
                }
            }

            //=== Apply discount
            if ($product_quantity >= $promo['condition_min_quantity']) {
                $promo['valid'] = true;
                $promo['cart_discount']['amount'] = 0 - $this->tax->calculate($shipping['cost'], $shipping['tax_class_id'], $this->config->get('config_tax'));
            }

            //=== Notification
            $ntf_param = array_merge(
                $this->module['notification_param'],
                array(
                    '{promo_name}' => $promo['title'],
                    '{promo_url}'  => $promo['page_url'],
                )
            );

            if ($promo['valid']) {
                $this->session->data['isl_promotions']['notifications'][] = array(
                    'alert'   => 'success',
                    'message' => $this->renderMessage($this->ntfTemplate('congrats', 'congrats', $promo), $ntf_param)
                );
            }
        }

        return $promo;
    }

    protected function shipping_freeshippingwhenoverx($promo)
    {
        // Block early
        if (!$promo['condition_min_amount'] || !$promo['zone_ids']) {
            return $promo;
        }

        if (isset($this->session->data['shipping_method'])) {
            $shipping = $this->session->data['shipping_method'];
            $products = $this->_cart['products'];

            // Check if not 'All Geo Zones'
            if (isset($this->session->data['shipping_address']) && $promo['zone_ids'] && !in_array(0, $promo['zone_ids'])) {
                $address = $this->session->data['shipping_address'];
                $result  = $this->db->query("SELECT * FROM " . DB_PREFIX . "zone_to_geo_zone WHERE geo_zone_id IN (" . implode(',', $promo['zone_ids']) . ") AND country_id = '" . (int)$address['country_id'] . "' AND (zone_id = '" . (int)$address['zone_id'] . "' OR zone_id = '0')");

                if (!$result->num_rows) {
                    return $promo;
                }
            }

            //=== Apply and Notification
            $ntf_param = array_merge(
                $this->module['notification_param'],
                array(
                    '{promo_name}'         => $promo['title'],
                    '{promo_url}'          => $promo['page_url']
                )
            );

            // Notification Upsell
            if ($this->_cart['total'] < $promo['condition_min_amount']) {
                $ntf_param['{condition_required}'] = $this->currency->format($promo['condition_min_amount'], $this->config->get('config_currency'));
                $ntf_param['{condition_match}']    = $this->currency->format($this->_cart['total'], $this->config->get('config_currency'));
                $ntf_param['{condition_remain}']   = $this->currency->format($promo['condition_min_amount'] - $this->_cart['total'], $this->config->get('config_currency'));

                $this->session->data['isl_promotions']['notifications'][] = array(
                    'alert'   => 'warning',
                    'message' => $this->renderMessage($this->ntfTemplate('upsell', 'upsell', $promo), $ntf_param)
                );
            }

            // Notification Congrats
            if ($this->_cart['total'] >= $promo['condition_min_amount']) {
                $this->session->data['isl_promotions']['notifications'][] = array(
                    'alert'   => 'success',
                    'message' => $this->renderMessage($this->ntfTemplate('congrats', 'congrats', $promo), $ntf_param)
                );

                 //=== Apply discount
                $promo['valid'] = true;
                $promo['cart_discount']['amount'] = 0 - $this->tax->calculate($shipping['cost'], $shipping['tax_class_id'], $this->config->get('config_tax'));
            }
        }

        return $promo;
    }

    protected function product_buyxgetx($promo)
    {
        // Block early
        if (!$promo['condition_min_quantity'] || !$promo['condition_product_ids'] || !$promo['discount_quantity']) {
            return $promo;
        }

        $products     = $this->_cart['products'];
        $p_conditions = array(); // Product conditions in cart
        $promos       = array(
            'valid'    => array(),
            'eligible' => array(),
            'upsell'   => array(),
        );

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            if (in_array($product['product_id'], $promo['condition_product_ids'])) {
                // Filtered products
                $_product = array(
                    'product_id' => $product['product_id'],
                    'name'       => $product['name'],
                    'quantity'   => isset($_product['quantity']) ? $_product['quantity'] + $product['quantity'] : $product['quantity'],
                    'tax_class_id' => $product['tax_class_id'],
                );
                if (!isset($_product['prices'][$product['product_id']])) {
                    $_product['prices'][$product['product_id']] = array($product['islpr_cart_id'] => $product['price']);
                } else {
                    $_product['prices'][$product['product_id']] = $_product['prices'][$product['product_id']] + array($product['islpr_cart_id'] => $product['price']);
                }

                $_product['least_price']    = min($_product['prices'][$product['product_id']]);
                $_product['most_price']     = max($_product['prices'][$product['product_id']]);
                $_product['condition_qty']  = $promo['condition_min_quantity'] + $promo['discount_quantity'];
                $_product['base_multiply']  = floor($product['quantity'] / $_product['condition_qty']);

                $_product['qty_free']       = 0;
                if ($_product['base_multiply']) {
                    // Note: qty_free indicate that required qty is available in cart and ready to discounted
                    $_product['qty_free'] = ($promo['apply_once'] ? 1 : $_product['base_multiply']) * $promo['discount_quantity'];
                }

                $p_conditions[$product['product_id']] = $_product;
            }
        }

        if (!$p_conditions) {
            return $promo;
        }

        // Promo filter
        $congrats = false;
        foreach ($p_conditions as $product) {
            if ($product['qty_free']) {
                $congrats = true;
                $promos['valid']['rows'][] = $product;
                $promos['valid']['items'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
            } else {
                if ($product['quantity'] == $promo['condition_min_quantity']) {
                    $promos['eligible']['rows'][]  = $product;
                    $promos['eligible']['items'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
                }

                if ($product['quantity'] < $promo['condition_min_quantity']) {
                    $promos['upsell']['rows'][]  = $product;
                    $promos['upsell']['items'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
                }
            }
        }

        //=== Apply and Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'  => $promo['title'],
                '{promo_url}'   => $promo['page_url']
            )
        );

        // Notification Upsell
        if (!empty($promos['upsell']['items'])) {
            $ntf_param['{promo_items}'] = implode(', ', $promos['upsell']['items']);
            $ntf_param['{condition_required}'] = $promo['condition_min_quantity'];

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'buyxgetx_upsell', $promo), $ntf_param)
            );
        }

        // Notification Eligible
        if (!empty($promos['eligible']['items'])) {
            $promo['valid'] = true;

            $ntf_param['{promo_items}'] = implode(', ', $promos['eligible']['items']);
            $ntf_param['{discount_quantity}'] = $promo['discount_quantity'];

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'info',
                'message' => $this->renderMessage($this->ntfTemplate('eligible', 'buyxgetx_eligible', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($congrats) {
            $discount_amount = 0;
            $discount_list   = array();

            foreach ($promos['valid']['rows'] as $product) {
                $discount_list[] = $product['name'];
                $discount_amount = $discount_amount - ($product['least_price'] * $product['qty_free']);

                // Recalculate taxes
                if ($product['tax_class_id']) {
                    $tax_rates = $this->tax->getRates($product['least_price'] * $product['qty_free'], $product['tax_class_id']);

                    foreach ($tax_rates as $tax_rate) {
                        if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                            $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                        }
                    }
                }
            }

            $ntf_param['{promo_items}'] = implode(', ', $promos['valid']['items']);

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'buyxgetx_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', $discount_list) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function product_buyxgety($promo)
    {
        // Block early
        if (!$promo['condition_min_quantity'] || !$promo['condition_product_ids'] || !$promo['discount_quantity'] || !$promo['discount_product_ids']) {
            return $promo;
        }

        $products     = $this->_cart['products'];
        $p_conditions = array(  // Product conditions in cart
            'products'       => array(),
            'total_quantity' => 0,
            'condition_qty'  => $promo['condition_min_quantity'],
            'base_multiply'  => 0,
            'qty_free'       => 0,
        );
        $p_discounts  = array(); // Product discounts in cart
        $p_discounts_total = 0; // total quantity of free product in cart

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            if (in_array($product['product_id'], $promo['condition_product_ids'])) {
                $p_conditions['products'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
                $p_conditions['total_quantity'] += $product['quantity'];
                $p_conditions['base_multiply']  = floor($p_conditions['total_quantity'] / $p_conditions['condition_qty']);
                if ($p_conditions['base_multiply']) {
                    // Note: qty_free indicate the quantity of free products customer get
                    $p_conditions['qty_free'] = ($promo['apply_once'] ? 1 : $p_conditions['base_multiply']) * $promo['discount_quantity'];
                }
            }

            if (in_array($product['product_id'], $promo['discount_product_ids'])) {
                $p_discounts[$product['product_id']] = array(
                    'product_id' => $product['product_id'],
                    'name'       => $product['name'],
                    'quantity'   => isset($p_discounts[$product['product_id']]['quantity']) ? $p_discounts[$product['product_id']]['quantity'] + $product['quantity'] : $product['quantity'],
                    'prices'     => isset($p_discounts[$product['product_id']]['prices']) ? $p_discounts[$product['product_id']]['prices'] + array($product['islpr_cart_id'] => $product['price']) : array($product['islpr_cart_id'] => $product['price']),
                    'tax_class_id' => $product['tax_class_id'],
                );
                $p_discounts[$product['product_id']]['least_price'] = min($p_discounts[$product['product_id']]['prices']);
                $p_discounts[$product['product_id']]['most_price']  = max($p_discounts[$product['product_id']]['prices']);

                $p_discounts_total = $p_discounts_total + $product['quantity'];
            }
        }

        if (!$p_conditions['total_quantity']) {
            return $promo;
        }

        // Sort by least price ASC
        usort($p_discounts, function ($a, $b) {
            if ($a['least_price'] == $b['least_price']) {
                return 0;
            }
            return ($a['least_price'] < $b['least_price']) ? -1 : 1;
        });

        //=== Apply and Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'  => $promo['title'],
                '{promo_url}'   => $promo['page_url']
            )
        );

        // Notification Upsell
        if ($p_conditions['condition_qty'] > $p_conditions['total_quantity']  && !$p_conditions['qty_free']) {
            $ntf_param['{promo_items}'] = implode(', ', $p_conditions['products']);
            $ntf_param['{condition_required}'] = $promo['condition_min_quantity'];

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'buyxgety_upsell', $promo), $ntf_param)
            );
        }

        // Notification Eligible
        if ($p_conditions['total_quantity'] >= $p_conditions['condition_qty'] && $p_conditions['qty_free'] > $p_discounts_total) {
            $promo['valid'] = true;

            $ntf_param['{promo_items}'] = implode(', ', $p_conditions['products']);
            $ntf_param['{discount_total}'] = $p_conditions['qty_free'];

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'info',
                'message' => $this->renderMessage($this->ntfTemplate('eligible', 'buyxgety_eligible', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($p_conditions['total_quantity'] >= $p_conditions['condition_qty'] && $p_discounts_total >= $p_conditions['qty_free']) {
            $discount_amount = 0;
            $discount_list   = array();
            $qty_free        = $p_conditions['qty_free'];

            foreach ($p_discounts as $product) {
                while ($product['quantity']) {
                    $discount_list[$product['name']] = true;
                    $discount_amount = $discount_amount - $product['least_price'];

                    // Recalculate taxes
                    if ($p_conditions['qty_free'] && $product['tax_class_id']) {
                        $tax_rates = $this->tax->getRates($product['least_price'], $product['tax_class_id']);

                        foreach ($tax_rates as $tax_rate) {
                            if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                                $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                            }
                        }
                    }

                    $product['quantity']--;
                    $p_conditions['qty_free']--;

                    if (!$p_conditions['qty_free']) {
                        break 2;
                    }
                }
            }

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'buyxgety_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($discount_list)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function product_buyxgetamountoffy($promo)
    {
        // Block early
        if (!$promo['condition_min_quantity'] || !$promo['condition_product_ids'] || !$promo['discount_quantity'] || !$promo['discount_product_ids'] || !$promo['discount_value']) {
            return $promo;
        }

        $products     = $this->_cart['products'];
        $p_conditions = array(  // Product conditions in cart
            'products'       => array(),
            'total_quantity' => 0,
            'condition_qty'  => $promo['condition_min_quantity'],
            'base_multiply'  => 0,
            'qty_off'        => 0,
        );
        $p_discounts  = array(); // Product discounts in cart
        $p_discounts_total = 0; // total quantity of free product in cart

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            if (in_array($product['product_id'], $promo['condition_product_ids'])) {
                $p_conditions['products'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
                $p_conditions['total_quantity'] += $product['quantity'];
                $p_conditions['base_multiply']  = floor($p_conditions['total_quantity'] / $p_conditions['condition_qty']);
                if ($p_conditions['base_multiply']) {
                    // Note: qty_off indicate the quantity of products that get discounts off
                    $p_conditions['qty_off'] = ($promo['apply_once'] ? 1 : $p_conditions['base_multiply']) * $promo['discount_quantity'];
                }
            }

            if (in_array($product['product_id'], $promo['discount_product_ids'])) {
                $p_discounts[$product['product_id']] = array(
                    'product_id' => $product['product_id'],
                    'name'       => $product['name'],
                    'quantity'   => isset($p_discounts[$product['product_id']]['quantity']) ? $p_discounts[$product['product_id']]['quantity'] + $product['quantity'] : $product['quantity'],
                    'prices'     => isset($p_discounts[$product['product_id']]['prices']) ? $p_discounts[$product['product_id']]['prices'] + array($product['islpr_cart_id'] => $product['price']) : array($product['islpr_cart_id'] => $product['price']),
                    'tax_class_id' => $product['tax_class_id'],
                );
                $p_discounts[$product['product_id']]['least_price'] = min($p_discounts[$product['product_id']]['prices']);
                $p_discounts[$product['product_id']]['most_price']  = max($p_discounts[$product['product_id']]['prices']);

                $p_discounts_total = $p_discounts_total + $product['quantity'];
            }
        }

        if (!$p_conditions['total_quantity']) {
            return $promo;
        }

        // Sort by least price ASC
        usort($p_discounts, function ($a, $b) {
            if ($a['least_price'] == $b['least_price']) {
                return 0;
            }
            return ($a['least_price'] < $b['least_price']) ? -1 : 1;
        });

        //=== Apply and Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'  => $promo['title'],
                '{promo_url}'   => $promo['page_url']
            )
        );

        // Notification Upsell
        if ($p_conditions['condition_qty'] > $p_conditions['total_quantity']  && !$p_conditions['qty_off']) {
            $ntf_param['{promo_items}'] = implode(', ', $p_conditions['products']);
            $ntf_param['{discount_value}'] = $promo['discount_type'] == 'percentage' ? $promo['discount_value'] . '%' : $this->currency->format($promo['discount_value'], $this->config->get('config_currency'));
            $ntf_param['{condition_required}'] = $promo['condition_min_quantity'];

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'buyxgetamountoffy_upsell', $promo), $ntf_param)
            );
        }

        // Notification Eligible
        if ($p_conditions['total_quantity'] >= $p_conditions['condition_qty'] && $p_conditions['qty_off'] > $p_discounts_total) {
            $promo['valid'] = true;

            $ntf_param['{promo_items}'] = implode(', ', $p_conditions['products']);
            $ntf_param['{discount_value}'] = $promo['discount_type'] == 'percentage' ? $promo['discount_value'] . '%' : $this->currency->format($promo['discount_value'], $this->config->get('config_currency'));
            $ntf_param['{discount_total}'] = $p_conditions['qty_off'];

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'info',
                'message' => $this->renderMessage($this->ntfTemplate('eligible', 'buyxgetamountoffy_eligible', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($p_conditions['total_quantity'] >= $p_conditions['condition_qty'] && $p_discounts_total >= $p_conditions['qty_off']) {
            $discount_amount = 0;
            $discount_list   = array();

            foreach ($p_discounts as $product) {
                while ($product['quantity']) {
                    $discount_list[$product['name']] = true;
                    $the_discount = $promo['discount_type'] == 'percentage' ? $product['least_price'] * ($promo['discount_value'] / 100) : $promo['discount_value'];
                    $discount_amount = $discount_amount - $the_discount;

                    // Recalculate taxes
                    if ($p_conditions['qty_off'] && $product['tax_class_id']) {
                        $tax_rates = $this->tax->getRates(abs($the_discount), $product['tax_class_id']);

                        foreach ($tax_rates as $tax_rate) {
                            if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                                $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                            }
                        }
                    }

                    $product['quantity']--;
                    $p_conditions['qty_off']--;

                    if (!$p_conditions['qty_off']) {
                        break 2;
                    }
                }
            }

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'buyxgetamountoffy_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($discount_list)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function product_amountoffy($promo)
    {
        // Block early
        if (!$promo['discount_quantity'] || !$promo['discount_product_ids'] || !$promo['discount_value']) {
            return $promo;
        }

        $products     = $this->_cart['products'];
        $p_discounts  = array(); // Product discounts in cart
        $p_conditions = array(  // Product conditions in cart
            'products'       => array(),
            'total_quantity' => 0,
            'condition_qty'  => $promo['discount_quantity'],
        );

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            if (in_array($product['product_id'], $promo['discount_product_ids'])) {
                $p_conditions['products'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
                $p_conditions['total_quantity'] += $product['quantity'];
                // Note: qty_off indicate the quantity of products that get discounts off
                $p_conditions['qty_off'] = $promo['apply_once'] ? $promo['discount_quantity'] : $p_conditions['total_quantity'];

                $p_discounts[$product['product_id']] = array(
                    'product_id' => $product['product_id'],
                    'name'       => $product['name'],
                    'quantity'   => isset($p_discounts[$product['product_id']]['quantity']) ? $p_discounts[$product['product_id']]['quantity'] + $product['quantity'] : $product['quantity'],
                    'prices'     => isset($p_discounts[$product['product_id']]['prices']) ? $p_discounts[$product['product_id']]['prices'] + array($product['islpr_cart_id'] => $product['price']) : array($product['islpr_cart_id'] => $product['price']),
                    'tax_class_id' => $product['tax_class_id'],
                );
                $p_discounts[$product['product_id']]['least_price'] = min($p_discounts[$product['product_id']]['prices']);
                $p_discounts[$product['product_id']]['most_price']  = max($p_discounts[$product['product_id']]['prices']);
            }
        }

        if (!$p_conditions['total_quantity']) {
            return $promo;
        }

        //=== Apply and Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'  => $promo['title'],
                '{promo_url}'   => $promo['page_url']
            )
        );

        // Notification Upsell
        if ($p_conditions['condition_qty'] > $p_conditions['total_quantity']) {
            $ntf_param['{promo_items}'] = implode(', ', $p_conditions['products']);
            $ntf_param['{discount_value}'] = $promo['discount_type'] == 'percentage' ? $promo['discount_value'] . '%' : $this->currency->format($promo['discount_value'], $this->config->get('config_currency'));
            $ntf_param['{condition_required}'] = $p_conditions['condition_qty'];

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'amountoffy_upsell', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($p_conditions['total_quantity'] >= $p_conditions['condition_qty']) {
            $discount_amount = 0;
            $discount_list   = array();

            foreach ($p_discounts as $product) {
                while ($product['quantity']) {
                    $discount_list[$product['name']] = true;
                    $the_discount = $promo['discount_type'] == 'percentage' ? $product['least_price'] * ($promo['discount_value'] / 100) : $promo['discount_value'];
                    $discount_amount = $discount_amount - $the_discount;

                    // Recalculate taxes
                    if ($p_conditions['qty_off'] && $product['tax_class_id']) {
                        $tax_rates = $this->tax->getRates(abs($the_discount), $product['tax_class_id']);

                        foreach ($tax_rates as $tax_rate) {
                            if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                                $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                            }
                        }
                    }

                    $product['quantity']--;
                    $p_conditions['qty_off']--;

                    if (!$p_conditions['qty_off']) {
                        break 2;
                    }
                }
            }

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'amountoffy_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($discount_list)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function manufacturer_xoffitemsinybrands($promo)
    {
        // Block early
        if (!$promo['discount_value'] || !$promo['discount_manufacturer_ids']) {
            return $promo;
        }

        $products        = $this->_cart['products'];
        $disount_list    = array();
        $_products       = array();
        $discount_amount = 0;

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            // All brand use manufacturer_id 0, mean apply to all products
            if (in_array(0, $promo['discount_manufacturer_ids']) || in_array($product['islpr_manufacturer_id'], $promo['discount_manufacturer_ids'])) {
                $disount_list[$product['name']] = true;
                $the_discount = $promo['discount_type'] == 'percentage' ? $product['price'] * ($promo['discount_value'] / 100) : $promo['discount_value'];
                $discount_amount = $discount_amount - ($the_discount * $product['quantity']);

                $product['discount'] = $the_discount;
                $_products[] = $product;
            }
        }

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'     => $promo['title'],
                '{promo_url}'      => $promo['page_url'],
                '{discount_value}' => $promo['discount_type'] == 'percentage' ? $promo['discount_value'] . '%' : $this->currency->format($promo['discount_value'], $this->config->get('config_currency'))
            )
        );

        // Notification Congrats
        if ($disount_list) {
            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'xoffitemsinybrands_congrats', $promo), $ntf_param)
            );

            // Recalculate taxes
            foreach ($_products as $product) {
                if ($product['tax_class_id']) {
                    $tax_rates = $this->tax->getRates(abs($product['discount']), $product['tax_class_id']);

                    foreach ($tax_rates as $tax_rate) {
                        if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                            $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                        }
                    }
                }
            }

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($disount_list)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function manufacturer_buyxgetysamebrands($promo)
    {
        // Block early
        if (!$promo['condition_min_quantity'] || !$promo['condition_manufacturer_ids'] || !$promo['discount_quantity']) {
            return $promo;
        }

        $products        = $this->_cart['products'];
        $p_conditions = array(  // Product conditions in cart
            'products'        => array(),
            'total_quantity'  => 0,
            'total_condition' => $promo['condition_min_quantity'] + $promo['discount_quantity'],
            'condition_qty'   => $promo['condition_min_quantity'],
            'base_multiply'   => 0,
            'qty_free'        => 0,
        );
        $p_discounts  = array(); // Product discounts in cart

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "product` WHERE product_id = '" . (int)$product['product_id'] . "' AND `manufacturer_id` IN (" . implode(',', $promo['condition_manufacturer_ids']) . ")");

            if ($query->num_rows) {
                $p_conditions['products'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
                $p_conditions['total_quantity'] += $product['quantity'];
                $p_conditions['base_multiply']  = floor($p_conditions['total_quantity'] / $p_conditions['total_condition']);
                if ($p_conditions['base_multiply']) {
                    // Note: qty_free indicate the quantity of free products
                    $p_conditions['qty_free'] = ($promo['apply_once'] ? 1 : $p_conditions['base_multiply']) * $promo['discount_quantity'];
                }

                $p_discounts[$product['product_id']] = array(
                    'product_id' => $product['product_id'],
                    'name'       => $product['name'],
                    'quantity'   => isset($p_discounts[$product['product_id']]['quantity']) ? $p_discounts[$product['product_id']]['quantity'] + $product['quantity'] : $product['quantity'],
                    'prices'     => isset($p_discounts[$product['product_id']]['prices']) ? $p_discounts[$product['product_id']]['prices'] + array($product['islpr_cart_id'] => $product['price']) : array($product['islpr_cart_id'] => $product['price']),
                    'tax_class_id' => $product['tax_class_id'],
                );
                $p_discounts[$product['product_id']]['least_price'] = min($p_discounts[$product['product_id']]['prices']);
                $p_discounts[$product['product_id']]['most_price']  = max($p_discounts[$product['product_id']]['prices']);
            }
        }

        if (!$p_conditions['total_quantity']) {
            return $promo;
        }

        // Sort by least price ASC
        usort($p_discounts, function ($a, $b) {
            if ($a['least_price'] == $b['least_price']) {
                return 0;
            }
            return ($a['least_price'] < $b['least_price']) ? -1 : 1;
        });

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'         => $promo['title'],
                '{promo_url}'          => $promo['page_url'],
                '{condition_required}' => $promo['condition_min_quantity'],
                '{discount_quantity}'  => $p_conditions['qty_free'] ? $p_conditions['qty_free'] : $promo['discount_quantity'],
                '{promo_items}'        => implode(', ', $p_conditions['products'])
            )
        );

        // Notification Upsell
        $minimum_condition = $p_conditions['base_multiply'] ? $p_conditions['condition_qty'] * $p_conditions['base_multiply'] : $p_conditions['condition_qty'];
        if ($minimum_condition > $p_conditions['total_quantity']) {
            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'buyxgetysamebrands_upsell', $promo), $ntf_param)
            );
        }

        // Notification Eligible
        if ($p_conditions['total_quantity'] >= $minimum_condition && $p_conditions['total_quantity'] < $p_conditions['total_condition']) {
            $promo['valid'] = true;

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'info',
                'message' => $this->renderMessage($this->ntfTemplate('eligible', 'buyxgetysamebrands_eligible', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($p_conditions['qty_free'] && $p_conditions['total_quantity'] >= ($minimum_condition + $p_conditions['qty_free'])) {
            $discount_amount = 0;
            $discount_list   = array();
            $qty_free        = $p_conditions['qty_free'];

            foreach ($p_discounts as $product) {
                while ($product['quantity']) {
                    $discount_list[$product['name']] = true;
                    $discount_amount = $discount_amount - $product['least_price'];

                    // Recalculate taxes
                    if ($p_conditions['qty_free'] && $product['tax_class_id']) {
                        $tax_rates = $this->tax->getRates($product['least_price'], $product['tax_class_id']);

                        foreach ($tax_rates as $tax_rate) {
                            if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                                $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                            }
                        }
                    }

                    $product['quantity']--;
                    $p_conditions['qty_free']--;

                    if (!$p_conditions['qty_free']) {
                        break 2;
                    }
                }
            }

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'buyxgetysamebrands_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($discount_list)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function category_percentoffitemsincat($promo)
    {
        // Block early
        if (!$promo['discount_value'] || !$promo['condition_category_ids']) {
            return $promo;
        }

        $products        = $this->_cart['products'];
        $disount_list    = array();
        $_products       = array();
        $discount_amount = 0;

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "product_to_category` WHERE product_id = '" . (int)$product['product_id'] . "' AND `category_id` IN (" . implode(',', $promo['condition_category_ids']) . ")");

            if ($query->num_rows) {
                $disount_list[$product['name']] = true;
                $the_discount = $promo['discount_type'] == 'percentage' ? $product['price'] * ($promo['discount_value'] / 100) : $promo['discount_value'];
                $discount_amount = $discount_amount - ($the_discount * $product['quantity']);

                $product['discount'] = $the_discount;
                $_products[] = $product;
            }
        }

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'     => $promo['title'],
                '{promo_url}'      => $promo['page_url'],
                '{discount_value}' => $promo['discount_type'] == 'percentage' ? $promo['discount_value'] . '%' : $this->currency->format($promo['discount_value'], $this->config->get('config_currency'))
            )
        );

        // Notification Congrats
        if ($disount_list) {
            // Recalculate taxes
            foreach ($_products as $product) {
                if ($product['tax_class_id']) {
                    $tax_rates = $this->tax->getRates(abs($product['discount']), $product['tax_class_id']);

                    foreach ($tax_rates as $tax_rate) {
                        if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                            $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                        }
                    }
                }
            }

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'percentoffitemsincat_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($disount_list)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function category_buyxgetysamecategory($promo)
    {
        // Block early
        if (!$promo['condition_min_quantity'] || !$promo['condition_category_ids'] || !$promo['discount_quantity']) {
            return $promo;
        }

        $products        = $this->_cart['products'];
        $p_conditions = array(  // Product conditions in cart
            'products'        => array(),
            'total_quantity'  => 0,
            'total_condition' => $promo['condition_min_quantity'] + $promo['discount_quantity'],
            'condition_qty'   => $promo['condition_min_quantity'],
            'base_multiply'   => 0,
            'qty_free'        => 0,
        );
        $p_discounts  = array(); // Product discounts in cart

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "product_to_category` WHERE product_id = '" . (int)$product['product_id'] . "' AND `category_id` IN (" . implode(',', $promo['condition_category_ids']) . ")");

            if ($query->num_rows) {
                $p_conditions['products'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
                $p_conditions['total_quantity'] += $product['quantity'];
                $p_conditions['base_multiply']  = floor($p_conditions['total_quantity'] / $p_conditions['total_condition']);
                if ($p_conditions['base_multiply']) {
                    // Note: qty_free indicate the quantity of free products
                    $p_conditions['qty_free'] = ($promo['apply_once'] ? 1 : $p_conditions['base_multiply']) * $promo['discount_quantity'];
                }

                $p_discounts[$product['product_id']] = array(
                    'product_id' => $product['product_id'],
                    'name'       => $product['name'],
                    'quantity'   => isset($p_discounts[$product['product_id']]['quantity']) ? $p_discounts[$product['product_id']]['quantity'] + $product['quantity'] : $product['quantity'],
                    'prices'     => isset($p_discounts[$product['product_id']]['prices']) ? $p_discounts[$product['product_id']]['prices'] + array($product['islpr_cart_id'] => $product['price']) : array($product['islpr_cart_id'] => $product['price']),
                    'tax_class_id' => $product['tax_class_id'],
                );
                $p_discounts[$product['product_id']]['least_price'] = min($p_discounts[$product['product_id']]['prices']);
                $p_discounts[$product['product_id']]['most_price']  = max($p_discounts[$product['product_id']]['prices']);
            }
        }

        if (!$p_conditions['total_quantity']) {
            return $promo;
        }

        // Sort by least price ASC
        usort($p_discounts, function ($a, $b) {
            if ($a['least_price'] == $b['least_price']) {
                return 0;
            }
            return ($a['least_price'] < $b['least_price']) ? -1 : 1;
        });

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'         => $promo['title'],
                '{promo_url}'          => $promo['page_url'],
                '{condition_required}' => $promo['condition_min_quantity'],
                '{discount_quantity}'  => $p_conditions['qty_free'] ? $p_conditions['qty_free'] : $promo['discount_quantity'],
                '{promo_items}'        => implode(', ', $p_conditions['products'])
            )
        );

        // Notification Upsell
        $minimum_condition = $p_conditions['base_multiply'] ? $p_conditions['condition_qty'] * $p_conditions['base_multiply'] : $p_conditions['condition_qty'];
        if ($minimum_condition > $p_conditions['total_quantity']) {
            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'buyxgetysamecategory_upsell', $promo), $ntf_param)
            );
        }

        // Notification Eligible
        if ($p_conditions['total_quantity'] >= $minimum_condition && $p_conditions['total_quantity'] < $p_conditions['total_condition']) {
            $promo['valid'] = true;

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'info',
                'message' => $this->renderMessage($this->ntfTemplate('eligible', 'buyxgetysamecategory_eligible', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($p_conditions['qty_free'] && $p_conditions['total_quantity'] >= ($minimum_condition + $p_conditions['qty_free'])) {
            $discount_amount = 0;
            $discount_list   = array();
            $qty_free        = $p_conditions['qty_free'];

            foreach ($p_discounts as $product) {
                while ($product['quantity']) {
                    $discount_list[$product['name']] = true;
                    $discount_amount = $discount_amount - $product['least_price'];

                    // Recalculate taxes
                    if ($p_conditions['qty_free'] && $product['tax_class_id']) {
                        $tax_rates = $this->tax->getRates($product['least_price'], $product['tax_class_id']);

                        foreach ($tax_rates as $tax_rate) {
                            if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                                $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                            }
                        }
                    }

                    $product['quantity']--;
                    $p_conditions['qty_free']--;

                    if (!$p_conditions['qty_free']) {
                        break 2;
                    }
                }
            }

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'buyxgetysamecategory_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($discount_list)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function category_buyxfromygetoffonz($promo)
    {
        // Block early
        if (!$promo['condition_min_quantity'] || !$promo['condition_category_ids']  || !$promo['discount_category_ids'] || !$promo['discount_value']) {
            return $promo;
        }

        $products     = $this->_cart['products'];
        $p_conditions = array(  // Product conditions in cart
            'products'       => array(),
            'total_quantity' => 0,
        );
        $p_discounts  = array(); // Product discounts in cart
        $_products    = array();
        $discount_amount = 0;

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            // Conditions
            $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "product_to_category` WHERE product_id = '" . (int)$product['product_id'] . "' AND `category_id` IN (" . implode(',', $promo['condition_category_ids']) . ")");

            if ($query->num_rows) {
                $p_conditions['products'][] = '<a href="' . $this->url->link('product/product', 'product_id=' . (int)$product['product_id'], true) . '">' . $product['name'] . '</a>';
                $p_conditions['total_quantity'] += $product['quantity'];
            }

            // Discounts
            $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "product_to_category` WHERE product_id = '" . (int)$product['product_id'] . "' AND `category_id` IN (" . implode(',', $promo['discount_category_ids']) . ")");

            if ($query->num_rows) {
                $p_discounts[$product['name']] = true;
                $the_discount = $promo['discount_type'] == 'percentage' ? $product['total'] * ($promo['discount_value'] / 100) : $promo['discount_value'];
                $discount_amount = $discount_amount - $the_discount;

                $product['discount'] = $the_discount;
                $_products[] = $product;
            }
        }

        if (!$p_conditions['total_quantity']) {
            return $promo;
        }

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'         => $promo['title'],
                '{promo_url}'          => $promo['page_url'],
                '{condition_required}' => $promo['condition_min_quantity'],
                '{discount_value}'     => $promo['discount_type'] == 'percentage' ? $promo['discount_value'] . '%' : $this->currency->format($promo['discount_value'], $this->config->get('config_currency')),
                '{promo_items}'        => implode(', ', $p_conditions['products'])
            )
        );

        // Notification Upsell
        if ($promo['condition_min_quantity'] > $p_conditions['total_quantity']) {
            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'buyxfromygetoffonz_upsell', $promo), $ntf_param)
            );
        }

        // Notification Eligible
        if (empty($p_discounts) && $p_conditions['total_quantity'] >= $promo['condition_min_quantity']) {
            $promo['valid'] = true;

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'info',
                'message' => $this->renderMessage($this->ntfTemplate('eligible', 'buyxgetysamecategory_eligible', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($p_discounts && $p_conditions['total_quantity'] >= $promo['condition_min_quantity']) {
            // Recalculate taxes
            foreach ($_products as $product) {
                if ($product['tax_class_id']) {
                    $tax_rates = $this->tax->getRates(abs($product['discount']), $product['tax_class_id']);

                    foreach ($tax_rates as $tax_rate) {
                        if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                            $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                        }
                    }
                }
            }

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'buyxgetysamecategory_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($p_discounts)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function category_xoffitemsinycategoriestiered($promo)
    {
        // Block early
        if (!$promo['condition_category_ids'] || !$promo['condition_min_quantities']) {
            return $promo;
        }

        $products    = $this->_cart['products'];
        $_products   = array();
        $p_discounts = array(
            'price'    => 0,
            'quantity' => 0,
        );

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            $query = $this->db->query("SELECT * FROM `" . DB_PREFIX . "product_to_category` WHERE product_id = '" . (int)$product['product_id'] . "' AND `category_id` IN (" . implode(',', $promo['condition_category_ids']) . ")");

            if ($query->num_rows) {
                $p_discounts['price'] = isset($p_discounts['price']) ? $p_discounts['price'] + ($product['price'] * $product['quantity']) : ($product['price'] * $product['quantity']);
                $p_discounts['quantity'] = isset($p_discounts['quantity']) ? $p_discounts['quantity'] + $product['quantity'] : $product['quantity'];

                $_products[] = $product;
            }
        }

        if (!$p_discounts['quantity']) {
            return $promo;
        }

        $categories = $this->db->query("SELECT cd.name, cd.category_id FROM `" . DB_PREFIX . "category_description` cd LEFT JOIN `" . DB_PREFIX . "category` c ON (c.category_id = cd.category_id AND c.status = 1) WHERE cd.category_id IN (" . implode(',', $promo['condition_category_ids']) . ")  AND cd.language_id = " . (int)$this->config->get('config_language_id'));
        $category_list = array();
        foreach ($categories->rows as $category) {
            $category_list[] = '<a href="' . $this->url->link('product/category', 'path=' . (int)$category['category_id'], true) . '">' . $category['name'] . '</a>';
        }

        $tiers = $tiers_kr = array_combine($promo['condition_min_quantities'], $promo['discount_values']);
        ksort($tiers);
        krsort($tiers_kr);

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'  => $promo['title'],
                '{promo_url}'   => $promo['page_url']
            )
        );

        // Get minimum condition
        $condition_required = 0;
        $minimum_discount = 0;
        foreach ($tiers as $tier_qty => $tier_value) {
            $condition_required = $tier_qty;
            $minimum_discount = $tier_value;
            break;
        }

        // Notification Upsell
        if ($p_discounts['quantity'] < $condition_required) {
            $ntf_param['{category_list}'] = implode(', ', $category_list);
            $ntf_param['{condition_required}'] = $condition_required;

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'xoffitemsinycategoriestiered_upsell', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        foreach ($tiers_kr as $tier_qty => $tier_value) {
            if ($p_discounts['quantity'] >= $tier_qty) {
                $ntf_param['{discount_value}'] = $promo['discount_type'] == 'percentage' ? $tier_value . '%' : $this->currency->format($tier_value, $this->config->get('config_currency'));

                // Recalculate taxes
                foreach ($_products as $product) {
                    if ($product['tax_class_id']) {
                        $the_discount = $promo['discount_type'] == 'percentage' ? $product['total'] * ($tier_value / 100) : $tier_value;
                        $tax_rates = $this->tax->getRates(abs($the_discount), $product['tax_class_id']);

                        foreach ($tax_rates as $tax_rate) {
                            if (isset($this->_cart['taxes'][$tax_rate['tax_rate_id']])) {
                                $this->_cart['taxes'][$tax_rate['tax_rate_id']] -= $tax_rate['amount'];
                            }
                        }
                    }
                }

                $this->session->data['isl_promotions']['notifications'][] = array(
                    'alert'   => 'success',
                    'message' => $this->renderMessage($this->ntfTemplate('congrats', 'xoffitemsinycategoriestiered_congrats', $promo), $ntf_param)
                );

                //=== Apply discount
                $promo['valid'] = true;
                $promo['cart_discount']['amount'] = 0 - ($promo['discount_type'] == 'percentage' ? $p_discounts['price'] * ($tier_value / 100) : $tier_value);
                $promo['title'] = $promo['title'] . ' (' . $tier_value .'%)';
                break;
            }
        }

        return $promo;
    }

    protected function order_xamountoffwhenxormore($promo)
    {
        // Block early
        if (!$promo['condition_min_amounts']) {
            return $promo;
        }

        $cart_total = $this->_cart['total'] - $this->_cart['tax_sum']; // Total no tax
        $tiers = $tiers_kr = array_combine($promo['condition_min_amounts'], $promo['discount_values']);
        ksort($tiers);
        krsort($tiers_kr);

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'  => $promo['title'],
                '{promo_url}'   => $promo['page_url']
            )
        );

        $condition_required = 0;
        $minimum_discount = 0;
        foreach ($tiers as $tier_amount => $tier_discount) {
            $condition_required = $tier_amount;
            $minimum_discount = $tier_discount;
            break;
        }

        // Notification Upsell
        if ($cart_total < $condition_required) {
            $ntf_param['{discount_value}'] = $promo['discount_type'] == 'percentage' ? $minimum_discount . '%' : $this->currency->format($minimum_discount, $this->config->get('config_currency'));
            $ntf_param['{condition_required}'] = $this->currency->format($condition_required, $this->config->get('config_currency'));

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'xamountoffwhenxormore_upsell', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        foreach ($tiers_kr as $tier_amount => $tier_discount) {
            if ($cart_total >= $tier_amount) {
                $ntf_param['{discount_value}'] = $promo['discount_type'] == 'percentage' ? $tier_discount . '%' : $this->currency->format($tier_discount, $this->config->get('config_currency'));

                $this->session->data['isl_promotions']['notifications'][] = array(
                    'alert'   => 'success',
                    'message' => $this->renderMessage($this->ntfTemplate('congrats', 'xamountoffwhenxormore_congrats', $promo), $ntf_param)
                );

                //=== Apply discount
                $promo['valid'] = true;
                $promo['cart_discount']['amount'] = 0 - ($promo['discount_type'] == 'percentage' ? $cart_total * ($tier_discount / 100) : $tier_discount);

                break;
            }
        }

        return $promo;
    }

    protected function order_ordersofatleastxgety($promo)
    {
        // Block early
        if (!$promo['condition_min_amount'] || !$promo['condition_product_ids'] || !$promo['discount_value']) {
            return $promo;
        }

        $cart_total  = $this->_cart['total'] - $this->_cart['tax_sum']; // Total no tax
        $products    = $this->_cart['products'];
        $p_discounts = array(); // Product discounts in cart

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            if (in_array($product['product_id'], $promo['condition_product_ids'])) {
                $p_discounts[$product['product_id']] = array(
                    'product_id' => $product['product_id'],
                    'name'       => $product['name'],
                    'quantity'   => isset($p_discounts[$product['product_id']]['quantity']) ? $p_discounts[$product['product_id']]['quantity'] + $product['quantity'] : $product['quantity'],
                    'prices'     => isset($p_discounts[$product['product_id']]['prices']) ? $p_discounts[$product['product_id']]['prices'] + array($product['islpr_cart_id'] => $product['price']) : array($product['islpr_cart_id'] => $product['price'])
                );
                $p_discounts[$product['product_id']]['least_price'] = min($p_discounts[$product['product_id']]['prices']);
                $p_discounts[$product['product_id']]['most_price']  = max($p_discounts[$product['product_id']]['prices']);
            }
        }

        // Sort by least price ASC
        usort($p_discounts, function ($a, $b) {
            if ($a['least_price'] == $b['least_price']) {
                return 0;
            }
            return ($a['least_price'] < $b['least_price']) ? -1 : 1;
        });

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'     => $promo['title'],
                '{promo_url}'      => $promo['page_url'],
                '{discount_value}' => $promo['discount_type'] == 'percentage' ? $promo['discount_value'] . '%' : $this->currency->format($promo['discount_value'], $this->config->get('config_currency'))
            )
        );

        // Notification Upsell
        if ($cart_total < $promo['condition_min_amount']) {
            $ntf_param['{condition_required}'] = $this->currency->format($promo['condition_min_amount'], $this->config->get('config_currency'));

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'ordersofatleastxgety_upsell', $promo), $ntf_param)
            );
        }

        // Notification Eligible
        if ($cart_total > $promo['condition_min_amount'] && !$p_discounts) {
            $promo['valid'] = true;

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'info',
                'message' => $this->renderMessage($this->ntfTemplate('eligible', 'ordersofatleastxgety_eligible', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($cart_total > $promo['condition_min_amount'] && $p_discounts) {
            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'ordersofatleastxgety_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . $p_discounts[0]['name'] . ')';
            $promo['cart_discount']['amount'] = 0 - ($promo['discount_type'] == 'percentage' ? $p_discounts[0]['least_price'] * ($promo['discount_value'] / 100) : $promo['discount_value']);
        }

        return $promo;
    }

    protected function order_ordersofatleastxgetyoffleastormostexpensiveproduct($promo)
    {
        // Block early
        if (!$promo['condition_min_amount'] || !$promo['discount_value'] || !$promo['discount_quantity']) {
            return $promo;
        }

        $cart_total  = $this->_cart['total'] - $this->_cart['tax_sum']; // Total no tax
        $products    = $this->_cart['products'];
        $p_discounts = array(); // Product discounts in cart
        $p_discounts_total = 0;

        foreach ($products as $product) {
            // Skip product with special price
            if ($product['islpr_is_special'] && !$promo['apply_special']) {
                continue;
            }

            $p_discounts[$product['product_id']] = array(
                'product_id' => $product['product_id'],
                'name'       => $product['name'],
                'quantity'   => isset($p_discounts[$product['product_id']]['quantity']) ? $p_discounts[$product['product_id']]['quantity'] + $product['quantity'] : $product['quantity'],
                'prices'     => isset($p_discounts[$product['product_id']]['prices']) ? $p_discounts[$product['product_id']]['prices'] + array($product['islpr_cart_id'] => $product['price']) : array($product['islpr_cart_id'] => $product['price'])
            );
            $p_discounts[$product['product_id']]['least_price'] = min($p_discounts[$product['product_id']]['prices']);
            $p_discounts[$product['product_id']]['most_price']  = max($p_discounts[$product['product_id']]['prices']);
            $p_discounts[$product['product_id']]['the_price']   = $promo['discount_qualifier'] == 'least' ? $p_discounts[$product['product_id']]['least_price'] : $p_discounts[$product['product_id']]['most_price'];

            $p_discounts_total = $p_discounts_total + $product['quantity'];
        }

        if ($promo['discount_qualifier'] == 'least') {
            usort($p_discounts, function ($a, $b) {
                if ($a['least_price'] == $b['least_price']) {
                    return 0;
                }
                return ($a['least_price'] < $b['least_price']) ? -1 : 1;
            });
        } else { // most
            usort($p_discounts, function ($a, $b) {
                if ($a['most_price'] == $b['most_price']) {
                    return 0;
                }
                return ($a['most_price'] > $b['most_price']) ? -1 : 1;
            });
        }

        //=== Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'     => $promo['title'],
                '{promo_url}'      => $promo['page_url'],
                '{discount_value}' => $promo['discount_value'] . '%'
            )
        );

        // Notification Upsell
        if ($cart_total < $promo['condition_min_amount']) {
            $ntf_param['{condition_required}'] = $this->currency->format($promo['condition_min_amount'], $this->config->get('config_currency'));

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', 'ordersofatleastxgetyoffleastormostexpensiveproduct_upsell', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($cart_total >= $promo['condition_min_amount']) {
            $discount_amount = 0;
            $discount_list   = array();

            foreach ($p_discounts as $product) {
                while ($product['quantity']) {
                    $discount_list[$product['name']] = true;
                    $discount_amount = $discount_amount - ($product['the_price'] * ($promo['discount_value'] / 100));

                    $product['quantity']--;
                    $promo['discount_quantity']--;

                    if (!$promo['discount_quantity']) {
                        break 2;
                    }
                }
            }

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', 'ordersofatleastxgetyoffleastormostexpensiveproduct_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $promo['valid'] = true;
            $promo['title'] = $promo['title'] . ' (' . implode(', ', array_keys($discount_list)) . ')';
            $promo['cart_discount']['amount'] = $discount_amount;
        }

        return $promo;
    }

    protected function customer_xoffforrepeatcustomers($promo)
    {
        return $this->customerDiscount($promo, 'xoffforrepeatcustomers');
    }

    protected function customer_xpercentoffforrepeatcustomers($promo)
    {
        return $this->customerDiscount($promo, 'xpercentoffforrepeatcustomers');
    }

    protected function customerDiscount($promo, $promo_rule)
    {
        // Block early
        if (!$this->customer->isLogged()) {
            return $promo;
        }

        $sub_total = $this->_cart['total'] - $this->_cart['tax_sum'];
        $query = $this->db->query("SELECT COUNT(*) AS `total_repeat`, sum(total) AS `total_amount` FROM `" . DB_PREFIX . "order` WHERE customer_id = '" . (int)$this->customer->getId() . "' AND `order_status_id` IN (" . implode(',', $this->config->get('config_complete_status')) . ") AND `store_id` = '" . (int)$this->config->get('config_store_id') . "'");

        // Only customer meet minimum_orders can continue
        if ($query->row['total_repeat'] < $promo['condition_min_orders']) {
            return $promo;
        }

        //=== Apply and Notification
        $ntf_param = array_merge(
            $this->module['notification_param'],
            array(
                '{promo_name}'  => $promo['title'],
                '{promo_url}'   => $promo['page_url']
            )
        );

        // Notification Upsell
        if ($sub_total < $promo['condition_min_amount']) {
            $ntf_param['{condition_orders}']   = $promo['condition_min_orders'];
            $ntf_param['{condition_required}'] = $this->currency->format($promo['condition_min_amount'], $this->config->get('config_currency'));
            $ntf_param['{condition_match}']    = $this->currency->format($sub_total, $this->config->get('config_currency'));
            $ntf_param['{condition_remain}']   = $this->currency->format($promo['condition_min_amount'] - $sub_total, $this->config->get('config_currency'));

            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'warning',
                'message' => $this->renderMessage($this->ntfTemplate('upsell', $promo_rule . '_upsell', $promo), $ntf_param)
            );
        }

        // Notification Congrats
        if ($sub_total >= $promo['condition_min_amount']) {
            $this->session->data['isl_promotions']['notifications'][] = array(
                'alert'   => 'success',
                'message' => $this->renderMessage($this->ntfTemplate('congrats', $promo_rule . '_congrats', $promo), $ntf_param)
            );

            //=== Apply discount
            $discount_value = $promo['discount_value'];
            if ($promo_rule == 'xpercentoffforrepeatcustomers') {
                $discount_value = $sub_total * ($promo['discount_value'] / 100);
            }

            $promo['valid'] = true;
            $promo['cart_discount']['amount'] = 0 - $discount_value;
        }

        return $promo;
    }

    /**
     * Responsible to check global access
     *
     * @return bool
     */
    protected function checkAccess()
    {
        if (!$this->cart->hasProducts()) {
            return false;
        }

        if (!$this->module['setting']['status']) {
            return false;
        }

        if ($this->module['setting']['test_mode']) {
            if (version_compare(VERSION, '2.2.0.0', '<')) {
                $this->load->library('user');
                $this->user = new User($this->registry);
            } else {
                $this->user = new Cart\User($this->registry);
            }

            if (!$this->user->isLogged()) {
                return false;
            }
        }

        return true;
    }

    protected function ntfTemplate($type, $promo_ntf, $promo)
    {
        $message = $promo_ntf;
        $message_check = trim(strip_tags(html_entity_decode($promo['message_' . $type])));

        if (!empty($message_check)) {
            $message = $promo['message_' . $type];
        } elseif (!empty($this->module['setting']['ntf_template'][$promo_ntf])) {
            $message = $this->module['setting']['ntf_template'][$promo_ntf];
        }

        return $message;
    }

    protected function renderMessage($message, $param = array())
    {
        $template       = array_keys($param);
        $replacement    = array_values($param);

        return html_entity_decode(str_replace($template, $replacement, $message), ENT_QUOTES, 'UTF-8');
    }
}
