/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF 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 org.apache.isis.viewer.wicket.ui.components.scalars.datepicker;

import de.agilecoders.wicket.jquery.AbstractConfig;
import de.agilecoders.wicket.jquery.IKey;

import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.Strings;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

/**
 * Configuration holder for all {@link TextFieldWithDateTimePicker} configurations.
 * Provides settings for <a href="https://github.com/Eonasdan/bootstrap-datetimepicker">Bootstrap Datetime picker</a>
 * JavaScript widget
 */
public class DateTimeConfig extends AbstractConfig {
    /**
     * The earliest date that may be selected; all earlier dates will be disabled.
     */
    private static final IKey<String> StartDate = newKey("startDate", null);

    /**
     * The latest date that may be selected; all later dates will be disabled.
     */
    private static final IKey<String> EndDate = newKey("endDate", null);

    /**
     * The view that the datepicker should show when it is opened. Accepts values of
     * 0 or 'month' for month view (the default); 1 or 'year' for the 12-month overview,
     * and 2 or 'decade' for the 10-year overview. Useful for date-of-birth datepickers.
     */
    private static final IKey<String> _ViewMode = newKey("viewMode", ViewMode.days.name());

    /**
     * If true, displays a "Today" button at the bottom of the datepicker to select
     * the current date. If true, the "Today" button will only move the current date
     * into view;
     */
    private static final IKey<TodayButton> ShowTodayButton = newKey("todayBtn", TodayButton.FALSE);

    /**
     * Whether or not to allow date navigation by arrow keys.
     */
    private static final IKey<Boolean> KeyboardNavigation = newKey("keyboardNavigation", true);

    /**
     * show the date and time picker side by side
     */
    private static final IKey<Boolean> SideBySide = newKey("sideBySide", false);

    /**
     * @see <a href="http://eonasdan.github.io/bootstrap-datetimepicker/Options/#usecurrent">online reference</a>
     */
    private static final IKey<Boolean> UseCurrent = newKey("useCurrent", true);

    /**
     * @see <a href="http://eonasdan.github.io/bootstrap-datetimepicker/Options/#mindate">online reference</a>
     */
    private static final IKey<String> MinDate = newKey("minDate", null);

    /**
     * @see <a href="http://eonasdan.github.io/bootstrap-datetimepicker/Options/#maxdate">online reference</a>
     */
    private static final IKey<String> MaxDate = newKey("maxDate", null);


    /**
     * The two-letter code of the language to use for month and day names.
     * These will also be used as the input's value (and subsequently sent to the
     * server in the case of form submissions). Currently ships with English ('en');
     * German ('de'), Brazilian ('br'), and Spanish ('es') translations, but others
     * can be added (see I18N below). If an unknown language code is given, English
     * will be used.
     */
    private static final IKey<String> Language = newKey("language", "en");

    /**
     * The date format, combination of d, dd, m, mm, M, MM, yy, yyyy.
     */
    private static final IKey<String> Format = newKey("format", null);

    /**
     * Day of the week start. 0 (Sunday) to 6 (Saturday)
     */
    private static final IKey<Integer> WeekStart = newKey("weekStart", 0);

    /**
     * If true, highlights the current date.
     */
    private static final IKey<Boolean> HighlightToday = newKey("todayHighlight", false);

    /**
     * Whether or not to close the datepicker immediately when a date is selected.
     */
    private static final IKey<Boolean> AutoClose = newKey("autoclose", false);

    /**
     * Whether or not to force parsing of the input value when the picker is closed.
     * That is, when an invalid date is left in the input field by the user, the picker
     * will forcibly parse that value, and set the input's value to the new, valid date,
     * conforming to the given format.
     */
    private static final IKey<Boolean> ForceParse = newKey("forceParse", true);

    /**
     * Whether or not to show the clear button.
     */
    private static final IKey<Boolean> ClearButton = newKey("clearBtn", false);

    /**
     * Whether or not to show week numbers to the left of week rows.
     */
    private static final IKey<Boolean> CalendarWeeks = newKey("calendarWeeks", false);

    /**
     * holds all week days in a specific sort order.
     */
    public enum Day {
        Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
    }

    /**
     * holds all view options.
     */
    public enum ViewMode {
        months, years, days
    }

    /**
     * Construct.
     */
    public DateTimeConfig() {
        super();
    }

    /**
     * @return the date format as string
     */
    public String getFormat() {
        return getString(Format);
    }

    /**
     * @return the language to use
     */
    public String getLanguage() {
        return getString(Language);
    }

    /**
     * @return true if default language should be used.
     */
    public boolean isDefaultLanguageSet() {
        return Language.isDefaultValue(getLanguage());
    }

    /**
     * The earliest date that may be selected; all earlier dates will be disabled.
     *
     * @param value the earliest start date
     * @return this instance for chaining
     */
    public DateTimeConfig withStartDate(final DateTime value) {
        String format = getFormat();
        String startDate;
        if (Strings.isEmpty(format)) {
            startDate = value.toString();
        } else {
            DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(format);
            startDate = dateTimeFormatter.print(value);
        }
        put(StartDate, startDate);
        return this;
    }

    /**
     * The latest date that may be selected; all later dates will be disabled.
     *
     * @param value the latest end date
     * @return this instance for chaining
     */
    public DateTimeConfig withEndDate(final DateTime value) {
        Args.notNull(value, "value");
        String format = getFormat();
        String endDate;
        if (Strings.isEmpty(format)) {
            endDate = value.toString();
        } else {
            DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(format);
            endDate = dateTimeFormatter.print(value);
        }
        put(EndDate, endDate);
        return this;
    }

    /**
     * The two-letter code of the language to use for month and day names.
     * These will also be used as the input's value (and subsequently sent to the
     * server in the case of form submissions). Currently ships with English ('en'),
     * German ('de'), Brazilian ('br'), and Spanish ('es') translations, but others
     * can be added (see I18N below). If an unknown language code is given, English
     * will be used.
     *
     * @param value two letter language code (optional 5 letter code like de_DE)
     * @return this instance for chaining
     */
    public DateTimeConfig withLanguage(final String value) {
        put(Language, value);
        return this;
    }

    /**
     * The default view to display when the picker is shown.
     * Useful for date-of-birth datepicker.
     *
     * @param value the start view to use
     * @return this instance for chaining
     */
    public DateTimeConfig withView(final ViewMode value) {
        put(_ViewMode, value.name());
        return this;
    }

    /**
     * The date format (java style), combination of d, dd, m, mm, M, MM, yy, yyyy.
     *
     * @param value The date format value (java style)
     * @return this instance for chaining
     */
    public DateTimeConfig withFormat(final String value) {
        put(Format, value);
        return this;
    }

    /**
     * Day of the week start. 0 (Sunday) to 6 (Saturday)
     *
     * @param value the {@link Day} the week starts
     * @return this instance for chaining
     */
    public DateTimeConfig withWeekStart(final Day value) {
        put(WeekStart, value.ordinal());
        return this;
    }

    /**
     * Whether or not to allow date navigation by arrow keys.
     *
     * @param value true, if keyboard navigation is allowed
     * @return this instance for chaining
     */
    public DateTimeConfig allowKeyboardNavigation(final boolean value) {
        put(KeyboardNavigation, value);
        return this;
    }

    public DateTimeConfig sideBySide(final boolean value) {
        put(SideBySide, value);
        return this;
    }

    /**
     * If true, highlights the current date.
     *
     * @param value If true, highlights the current date.
     * @return this instance for chaining
     */
    public DateTimeConfig highlightToday(final boolean value) {
        put(HighlightToday, value);
        return this;
    }

    /**
     * If true, displays a "Today" button at the bottom of the datepicker to select
     * the current date. If true, the "Today" button will only move the current date
     * into view;
     *
     * @param value whether to show today button or not
     * @return this instance for chaining
     */
    public DateTimeConfig showTodayButton(final TodayButton value) {
        put(ShowTodayButton, value);
        return this;
    }

    /**
     * Whether or not to force parsing of the input value when the picker is closed.
     * That is, when an invalid date is left in the input field by the user, the picker
     * will forcibly parse that value, and set the input's value to the new, valid date,
     * conforming to the given format.
     *
     * @param value Whether or not to force parsing of the input value when the picker is closed
     * @return this instance for chaining
     */
    public DateTimeConfig forceParse(final boolean value) {
        put(ForceParse, value);
        return this;
    }

    /**
     * @param value Whether or not to display a 'clear' button, which allows for clearing the input field.
     * @return this instance for chaining
     */
    public DateTimeConfig clearButton(final boolean value) {
        put(ClearButton, value);
        return this;
    }

    /**
     * @param value Whether or not to show week numbers to the left of week rows.
     * @return this instance for chaining
     */
    public DateTimeConfig calendarWeeks(final boolean value) {
        put(CalendarWeeks, value);
        return this;
    }

    public DateTimeConfig useCurrent(final boolean value) {
        put(UseCurrent, value);
        return this;
    }

    public DateTimeConfig minDate(final String value) {
        put(MinDate, value);
        return this;
    }

    public DateTimeConfig maxDate(final String value) {
        put(MaxDate, value);
        return this;
    }

    /**
     * Whether or not to close the datepicker immediately when a date is selected.
     *
     * @param value true, if datepicker should close immediately when date is selected.
     * @return this instance for chaining
     */
    public DateTimeConfig autoClose(final boolean value) {
        put(AutoClose, value);
        return this;
    }

    /**
     * See <a href="http://bootstrap-datepicker.readthedocs.org/en/latest/options.html#todaybtn">docs</a>.
     * Today button could be a boolean or string <em>"linked"</em>:
     * <cite>If true or “linked”, displays a “Today” button at the bottom of the datepicker to select the
     * current date. If true, the “Today” button will only move the current date into view; if “linked”,
     * the current date will also be selected.</cite>
     */
    @JsonSerialize(using = TodayButtonSerializer.class)
    public static enum TodayButton {
        TRUE,
        FALSE,
        LINKED
    }

    /**
     * Serializer for TodayButton
     */
    private static class TodayButtonSerializer extends JsonSerializer<TodayButton> {

        @Override
        public void serialize(TodayButton value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
            switch (value) {
                case TRUE:
                    jgen.writeBoolean(true);
                    break;
                case FALSE:
                    jgen.writeBoolean(false);
                    break;
                case LINKED:
                    jgen.writeString("linked");
                    break;
            }
        }
    }
}
