/**
 * Copyright (c) 2012, 2020, Anatole Tresch, Werner Keil and others by the @author tag.
 *
 * 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.javamoney.moneta.function;

import javax.money.*;
import javax.money.spi.RoundingProviderSpi;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;

public class TestRoundingProvider implements RoundingProviderSpi {

    private Set<String> customIds = new HashSet<>();

    public TestRoundingProvider() {
        customIds.add("zero");
        customIds.add("minusOne");
        customIds.add("CHF-cash");
    }

    private MonetaryRounding zeroRounding = new MonetaryRounding() {
        private final RoundingContext CONTEXT = RoundingContextBuilder.of("TestRoundingProvider", "zero").build();

        @Override
        public RoundingContext getRoundingContext() {
            return CONTEXT;
        }

        @Override
        public MonetaryAmount apply(MonetaryAmount amount) {
            return amount.getFactory().setCurrency(amount.getCurrency()).setNumber(0L).create();
        }

    };

    private MonetaryRounding minusOneRounding = new MonetaryRounding() {
        private final RoundingContext CONTEXT = RoundingContextBuilder.of("TestRoundingProvider", "minusOne").build();

        @Override
        public RoundingContext getRoundingContext() {
            return CONTEXT;
        }

        @Override
        public MonetaryAmount apply(MonetaryAmount amount) {
            return amount.getFactory().setCurrency(amount.getCurrency()).setNumber(-1).create();
        }
    };

    private MonetaryRounding chfCashRounding = new MonetaryRounding() {
        private final RoundingContext CONTEXT =
                RoundingContextBuilder.of("TestRoundingProvider", "chfCashRounding").build();

        @Override
        public RoundingContext getRoundingContext() {
            return CONTEXT;
        }

        @Override
        public MonetaryAmount apply(MonetaryAmount amount) {
            MonetaryOperator minorRounding = Monetary
                    .getRounding(RoundingQueryBuilder.of().set("scale", 2).set(RoundingMode.HALF_UP).build());
            MonetaryAmount amt = amount.with(minorRounding);
            MonetaryAmount mp = amt.with(MonetaryOperators.minorPart());
            if (mp.isGreaterThanOrEqualTo(
                    Monetary.getDefaultAmountFactory().setCurrency(amount.getCurrency()).setNumber(0.03)
                            .create())) {
                // add
                return amt.add(Monetary.getDefaultAmountFactory().setCurrency(amt.getCurrency())
                        .setNumber(new BigDecimal("0.05")).create().subtract(mp));
            } else {
                // subtract
                return amt.subtract(mp);
            }
        }
    };

    @Override
    public MonetaryRounding getRounding(RoundingQuery roundingQuery) {
        LocalDate timestamp = roundingQuery.get(LocalDate.class);
        if (roundingQuery.getRoundingName() == null) {
            return getRounding(roundingQuery, timestamp, "default");
        } else {
            return getRounding(roundingQuery, timestamp, roundingQuery.getRoundingName());
        }
    }

    private MonetaryRounding getRounding(RoundingQuery roundingQuery, LocalDate timestamp, String roundingId) {
        if ("foo".equals(roundingId)) {
            return null;
        }
        if ("default".equals(roundingId)) {
            CurrencyUnit currency = roundingQuery.getCurrency();
            if (Objects.nonNull(currency)) {
                if ("XXX".equals(currency.getCurrencyCode())) {
                    if (timestamp != null && timestamp.isAfter(LocalDate.now())) {
                        return minusOneRounding;
                    } else {
                        return zeroRounding;
                    }
                } else if (Optional.ofNullable(roundingQuery.getBoolean("cashRounding")).orElse(Boolean.FALSE)) {
                    if ("CHF".equals(currency.getCurrencyCode())) {
                        return chfCashRounding;
                    }
                }
            }
        } else {
            MonetaryRounding r = getCustomRounding(roundingId);
            if (r != null) {
                return r;
            }
        }
        return null;
    }


    private MonetaryRounding getCustomRounding(String customRoundingId) {
        switch (customRoundingId) {
            case "CHF-cash":
                return chfCashRounding;
            case "zero":
                return zeroRounding;
            case "minusOne":
                return minusOneRounding;
        }
        return null;
    }

    @Override
    public Set<String> getRoundingNames() {
        return customIds;
    }

}