/*
 * The MIT Licence
 *
 * Copyright © 2021 BlakeTNC <https://github.com/LGoodDatePicker/LGoodDatePicker>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package com.github.lgooddatepicker.tableeditors;

import com.github.lgooddatepicker.components.TimePicker;
import com.github.lgooddatepicker.components.TimePickerSettings;
import com.github.lgooddatepicker.zinternaltools.InternalUtilities;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.time.LocalTime;
import java.util.EventObject;
import javax.swing.AbstractCellEditor;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

/**
 * TimeTableEditor, This class is used to add a TimePicker to cells (or entire columns) of a Swing
 * JTable or a SwingX JXTable component. This class can be used as a table cell "Editor", or as a
 * "Renderer", or as both editor and renderer. Separate class instances should be used for the
 * editor and the renderer.
 *
 * <code>
 * // Usage example:
 * // Create a table.
 * JTable table = new JTable();
 * //Set up a renderer and editor for the LocalTime type.
 * table.setDefaultRenderer(LocalTime.class, new TimeTableEditor());
 * table.setDefaultEditor(LocalTime.class, new TimeTableEditor());
 * </code>
 *
 * The TimePicker and TimePickerSettings: The time picker and picker settings can be accessed with
 * the supplied getter functions. Note that most of the settings for the time picker are left at
 * their default values, but a few (mostly cosmetic) settings are changed in the TimeTableEditor
 * constructor. See the TimeTableEditor constructor implementation to learn which default settings
 * were changed.
 *
 * Auto adjust row height: By default, this class will automatically adjust the minimum height of
 * all table rows. This occurs during the first time that any cell with an editor or render is
 * displayed. The row height is only adjusted if the current table row height value is below the
 * minimum value that is needed to display the time picker component. This auto adjust behavior can
 * be turned off from the TimeTableEditor constructor.
 *
 */
public class TimeTableEditor extends AbstractCellEditor
        implements TableCellEditor, TableCellRenderer {

    /**
     * autoAdjustMinimumTableRowHeight, This indicates whether the minimum table row height (for all
     * rows) should be modified when an editor or render is displayed. The row height is only
     * adjusted if it is below the minimum value needed to display the time picker component.
     */
    private boolean autoAdjustMinimumTableRowHeight = true;

    /**
     * clickCountToEdit, An integer specifying the number of clicks needed to start editing. Even if
     * clickCountToEdit is defined as zero, it will not initiate until a click occurs.
     */
    public int clickCountToEdit = 1;

    /**
     * matchTableBackgroundColor, This indicates whether this table editor should set the picker
     * text area background color to match the background color of the table. The default value is
     * true.
     */
    private boolean matchTableBackgroundColor = true;

    /**
     * matchTableSelectionBackgroundColor, This indicates whether this table editor should set the
     * picker text area background color to match the background color of the table selection (when
     * selected). The default value is true.
     */
    private boolean matchTableSelectionBackgroundColor = true;

    /**
     * borderFocusedCell, This holds the border that is used when a cell has focus.
     */
    private Border borderFocusedCell;

    /**
     * borderUnfocusedCell, This holds the border that is used when a cell does not have focus.
     */
    private Border borderUnfocusedCell;

    /**
     * timepicker, This holds the time picker instance.
     */
    private TimePicker timePicker;

    /**
     * minimumRowHeight, This holds the minimum row height needed to display the time picker.
     */
    private int minimumRowHeightInPixels;

    /**
     * Constructor, default.
     */
    public TimeTableEditor() {
        this(true, true, true);
    }

    /**
     * Constructor, with options.
     *
     * @param autoAdjustMinimumTableRowHeight Set this to true to have this class automatically
     * adjust the the minimum row height of all rows in the table, the first time that a
     * TimeTableEditor is displayed. Set this to false to turn off any row height adjustments. The
     * default value is true.
     */
    public TimeTableEditor(boolean autoAdjustMinimumTableRowHeight,
            boolean matchTableBackgroundColor, boolean matchTableSelectionBackgroundColor) {
        // Save the constructor parameters.
        this.autoAdjustMinimumTableRowHeight = autoAdjustMinimumTableRowHeight;
        this.matchTableBackgroundColor = matchTableBackgroundColor;
        this.matchTableSelectionBackgroundColor = matchTableSelectionBackgroundColor;
        // Create the borders that should be used for focused and unfocused cells.
        JLabel exampleDefaultRenderer = (JLabel) new DefaultTableCellRenderer().
                getTableCellRendererComponent(new JTable(), "", true, true, 0, 0);
        borderFocusedCell = exampleDefaultRenderer.getBorder();
        borderUnfocusedCell = new EmptyBorder(1, 1, 1, 1);
        // Create and set up the time picker.
        timePicker = new TimePicker();
        timePicker.setEnableArrowKeys(false);
        timePicker.setBorder(borderUnfocusedCell);
        timePicker.getComponentTimeTextField().setBorder(null);
        // Adjust any needed time picker settings.
        TimePickerSettings settings = timePicker.getSettings();
        settings.setGapBeforeButtonPixels(0);
        settings.setSizeTextFieldMinimumWidthDefaultOverride(false);
        settings.setSizeTextFieldMinimumWidth(20);
        // Calculate and store the minimum row height needed to display the time picker.
        minimumRowHeightInPixels = (timePicker.getPreferredSize().height + 1);
    }

    /**
     * getCellEditorValue, This returns the value contained in the editor. This is required by the
     * CellEditor interface.
     */
    @Override
    public Object getCellEditorValue() {
        return timePicker.getTime();
    }

    /**
     * getTimePicker, Returns the TimePicker which is used by this class.
     */
    public TimePicker getTimePicker() {
        return timePicker;
    }

    /**
     * getTimePickerSettings, Returns the TimePickerSettings for the TimePicker being used by this
     * class. These settings can be adjusted as desired by the programmer.
     */
    public TimePickerSettings getTimePickerSettings() {
        return timePicker.getSettings();
    }

    /**
     * getTableCellEditorComponent, this returns the editor that is used for editing the cell. This
     * is required by the TableCellEditor interface.
     *
     * For additional details, see the Javadocs for the function:
     * TableCellEditor.getTableCellEditorComponent().
     */
    @Override
    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int row, int column) {
        // Save the supplied value to the time picker.
        setCellEditorValue(value);
        // If needed, adjust the minimum row height for the table.
        zAdjustTableRowHeightIfNeeded(table);
        // This fixes a bug where the time text could "move around" during a table resize event.
        timePicker.getComponentTimeTextField().setScrollOffset(0);
        // Return the time picker component.
        return timePicker;
    }

    /**
     * getTableCellRendererComponent, Returns the renderer that is used for drawing the cell. This
     * is required by the TableCellRenderer interface.
     *
     * For additional details, see the Javadocs for the function:
     * TableCellRenderer.getTableCellRendererComponent().
     */
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        // Save the supplied value to the time picker.
        setCellEditorValue(value);
        // Draw the appropriate background colors to indicate a selected or unselected state.
        if (isSelected) {
            if (matchTableSelectionBackgroundColor) {
                timePicker.getComponentTimeTextField().setBackground(table.getSelectionBackground());
                timePicker.setBackground(table.getSelectionBackground());
            } else {
                timePicker.zDrawTextFieldIndicators();
            }
        }
        if (!isSelected) {
            if (matchTableBackgroundColor) {
                timePicker.getComponentTimeTextField().setBackground(table.getBackground());
                timePicker.setBackground(table.getBackground());
            } else {
                timePicker.zDrawTextFieldIndicators();
            }
        }
        // Draw the appropriate borders to indicate a focused or unfocused state.
        if (hasFocus) {
            timePicker.setBorder(borderFocusedCell);
        } else {
            timePicker.setBorder(borderUnfocusedCell);
        }
        // If needed, adjust the minimum row height for the table.
        zAdjustTableRowHeightIfNeeded(table);
        // This fixes a bug where the time text could "move around" during a table resize event.
        timePicker.getComponentTimeTextField().setScrollOffset(0);
        // Return the time picker component.
        return timePicker;
    }

    /**
     * isCellEditable, Returns true if anEvent is not a MouseEvent. Otherwise, it returns true if
     * the necessary number of clicks have occurred, and returns false otherwise.
     */
    @Override
    public boolean isCellEditable(EventObject anEvent) {
        if (anEvent instanceof MouseEvent) {
            return ((MouseEvent) anEvent).getClickCount() >= clickCountToEdit;
        }
        return true;
    }

    /**
     * setCellEditorValue, This sets the picker to an appropriate value for the supplied object. If
     * the value is null, then picker will be cleared. If the value is a LocalTime instance, then
     * the picker will be set to that value. All other types (including strings) will be read or
     * converted to a string with a maximum length of the first 100 characters. The picker text will
     * be set with the resulting string.
     */
    public void setCellEditorValue(Object value) {
        timePicker.clear();
        if (value == null) {
            return;
        }
        if (value instanceof LocalTime) {
            LocalTime nativeValue = (LocalTime) value;
            timePicker.setTime(nativeValue);
        } else {
            String text = value.toString();
            String shorterText = InternalUtilities.safeSubstring(text, 0, 100);
            timePicker.setText(shorterText);
        }
    }

    /**
     * zAdjustTableRowHeightIfNeeded, If needed, this will adjust the row height for all rows in the
     * supplied table to fit the minimum row height that is needed to display the time picker
     * component.
     *
     * If autoAdjustMinimumTableRowHeight is false, this will do nothing. If the row height of the
     * table is already greater than or equal to "minimumRowHeightInPixels", then this will do
     * nothing.
     */
    private void zAdjustTableRowHeightIfNeeded(JTable table) {
        if (!autoAdjustMinimumTableRowHeight) {
            return;
        }
        if ((table.getRowHeight() < minimumRowHeightInPixels)) {
            table.setRowHeight(minimumRowHeightInPixels);
        }
    }

}
