/*
 * Copyright 2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.broadleafcommerce.core.order.service;

import org.apache.commons.lang.StringUtils;
import org.broadleafcommerce.core.order.domain.BundleOrderItem;
import org.broadleafcommerce.core.order.domain.DiscreteOrderItem;
import org.broadleafcommerce.core.order.domain.GiftWrapOrderItem;
import org.broadleafcommerce.core.order.domain.Order;
import org.broadleafcommerce.core.order.domain.OrderItem;
import org.broadleafcommerce.core.order.service.call.MergeCartResponse;
import org.broadleafcommerce.core.order.service.call.ReconstructCartResponse;
import org.broadleafcommerce.core.order.service.exception.RemoveFromCartException;
import org.broadleafcommerce.core.order.service.type.OrderStatus;
import org.broadleafcommerce.core.pricing.service.exception.PricingException;
import org.broadleafcommerce.profile.core.domain.Customer;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * The 2.0 implementation of merge cart service. Instead of merging items directly from one cart to another, we will
 * convert the previous cart to a named order that the customer is able to interact with as they see fit.
 * 
 * @author Andre Azzolini (apazzolini)
 */
@Service("blMergeCartService")
public class MergeCartServiceImpl implements MergeCartService {

    @Resource(name = "blOrderService")
    protected OrderService orderService;

    @Resource(name = "blOrderItemService")
    protected OrderItemService orderItemService;

    @Resource(name = "blFulfillmentGroupService")
    protected FulfillmentGroupService fulfillmentGroupService;

    @Override
    public MergeCartResponse mergeCart(Customer customer, Order anonymousCart)
            throws PricingException, RemoveFromCartException {
        return mergeCart(customer, anonymousCart, true);
    }

    @Override
    public ReconstructCartResponse reconstructCart(Customer customer) throws PricingException, RemoveFromCartException {
        return reconstructCart(customer, true);
    }

    @Override
    public MergeCartResponse mergeCart(Customer customer, Order anonymousCart, boolean priceOrder)
            throws PricingException, RemoveFromCartException {
        MergeCartResponse mergeCartResponse = new MergeCartResponse();
        mergeCartResponse.setMerged(false); // We no longer merge items, only transition cart states

        // We need to make sure that the old, saved customer cart is reconstructed with availability concerns in mind
        ReconstructCartResponse reconstructCartResponse = reconstructCart(customer, false);
        mergeCartResponse.setRemovedItems(reconstructCartResponse.getRemovedItems());
        Order customerCart = reconstructCartResponse.getOrder();

        if (anonymousCart == null || anonymousCart.getOrderItems().size() == 0) {
            // The anonymous cart is of no use, use the customer cart
            mergeCartResponse.setOrder(customerCart);
        } else if (customerCart == null || customerCart.getOrderItems().size() == 0) {
            // The customer cart is of no use, use the anonymous cart
            setNewCartOwnership(anonymousCart, customer);
            mergeCartResponse.setOrder(anonymousCart);
        } else {
            // Both carts have some items. The anonymous cart will always be the more recent one by definition
            // Save off the old customer cart and use the anonymous cart
            setSavedCartAttributes(customerCart);
            orderService.save(customerCart, false);

            setNewCartOwnership(anonymousCart, customer);
            mergeCartResponse.setOrder(anonymousCart);
        }
        
        if (mergeCartResponse.getOrder() != null) {
            Order savedCart = orderService.save(mergeCartResponse.getOrder(), priceOrder);
            mergeCartResponse.setOrder(savedCart);
        }
        
        return mergeCartResponse;
    }

    @Override
    public ReconstructCartResponse reconstructCart(Customer customer, boolean priceOrder)
            throws PricingException, RemoveFromCartException {
        ReconstructCartResponse reconstructCartResponse = new ReconstructCartResponse();
        Order customerCart = orderService.findCartForCustomer(customer);

        if (customerCart != null) {
            List<OrderItem> itemsToRemove = new ArrayList<OrderItem>();

            for (OrderItem orderItem : customerCart.getOrderItems()) {
                if (orderItem instanceof DiscreteOrderItem) {
                    DiscreteOrderItem doi = (DiscreteOrderItem) orderItem;
                    if (!checkActive(doi) || !checkInventory(doi) || !checkOtherValidity(orderItem)) {
                        itemsToRemove.add(orderItem);
                    }
                } else if (orderItem instanceof BundleOrderItem) {
                    BundleOrderItem bundleOrderItem = (BundleOrderItem) orderItem;
                    for (DiscreteOrderItem doi : bundleOrderItem.getDiscreteOrderItems()) {
                        if (!checkActive(doi) || !checkInventory(doi) || !checkOtherValidity(orderItem)) {
                            itemsToRemove.add(doi.getBundleOrderItem());
                        }
                    }
                }
            }

            //Remove any giftwrap items who have one or more wrapped item members that have been removed
            for (OrderItem orderItem : customerCart.getOrderItems()) {
                if (orderItem instanceof GiftWrapOrderItem) {
                    for (OrderItem wrappedItem : ((GiftWrapOrderItem) orderItem).getWrappedItems()) {
                        if (itemsToRemove.contains(wrappedItem)) {
                            itemsToRemove.add(orderItem);
                            break;
                        }
                    }
                }
            }

            for (OrderItem item : itemsToRemove) {
                orderService.removeItem(customerCart.getId(), item.getId(), false);
            }

            reconstructCartResponse.setRemovedItems(itemsToRemove);
            customerCart = orderService.save(customerCart, priceOrder);
        }

        reconstructCartResponse.setOrder(customerCart);
        return reconstructCartResponse;
    }
    
    protected void setSavedCartAttributes(Order cart) {
        SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, 'yy");
        Date cartLastUpdated = cart.getAuditable().getDateUpdated();
        
        cart.setName("Previously saved cart - " + sdf.format(cartLastUpdated));
        cart.setStatus(OrderStatus.NAMED);
    }

    protected void setNewCartOwnership(Order cart, Customer customer) {
        cart.setCustomer(customer);

        // copy the customer's email to this order, overriding any previously set email
        if (cart != null && StringUtils.isNotBlank(customer.getEmailAddress())) {
            cart.setEmailAddress(customer.getEmailAddress());
        }
    }

    /**
     * @param orderItem
     * @return whether or not the discrete order item's sku is active
     */
    protected boolean checkActive(DiscreteOrderItem orderItem) {
        return orderItem.getSku().isActive(orderItem.getProduct(), orderItem.getCategory());
    }

    /**
     * By default, Broadleaf does not provide an inventory check. This is set up as an extension point if your
     * application needs it.
     * 
     * @param orderItem
     * @return whether or not the item is in stock
     */
    protected boolean checkInventory(DiscreteOrderItem orderItem) {
        return true;
    }

    /**
     * By default, Broadleaf does not provide additional validity checks. This is set up as an extension point if your
     * application needs it.
     * 
     * @param orderItem
     * @return whether or not the orderItem is valid
     */
    protected boolean checkOtherValidity(OrderItem orderItem) {
        return true;
    }

}

@@ -7022,16 +7022,17 @@
 MMM dd, 
+'
 'yy%22);%0A 

Fix default date formatting