/*
 * Licensed by the author of Time4J-project.
 *
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership. The copyright owner
 * 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 net.time4j.calendar;

import net.time4j.Moment;
import net.time4j.engine.AttributeQuery;
import net.time4j.engine.CalendarDays;
import net.time4j.engine.ChronoDisplay;
import net.time4j.engine.ChronoElement;
import net.time4j.engine.ChronoException;
import net.time4j.engine.ElementRule;
import net.time4j.format.Attributes;
import net.time4j.format.TextElement;

import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.text.ParsePosition;
import java.util.Locale;


/**
 * Represents a generic element for the solar term of East Asian lunisolar calendars.
 *
 * @author  Meno Hochschild
 * @since   3.40/4.35
 */
class EastAsianST<D extends EastAsianCalendar<?, D>>
    implements TextElement<SolarTerm>, ElementRule<D, SolarTerm>, Serializable {

    //~ Statische Felder/Initialisierungen ----------------------------

    private static final EastAsianST SINGLETON = new EastAsianST();

    private static final long serialVersionUID = 4572549754637955194L;

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

    @SuppressWarnings("unchecked")
    static <D extends EastAsianCalendar<?, D>> EastAsianST<D> getInstance() {
        return SINGLETON;
    }

    @Override
    public String name() {
        return "SOLAR_TERM";
    }

    @Override
    public Class<SolarTerm> getType() {
        return SolarTerm.class;
    }

    @Override
    public char getSymbol() {
        return '\u0000';
    }

    @Override
    public int compare(
        ChronoDisplay o1,
        ChronoDisplay o2
    ) {
        return o1.get(this).compareTo(o2.get(this));
    }

    @Override
    public SolarTerm getDefaultMinimum() {
        return SolarTerm.MINOR_01_LICHUN_315;
    }

    @Override
    public SolarTerm getDefaultMaximum() {
        return SolarTerm.MAJOR_12_DAHAN_300;
    }

    @Override
    public boolean isDateElement() {
        return true;
    }

    @Override
    public boolean isTimeElement() {
        return false;
    }

    @Override
    public boolean isLenient() {
        return false;
    }

    @Override
    public String getDisplayName(Locale locale) {
        String lang = locale.getLanguage();

        if (lang.equals("zh")) {
            return locale.getCountry().equals("TW") ? "節氣" : "节气";
        } else if (lang.equals("ko")) {
            return "절기";
        } else if (lang.equals("vi")) {
            return "tiết khí";
        } else if (lang.equals("ja")) {
            return "節気";
        } else if (lang.isEmpty()) {
            return "jieqi";
        } else {
            return "jiéqì"; // pinyin
        }
    }

    @Override
    public void print(
        ChronoDisplay context,
        Appendable buffer,
        AttributeQuery attributes
    ) throws IOException, ChronoException {
        Locale loc = attributes.get(Attributes.LANGUAGE, Locale.ROOT);
        SolarTerm st = context.get(this);
        buffer.append(st.getDisplayName(loc));
    }

    @Override
    public SolarTerm parse(
        CharSequence text,
        ParsePosition status,
        AttributeQuery attributes
    ) {
        Locale loc = attributes.get(Attributes.LANGUAGE, Locale.ROOT);
        int len = text.length();
        int start = status.getIndex();

        if (start >= len) {
            status.setErrorIndex(len);
            return null;
        }

        return SolarTerm.parse(text, loc, status);
    }

    @Override
    public SolarTerm getValue(D context) {
        Moment endOfDay = context.getCalendarSystem().midnight(context.getDaysSinceEpochUTC() + 1);
        return SolarTerm.of(endOfDay);
    }

    @Override
    public SolarTerm getMinimum(D context) {
        EastAsianCS<D> calsys = context.getCalendarSystem();
        long first = calsys.newYear(context.getCycle(), context.getYear().getNumber());
        return SolarTerm.of(calsys.midnight(first + 1));
    }

    @Override
    public SolarTerm getMaximum(D context) {
        EastAsianCS<D> calsys = context.getCalendarSystem();
        long first = calsys.newYear(context.getCycle(), context.getYear().getNumber());
        return SolarTerm.of(calsys.midnight(first + context.lengthOfYear()));
    }

    @Override
    public boolean isValid(
        D context,
        SolarTerm value
    ) {
        return (value != null);
    }

    @Override
    public D withValue(
        D context,
        SolarTerm value,
        boolean lenient
    ) {
        if (value == null) {
            throw new IllegalArgumentException("Missing solar term.");
        } else {
            long newYear = context.getCalendarSystem().newYear(context.getCycle(), context.getYear().getNumber());
            return value.onOrAfter(context.minus(CalendarDays.of(context.getDaysSinceEpochUTC() - newYear)));
        }
    }

    @Override
    public ChronoElement<?> getChildAtFloor(D context) {
        throw new AbstractMethodError(); // never called
    }

    @Override
    public ChronoElement<?> getChildAtCeiling(D context) {
        throw new AbstractMethodError(); // never called
    }

    /**
     * @serialData  Preserves the singleton semantic
     * @return      singleton instance
     */
    protected Object readResolve() throws ObjectStreamException {
        return SINGLETON;
    }

}