/*
 *  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.causeway.viewer.wicket.ui.components.attributes.temporal;

import java.io.IOException;
import java.util.Locale;
import java.util.Map;

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;

import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.datetime.DatetimePickerConfig;
import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.datetime.DatetimePickerIconConfig;
import de.agilecoders.wicket.jquery.AbstractConfig;
import de.agilecoders.wicket.jquery.IKey;

/**
 * Configuration holder for all {@link TextFieldWithDateTimePicker} configurations.
 * Provides settings for <a href="https://github.com/tempusdominus/bootstrap-4">Tempus Dominus Bootstrap 4 Datetime Picker</a>
 * JavaScript widget
 * @see DatetimePickerConfig
 */
public class DateTimeConfig extends AbstractConfig {
    private static final long serialVersionUID = 1L;

    /**
     *
     */
    private static final IKey<Boolean> Readonly = newKey("readonly", false);

    /**
     *
     */
    private static final IKey<Boolean> IgnoreReadonly = newKey("ignoreReadonly", false);

    // whether to immediately show the date picker on focus
    private static final IKey<Boolean> AllowInputToggle = newKey("allowInputToggle", false);

    /**
     *
     */
    private static final IKey<Boolean> KeepOpen = newKey("keepOpen", false);

    /**
     * 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");

    private static final IKey<String> Locale = newKey("locale", null);

    /**
     * @param locale The moment.js locale
     * @return current instance
     */
    public DateTimeConfig useLocale(final Locale locale) {
        put(Locale, locale.getLanguage());
        return this;
    }

    /**
     * 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 week numbers to the left of week rows.
     */
    private static final IKey<Boolean> CalendarWeeks = newKey("calendarWeeks", false);

    private static final IKey<DatetimePickerIconConfig> Icons = newKey("icons", null);

    /**
     * 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);
    }

    /**
     * Will cause the date picker to stay open after selecting a date.
     *
     * @param keepOpen whether to keep open
     * @return this instance for chaining
     */
    public DateTimeConfig keepOpen(final boolean keepOpen) {
        put(KeepOpen, keepOpen);
        return this;
    }

    public DateTimeConfig allowInputToggle(final boolean allowInputToggle) {
    	put(AllowInputToggle, allowInputToggle);
    	return this;
    }

    public DateTimeConfig ignoreReadonly(final boolean ignoreReadonly) {
    	put(IgnoreReadonly, ignoreReadonly);
    	return this;
    }

    public DateTimeConfig readonly(final boolean readonly) {
    	put(Readonly, readonly);
    	return this;
    }

    /**
     * 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 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 show week numbers to the left of week rows.
     * @return this instance for chaining
     */
    public DateTimeConfig useCalendarWeeks(final boolean value) {
        put(CalendarWeeks, value);
        return this;
    }

    /**
     * @param value Whether on show, will set the picker to the current date/time.
     */
    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;
    }

    /**
     * Sets buttons.
     *
     * @param buttons buttons to show/hide
     * @return config instance
     */
    public DateTimeConfig withButtons(final Map<String, Boolean> buttons) {
        put(DatetimePickerConfig.newKey("buttons", null), Map.of(
                DatetimePickerConfig.BTN_SHOW_TODAY,
                Boolean.TRUE.equals(buttons.get(DatetimePickerConfig.BTN_SHOW_TODAY))
                , DatetimePickerConfig.BTN_SHOW_CLEAR,
                Boolean.TRUE.equals(buttons.get(DatetimePickerConfig.BTN_SHOW_CLEAR))
                , DatetimePickerConfig.BTN_SHOW_CLOSE,
                Boolean.TRUE.equals(buttons.get(DatetimePickerConfig.BTN_SHOW_CLOSE))));
        return this;
    }

    /**
     * Set icon config.
     *
     * @param iconConfig icon config
     * @return config instance
     */
    public DateTimeConfig withIcons(final DatetimePickerIconConfig iconConfig) {
        put(Icons, iconConfig);
        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(final TodayButton value, final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
            switch (value) {
            case TRUE:
                jgen.writeBoolean(true);
                break;
            case FALSE:
                jgen.writeBoolean(false);
                break;
            case LINKED:
                jgen.writeString("linked");
                break;
            }
        }
    }
}
