/*
 * Copyright 2010-2014 Ning, Inc.
 *
 * Ning licenses this file to you 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 com.ning.billing.invoice.tree;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.UUID;

import org.joda.time.LocalDate;

import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.tree.Item.ItemAction;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

public class ItemsInterval {

    private final NodeInterval interval;
    private LinkedList<Item> items;

    public ItemsInterval(final NodeInterval interval) {
        this(interval, null);
    }

    public ItemsInterval(final NodeInterval interval, final Item initialItem) {
        this.interval = interval;
        this.items = Lists.newLinkedList();
        if (initialItem != null) {
            items.add(initialItem);
        }
    }

    public boolean containsItem(final UUID targetId) {
        return Iterables.tryFind(items, new Predicate<Item>() {
            @Override
            public boolean apply(final Item input) {
                return input.getId().equals(targetId);
            }
        }).orNull() != null;
    }

    public void setAdjustment(final BigDecimal amount, final UUID targetId) {
        final Item item = Iterables.tryFind(items, new Predicate<Item>() {
            @Override
            public boolean apply(final Item input) {
                return input.getId().equals(targetId);
            }
        }).get();
        item.incrementAdjustedAmount(amount);
    }

    public List<Item> getItems() {
        return items;
    }

    public void buildForMissingInterval(final LocalDate startDate, final LocalDate endDate, final List<Item> output, final boolean addRepair) {
        final Item item = createNewItem(startDate, endDate, addRepair);
        if (item != null) {
            output.add(item);
        }
    }

    public void buildFromItems(final List<Item> output, final boolean isParentRepair, final boolean mergeMode) {
        final Set<UUID> repairedIds = new HashSet<UUID>();
        final ListIterator<Item> it = items.listIterator(items.size());

        while (it.hasPrevious()) {
            final Item cur = it.previous();
            switch (cur.getAction()) {
                case ADD:
                    if (!mergeMode && !repairedIds.contains(cur.getId())) {
                        output.add(cur);
                    }

                    if (mergeMode && !isParentRepair) {
                        output.add(cur);
                    }
                    break;
                case CANCEL:
                    if (cur.getLinkedId() != null) {
                        repairedIds.add(cur.getLinkedId());
                    }
                    if (mergeMode) {
                        output.add(cur);
                    }
                    break;
            }
        }
    }

    // Just ensure that ADD items precedes CANCEL items
    public void insertSortedItem(final Item item) {
        items.add(item);
        Collections.sort(items, new Comparator<Item>() {
            @Override
            public int compare(final Item o1, final Item o2) {
                if (o1.getAction() == ItemAction.ADD && o2.getAction() == ItemAction.CANCEL) {
                    return -1;
                } else if (o1.getAction() == ItemAction.CANCEL && o2.getAction() == ItemAction.ADD) {
                    return 1;
                } else {
                    return 0;
                }
            }
        });
    }

    public void cancelItems(final Item item) {
        Preconditions.checkState(item.getAction() == ItemAction.ADD);
        Preconditions.checkState(items.size() == 1);
        Preconditions.checkState(items.get(0).getAction() == ItemAction.CANCEL);
        items.clear();
    }

    // STEPH so complicated...
    public boolean isRepairNode() {
       return items.size() == 1 && items.get(0).getAction() == ItemAction.CANCEL;
    }

    private Item createNewItem(LocalDate startDate, LocalDate endDate, final boolean mergeMode) {

        final List<Item> itemToConsider = new LinkedList<Item>();
        // STEPH flags...
        buildFromItems(itemToConsider, true, mergeMode);

        Iterator<Item> it = itemToConsider.iterator();
        while (it.hasNext()) {
            final Item cur = it.next();
            if (cur.getAction() == ItemAction.CANCEL && !mergeMode) {
                continue;
            }
            final Item result = new Item(cur.toProratedInvoiceItem(startDate, endDate), cur.getAction());
            if (cur.getAction() == ItemAction.CANCEL && result != null) {
                cur.incrementCurrentRepairedAmount(result.getAmount());
            }
            return result;
        }
        return null;
    }
}
