/*
 * Copyright 2014-2016 Groupon, Inc
 * Copyright 2014-2016 The Billing Project, LLC
 *
 * The Billing Project 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 org.killbill.billing.catalog;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.CatalogEntity;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.MutableStaticCatalog;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.Price;
import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;

public class DefaultMutableStaticCatalog extends StandaloneCatalog implements MutableStaticCatalog {

    public DefaultMutableStaticCatalog() {
    }

    public DefaultMutableStaticCatalog(final Date effectiveDate) {
        super(effectiveDate);
    }

    public DefaultMutableStaticCatalog(final StandaloneCatalog input) {
        this.setCatalogName(input.getCatalogName())
            .setRecurringBillingMode(input.getRecurringBillingMode())
            .setEffectiveDate(input.getEffectiveDate())
            .setSupportedCurrencies(input.getCurrentSupportedCurrencies())
            .setUnits(input.getCurrentUnits())
            .setProducts(input.getCurrentProducts())
            .setPlans(input.getCurrentPlans())
            .setPlanRules(input.getPlanRules())
            .setPriceLists(input.getPriceLists());
        initialize(this);
    }

    @Override
    public void addCurrency(final Currency currency) throws CatalogApiException {
        final Currency[] newEntries = allocateNewEntries(getCurrentSupportedCurrencies(), currency);
        setSupportedCurrencies(newEntries);
    }

    @Override
    public void addProduct(final Product product) throws CatalogApiException {
        getCatalogEntityCollectionProduct().add(product);
    }

    @Override
    public void addPlan(final Plan plan) throws CatalogApiException {

        getCatalogEntityCollectionPlan().add(plan);

        final DefaultPriceList priceList = getPriceLists().findPriceListFrom(plan.getPriceListName());
        priceList.getCatalogEntityCollectionPlan().add(plan);
    }

    @Override
    public void addPriceList(final PriceList priceList) throws CatalogApiException {
        final PriceList[] newEntries = allocateNewEntries(getPriceLists().getChildPriceLists(), priceList);
        final DefaultPriceListSet priceListSet = new DefaultPriceListSet((PriceListDefault) getPriceLists().getDefaultPricelist(), (DefaultPriceList[]) newEntries);
        setPriceLists(priceListSet);
    }

    public void addRecurringPriceToPlan(final DefaultInternationalPrice currentPrices, final Price newPrice) throws CatalogApiException {
        final Price[] newEntries = allocateNewEntries(currentPrices.getPrices(), newPrice);
        currentPrices.setPrices((DefaultPrice[]) newEntries);
    }

    public void addProductAvailableAO(final Product targetBasePlan, final DefaultProduct aoProduct) throws CatalogApiException {
        ((DefaultProduct) targetBasePlan).getCatalogEntityCollectionAvailable().add(aoProduct);
    }

    private <T> T[] allocateNewEntries(final T[] existingEntries, final T newEntry) throws CatalogApiException {

        if (existingEntries != null) {
            for (T input : existingEntries) {
                boolean found;
                if (input instanceof CatalogEntity) {
                    found = ((CatalogEntity) input).getName().equals(((CatalogEntity) newEntry).getName());
                } else if (input instanceof Enum) {
                    found = ((Enum) input).name().equals(((Enum) newEntry).name());
                } else if (input instanceof Price) {
                    found = ((Price) input).getCurrency().equals(((Price) newEntry).getCurrency());
                } else {
                    throw new IllegalStateException("Unexpected type " + newEntry.getClass());
                }
                if (found) {
                    //throw new CatalogApiException();
                    throw new IllegalStateException("Already existing " + newEntry);
                }
            }
        }

        // Realloc and assign new entry
        final int length = existingEntries != null ? existingEntries.length : 0;
        final T[] newEntries = (T[]) Array.newInstance(newEntry.getClass(), length + 1);
        for (int i = 0; i < newEntries.length + 1; i++) {
            if (i < newEntries.length - 1) {
                newEntries[i] = existingEntries[i];
            } else {
                newEntries[newEntries.length - 1] = newEntry;
            }
        }
        return newEntries;
    }

}
