/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2017 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */
package calendarView;

import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.Calendar;
import java.util.Date;

import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import javax.swing.event.MouseInputAdapter;

import ilog.views.gantt.IlvActivity;
import ilog.views.gantt.IlvGanttModel;
import ilog.views.gantt.IlvHierarchyChart;
import ilog.views.gantt.IlvTimeInterval;
import ilog.views.gantt.IlvTimeUtil;
import ilog.views.gantt.swing.calendarview.IlvDayView;
import ilog.views.gantt.swing.calendarview.IlvMonthPanel;
import ilog.views.util.time.IlvCalendarUtil;

/**
 * This class is an interactor that can be registered via the {@link #connect}
 * method on the month panel of the Calendar View Example.
 */
public class MonthPanelInteractor {

  /**
   * An Escape keystroke.
   */
  private static final KeyStroke ESCAPE_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
  /**
   * The example that the interactor is bound to.
   */
  private CalendarViewExample example;

  /**
   * The example's month panel that the interactor is bound to.
   */
  private IlvMonthPanel monthPanel;

  /**
   * The example's day view.
   */
  private IlvDayView dayView;

  /**
   * The example's chart.
   */
  private IlvHierarchyChart chart;

  /**
   * The example's day view customizer.
   */
  private JComponent dayViewCustomizer;

  /**
   * The input handler on the month panel.
   */
  private MonthPanelInputHandler monthPanelHandler;

  // =========================================
  // Instance Construction and Initialization
  // =========================================

  /**
   * Creates a new month panel interactor that can be used within a Calendar
   * View example. Use the {@link #connect} method to install the interactor on
   * the example.
   */
  public MonthPanelInteractor() {
  }

  /**
   * Installs the interactor on the month panel of the specified example.
   *
   * @param example
   *          The example.
   */
  public void connect(CalendarViewExample example) {
    disconnect();
    this.example = example;
    monthPanel = example.getMonthView().getMonthPanel();
    dayView = example.getDayView();
    chart = example.getChart();
    dayViewCustomizer = example.getDayViewCustomizer();
    monthPanelHandler = new MonthPanelInputHandler();
    monthPanel.addMouseListener(monthPanelHandler);
    monthPanel.addMouseMotionListener(monthPanelHandler);
  }

  /**
   * Deinstalls the interactor from the current example to which it is
   * connected.
   */
  public void disconnect() {
    if (example != null) {
      monthPanel.removeMouseListener(monthPanelHandler);
      monthPanel.removeMouseMotionListener(monthPanelHandler);
      example = null;
      monthPanel = null;
      dayView = null;
      chart = null;
      dayViewCustomizer = null;
      monthPanelHandler = null;
    }
  }

  /**
   * Returns the data model that the month panel is currently displaying, or
   * <code>null</code> if the interactor is not installed.
   *
   * @return The current Gantt data model, or <code>null</code> if the
   *         interactor is not installed.
   */
  public IlvGanttModel getGanttModel() {
    return (monthPanel != null) ? monthPanel.getGanttModel() : null;
  }

  // =========================================
  // Handling Mouse and Keyboard Events
  // =========================================

  /**
   * Updates the example's day view and the chart to display the specified date.
   *
   * @param cal
   *          The calendar date to display.
   */
  private void syncDisplayedDate(Calendar cal) {
    chart.setVisibleTime(IlvTimeUtil.subtract(cal.getTime(), chart.getVisibleDuration().divide(2)));
    dayView.setCalendar(cal);
  }

  /**
   * <code>MonthPanelInputHandler</code> is registered on the example's month
   * panel to handle mouse and keyboard input events.
   */
  private class MonthPanelInputHandler extends MouseInputAdapter implements ActionListener {

    /**
     * The month panel's original cursor.
     */
    private Cursor oldCursor = null;

    /**
     * Date corresponding to the last mouse down event, or <code>null</code> if
     * the mouse was pressed outside of a day cell.
     */
    private Calendar mouseDownCalendar;

    /**
     * The activity being dragged.
     */
    private IlvActivity activity;

    /**
     * The activity's original time interval when the mouse was pressed.
     */
    private IlvTimeInterval oldInterval;

    /**
     * The number of days that the activity has been dragged since the mouse was
     * pressed.
     */
    private int dayDelta;

    /**
     * If an activity is being dragged, drags it to the specified mouse location
     * within the month panel. Otherwise, does nothing.
     *
     * @param pt
     *          The mouse location within the month panel.
     */
    private void doDrag(Point pt) {
      Calendar cal = monthPanel.getCellDate(pt);
      if (activity == null || cal == null) {
        return;
      }
      int newDelta = IlvCalendarUtil.elapsedDays(mouseDownCalendar, cal);
      if (newDelta != dayDelta) {
        dayDelta = newDelta;
        Calendar newStart = (Calendar) cal.clone();
        newStart.setTime(oldInterval.getStart());
        newStart.add(Calendar.DAY_OF_YEAR, dayDelta);
        IlvTimeInterval newInterval = new IlvTimeInterval(newStart.getTime(), oldInterval.getDuration());
        activity.setTimeInterval(newInterval);
        // When dragging an activity, center the new date in the Gantt sheet
        // and select the same date in the day panel.
        syncDisplayedDate(cal);
      }
    }

    /**
     * Ends the current activity drag session, if one is active.
     */
    private void endDrag() {
      if (activity != null) {
        // Finalize the data model adjustment session. If calculations have been
        // deferred
        // that now cause the dragged activity to be rescheduled, then update
        // the date
        // displayed so the activity is still visible.
        Date start0 = activity.getStartTime();
        getGanttModel().setAdjusting(false);
        Date start1 = activity.getStartTime();
        if (!start1.equals(start0)) {
          Calendar cal = monthPanel.getCalendar();
          cal.setTime(start1);
          syncDisplayedDate(cal);
        }

        activity = null;
        oldInterval = null;
        dayDelta = 0;
        monthPanel.unregisterKeyboardAction(ESCAPE_KEY);
      }
      if (oldCursor != null) {
        monthPanel.setCursor(oldCursor);
        oldCursor = null;
      }
    }

    /**
     * Aborts the current activity drag session, if one is active.
     */
    private void abortDrag() {
      if (activity != null) {
        activity.setTimeInterval(oldInterval);
        syncDisplayedDate(mouseDownCalendar);
      }
      endDrag();
    }

    /**
     * This method is invoked when a mouse button has been pressed on the month
     * panel.
     *
     * @param e
     *          The mouse event.
     */
    Override
    public void mousePressed(MouseEvent e) {
      Point point = e.getPoint();
      mouseDownCalendar = monthPanel.getCellDate(point);
      if (mouseDownCalendar == null) {
        return;
      }

      // When pressing the mouse on a day cell, center the date in the Gantt
      // sheet
      // and select the same date in the day panel.
      syncDisplayedDate(mouseDownCalendar);

      // Check for starting to drag an activity only if we are not already
      // dragging.
      if (activity != null) {
        return;
      }

      // If the mouse was pressed on an activity bar, then setup for possible
      // dragging of the activity.
      activity = monthPanel.getActivity(point);
      if (activity != null) {
        // Make sure that we only drag activities in the data model and not
        // holidays.
        IlvGanttModel ganttModel = getGanttModel();
        if (ganttModel != null && ganttModel.contains(activity)) {
          oldInterval = activity.getTimeInterval();
          dayDelta = 0;
          // Initiate a data model adjustment session, so that certain
          // calculations
          // (e.g. the critical path, etc.) are deferred until dragging is
          // completed.
          ganttModel.setAdjusting(true);
          oldCursor = monthPanel.getCursor();
          monthPanel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
          monthPanel.registerKeyboardAction(this, ESCAPE_KEY, JComponent.WHEN_IN_FOCUSED_WINDOW);
        } else {
          activity = null;
        }
      }
    }

    /**
     * This method is invoked when a mouse button has been clicked on the month
     * panel.
     *
     * @param e
     *          The mouse event.
     */
    Override
    public void mouseClicked(MouseEvent e) {
      // If we have double-clicked on a day-cell, then make the day view
      // visible.
      Point point = e.getPoint();
      if (e.getClickCount() == 2 && monthPanel.getCellDate(point) != null) {
        ((JTabbedPane) example.getCustomizerPanel()).setSelectedComponent(dayViewCustomizer);
      }
    }

    /**
     * This method is invoked when the mouse has been dragged in the month
     * panel.
     *
     * @param e
     *          The mouse event.
     */
    Override
    public void mouseDragged(MouseEvent e) {
      doDrag(e.getPoint());
    }

    /**
     * This method is invoked when a mouse button has been released on the month
     * panel.
     *
     * @param e
     *          The mouse event.
     */
    Override
    public void mouseReleased(MouseEvent e) {
      doDrag(e.getPoint());
      endDrag();
    }

    /**
     * This method is invoked when the ESC key has been pressed.
     *
     * @param e
     *          The event.
     */
    Override
    public void actionPerformed(ActionEvent e) {
      abortDrag();
    }
  }

}