/*
 * Copyright (c) 2015-2015 Amedia Utvikling AS.
 *
 * 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 no.api.freemarker.java8.time;

import freemarker.core.Environment;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateModelException;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.zone.ZoneRulesException;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

/**
 * Helper methods and constants in use by the adapters.
 */
public final class DateTimeTools {

    public static final String METHOD_EQUALS = "isEqual";
    public static final String METHOD_BEFORE = "isBefore";
    public static final String METHOD_AFTER = "isAfter";
    public static final String METHOD_FORMAT = "format";
    public static final String METHOD_DAYS = "days";
    public static final String METHOD_MONTHS = "months";
    public static final String METHOD_YEARS = "years";
    public static final String METHOD_NANO = "nano";
    public static final String METHOD_SECONDS = "seconds";

    public static final String METHOD_UNKNOWN_MSG = "Unknown method call: ";
    public static final String ILLEGAL_ZONE_ID_MSG = "Illegal Zone ID";


    private DateTimeTools() {
        throw new UnsupportedOperationException();
    }


    /**
     * Create a DateTimeFormatter from a pattern found in a List on a given index.
     *
     * @param list             A list of Strings containing the pattern
     * @param index            The index on where in the list the pattern is located.
     * @param defaultFormatter A default formatter to be returned if the given list size is lower than the given index.
     * @return A DateTimeFormatter for the given pattern, or the default formatter.
     */
    public static DateTimeFormatter createDateTimeFormatter(final List list, final int index,
                                                            final DateTimeFormatter defaultFormatter) {
        if (list.size() > 0) {
            final String format = ((SimpleScalar) list.get(index)).getAsString();
            final ExtFormatStyle style = DateTimeTools.getFormatStyle(format);

            if (style != null) {
                return style.getFormatter().withLocale(DateTimeTools.getLocale());
            }
            final Optional<DateTimeFormatter> builtin = DateTimeTools.getJreBuiltinFormatter(format);
            return builtin.orElseGet(() -> DateTimeFormatter.ofPattern(format, DateTimeTools.getLocale()));
        }
        return defaultFormatter.withLocale(DateTimeTools.getLocale());
    }


    /**
     * Create a DateTimeFormatter from a pattern found in a List on a given index.
     *
     * @param list           A list of Strings containing the pattern
     * @param index          The index on where in the list the pattern is located.
     * @param defaultPattern The pattern to be used for the formatter if the list size is lower than the given index.
     * @return A DateTimeFormatter for the given pattern, or the default pattern.
     */
    public static DateTimeFormatter createDateTimeFormatter(List list,
                                                            int index,
                                                            final String defaultPattern) {
        return DateTimeFormatter.ofPattern(
                list.size() > index
                        ? ((SimpleScalar) list.get(index)).getAsString()
                        : defaultPattern, getLocale());
    }


    /**
     * Look up a ZoneId based on a String in a list on a given index.
     *
     * @param list  A list of Strings containing the String representation of the ZoneId.
     * @param index The index on where in the list the ZoneId string is located.
     * @return A ZoneId instance for the given ZoneId string. If index is lower than the list size, then an empty {@link Optional} will be returned.
     * @throws TemplateModelException If Illegal ZoneId string was found in the list.
     */
    public static Optional<ZoneId> zoneIdLookup(final List list, final int index) throws TemplateModelException {
        if (list.size() > index) {
            final String zoneIdString = ((SimpleScalar) list.get(index)).getAsString();
            try {
                return Optional.of(ZoneId.of(zoneIdString));
            } catch (final ZoneRulesException e) {
                throw new TemplateModelException(DateTimeTools.ILLEGAL_ZONE_ID_MSG, e);
            }
        }
        return Optional.empty();
    }


    private static Locale getLocale() {
        if (Environment.getCurrentEnvironment() != null) {
            return Environment.getCurrentEnvironment().getLocale();
        } else {
            return Locale.getDefault();
        }
    }


    private static ExtFormatStyle getFormatStyle(final String format) {
        try {
            return PreparedFormatStyle.valueOf(format);
        } catch (IllegalArgumentException | NullPointerException ex) {
            return null;
        }
    }


    private static Optional<DateTimeFormatter> getJreBuiltinFormatter(final String name) {
        try {
            final Field dateTimeFormatterField = DateTimeFormatter.class.getField(name);
            if ((dateTimeFormatterField.getModifiers() & Modifier.STATIC) != 0 // Check if field is static
                    && DateTimeFormatter.class.isAssignableFrom(dateTimeFormatterField.getType())) {
                return Optional.ofNullable((DateTimeFormatter) dateTimeFormatterField.get(null));
            }
            // Not static, or not of the correct type
            return Optional.empty();
        } catch (final NoSuchFieldException e) {
            // Seems like name is no built in DateTimeFormatter
            return Optional.empty();
        } catch (final IllegalArgumentException e) {
            // As this field is checked to be static, this should never occur
            throw new RuntimeException("Field \"" + name + "\" has modifier STATIC but seems to be not static!", e);
        } catch (final IllegalAccessException e) {
            // Well, if you use a SecurityManager, we cannot do this
            throw new RuntimeException(
                    "Not allowed to access Field \"" + name + "\" of class " + DateTimeFormatter.class, e);
        }
    }

}