/*
 * -----------------------------------------------------------------------
 * Copyright © 2013-2015 Meno Hochschild, <http://www.menodata.de/>
 * -----------------------------------------------------------------------
 * This file (NavigationOperator.java) is part of project Time4J.
 *
 * Time4J is free software: You can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Time4J is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Time4J. If not, see <http://www.gnu.org/licenses/>.
 * -----------------------------------------------------------------------
 */

package net.time4j;

import net.time4j.engine.AdvancedElement;
import net.time4j.engine.ChronoEntity;
import net.time4j.engine.ChronoException;
import net.time4j.engine.ChronoOperator;


/**
 * <p>Spezialoperator zum Navigieren zu bestimmten Elementwerten. </p>
 *
 * @param       <V> generic enum type of element values
 * @author      Meno Hochschild
 * @concurrency <immutable>
 */
final class NavigationOperator<V extends Enum<V>>
    extends ElementOperator<PlainDate> {

    //~ Instanzvariablen --------------------------------------------------

    private final V value;
    private final int len;
    private final ChronoOperator<PlainTimestamp> navTS;

    //~ Konstruktoren -----------------------------------------------------

    /**
     * <p>Konstruiert eine neue Instanz. </p>
     *
     * @param   element     reference element
     * @param   mode        navigation mode
     * @param   value       target value of navigation
     */
    NavigationOperator(
        AdvancedElement<V> element,
        int mode,
        V value
    ) {
        super(element, mode);

        if (value == null) {
            throw new NullPointerException("Missing value.");
        }

        this.value = value;
        this.len = element.getType().getEnumConstants().length;
        this.navTS = this::doApply;

    }

    //~ Methoden ----------------------------------------------------------

    @Override
    public PlainDate apply(PlainDate entity) {

        return this.doApply(entity);

    }

    @Override
    ChronoOperator<PlainTimestamp> onTimestamp() {

        return this.navTS;

    }

    private <T extends ChronoEntity<T>> T doApply(T entity) {

        if (entity.contains(PlainDate.CALENDAR_DATE)) {
            PlainDate date = entity.get(PlainDate.CALENDAR_DATE);
            Object enumValue = date.get(this.getElement());
            int oldOrdinal = Enum.class.cast(enumValue).ordinal();
            int newOrdinal = this.delta(oldOrdinal);

            if (newOrdinal == oldOrdinal) {
                return entity;
            } else {
                return entity.with(
                    PlainDate.CALENDAR_DATE,
                    date.plus(
                        (newOrdinal - oldOrdinal),
                        date.getChronology().getBaseUnit(this.getElement()))
                );
            }
        }

        String navigation;

        switch (this.getType()) {
            case OP_NAV_NEXT:
                navigation = "setToNext";
                break;
            case OP_NAV_PREVIOUS:
                navigation = "setToPrevious";
                break;
            case OP_NAV_NEXT_OR_SAME:
                navigation = "setToNextOrSame";
                break;
            case OP_NAV_PREVIOUS_OR_SAME:
                navigation = "setToPreviousOrSame";
                break;
            default:
                throw new AssertionError("Unknown: " + this.getType());
        }

        throw new ChronoException(
            navigation
            + "()-operation not supported on: "
            + this.getElement().name());

    }

    private int delta(int oldOrdinal) {

        int newOrdinal = this.value.ordinal();

        switch (this.getType()) {
            case OP_NAV_NEXT:
                if (newOrdinal <= oldOrdinal) {
                    newOrdinal += this.len;
                }
                break;
            case OP_NAV_PREVIOUS:
                if (newOrdinal >= oldOrdinal) {
                    newOrdinal -= this.len;
                }
                break;
            case OP_NAV_NEXT_OR_SAME:
                if (newOrdinal < oldOrdinal) {
                    newOrdinal += this.len;
                }
                break;
            case OP_NAV_PREVIOUS_OR_SAME:
                if (newOrdinal > oldOrdinal) {
                    newOrdinal -= this.len;
                }
                break;
            default:
                throw new AssertionError("Unknown: " + this.getType());
        }

        return newOrdinal;

    }

}

@@ -1058,24 +1058,22 @@
 .engine.
-Advanced
+Chrono
 Element;
@@ -2002,16 +2002,14 @@
     
-Advanced
+Chrono
 Elem

remove reference to AdvancedElement