package org.ofbiz.extended.batch;

/*
 * 
 */
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilDateTime;
import org.ofbiz.entity.DelegatorInterface;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.condition.EntityComparisonOperator;
import org.ofbiz.entity.condition.EntityExpr;
import org.ofbiz.entity.condition.EntityJoinOperator;
import org.ofbiz.entity.condition.EntityQuery;
import org.ofbiz.entity.model.ModelEntity;
import org.ofbiz.entity.transaction.GenericTransactionException;
import org.ofbiz.entity.transaction.TransactionUtil;
import org.ofbiz.service.DispatchContext;

/**
 * 
 */
public class PriceMappingServices {

    public static Map updateProductPriceMap(DispatchContext dctx, Map context) throws GenericEntityException {
        System.out.println("executing updateProductPriceMap -- started");
        EntityQuery query = new EntityQuery("ProductPriceMap");

        String productId = (String) context.get("productId");
        if (productId != null) {
            query.addCondition(new EntityExpr("product_id", EntityComparisonOperator.EQUALS, productId));
        }

        query.addCondition(new EntityExpr("activation_date", EntityComparisonOperator.LESS_THAN_EQUAL_TO, UtilDateTime.nowTimestamp()));
        query.addCondition(new EntityExpr("consumed", EntityComparisonOperator.EQUALS, "N"));

        query.addOrderBy("activationDate");
        query.addOrderBy("productStoreGroupId");
        query.addOrderBy("productId");
        query.addOrderBy("productPriceTypeId");
        query.addOrderBy("productPricePurposeId");
        query.addOrderBy("currencyUomId");
        query.addOrderBy("fromDate");

        Collection mapRecords = query.findByCondition(dctx.getDelegator());
        Iterator iterator = mapRecords.iterator();

        while (iterator.hasNext()) {
            try {
                TransactionUtil.begin();
                applyMapPrice(dctx, (GenericValue) iterator.next());
                TransactionUtil.commit();
            } catch (GenericTransactionException e) {
                TransactionUtil.rollback();
                e.printStackTrace();
                throw e;
            } catch (GenericEntityException e) {
                TransactionUtil.rollback();
                e.printStackTrace();
                throw e;
            }
        }
        System.out.println("executing updateProductPriceMap -- finished");
        return new HashMap();
    }

    private static void applyMapPrice(DispatchContext dctx, GenericValue mapRecord) throws GenericEntityException {
        try {
            
            Date mapFrom = (Date) mapRecord.get("fromDate");
            Date mapThru = (Date) mapRecord.get("thruDate");
            System.out.println("applyMapPrice from:" + mapFrom + " thru:" + mapThru);

            EntityQuery qry, qry2, qry3, qry4;

            qry = createCommonFieldQuery(mapRecord);

            qry2 = new EntityQuery(EntityJoinOperator.OR);

            qry3 = new EntityQuery(EntityJoinOperator.AND);
            qry3.addCondition(new EntityExpr("from_date", EntityComparisonOperator.GREATER_THAN_EQUAL_TO, mapFrom));
            if (mapThru != null) {
                qry3.addCondition(new EntityExpr("from_date", EntityComparisonOperator.LESS_THAN, mapThru));
            }
            qry2.addCondition(qry3.getCondition());

            qry3 = new EntityQuery(EntityJoinOperator.AND);
            qry3.addCondition(new EntityExpr("thru_date", EntityComparisonOperator.GREATER_THAN, mapFrom));
            if (mapThru != null) {
                qry3.addCondition(new EntityExpr("thru_date", EntityComparisonOperator.LESS_THAN_EQUAL_TO, mapThru));
            }
            qry2.addCondition(qry3.getCondition());

            qry3 = new EntityQuery(EntityJoinOperator.AND);
            qry3.addCondition(new EntityExpr("from_date", EntityComparisonOperator.LESS_THAN, mapFrom));
            if (mapThru != null) {
                qry4 = new EntityQuery(EntityJoinOperator.OR);
                qry4.addCondition(new EntityExpr("thru_date", EntityComparisonOperator.GREATER_THAN, mapThru));
                qry4.addCondition(new EntityExpr("thru_date", EntityComparisonOperator.EQUALS, null));
                qry3.addCondition(qry4.getCondition());
            }
            qry2.addCondition(qry3.getCondition());

            qry.addCondition(qry2.getCondition());

            System.out.println(qry.getCondition().toString());

            Collection records = qry.findByCondition(dctx.getDelegator());
            Iterator iterator = records.iterator();

            while (iterator.hasNext()) {
                handleRecord((GenericValue) iterator.next(), mapRecord);
            }

            insertMapRecord(mapRecord);
        } catch (Exception e) {
            mapRecord.set("broken", "Y");
            Debug.logError(e, "Could not handle ProductPriceMap record:" + mapRecord.toString(), "priceMapBatch");
        }
    }

    private static void handleRecord(GenericValue record, GenericValue mapRecord) throws GenericEntityException {
        System.out.println("handleRecord");
        long mapFromDate = ((Date) mapRecord.get("fromDate")).getTime();
        long fromDate = ((Timestamp) record.get("fromDate")).getTime();
        long mapThruDate = mapRecord.get("thruDate")==null?0:((Date) mapRecord.get("thruDate")).getTime();
        long thruDate =  record.get("thruDate")==null?0:((Timestamp) record.get("thruDate")).getTime();

        System.out.println("\tmapFromDate:" + mapRecord.get("fromDate"));
        System.out.println("\tfromDate:" + record.get("fromDate"));
        System.out.println("\tmapThruDate:" + mapRecord.get("thruDate"));
        System.out.println("\tthruDate:" + record.get("thruDate"));
        System.out.println("\tmapprice:" + mapRecord.get("price"));
        System.out.println("\tprice:" + record.get("price"));
        
        if(mapThruDate==0){
            // M |---------- - - - -
            if (fromDate < mapFromDate && (thruDate > mapThruDate || thruDate==0)){
                // M     |---------- - - - -
                // R |---------|
                // R |----------- - - - - 
                // = |-1-|---------- - - - - 
                record.set("thruDate", new Timestamp(decrementDate(mapFromDate)));
                insertProductPrice(record, false); //1
            } else if (fromDate < mapFromDate && thruDate < mapThruDate){
                // M     |---------- - - - -
                // R |---|
                Debug.logImportant("WARNING STRANGE DATA!!!\n" + mapRecord + '\n' + record, "");                
            } else {
                // M      |---------- - - - -
                // R       |-----|
                // R       |---------- - - - - 
                // R      |-----|
                // R      |---------- - - - - 
                // =      |--X--- - - - - - - 
                deleteRecord(record);    
            }
        } else if(thruDate==0){
            // R |------------ - - - 
            if(mapFromDate<fromDate){
                // M |--------|
                // R     |------------ - - -
                // = |-X-|------------ - - -
                deleteRecord(record);    
                record.set("fromDate", new Timestamp(incrementDate(mapThruDate)));
                insertProductPrice(record, true);
                
            } else {
                // M       |-----|
                // R |------------ - - -
                // = |--1--|--X--|--2--- - - - - 
                deleteRecord(record);
                record.set("fromDate", new Timestamp(incrementDate(mapThruDate)));
                insertProductPrice(record, true);//2;

                GenericValue value = GenericValue.create(record);
                value.set("fromDate", new Timestamp(fromDate));
                value.set("thruDate", new Timestamp(decrementDate(mapFromDate)));
                value.set("price", record.get("price"));

                insertProductPrice(value, true);//1
            }
            
        } else if (fromDate > mapFromDate && thruDate < mapThruDate) {
            // M |-----------|
            // R   |------|
            // = |-----X-----|
            deleteRecord(record);

        } else if (fromDate > mapFromDate && thruDate == mapThruDate) {
            // M |-----------|
            // R    |--------|
            // = |-----X-----|
            deleteRecord(record);

        } else if (fromDate < mapFromDate && thruDate == mapThruDate) {
            // M     |---------|
            // R |-------------|
            // = |-1-|----X----|
            
            deleteRecord(record);
            record.set("thruDate", new Timestamp(decrementDate(mapFromDate)));

            insertProductPrice(record, true);//1

        } else if (fromDate == mapFromDate && thruDate < mapThruDate) {
            // M |-----------|
            // R |-----|
            // = |-----X-----|
            deleteRecord(record);

        } else if (fromDate == mapFromDate && thruDate == mapThruDate) {
            // M |-----------|
            // R |-----------|
            // = |-----X-----|
            deleteRecord(record);

        } else if (fromDate == mapFromDate && thruDate > mapThruDate) {
            // M |--------|
            // R |--------------|
            // = |----X---|--1--|
            deleteRecord(record);
            record.set("fromDate", new Timestamp(incrementDate(mapThruDate)));
            insertProductPrice(record, true);//1

        } else if (fromDate < mapFromDate && thruDate > mapThruDate) {
            // M     |-------|
            // R |---------------|
            // = |-1-|---X---|-2-|
            record.set("thruDate", new Timestamp(decrementDate(mapFromDate)));
            
            insertProductPrice(record, false);//1

            GenericValue value = GenericValue.create(record);
            value.set("fromDate", new Timestamp(incrementDate(mapThruDate)));
            value.set("thruDate", new Timestamp(thruDate));
            value.set("price", record.get("price"));

            insertProductPrice(value, true);//2

        } else if (thruDate > mapFromDate && thruDate < mapThruDate) {
            // M       |---------|
            // R |--------|
            // = |--1--|----X----|
            record.set("thruDate", new Timestamp(decrementDate(mapFromDate)));
            insertProductPrice(record, false);//1

        } else if (fromDate > mapFromDate && fromDate < mapThruDate) {
            // M |-------|
            // R     |---------|
            // = |---X---|--1--|
            deleteRecord(record);
            record.set("fromDate", new Timestamp(incrementDate(mapThruDate)));
            insertProductPrice(record, true);//1

        } else {
            Debug.logImportant("WARNING STRANGE DATA!!!\n" + mapRecord + '\n' + record, "");
        }
    }

    private static long incrementDate(long timeInMillis){
        Calendar x = new GregorianCalendar();
        x.setTimeInMillis(timeInMillis);
        x.add(Calendar.DATE,1);
        return x.getTimeInMillis();
    }

    private static long decrementDate(long timeInMillis){
        Calendar x = new GregorianCalendar();
        x.setTimeInMillis(timeInMillis);
        x.add(Calendar.DATE,-1);
        return x.getTimeInMillis();
    }
    
    
    private static void insertMapRecord(GenericValue mapRecord) throws GenericEntityException {
        System.out.println("insertMapRecord from:"+mapRecord.get("fromDate")+" thru:"+mapRecord.get("thruDate") );
        ModelEntity entity = mapRecord.getDelegator().getModelEntity("ProductPrice");
        
        GenericValue value = GenericValue.create(entity);
        value.setAllFields(mapRecord, true, null, null);

        insertProductPrice(value, true);

        mapRecord.set("consumed", "Y");
        mapRecord.store();
    }

    private static void deleteRecord(GenericValue record) throws GenericEntityException {
        System.out.println("deleteRecord from:"+record.get("fromDate")+" thru:"+record.get("thruDate") );
        removeProductPriceCalendar(record);
        record.remove();
    }

    private static void insertProductPrice(GenericValue record, boolean isNew) throws GenericEntityException {
        chopDates(record);
        removeProductPriceCalendar(record);

        if (isNew) {
            record.create();
        } else {
            record.store();
        }
        
        if("CALENDAR_PRICE".equals(record.getString("productPriceTypeId"))){
            Timestamp fromDate = (Timestamp) record.get("fromDate");
            Timestamp thruDate = (Timestamp) record.get("thruDate");
            
            if(thruDate==null){
                Calendar cal = new GregorianCalendar();
                cal.add(Calendar.YEAR,1);
                thruDate = new Timestamp(cal.getTimeInMillis());
            }
            
            // now we insert a row for each date...
            ModelEntity modelEntity = record.getDelegator().getModelEntity("ProductPriceCalendar");
        
            Calendar calendar = new GregorianCalendar();
            calendar.setTime(fromDate);
    
            while (calendar.getTime().before(thruDate) || calendar.getTime().equals(thruDate)) {
                System.out.println("insertProductPriceCalendar date:" + calendar.getTime() );
                GenericValue value = GenericValue.create(modelEntity);
                value.setAllFields(record, true, null, null);
                value.set("date", new Timestamp(calendar.getTimeInMillis()));
                value.create();
                calendar.add(Calendar.DATE, 1);
            }
        }
    }

    /**
     * @param record
     * @throws GenericEntityException
     */
    private static void removeProductPriceCalendar(GenericValue record) throws GenericEntityException {
        if ("CALENDAR_PRICE".equals(record.getString("productPriceTypeId"))) {
            DelegatorInterface delegator = record.getDelegator();
            Timestamp fromDate = (Timestamp) record.get("fromDate");
            Timestamp thruDate = (Timestamp) record.get("thruDate");

            EntityQuery qry = createCommonFieldQuery(record);

            qry.addCondition(new EntityExpr("date", EntityComparisonOperator.GREATER_THAN_EQUAL_TO, fromDate));
            if(thruDate!=null){
                qry.addCondition(new EntityExpr("date", EntityComparisonOperator.LESS_THAN_EQUAL_TO, thruDate));
            }
            int delete = delegator.removeByCondition("ProductPriceCalendar", qry.getCondition());
            System.out.println("Deleted " + delete + " calendar prices!");
        }
    }

    /**
     * @param record
     * @return
     */
    private static EntityQuery createCommonFieldQuery(GenericValue record) {
        String productStoreGroupId = record.getString("productStoreGroupId");
        String productId = record.getString("productId");
        String productPriceTypeId = record.getString("productPriceTypeId");
        String productPricePurposeId = record.getString("productPricePurposeId");
        String currencyUomId = record.getString("currencyUomId");

        EntityQuery qry = new EntityQuery("ProductPrice");

        qry.addCondition(new EntityExpr("product_store_group_id", EntityComparisonOperator.EQUALS, productStoreGroupId));
        qry.addCondition(new EntityExpr("product_id", EntityComparisonOperator.EQUALS, productId));
        qry.addCondition(new EntityExpr("product_price_type_id", EntityComparisonOperator.EQUALS, productPriceTypeId));
        qry.addCondition(new EntityExpr("product_price_purpose_id", EntityComparisonOperator.EQUALS, productPricePurposeId));
        qry.addCondition(new EntityExpr("currency_uom_id", EntityComparisonOperator.EQUALS, currencyUomId));

        return qry;
    }

    private static void chopDates(GenericValue record){
        java.util.Date fromDate = (java.util.Date) record.get("fromDate");
        java.util.Date thruDate = (java.util.Date) record.get("thruDate");
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(fromDate);
        calendar.set(Calendar.HOUR, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        fromDate = new Timestamp(calendar.getTimeInMillis());
        if (thruDate!=null){
            calendar.setTime(thruDate);
            calendar.set(Calendar.HOUR, 0);
            calendar.set(Calendar.MINUTE, 0);
            calendar.set(Calendar.SECOND, 0);
            calendar.set(Calendar.MILLISECOND, 0);
            thruDate = new Timestamp(calendar.getTimeInMillis());
        }
        
        record.set("fromDate", fromDate);
        record.set("thruDate", thruDate);
    }
}
