/*
 *  Copyright 2001-2005 Stephen Colebourne
 *
 *  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.joda.time;

import java.io.Serializable;

/**
 * Identifies a duration field, such as years or minutes, in a chronology-neutral way.
 * <p>
 * A duration field type defines the type of the field, such as hours.
 * If does not directly enable any calculations, however it does provide a
 * {@link #getField(Chronology)} method that returns the actual calculation engine
 * for a particular chronology.
 * <p>
 * Instances of <code>DurationFieldType</code> are singletons.
 * They can be compared using <code>==</code>.
 * <p>
 * If required, you can create your own field, for example a quarters.
 * You must create a subclass of <code>DurationFieldType</code> that defines the field type.
 * This class returns the actual calculation engine from {@link #getField(Chronology)}.
 *
 * @author Stephen Colebourne
 * @author Brian S O'Neill
 * @since 1.0
 */
public abstract class DurationFieldType implements Serializable {

    /** Serialization version */
    private static final long serialVersionUID = 8765135187319L;

    // Ordinals for standard field types.
    static final byte
        ERAS = 1,
        CENTURIES = 2,
        WEEKYEARS = 3,
        YEARS = 4,
        MONTHS = 5,
        WEEKS = 6,
        DAYS = 7,
        HALFDAYS = 8,
        HOURS = 9,
        MINUTES = 10,
        SECONDS = 11,
        MILLIS = 12;

    /** The eras field type. */
    static final DurationFieldType ERAS_TYPE = new StandardDurationFieldType("eras", ERAS);
    /** The centuries field type. */
    static final DurationFieldType CENTURIES_TYPE = new StandardDurationFieldType("centuries", CENTURIES);
    /** The weekyears field type. */
    static final DurationFieldType WEEKYEARS_TYPE = new StandardDurationFieldType("weekyears", WEEKYEARS);
    /** The years field type. */
    static final DurationFieldType YEARS_TYPE = new StandardDurationFieldType("years", YEARS);
    /** The months field type. */
    static final DurationFieldType MONTHS_TYPE = new StandardDurationFieldType("months", MONTHS);
    /** The weeks field type. */
    static final DurationFieldType WEEKS_TYPE = new StandardDurationFieldType("weeks", WEEKS);
    /** The days field type. */
    static final DurationFieldType DAYS_TYPE = new StandardDurationFieldType("days", DAYS);
    /** The halfdays field type. */
    static final DurationFieldType HALFDAYS_TYPE = new StandardDurationFieldType("halfdays", HALFDAYS);
    /** The hours field type. */
    static final DurationFieldType HOURS_TYPE = new StandardDurationFieldType("hours", HOURS);
    /** The minutes field type. */
    static final DurationFieldType MINUTES_TYPE = new StandardDurationFieldType("minutes", MINUTES);
    /** The seconds field type. */
    static final DurationFieldType SECONDS_TYPE = new StandardDurationFieldType("seconds", SECONDS);
    /** The millis field type. */
    static final DurationFieldType MILLIS_TYPE = new StandardDurationFieldType("millis", MILLIS);

    /** The name of the field type. */
    private final String iName;

    //-----------------------------------------------------------------------
    /**
     * Constructor.
     * 
     * @param name  the name to use, which by convention, are plural.
     */
    protected DurationFieldType(String name) {
        super();
        iName = name;
    }

    //-----------------------------------------------------------------------
    /**
     * Get the millis field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType millis() {
        return MILLIS_TYPE;
    }

    /**
     * Get the seconds field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType seconds() {
        return SECONDS_TYPE;
    }

    /**
     * Get the minutes field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType minutes() {
        return MINUTES_TYPE;
    }

    /**
     * Get the hours field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType hours() {
        return HOURS_TYPE;
    }

    /**
     * Get the halfdays field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType halfdays() {
        return HALFDAYS_TYPE;
    }

    //-----------------------------------------------------------------------
    /**
     * Get the days field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType days() {
        return DAYS_TYPE;
    }

    /**
     * Get the weeks field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType weeks() {
        return WEEKS_TYPE;
    }

    /**
     * Get the weekyears field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType weekyears() {
        return WEEKYEARS_TYPE;
    }

    /**
     * Get the months field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType months() {
        return MONTHS_TYPE;
    }

    /**
     * Get the years field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType years() {
        return YEARS_TYPE;
    }

    /**
     * Get the centuries field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType centuries() {
        return CENTURIES_TYPE;
    }

    /**
     * Get the eras field type.
     * 
     * @return the DurationFieldType constant
     */
    public static DurationFieldType eras() {
        return ERAS_TYPE;
    }

    //-----------------------------------------------------------------------
    /**
     * Get the name of the field.
     * By convention, names are plural.
     * 
     * @return field name
     */
    public String getName() {
        return iName;
    }

    /**
     * Gets a suitable field for this type from the given Chronology.
     *
     * @param chronology  the chronology to use, null means ISOChronology in default zone
     * @return a suitable field
     */
    public abstract DurationField getField(Chronology chronology);

    /**
     * Checks whether this field supported in the given Chronology.
     *
     * @param chronology  the chronology to use, null means ISOChronology in default zone
     * @return true if supported
     */
    public boolean isSupported(Chronology chronology) {
        return getField(chronology).isSupported();
    }

    /**
     * Get a suitable debug string.
     * 
     * @return debug string
     */
    @Override
    public String toString() {
        return getName();
    }

    private static class StandardDurationFieldType extends DurationFieldType {
        /** Serialization version */
        private static final long serialVersionUID = 31156755687123L;

        /** The ordinal of the standard field type, for switch statements */
        private final byte iOrdinal;

        /**
         * Constructor.
         * 
         * @param name  the name to use
         */
        StandardDurationFieldType(String name, byte ordinal) {
            super(name);
            iOrdinal = ordinal;
        }

        /** @inheritdoc */
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof StandardDurationFieldType) {
                return iOrdinal == ((StandardDurationFieldType) obj).iOrdinal;
            }
            return false;
        }

        /** @inheritdoc */
        @Override
        public int hashCode() {
            return (1 << iOrdinal);
        }

        @Override
        public DurationField getField(Chronology chronology) {
            chronology = DateTimeUtils.getChronology(chronology);
            
            switch (iOrdinal) {
                case ERAS:
                    return chronology.eras();
                case CENTURIES:
                    return chronology.centuries();
                case WEEKYEARS:
                    return chronology.weekyears();
                case YEARS:
                    return chronology.years();
                case MONTHS:
                    return chronology.months();
                case WEEKS:
                    return chronology.weeks();
                case DAYS:
                    return chronology.days();
                case HALFDAYS:
                    return chronology.halfdays();
                case HOURS:
                    return chronology.hours();
                case MINUTES:
                    return chronology.minutes();
                case SECONDS:
                    return chronology.seconds();
                case MILLIS:
                    return chronology.millis();
                default:
                    // Shouldn't happen.
                    throw new InternalError();
            }
        }

        /**
         * Ensure a singleton is returned.
         * 
         * @return the singleton type
         */
        private Object readResolve() {
            switch (iOrdinal) {
                case ERAS:
                    return ERAS_TYPE;
                case CENTURIES:
                    return CENTURIES_TYPE;
                case WEEKYEARS:
                    return WEEKYEARS_TYPE;
                case YEARS:
                    return YEARS_TYPE;
                case MONTHS:
                    return MONTHS_TYPE;
                case WEEKS:
                    return WEEKS_TYPE;
                case DAYS:
                    return DAYS_TYPE;
                case HALFDAYS:
                    return HALFDAYS_TYPE;
                case HOURS:
                    return HOURS_TYPE;
                case MINUTES:
                    return MINUTES_TYPE;
                case SECONDS:
                    return SECONDS_TYPE;
                case MILLIS:
                    return MILLIS_TYPE;
                default:
                    // Shouldn't happen.
                    return this;
            }
        }
    }
}
