<?php
/**
 *
 */
class Prices{

    /**
     * calculate total price for a product in a package
     * @param  boolean $isRecurring is the price recurring
     * @param  FLOAT  $unitCost    cost for the product
     * @param  INT  $payPeriod   pay period in case of recurring
     * @param  INT  $quantity    quantity of the product in the package
     * @return FLOAT              total cost for a prodcut
     */
    private function calculateTotalPrice($isRecurring, $unitCost, $payPeriod, $quantity){
        $totalUnitCost = intVal($isRecurring) === 1
            ? floatval($unitCost) * intVal($payPeriod)
            : floatval($unitCost);
        $totalUnitCost *= $quantity;

        return $totalUnitCost;
    }

    /**
     * get max price for products with multiple selection
     * @param  FLOAT $totalCategoryUnitCost max value for category
     * @param  FLOAT $totalUnitCost         cost of the product
     * @return FLOAT                        final cost for the product
     */
    private function getMultiProductsTotal($totalCategoryUnitCost, $totalUnitCost){
        return $totalCategoryUnitCost < $totalUnitCost
        ? $totalUnitCost
        : $totalCategoryUnitCost;
    }

    /**
     * check margin for a package in case a product price is updated
     * @param  INT $idProduct id for the product that has been updated
     * @return INT            number of affected packages
     */
    function checkPackagePriceMargin($idProduct = 0, $idPackage = 0){
        global $database;

        if($idProduct === 0 && $idPackage === 0){
            return 0;
        }

        if($idProduct !== 0){
            $whereSql = "idProduct=$idProduct";
        }

        if($idPackage !== 0){
            $whereSql = "idPackage=$idPackage";
        }

        $sql = "SELECT rpp.idPackage,
                       scp.unitCostPrice AS unitCostPrice,
                       scp.unitVatCost AS unitVatCost,
                       scp.isPriceRecurring AS isPriceRecurring,
                       scp.payPeriod AS payPeriod,
                       rpp.quantity AS quantity,
                       pc.hasMultipleRealProducts,
                       scp.idProductCategory AS idCategory
                FROM ".TABLES['suppliers_countries_products']." scp
                INNER JOIN ".TABLES['rel_package_products']." rpp
                    ON rpp.idProduct=scp.idProduct
                INNER JOIN ".TABLES['product_categories']." pc
                    ON scp.idProductCategory=pc.id
                INNER JOIN (
                    SELECT DISTINCT idPackage
                    FROM ".TABLES['rel_package_products']."
                    WHERE $whereSql
                ) affectedPackages
                ON rpp.idPackage=affectedPackages.idPackage
                INNER JOIN
                    (SELECT
                      rpp_last.idPackage,
                      MAX(rpp_last.packageInstance) AS maxInstance
                    FROM
                      ".TABLES['rel_package_products']." rpp_last
                    GROUP BY rpp_last.idPackage) last_instance
                    ON last_instance.idPackage = rpp.idPackage
                    AND last_instance.maxInstance = rpp.packageInstance";
        $query = $database->query($sql);
        $totalPrices = [];
        $multiProdTotals = [];
        while($row = $database->fetchArray($query)){
            if(!isset($totalPrices[$row['idPackage']])){
                $totalPrices[$row['idPackage']] = 0;
            }
            $tempTotal = $this->calculateTotalPrice($row['isPriceRecurring'], $row['unitCostPrice'], $row['payPeriod'], $row['quantity']);
            if(intval($row['hasMultipleRealProducts']) === 1){
                if(!isset($multiProdTotals[$row['idPackage']][$row['idCategory']])){
                    $multiProdTotals[$row['idPackage']][$row['idCategory']] = 0;
                }
                $multiProdTotals[$row['idPackage']][$row['idCategory']] = $this->getMultiProductsTotal($multiProdTotals[$row['idPackage']][$row['idCategory']], $tempTotal);
            }else{
                $totalPrices[$row['idPackage']] += $tempTotal;
            }
        }

        foreach ($multiProdTotals as $idPackage => $categories) {
            foreach($categories as $categoryTotal){
                $totalPrices[$idPackage] += $categoryTotal;
            }
        }

        $sql = "SELECT plb.idPackage,
                       plb.payMargin
                FROM ".TABLES['broker_commission_split']." plb
                INNER JOIN (
                    SELECT DISTINCT idPackage
                    FROM ".TABLES['rel_package_products']."
                    WHERE $whereSql
                ) affectedPackages
                ON plb.idPackage=affectedPackages.idPackage
                WHERE plb.payMargin!=0";
        $query = $database->query($sql);
        $invalidPackages = [];
        $validPackages = [];
        while($row = $database->fetchArray($query)){
            if($row['payMargin'] < $totalPrices[$row['idPackage']]){
                $invalidPackages[] = $row['idPackage'];
            }else{
                $validPackages[] = $row['idPackage'];
            }
        }

        if(count($invalidPackages) > 0){
            $sqlUpd = "UPDATE ".TABLES['packages']."
                       SET status='high-cost'
                       WHERE id IN(".implode(',', $invalidPackages).") AND status!='not-available'";
            $query = $database->query($sqlUpd);

            return $database->affectedRows();
        }

        if(count($validPackages) > 0){
            $sqlUpd = "UPDATE ".TABLES['packages']."
                       SET status='available'
                       WHERE id IN(".implode(',', $validPackages).") AND status='high-cost'";
            $query = $database->query($sqlUpd);

            return $database->affectedRows();
        }

        return 0;
    }

    public function getPackageProductPrices($idPackage, $sumBy = 'category'){
        global $database;

        $sql = "SELECT scp.unitCostPrice AS unitCostPrice,
                       scp.unitVatCost AS unitVatCost,
                       scp.isPriceRecurring AS isPriceRecurring,
                       scp.payPeriod AS payPeriod,
                       pc.category AS productCategory,
                       pt.type AS productType,
                       rpp.quantity AS quantity
                FROM ".TABLES['suppliers_countries_products']." scp
                INNER JOIN ".TABLES['product_categories']." pc
                    ON pc.id=scp.idProductCategory
                INNER JOIN ".TABLES['product_types']." pt
                    ON pt.id = pc.idType
                INNER JOIN ".TABLES['rel_package_products']." rpp
                    ON rpp.idProduct=scp.idProduct
                INNER JOIN
                    (SELECT
                      rpp_last.idPackage,
                      MAX(rpp_last.packageInstance) AS maxInstance
                    FROM
                      ".TABLES['rel_package_products']." rpp_last
                    GROUP BY rpp_last.idPackage) last_instance
                    ON last_instance.idPackage = rpp.idPackage
                    AND last_instance.maxInstance = rpp.packageInstance
                WHERE rpp.idPackage=$idPackage";

        $query = $database->query($sql);
        $data['productsPrices'] = [];

        while($row = $database->fetchArray($query)) {
            $productKey = $sumBy === 'type' ? $row['productType'] : $row['productCategory'];
            if(!isset($data['productsPrices'][$productKey])){
                $data['productsPrices'][$productKey]['totalUnitCost'] = 0;
                $data['productsPrices'][$productKey]['totalVatCost'] = 0;
            }

            unset($totalCategoryUnitCost);
            unset($totalCategoryVatCost);

            $totalCategoryUnitCost =& $data['productsPrices'][$productKey]['totalUnitCost'];
            $totalCategoryVatCost =& $data['productsPrices'][$productKey]['totalVatCost'];

            if($sumBy === 'type' && $row['productType'] === 'service') {
                $totalUnitCost = $this->calculateTotalPrice(0, $row['unitCostPrice'], $row['payPeriod'], $row['quantity']);
                $totalVatCost = $this->calculateTotalPrice(0, $row['unitVatCost'], $row['payPeriod'], $row['quantity']);

                if($row['isPriceRecurring'] == 1) {
                    if(!isset($data['productsPrices'][$row['productType']]['recurringPrice'])) {
                        $data['productsPrices'][$row['productType']]['recurringPrice'] = 0;
                        $data['productsPrices'][$row['productType']]['recurringVatPrice'] = 0;
                    }
                    $data['productsPrices'][$row['productType']]['recurringPrice'] += $totalUnitCost;
                    $data['productsPrices'][$row['productType']]['recurringVatPrice'] += $totalVatCost;
                } else {
                    if(!isset($data['productsPrices'][$row['productType']]['recurringPrice'])) {
                        $data['productsPrices'][$row['productType']]['fixedPrice'] = 0;
                        $data['productsPrices'][$row['productType']]['fixedVatPrice'] = 0;
                    }
                    $data['productsPrices'][$row['productType']]['fixedPrice'] += $totalUnitCost;
                    $data['productsPrices'][$row['productType']]['fixedVatPrice'] += $totalVatCost;
                }
            } else {
                $totalUnitCost = $this->calculateTotalPrice($row['isPriceRecurring'], $row['unitCostPrice'], $row['payPeriod'], $row['quantity']);
                $totalVatCost = $this->calculateTotalPrice($row['isPriceRecurring'], $row['unitVatCost'], $row['payPeriod'], $row['quantity']);
            }

            if($row['productType'] === 'installation'){
                $totalCategoryUnitCost = $this->getMultiProductsTotal($totalCategoryUnitCost, $totalUnitCost);
                $totalCategoryVatCost = $this->getMultiProductsTotal($totalCategoryVatCost, $totalVatCost);
            } else {
                $totalCategoryUnitCost += $totalUnitCost;
                $totalCategoryVatCost += $totalVatCost;
            }
        }

        return $data['productsPrices'];
    }

    public function getCommissionSplit($idPackage){
        global $database;

        $sql = "SELECT bcs.brokerSplit as broker,
                bcs.commercialLeadSplit as commercialLead,
                bcs.payMargin
                FROM ".TABLES['broker_commission_split']." bcs
                WHERE idPackage=$idPackage";
        $commissionSplit = $database->fetchResultArray($sql);

        return !empty($commissionSplit) ? $commissionSplit[0] : [];
    }

    /**
     * get product prices list and comissions
     * @param  INT $idPackage id of the package
     * @param  BOOLEAN $getOnlySelectedTpes if true will return just prices set by the broker
     * @param  String $sumBy type to sum by 'category' or 'type'
     * @param  Float $interestRate interest rate to calculate the prices with
     * @return json   list of prices and list of comission
     */
    public function getPriceTypes($idPackage, $getOnlySelectedTpes = false, $sumBy = 'category', $interestRate = -1){
        global $database;
        $joinType = $getOnlySelectedTpes ? "INNER JOIN" : "LEFT OUTER JOIN";
        $data = [];

        if($interestRate !== -1) {
            $interestRateValue = $interestRate;
        } else {
            $interestRate = new InterestRate();
            $interestRateValue = $interestRate->getInterestRate()['interestRate'];
        }

        $interestRatePercentage = $interestRateValue ? floatval($interestRateValue) / 100 : 0;
        $data['interestRate'] = $interestRateValue;
        $data['priceTypes'] = [];

        $sql = "SELECT pt.id as idPayType,
                    pt.payType,
                    pt.packagePayPeriod,
                    pt.servicesContractPeriod,
                    pt.maxContractPeriod,
                    pt.periodUnit,
                    plb.minimalFixedPrice,
                    plb.principalAmount,
                    plb.minimalServicesPrice
                FROM ".TABLES['payment_types']." pt
                $joinType
                (SELECT idPaymentType,
					           minimalFixedPrice,
                               principalAmount,
                               minimalServicesPrice
					FROM ".TABLES['price_list_broker']."
                    WHERE idPackage=$idPackage) plb
                ON pt.id=plb.idPaymentType";
        $query = $database->query($sql);
        while($row = $database->fetchArray($query)){
            $row['minimalRecurentPrice'] = $row['packagePayPeriod'] > 0
                ? $this->PMT($interestRatePercentage, $row['packagePayPeriod'], $row['principalAmount'])
                : 0;
            $row['minimalRecurentPrice'] = number_format($row['minimalRecurentPrice'], 2, '.', '');
            $data['priceTypes'][] = $row;
        }

        $data['productsPrices'] = $this->getPackageProductPrices($idPackage, $sumBy);

        $data['commissionSplit'] = $this->getCommissionSplit($idPackage);

        return $data;
    }

    public function getCommercialLeadPrices($idPackage = 0, $idUserCommercialLead = 0){
        global $database, $user;
        $idPackage = $database->escapeValue($idPackage);
        $whereSql = "";
        $clPrices = [];

        if($idPackage){
            $whereSql .= " AND plcl.idPackage=$idPackage";
        }

        if($idUserCommercialLead){
            $whereSql .= " AND cl.idUser=$idUserCommercialLead";
        }

        $sql = "SELECT
                    plcl.idPackage,
                    plcl.idPaymentType,
                    pt.payType,
                    IFNULL(plcl.idCustomer, 0) as idCustomer,
                    plcl.fixedExtra ,
                    plcl.recurentExtra,
                    plcl.servicesExtra,
                    plcl.visibleToCustomer,
                    pt.packagePayPeriod
                FROM ".TABLES['price_list_commercial_lead']." plcl
                INNER JOIN ".TABLES['payment_types']." pt
                    ON pt.id = plcl.idPaymentType
                INNER JOIN ".TABLES['commercial_leads']." cl
                    ON cl.id=plcl.idCommercialLead
                WHERE 1=1 $whereSql";
        $query = $database->query($sql);

        while($row = $database->fetchArray($query)){
            $clPrices[$row['idPackage']][$row['idCustomer']][$row['idPaymentType']] = $row;
        }

        return $clPrices;
    }

    /**
     * validate values to insert/update broker prices
     * @param  INT $idPackage   id of package
     * @param  Object $price    object containtg the info for prices
     * @return Array            error message array or empty array if no error
     */
    public function validateBrokerPrices($idPackage, $price){
        global $database;

        $data =[];
        $max_value = pow(10, 13);

        if(filter_var($idPackage, FILTER_VALIDATE_INT) === false || $idPackage == 0){
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'INVALID_PACKAGE_ID'
            ];

            return $data;
        }

        if(filter_var($price->idPayType, FILTER_VALIDATE_INT) === false || $price->idPayType == 0){
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'INVALID_PAY_TYPE_ID'
            ];

            return $data;
        }

        $checkMessage = $database->invalidNumber('minimalFixedPrice', $price->minimalFixedPrice, 0, $max_value);
        if($checkMessage){
            $data['messages'][] = $checkMessage;
        }

        $checkMessage = $database->invalidNumber('principalAmount', $price->principalAmount, 0, $max_value);
        if($checkMessage){
            $data['messages'][] = $checkMessage;
        }

        $checkMessage = $database->invalidNumber('minimalServicesPrice', $price->minimalServicesPrice, 0, $max_value);
        if($checkMessage){
            $data['messages'][] = $checkMessage;
        }

        return $data;
    }

    /**
     * validate values for commission split
     * @param  Obect $commissionSplit   info for commissison split
     * @return Array                    error message array or empty array if no error
     */
    public function validateComissionSplit($commissionSplit){
        global $database;

        $data =[];
        $max_value = 100;

        $checkMessage = $database->invalidNumber('brokerSplit', $commissionSplit->broker, 0, $max_value);
        if($checkMessage){
            $data['messages'][] = $checkMessage;
        }

        $checkMessage = $database->invalidNumber('commercialLeadSplit', $commissionSplit->commercialLead, 0, $max_value);
        if($checkMessage){
            $data['messages'][] = $checkMessage;
        }

        return $data;
    }

    /**
     * update values for broker prices and comissions
     * @param  INT $idPackage                 id for package
     * @param  Objects Array $prices          Array with prices to be inserted/updated
     * @param  Objects Array $commissionSplit Array with commission to be inserted/updated
     * @return Array                          Update message
     */
    public function updateBrokerPricesAndCommission($idPackage, $prices, $commissionSplit){
        global $database;

        $idPackage = $database->escapeValue($idPackage);
        $prices = json_decode($prices);
        $commissionSplit = json_decode($commissionSplit);

        $valuesSql = "";
        $idsToNotDelete = "";
        $data = [];

        if(empty($prices)){
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'ONE_PRICE_REQUIRED'
            ];

            return $data;
        }

        foreach ($prices as $price) {
            $price->idPayType = $database->escapeValue($price->idPayType);
            $price->minimalFixedPrice = $price->minimalFixedPrice ? $database->escapeValue($price->minimalFixedPrice) : 0;
            $price->principalAmount = $price->principalAmount ? $database->escapeValue($price->principalAmount) : 0;
            $price->minimalServicesPrice =$price->minimalServicesPrice ? $database->escapeValue($price->minimalServicesPrice) : 0;
            $data = array_merge($data, $this->validateBrokerPrices($idPackage, $price));
            $valuesSql .= "(".$idPackage.",
                            ".$database->escapeValue($price->idPayType).",
                            ".$price->minimalFixedPrice.",
                            ".$price->principalAmount.",
                            ".$price->minimalServicesPrice."
                        ),";
            $idsToNotDelete .= $price->idPayType.",";
        }

        if(!empty($data)){
            return $data;
        }

        if($valuesSql !== ""){
            $valuesSql = rtrim($valuesSql, ',');
            $idsToNotDelete = rtrim($idsToNotDelete, ',');
            $sql = "INSERT INTO ".TABLES['price_list_broker']."
                    (idPackage, idPaymentType, minimalFixedPrice, principalAmount, minimalServicesPrice)
                    VALUES $valuesSql
                    ON DUPLICATE KEY UPDATE
                    minimalFixedPrice=VALUES(minimalFixedPrice),
                    principalAmount=VALUES(principalAmount),
                    minimalServicesPrice=VALUES(minimalServicesPrice)";
            $query = $database->query($sql);
            $updatedPrices = $database->affectedRows();

            $sql = "DELETE FROM ".TABLES['price_list_broker']."
                    WHERE idPackage=$idPackage AND idPaymentType NOT IN($idsToNotDelete)";
            $query = $database->query($sql);
            $deletePrices = $database->affectedRows();

            if($updatedPrices === 0 && $deletePrices === 0){
                $data['messages'][] = [
                    'code' => 'warning',
                    'message' => 'NO_CHANGES_PRICES'
                ];
            }else{
                $data['messages'][] = [
                    'code' => 'success',
                    'message' => 'BROKER_PRICES_UPDATED'
                ];
            }
        }

        $data = array_merge($data, $this->validateComissionSplit($commissionSplit));
        $commissionSplit->broker = $commissionSplit->broker ? $database->escapeValue($commissionSplit->broker) : 0;
        $commissionSplit->commercialLead = $commissionSplit->commercialLead ? $database->escapeValue($commissionSplit->commercialLead) : 0;
        $commissionSplit->payMargin = $commissionSplit->payMargin ? $database->escapeValue($commissionSplit->payMargin) : 0;

        $sql = "INSERT INTO ".TABLES['broker_commission_split']."
                (idPackage, brokerSplit, commercialLeadSplit, payMargin)
                VALUES(".$database->escapeValue($idPackage).",
                    ".$commissionSplit->broker.",
                    ".$commissionSplit->commercialLead.",
                    ".$commissionSplit->payMargin."
                )
                ON DUPLICATE KEY UPDATE
                brokerSplit=VALUES(brokerSplit),
                commercialLeadSplit=VALUES(commercialLeadSplit),
                payMargin=VALUES(payMargin)
                ";
        $query = $database->query($sql);

        if($database->affectedRows() < 1){
            $data['messages'][] = [
                'code' => 'warning',
                'message' => 'NO_CHANGES_COMMISSION_SPLIT'
            ];
        }else{
            $data['messages'][] = [
                'code' => 'success',
                'message' => 'COMMISSION_SPLIT_UPDATED'
            ];
        }

        $priceChange = $this->checkPackagePriceMargin(0, $idPackage);
        if($priceChange > 0){
            $data['messages'][] = [
                'code' => 'warning',
                'message' => 'PACKAGES_HIGH_PRICE_CHANGED'
            ];
        }

        return $data;
    }

    public function getPackagesAvailablePayTypes(){
        global $database;
        $data = [];

        $sql = "SELECT
                    plb.idPackage,
                    plb.idPaymentType,
                    pt.payType
                FROM ".TABLES['price_list_broker']." plb
                INNER JOIN ".TABLES['payment_types']." pt
                ON pt.id=plb.idPaymentType";
        $query = $database->query($sql);
        while($row = $database->fetchArray($query)){
            $data[$row['idPackage']][] = $row;
        }

        return $data;
    }

    public function getClDefaultPrices($idPackage){
        global $database, $user;
        $data = [];

        $sql = "SELECT d.idPackage,
                d.idPaymentType,
                d.fixedExtra,
                d.recurentExtra,
                d.servicesExtra
                FROM ".TABLES['price_list_commercial_lead']." d
                INNER JOIN ".TABLES['commercial_leads']." cl
                    ON cl.id=d.idCommercialLead
                WHERE idUser=".$user->getUserId()." AND idPackage=$idPackage AND d.idCustomer IS NULL";
        $query = $database->query($sql);
        while($row = $database->fetchArray($query)){
            $data[$row['idPaymentType']] = $row;
        }
        return $data;
    }

    public function calculatePackageTotalCost($productsPrices){
        $total = 0;
        foreach ($productsPrices as $category) {
            $total += $category['totalUnitCost'];
        }

        return $total;
    }

    /**
     * Copy of Excel's PMT function.
     * Credit: http://thoughts-of-laszlo.blogspot.nl/2012/08/complete-formula-behind-excels-pmt.html
     *
     * @param double $interest        The interest rate for the loan.
     * @param int    $num_of_payments The total number of payments for the loan in months.
     * @param double $PV              The present value, or the total amount that a series of future payments is worth now;
     *                                Also known as the principal.
     * @param double $FV              The future value, or a cash balance you want to attain after the last payment is made.
     *                                If fv is omitted, it is assumed to be 0 (zero), that is, the future value of a loan is 0.
     * @param int    $Type            Optional, defaults to 0. The number 0 (zero) or 1 and indicates when payments are due.
     *                                0 = At the end of period
     *                                1 = At the beginning of the period
     *
     * @return float
     */
    public function PMT($interest, $num_of_payments, $PV, $FV = 0.00, $Type = 0){
        /*$interest = $interest / 12;
    	$xp=pow((1+$interest),$num_of_payments);
    	return
    		($PV* $interest*$xp/($xp-1)+$interest/($xp-1)*$FV)*
    		($Type==0 ? 1 : 1/($interest+1));*/
        $rates = [
            24 => 4.282,
            30 => 3.451,
            36 => 2.896,
            42 => 2.500,
            48 => 2.223,
            54 => 2.025,
            60 => 1.834
        ];

        $interest = isset($rates[$num_of_payments]) ? $rates[$num_of_payments] : 10;

        return round($PV * ($interest / 100));
    }

    private function calculateMargin($payType, $totalCost){
        $totalGain = $payType['minimalFixedPrice'] + $payType['principalAmount'];

        return $totalGain - $totalCost;
    }

    public function getPriceMargins($commisisonPercent, $payType, $totalCost){
        $margin = $this->calculateMargin($payType, $totalCost);

        return [
            'margin' => $margin,
            'clMargin' => ($margin * $commisisonPercent['commercialLead'] / 100),
            'brokerMargin' => ($margin * $commisisonPercent['broker'] / 100)
        ];
    }

    /**
     * calculate fixed mortage based on principal amount
     * @param  HashArray $payType      pay type hash array (principalAmount and packagePayPeriod keys required)
     * @param  Float $clMargin         commercial lead margin
     * @param  Float $interestRate     interest rate percent value
     * @return Float                   fixed mortage formated to two decimals
     */
    public function getRecurrentPriceMortage($payType, $clMargin, $interestRate){
        $newPrincipalAmount = $payType['principalAmount'] - $clMargin;
        $interestRate = $interestRate / 100;
        $fixedMortage = $this->PMT($interestRate, $payType['packagePayPeriod'], $newPrincipalAmount);

        return number_format($fixedMortage, 2, '.', '');
    }

}
