/*
 * Licensed Materials - Property of Perforce Software, Inc. 
 * © Copyright Perforce Software, Inc. 2014, 2021 
 * © 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 ganttviewer.viewer;

import ganttviewer.viewer.event.CurrentViewChangedEvent;
import ganttviewer.viewer.event.CurrentViewListener;
import ganttviewer.viewer.exceptions.AttachModelException;
import ganttviewer.viewer.exceptions.ViewException;
import ganttviewer.views.ResourceUsageView;
import ilog.views.gantt.IlvTimeInterval;
import ilog.views.gantt.util.event.IlvHashedEventService;
import ilog.views.util.event.IlvEventListenerCollection;
import ilog.views.util.event.IlvEventListenerSet;

import java.awt.CardLayout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/**
 * <code>Viewer</code> is used by the demo to display several views.
 */
SuppressWarnings("serial")
public class Viewer extends JPanel {

  /**
   * Action instances
   */
  private final Map<String, ViewerAbstractAction> actions = new HashMap<String, ViewerAbstractAction>();

  /**
   * Views.
   */
  private final Map<String, View> views = new HashMap<String, View>();

  /**
   * Views list (to preserve the order)
   */
  private final List<View> viewList = new ArrayList<View>();

  /**
   * Current view.
   */
  private View currentView = null;

  /**
   * The model.
   */
  private Object model = null;

  /**
   * The event service.
   */
  private IlvHashedEventService eventService;

  /**
   * The view listener(s).
   */
  private IlvEventListenerCollection<CurrentViewListener> currentViewListeners;

  /**
   * Indicates whether selection is synchronized or not. 
   */
  private boolean synchronizedSelection = true;

  /**
   * Builds a <code>Viewer</code>
   */
  public Viewer() {
    super(new CardLayout());
    initEventService();
    setOpaque(false);
  }

  /**
   * Registers an action.
   * @param action The action.
   */
  public void registerAction(ViewerAbstractAction action) {
    actions.put(action.getID(), action);
  }

  /**
   * Unregisters an action.
   * @param action The action.
   */
  public void unregisterAction(ViewerAbstractAction action) {
    actions.remove(action.getID());
  }

  /**
   * Returns an action.
   * @param actionID The action ID.
   * @return The action.
   */
  public ViewerAbstractAction getAction(String actionID) {
    if (!actions.containsKey(actionID)) {
      return null;
    }
    return actions.get(actionID);
  }

  /**
   * Sets the model.
   * @param model The model.
   */
  public void setModel(Object model) throws AttachModelException {
    // disables the synchronized selection
    setSynchronizedSelection(false);
    deSelectAll();
    this.model = model;
    for (View view : views()) {
      try {
        view.setModel(model);
      } catch (Exception e) {
        throw new AttachModelException(e);
      }
    }
    // reactivates the synchronized selection
    setSynchronizedSelection(true);
  }

  /**
   * Internal selection (with view synchronization).
   * @param obj The object to (de)select.
   * @param selected The selection status.
   * @param caller The view at the origin of this call.
   */
  void setSelected(Object obj, boolean selected, View caller) {
    if (model != null && this.isSynchronizedSelection()) {
      for (View view : views()) {
        if ((caller != null) && caller.getID().equals(view.getID())) {
          continue;
        }
        view.setSelected(obj, selected);
      }
    }
  }

  /**
   * Activates or disables the synchronized selection.
   * @param enabled A boolean indicating whether this should be enabled or not.
   */
  private void setSynchronizedSelection(boolean enabled) {
    synchronizedSelection = enabled;
  }

  /**
   * Indicates whether the synchronized selection is enabled or not.
   * @return A boolean indicating whether it is enabled or not.
   */
  private boolean isSynchronizedSelection() {
    return synchronizedSelection;
  }

  /**
   * Selects or deselects an object.
   * @param obj The object to (de)select.
   * @param selected The selection status.
   */
  public void setSelected(Object obj, boolean selected) {
    setSelected(obj, selected, null);
  }

  /**
   * Deselects all objects.
   * @param caller The view at the origin of this call. 
   */
  void deSelectAll(View caller) {
    if (model != null && isSynchronizedSelection()) {
      for (View view : views()) {
        if ((caller != null) && caller.getID().equals(view.getID())) {
          continue;
        }
        view.deSelectAll();
      }
    }
  }

  /**
   * Deselects all objects.
   *
   */
  public void deSelectAll() {
    deSelectAll(null);
  }

  /**
   * Returns the model.
   * @return The model.
   */
  public Object getModel() {
    return model;
  }

  /**
   * Initializes the event services.
   */
  private void initEventService() {
    eventService = new IlvHashedEventService();
    currentViewListeners = new IlvEventListenerSet<CurrentViewListener>();
  }

  /**
   * Adds the specified listener to receive notifications when the current view
   * has been changed.
   * @param listener The listener that will be subscribed to subsequent view-change event.
   * @see #removeCurrentViewListener
   */
  public void addCurrentViewListener(CurrentViewListener listener) {
    currentViewListeners.addListener(listener);
  }

  /**
   * Removes the specified listener to receive notifications when the current
   * view has been changed.
   * @param listener The listener that will be unsubscribed from receiving view-change events.
   * @see #addCurrentViewListener
   */
  public void removeCurrentViewListener(CurrentViewListener listener) {
    currentViewListeners.removeListener(listener);
  }

  /**
   * Returns the current view.
   * @return The current view.
   */
  public View getCurrentView() {
    return currentView;
  }

  /**
   * Indicates whether the given view is set as current.
   * @param view The view.
   * @return A boolean indicating whether the view is set as the current one.
   */
  public boolean isCurrentView(View view) {
    if (getCurrentView() == null) {
      return false;
    }
    return getCurrentView().getID().equals(view.getID());
  }

  /**
   * Registers a view.
   * @param view The view.
   */
  void registerView(View view) {
    if (!views.containsKey(view.getID())) {
      views.put(view.getID(), view);
      viewList.add(view);
    }
    if (viewList.size() == 1) {
      // affects a current view
      currentView = view;
    }
  }

  /**
   * Sets the current view and notifies listeners
   * @param currentView The current view.
   */
  public void setCurrentView(View currentView) {
    View previousView = getCurrentView();
    if (currentView == null) {
      throw new ViewException("Null view set as current");
    }
    // update current view
    this.currentView = currentView;
    if (!views.containsKey(currentView.getID())) {
      throw new ViewException("View " + currentView.getID() + " not registered");
    }

    // Fix JV-6642
    SwingUtilities.invokeLater(new Runnable() {
      Override
      public void run() {
        View currentView = getCurrentView();
        if (currentView instanceof ResourceUsageView) {
          ResourceUsageView view = (ResourceUsageView)currentView;        
          IlvTimeInterval chartInterval = view.getScheduleChart().getTimeScale().getVisibleInterval();
          IlvTimeInterval sheetInterval = view.getScheduleChart().getGanttSheet().getVisibleInterval();
          if (!sheetInterval.equals(chartInterval))
            view.getScheduleChart().getGanttSheet().setVisibleInterval(chartInterval.getStart(), chartInterval.getDuration());
        }
      }
    });
    
    // makes the current view visible
    ((CardLayout) getLayout()).show(this, currentView.getID());
    fireCurrentViewChanged(previousView);
    currentView.setAsCurrentView();
  }

  /**
   * Fires a <code>CurrentViewChanged</code> event.
   * @param previousView The previous view.
   */
  protected void fireCurrentViewChanged(View previousView) {
    CurrentViewChangedEvent e = new CurrentViewChangedEvent(this, previousView,
        getCurrentView());
    eventService.publish(e);
    for (Iterator<CurrentViewListener> it = currentViewListeners.getListeners(); it.hasNext();) {
      CurrentViewListener listener = it.next();
      listener.currentViewChanged(e);
    }
  }

  /**
   * Returns views.
   * @return The views.
   */
  public Collection<View> views() {
    return Collections.unmodifiableCollection(this.viewList);
  }

  /**
   * Returns the view identified by this ID.
   * @param iD The ID.
   * @return The view or <code>null</code>.
   */
  public View getView(String iD) {
    if (views.containsKey(iD)) {
      return views.get(iD);
    }
    return null;
  }

}