package com.jidesoft.spinner;

import javax.swing.*;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.InternationalFormatter;
import java.text.DateFormat;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
 * <code>DateSpinner</code> is a spinner that is specialized in displaying or editing a a date or time.
 * <p/>
 * To change the value, you can use {@link #setValue(Object)} and pass in a Date. To get the Date, using {@link
 * #getValue()}.
 */
public class DateSpinner extends JSpinner {
    public DefaultFormatter _formatter;
    public DateEditor _timeEditor;
    public DateFormat _format;

    /**
     * Creates a date spinner using locale default as the format string.
     */
    public DateSpinner() {
        this(((SimpleDateFormat) SimpleDateFormat.getTimeInstance(SimpleDateFormat.DEFAULT, Locale.getDefault())).toPattern());
    }

    /**
     * Creates a date spinner using the specified format string.
     *
     * @param format the format string as defined in {@link java.text.SimpleDateFormat}.
     */
    public DateSpinner(String format) {
        this(format, new Date());
    }

    /**
     * Creates a date spinner using the specified format string and an initial value.
     *
     * @param format the format string as defined in {@link java.text.SimpleDateFormat}.
     * @param date   initial value
     */
    public DateSpinner(String format, Date date) {
        super(new SpinnerDateModel(date, null, null, Calendar.DAY_OF_MONTH));
//        setBorder(BorderFactory.createEmptyBorder());
//        setOpaque(true);

        setFormat(format);

        customizeSpinner();
    }

    private void customizeDateEditor() {
//        _timeEditor.setBorder(BorderFactory.createEmptyBorder());
        JFormattedTextField.AbstractFormatter formatter = _timeEditor.getTextField().getFormatter();
        if (formatter instanceof DefaultFormatter) {
            _formatter = (DefaultFormatter) formatter;
        }
        else {
            throw new IllegalStateException("The formatter is not an instance of DefaultFormatter.");
        }

        if (formatter instanceof InternationalFormatter) {
            Format f = ((InternationalFormatter) formatter).getFormat();
            if (f instanceof DateFormat) {
                _format = ((DateFormat) f);
            }
        }

        if (_format == null) {
            throw new IllegalStateException("The format is not an instance of SimpleDateFormat.");
        }
    }

    /**
     * Sets the date format string used by this DateSpinner. Please note, this method call will recreate the DateEditor
     * used by DateSpinner.
     *
     * @param format the format
     */
    public void setFormat(String format) {
        _timeEditor = createDateEditor(format);
        customizeDateEditor();
        setEditor(_timeEditor);
    }

    /**
     * Customizes the spinner.
     */
    protected void customizeSpinner() {
        setLenient(false);
        setCommitsOnValidEdit(true);
        setAllowsInvalid(false);
        setOverwriteMode(true);
        SpinnerWheelSupport.installMouseWheelSupport(this);
    }

    /**
     * Creates the DateEditor.
     *
     * @param format the format
     * @return the DateEditor.
     */
    protected DateEditor createDateEditor(String format) {
        return new DateEditor(this, format);
    }

    /**
     * Sets when edits are published back to the <code>JFormattedTextField</code>. If true, <code>commitEdit</code> is
     * invoked after every valid edit (any time the text is edited). On the other hand, if this is false than the
     * <code>DefaultFormatter</code> does not publish edits back to the <code>JFormattedTextField</code>. As such, the
     * only time the value of the <code>JFormattedTextField</code> will change is when <code>commitEdit</code> is
     * invoked on <code>JFormattedTextField</code>, typically when enter is pressed or focus leaves the
     * <code>JFormattedTextField</code>.
     *
     * @param commit Used to indicate when edits are committed back to the JTextComponent
     */
    public void setCommitsOnValidEdit(boolean commit) {
        _formatter.setCommitsOnValidEdit(commit);
    }

    /**
     * Returns when edits are published back to the <code>JFormattedTextField</code>.
     *
     * @return true if edits are committed aftereveryy valid edit
     */
    public boolean getCommitsOnValidEdit() {
        return _formatter.getCommitsOnValidEdit();
    }

    /**
     * Configures the behavior when inserting characters. If <code>overwriteMode</code> is true (the default), new
     * characters overwrite existing characters in the model.
     *
     * @param overwriteMode Indicates if overwrite or overstrike mode is used
     */
    public void setOverwriteMode(boolean overwriteMode) {
        _formatter.setOverwriteMode(overwriteMode);
    }

    /**
     * Returns the behavior when inserting characters.
     *
     * @return true if newly inserted characters overwrite existing characters
     */
    public boolean getOverwriteMode() {
        return _formatter.getOverwriteMode();
    }

    /**
     * Sets whether or not the value being edited is allowed to be invalid for a length of time (that is,
     * <code>stringToValue</code> throws a <code>ParseException</code>). It is often convenient to allow the user to
     * temporarily input an invalid value.
     *
     * @param allowsInvalid Used to indicate if the edited value must always be valid
     */
    public void setAllowsInvalid(boolean allowsInvalid) {
        _formatter.setAllowsInvalid(allowsInvalid);
    }

    /**
     * Returns whether or not the value being edited is allowed to be invalid for a length of time.
     *
     * @return false if the edited value must always be valid
     */
    public boolean getAllowsInvalid() {
        return _formatter.getAllowsInvalid();
    }

    /**
     * Sets the time zone for the calendar of this DateFormat object.
     *
     * @param zone the given new time zone.
     */
    public void setTimeZone(TimeZone zone) {
        _format.setTimeZone(zone);
    }

    /**
     * Gets the time zone.
     *
     * @return the time zone associated with the calendar of DateFormat.
     */
    public TimeZone getTimeZone() {
        return _format.getTimeZone();
    }

    /**
     * Specify whether or not date/time parsing is to be lenient.  With lenient parsing, the parser may use heuristics
     * to interpret inputs that do not precisely match this object's format.  With strict parsing, inputs must match
     * this object's format.
     *
     * @param lenient when true, parsing is lenient
     * @see java.util.Calendar#setLenient
     */
    public void setLenient(boolean lenient) {
        _format.setLenient(lenient);
    }

    /**
     * Tell whether date/time parsing is to be lenient. It is the same as {@link java.text.DateFormat#isLenient()}.
     *
     * @return true or false.
     */
    public boolean isLenient() {
        return _format.isLenient();
    }
}
