/*
 * The MIT License
 *
 *
 * 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.
 *
 * <p>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.
   *
   * <p>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.
   *
   * <p>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.
   *
   * <p>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);
    }
  }
}
