//
// (c) 2006 DS Data Systems UK Ltd, All rights reserved.
//
// DS Data Systems and KonaKart and their respective logos, are 
// trademarks of DS Data Systems UK Ltd. All rights reserved.
//
// The information in this document is free software; you can redistribute 
// it and/or modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This software is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//

package com.konakart.bl.modules.ordertotal.quantitydiscount;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;

import org.apache.torque.TorqueException;

import com.konakart.app.KKConfiguration;
import com.konakart.app.KKException;
import com.konakart.app.Order;
import com.konakart.app.OrderTotal;
import com.konakart.app.Promotion;
import com.konakart.appif.KKEngIf;
import com.konakart.appif.OrderProductIf;
import com.konakart.bl.modules.BaseModule;
import com.konakart.bl.modules.ordertotal.BaseOrderTotalModule;
import com.konakart.bl.modules.ordertotal.OrderTotalInterface;
import com.workingdogs.village.DataSetException;

/**
 * Module that creates an OrderTotal object for applying a percentage discount or an amount discount
 * on a single product normally because of a certain quantity is being purchased. The discount may
 * be applied on prices before or after tax.
 * 
 * The promotion may be activated on a product only if:
 * <ul>
 * <li>The total amount of the order is greater than a minimum amount</li>
 * <li>The total number of a single product ordered is greater than a minimum amount</li>
 * </ul>
 * 
 * There may be multiple valid promotions applicable for an order. If this is the case, the logic
 * applied is the following: All cumulative promotions are summed into one order total object. Then
 * we loop through the order total objects and choose the one that offers the largest discount.
 */
public class QuantityDiscount extends BaseOrderTotalModule implements OrderTotalInterface
{ int       count = 0;
  private static int sortOrder = -1;

  private static String code = "ot_quantity_discount";

  private static String bundleName = BaseModule.basePackage+".ordertotal.quantitydiscount.QuantityDiscount";

  private static HashMap<Locale, ResourceBundle> resourceBundleMap = new HashMap<Locale, ResourceBundle>();

  private static String mutex = "otQuantityDiscountMutex";

  // Configuration Keys

  private final static String MODULE_ORDER_TOTAL_QUANTITY_DISCOUNT_SORT_ORDER = "MODULE_ORDER_TOTAL_QUANTITY_DISCOUNT_SORT_ORDER";

  private final static String MODULE_ORDER_TOTAL_QUANTITY_DISCOUNT_STATUS = "MODULE_ORDER_TOTAL_QUANTITY_DISCOUNT_STATUS";

  // Message Catalogue Keys
  private final static String MODULE_ORDER_TOTAL_QUANTITY_DISCOUNT_TITLE = "module.order.total.quantitydiscount.title";

  /**
   * Constructor
   *
   * @param eng
   *
   * @throws DataSetException
   * @throws KKException
   * @throws TorqueException
   *
   */
  public QuantityDiscount(KKEngIf eng) throws TorqueException, KKException, DataSetException
  { super.init(eng);
    
    // Create the static maps from the configuration info
    if (sortOrder == -1)
    { synchronized (mutex)
      { if (sortOrder == -1)
          setStaticVariables();
      }
    }
  }
  /**
   * Sets some static variables during setup
   *
   * @throws KKException
   *
   */
  public void setStaticVariables() throws KKException
  { KKConfiguration conf;
    conf = getEng().getConfiguration(MODULE_ORDER_TOTAL_QUANTITY_DISCOUNT_SORT_ORDER);
    if (conf == null)
      sortOrder = 0;
    else
      sortOrder = new Integer(conf.getValue()).intValue();
  }

  /**
   * Returns true or false
   *
   * @throws KKException
   */
  public boolean isAvailable() throws KKException
  { return isAvailable(getEng(), MODULE_ORDER_TOTAL_QUANTITY_DISCOUNT_STATUS);
  }

  /**
   * Create and return an OrderTotal object for the discount amount.
   *
   * @param order
   * @param dispPriceWithTax
   * @param locale
   * @return Returns an OrderTotal object for this module
   * @throws Exception
   */
  public OrderTotal getOrderTotal(Order order, boolean dispPriceWithTax, Locale locale) throws Exception
  { OrderTotal ot;
    // Get the resource bundle
    ResourceBundle rb = getResourceBundle(mutex, bundleName, resourceBundleMap, locale);
    if (rb == null)
      throw new KKException("A resource file cannot be found for the country "+ locale.getCountry());

    // Get the promotions
    Promotion[] promArray = getPromMgr().getPromotions(code, order);

    // List to contain an order total for each promotion
    List<OrderTotal> orderTotalList = new ArrayList<OrderTotal>();

    if (promArray != null)
    { for (int i = 0; i < promArray.length; i++)
      { Promotion promotion = promArray[i];
        System.out.println("Promotion Name: "+promotion.getName());
        /*
         * Get the configuration parameters from the promotion
         */
        // Need to order at least this quantity of a series for promotion to apply
        int minSeriesQuantity = getCustomInt(promotion.getCustom2(), 2);
        System.out.println("MinSeriesQty: "+minSeriesQuantity);

        // Actual discount. Could be a percentage or an amount.
        BigDecimal discountApplied = getCustomBigDecimal(promotion.getCustom3(), 3);
        System.out.println("QuantityDisc: "+discountApplied);
        // If set to true it is a percentage. Otherwise it is an amount.
        boolean percentageDiscount = getCustomBoolean(promotion.getCustom4(), 4);

        // If set to true, discount is applied to pre-tax value. Only relevant for
        // percentage discount.
        boolean applyBeforeTax = getCustomBoolean(promotion.getCustom5(), 5);

        // Don't bother going any further if there is no discount
        if (discountApplied == null || discountApplied.equals(new BigDecimal(0)))
          continue;

        // Get the order value
        BigDecimal orderValue = null;
        if (applyBeforeTax)
          orderValue = order.getSubTotalExTax();
        else
          orderValue = order.getSubTotalIncTax();

        // If promotion doesn't cover any of the products in the order then go on to the
        // next promotion
        if (promotion.getApplicableProducts() == null || promotion.getApplicableProducts().length < minSeriesQuantity)
          continue;

        ot = new OrderTotal();
        ot.setSortOrder(sortOrder);
        ot.setClassName(code);
        ot.setPromotions(new Promotion[]
        { promotion });

        // Continue if promotion has no applicable products (should never happen)
        if (promotion.getApplicableProducts() == null)
          continue;

        // Loop through promotion products to determine whether to apply a discount
        int         seriesCount   = 0;
        String      titles        = "";
        BigDecimal  totalPrice    = null;
        BigDecimal  currentPrice  = null;
        System.out.println("Going to loop through ordered products");
        boolean     firstLoop     = true;
        for (int j = 0; j < promotion.getApplicableProducts().length; j++)
        { OrderProductIf op = promotion.getApplicableProducts()[j];
          if (op != null && op.getQuantity() >= 1)
          { seriesCount++;
            titles         = titles+op.getName()+", ";
            if (applyBeforeTax)
              currentPrice = op.getFinalPriceExTax();
            else
              currentPrice = op.getFinalPriceIncTax();
            if(totalPrice == null)
              totalPrice  = currentPrice;
            else
              totalPrice  = totalPrice.add(currentPrice);
            System.out.println("Promo product series count: "+seriesCount+" TotalPrice: "+totalPrice);
            // Get the current total price of the product(s)
          }
        }
        if(seriesCount >= minSeriesQuantity)
        { // Apply the discount
          BigDecimal discount = null;
          if (percentageDiscount)
          { // Apply a percentage discount
            discount = (totalPrice.multiply(discountApplied)).divide(new BigDecimal(100));
          }
          else
          { // Apply an amount based discount
            discount = discountApplied;
          }
          // Determine whether it is the first discounted product or not
          // Set the order total attributes
          //if(firstLoop)
          { ot.setValue(discount);
            if (percentageDiscount)
            { ot.setText("-"+ getCurrMgr().formatPrice(ot.getValue(), order.getCurrencyCode()));
              // Title looks like "-10% Philips TV"
              ot.setTitle("-" + discountApplied + "% " + titles);
            }
            else
            { String formattedDiscount = getCurrMgr().formatPrice(ot.getValue(), order.getCurrencyCode());
              ot.setText("-" + formattedDiscount);
              // Title looks like "-10EUR Philips TV"
              ot.setTitle("-" + formattedDiscount + " " + titles);
            }
            //firstLoop     = false;
          }
          /*else
          { ot.setValue(ot.getValue().add(discount));
            ot.setText("-"+ getCurrMgr().formatPrice(ot.getValue(), order.getCurrencyCode()));
            ot.setTitle(ot.getTitle()+","+titles);
          }*/
        }
        if (ot.getValue() != null)
        { int scale = new Integer(order.getCurrency().getDecimalPlaces()).intValue();
          ot.setValue(ot.getValue().setScale(scale, BigDecimal.ROUND_HALF_UP));
          orderTotalList.add(ot);
        }
      }
    }
    else
    { // Return null if there are no promotions
      return null;
    }
    System.out.println("OrderTotalList size: "+orderTotalList.size());
    // Call a helper method to decide which OrderTotal we should return
    OrderTotal retOT = getDiscountOrderTotalFromList(order, orderTotalList);
    return retOT;
  }

  public int getSortOrder()
  { return sortOrder;
  }
  public String getCode()
  { return code;
  }
}