//
// (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.shipping.table;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ResourceBundle;

import org.apache.torque.TorqueException;

import com.konakart.app.KKConfiguration;
import com.konakart.app.KKEng;
import com.konakart.app.KKException;
import com.konakart.app.Order;
import com.konakart.app.ShippingQuote;
import com.konakart.bl.modules.BaseModule;
import com.konakart.bl.modules.shipping.BaseShippingModule;
import com.konakart.bl.modules.shipping.ShippingInfo;
import com.konakart.bl.modules.shipping.ShippingInterface;
import com.konakart.bl.modules.shipping.WeightCost;
import com.workingdogs.village.DataSetException;

/**
 * This shipping module implements a rate per item weight or a rate based on the total cost. The
 * items passed to this module have already been split up into individual packages based on the
 * maximum weight allowed per single package. If MODULE_SHIPPING_TABLE_TAX_CLASS is greater than
 * zero, then tax is added if the shipping address is in a taxable zone. The handling charge defined
 * by MODULE_SHIPPING_TABLE_HANDLING is also added.
 * 
 */
public class Table extends BaseShippingModule implements ShippingInterface
{
    private static int sortOrder = -1;

    private static int taxClass;

    private static int zone;

    private static BigDecimal handling;

    private static String code = "table";

    private static String mode;

    private static final String weight = "weight";

    private static final String price = "price";

    private static String icon = "";

    private static List weightCostList = null;

    private static String bundleName = BaseModule.basePackage + ".shipping.table.Table";

    private static HashMap resourceBundleMap = new HashMap();

    private static String mutex = "tableMutex";

    private KKEng eng;

    // Configuration Keys

    private final static String MODULE_SHIPPING_TABLE_STATUS = "MODULE_SHIPPING_TABLE_STATUS";

    private final static String MODULE_SHIPPING_TABLE_COST = "MODULE_SHIPPING_TABLE_COST";

    private final static String MODULE_SHIPPING_TABLE_HANDLING = "MODULE_SHIPPING_TABLE_HANDLING";

    private final static String MODULE_SHIPPING_TABLE_TAX_CLASS = "MODULE_SHIPPING_TABLE_TAX_CLASS";

    private final static String MODULE_SHIPPING_TABLE_ZONE = "MODULE_SHIPPING_TABLE_ZONE";

    private final static String MODULE_SHIPPING_TABLE_SORT_ORDER = "MODULE_SHIPPING_TABLE_SORT_ORDER";

    private final static String MODULE_SHIPPING_TABLE_MODE = "MODULE_SHIPPING_TABLE_MODE";

    // Message Catalogue Keys

    private final static String MODULE_SHIPPING_TABLE_TEXT_TITLE = "module.shipping.table.text.title";

    private final static String MODULE_SHIPPING_TABLE_TEXT_DESCRIPTION = "module.shipping.table.text.description";

    private final static String MODULE_SHIPPING_TABLE_TEXT_WAY = "module.shipping.table.text.way";

    // private final static String MODULE_SHIPPING_TABLE_TEXT_WEIGHT =
    // "module.shipping.table.text.weight";

    // private final static String MODULE_SHIPPING_TABLE_TEXT_AMOUNT =
    // "module.shipping.table.text.amount";

    /**
     * Constructor
     * 
     * @throws DataSetException
     * @throws KKException
     * @throws TorqueException
     * 
     */
    public Table() throws TorqueException, KKException, DataSetException
    {
        // Instantiate an object to interface to the main engine
        eng = new KKEng();

        // Create the static maps from the configuration info
        if (sortOrder == -1)
        {
            synchronized (mutex)
            {
                if (sortOrder == -1)
                {
                    setStaticVariables();
                }
            }
        }
    }

    /**
     * From the ShippingCountry we find the iso code of the country and determine its zone.
     * 
     * @param order
     * @return Returns a ShippingQuote object
     * @throws KKException
     */
    public ShippingQuote getQuote(Order order, ShippingInfo info) throws Exception
    {

        // Get the resource bundle
        ResourceBundle rb = getResourceBundle(mutex, bundleName, resourceBundleMap, info
                .getLocale());
        if (rb == null)
        {
            throw new KKException("A resource file cannot be found for the country "
                    + info.getLocale().getCountry());
        }

        // Get a partially filled ShippingQuote object
        ShippingQuote quote = this.getShippingQuote(rb);

        /*
         * The global parameter zone, if greater than zero, should reference a GeoZone. If the
         * DeliveryAddress of the order isn't within that GeoZone, then we throw an exception
         */
        if (zone > 0)
        {
            checkZone(info, zone);
        }

        BigDecimal cost = new BigDecimal(0);
        if (mode.equalsIgnoreCase(weight))
        {
            // Go through the weight cost list and figure out the cost

            /*
             * There is a list of weights since the total order weight may exceed the maximum weight
             * for a single package and so it has already been split up by the manager calling this
             * module.
             */
            for (Iterator iter = info.getOrderWeightList().iterator(); iter.hasNext();)
            {
                BigDecimal totalWeight = ((BigDecimal) iter.next()).add(info.getBoxWeight());
                for (Iterator iter1 = weightCostList.iterator(); iter1.hasNext();)
                {
                    WeightCost wc = (WeightCost) iter1.next();
                    if (totalWeight.compareTo(wc.getWeight()) == -1)
                    {
                        cost = cost.add(wc.getCost());
                        break;
                    }
                }
            }
        } else if (mode.equalsIgnoreCase(price))
        {
            if (order.getTotalIncTax() == null)
            {
                order.calculateTotals();
            }
            for (Iterator iter1 = weightCostList.iterator(); iter1.hasNext();)
            {
                WeightCost wc = (WeightCost) iter1.next();
                if (order.getTotalIncTax().compareTo(wc.getWeight()) == -1)
                {
                    cost = wc.getCost();
                }
            }
        } else
        {
            throw new KKException(
                    "The mode (MODULE_SHIPPING_TABLE_MODE) must be set to price or weight");
        }

        // Set all of the cost attributes
        quote.setCost(cost);
        quote.setHandlingCost(handling);
        BigDecimal costPlusHandling = cost.add(handling);
        if (taxClass > 0 && info.getDeliveryZone() != null)
        {
            quote.setTax(eng.getTax(costPlusHandling, info.getDeliveryCountry().getId(), info
                    .getDeliveryZone().getZoneId(), taxClass));
            quote.setTotalExTax(costPlusHandling);
            quote.setTotalIncTax(quote.getTax().add(costPlusHandling));
        } else
        {
            quote.setTax(new BigDecimal(0));
            quote.setTotalExTax(costPlusHandling);
            quote.setTotalIncTax(costPlusHandling);
        }

        // Create the return string
        StringBuffer retTextBuf = new StringBuffer();
        retTextBuf.append(rb.getString(MODULE_SHIPPING_TABLE_TEXT_WAY));
        quote.setResponseText(retTextBuf.toString());

        return quote;
    }

    /**
     * Sets some static variables during setup
     * 
     * @throws KKException
     * 
     */
    public void setStaticVariables() throws KKException
    {
        KKConfiguration conf;

        conf = eng.getConfiguration(MODULE_SHIPPING_TABLE_SORT_ORDER);
        if (conf == null)
        {
            sortOrder = 0;
        } else
        {
            sortOrder = new Integer(conf.getValue()).intValue();
        }

        conf = eng.getConfiguration(MODULE_SHIPPING_TABLE_TAX_CLASS);
        if (conf == null)
        {
            taxClass = 0;
        } else
        {
            taxClass = new Integer(conf.getValue()).intValue();
        }

        conf = eng.getConfiguration(MODULE_SHIPPING_TABLE_ZONE);
        if (conf == null)
        {
            zone = 0;
        } else
        {
            zone = new Integer(conf.getValue()).intValue();
        }

        conf = eng.getConfiguration(MODULE_SHIPPING_TABLE_HANDLING);
        if (conf == null)
        {
            handling = new BigDecimal(0);
        } else
        {
            handling = new BigDecimal(conf.getValue());
        }

        conf = eng.getConfiguration(MODULE_SHIPPING_TABLE_MODE);
        if (conf == null)
        {
            mode = "";
        } else
        {
            mode = new String(conf.getValue());
        }

        // Create the static list
        createWeightCostList();
    }

    /**
     * From the configuration data, we fill a list with the weight cost info
     * 
     * @throws KKException
     * 
     */
    @SuppressWarnings("unchecked")
    private void createWeightCostList() throws KKException
    {
        KKConfiguration conf = eng.getConfiguration(MODULE_SHIPPING_TABLE_COST);
        if (conf != null)
        {
            if (weightCostList == null)
            {
                weightCostList = new ArrayList();
            } else
            {
                weightCostList.clear();
            }

            String weightCosts = conf.getValue();
            String[] weightCostsArray = weightCosts.split(",");
            for (int i = 0; i < weightCostsArray.length; i++)
            {
                String weightCost = weightCostsArray[i];
                String[] weightCostArray = weightCost.split(":");
                if (weightCostArray.length == 2)
                {
                    WeightCost wc = new WeightCost(new BigDecimal(weightCostArray[0]),
                            new BigDecimal(weightCostArray[1]));
                    weightCostList.add(wc);
                }
            }
        }
    }

    /**
     * 
     * @param rb
     * @return A ShippingQuote object
     * @throws KKException
     */
    private ShippingQuote getShippingQuote(ResourceBundle rb) throws KKException
    {

        ShippingQuote quote = new ShippingQuote();

        // Populate some attributes from static data
        quote.setCode(code);
        quote.setSortOrder(sortOrder);
        quote.setIcon(icon);
        quote.setTaxClass(taxClass);

        // Populate locale specific attributes from the resource bundle
        quote.setDescription(rb.getString(MODULE_SHIPPING_TABLE_TEXT_DESCRIPTION));
        quote.setTitle(rb.getString(MODULE_SHIPPING_TABLE_TEXT_TITLE));

        return quote;
    }

    /**
     * Returns true or false
     * 
     * @throws KKException
     */
    public boolean isAvailable() throws KKException
    {
        return isAvailable(eng, MODULE_SHIPPING_TABLE_STATUS);
    }

}
