/*
 * 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 database;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import ilog.views.gantt.IlvActivity;
import ilog.views.gantt.IlvConstraint;
import ilog.views.gantt.IlvConstraintType;
import ilog.views.gantt.IlvGanttModel;
import ilog.views.gantt.IlvGanttModelUtil;
import ilog.views.gantt.IlvReservation;
import ilog.views.gantt.IlvResource;
import ilog.views.gantt.IlvTimeInterval;

/**
 * <code>GanttModelDBROWrapper</code> simulates a read-only database of
 * scheduling data by wrapping an existing
 * {@link ilog.views.gantt.IlvGanttModel}. The supplied Gantt data model is
 * fully traversed to create simulated database lookup keys for each activity,
 * resource, constraint, and reservation. This class supports relational queries
 * on the data through the <code>GanttDBRO</code> interface.
 */
public class GanttModelDBROWrapper implements GanttDBRO {
  // =========================================
  // Class Constants
  // =========================================

  /**
   * Log ID.
   */
  public static final String logID = "GanttDB";

  // =========================================
  // Instance Variables
  // =========================================

  /**
   * The underlying data model.
   */
  private IlvGanttModel ganttData;

  /**
   * The simulated database latency, in milliseconds.
   */
  private long delay;

  /**
   * The monitor that serializes access to the <code>doQueryDelay()</code>
   * method.
   */
  private Object delayLock;

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

  /**
   * Creates a new <code>GanttModelDBROWrapper</code> using the data from the
   * specified Gantt data model.
   */
  public GanttModelDBROWrapper(IlvGanttModel data) {
    delay = 0;
    delayLock = new Object();
    ganttData = data;
    initDBKeys();
  }

  /**
   * Computes and stores all the database lookup keys.
   */
  private void initDBKeys() {
    initActivityKeys();
    initResourceKeys();
    initReservationKeys();
    initConstraintKeys();
  }

  /**
   * Implements a delay that simulates the latency of performing a real database
   * query.
   */
  private void doQueryDelay() {
    if (delay > 0) {
      // Synchronized so multiple threads must wait for each other to do their
      // delay. This simulates a single database connection accessed from
      // multiple threads.
      synchronized (delayLock) {
        try {
          Thread.sleep(delay);
        } catch (InterruptedException e) {
        }
      }
    }
  }

  /**
   * Returns the delay, in milliseconds, that simulates database access latency.
   */
  public long getLatency() {
    return delay;
  }

  /**
   * Sets the delay, in milliseconds, that simulates database access latency.
   */
  public void setLatency(long delay) {
    this.delay = delay;
  }

  // =========================================
  // Activities
  // =========================================

  /**
   * Lookup table of activities, keyed by their lookup key.
   */
  private Map<String, IlvActivity> activities;

  /**
   * Reverse lookup table of activity keys, keyed by the activity.
   */
  private Map<IlvActivity, String> activityKeys;

  /**
   * The next assignable activity key value.
   */
  private long nextActivityKey = 0;

  /**
   * Computes and stores the activity lookup keys.
   */
  private void initActivityKeys() {
    activities = new HashMap<String, IlvActivity>();
    activityKeys = new HashMap<IlvActivity, String>();
    for (Iterator<IlvActivity> i = IlvGanttModelUtil.activityPreorderIterator(ganttData); i.hasNext();) {
      IlvActivity a = i.next();
      String key = "Act" + (nextActivityKey++);
      activities.put(key, a);
      activityKeys.put(a, key);
    }
  }

  /**
   * Returns a descriptive string for the activity specified by its lookup key.
   */
  protected String activityString(String key) {
    IlvActivity a = activities.get(key);
    StringBuffer buf = new StringBuffer();
    buf.append('[');
    buf.append(key);
    buf.append(',');
    if (a == null)
      buf.append("???");
    else {
      buf.append('"');
      buf.append(a.getName());
      buf.append('"');
    }
    buf.append(']');
    return buf.toString();
  }

  /**
   * Returns the lookup key for the root activity or <code>null</code> if there
   * are no activities.
   */
  Override
  public String queryRootActivityKey() {
    IlvActivity root = ganttData.getRootActivity();
    String key = (root == null) ? null : activityKeys.get(root);
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query root activity key: {0}", key);
    doQueryDelay();
    return key;
  }

  /**
   * Returns the <code>Activity</code> record for the activity specified by its
   * lookup key.
   */
  Override
  public GanttDBRO.ActivityRecord queryActivity(String key) {
    IlvActivity a = activities.get(key);
    ActivityRec arec;
    if (a == null) {
      arec = null;
    } else {
      arec = new ActivityRec(key, a.getID(), a.getName(), a.getStartTime(), a.getEndTime());
    }
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query activity properties: {0}", activityString(key));
    doQueryDelay();
    return arec;
  }

  /**
   * Returns the lookup key for the parent of the specified parent activity. If
   * the activity is the root activity, then it has no parent and this method
   * will return <code>null</code>.
   */
  Override
  public String queryActivityParent(String key) {
    IlvActivity a = activities.get(key);
    String parentKey;
    if (a == null) {
      parentKey = null;
    } else {
      IlvActivity parent = ganttData.getParentActivity(a);
      if (parent == null)
        parentKey = null;
      else
        parentKey = activityKeys.get(parent);
    }
    String[] logParams = { activityString(key), activityString(parentKey) };
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query parent key of activity: {0}, parent={1}", logParams);
    doQueryDelay();
    return parentKey;
  }

  /**
   * Returns an array of lookup keys for the children of the specified parent
   * activity.
   */
  Override
  public String[] queryActivityChildren(String key) {
    IlvActivity a = activities.get(key);
    String[] childKeys;
    if (a == null) {
      childKeys = new String[0];
    } else {
      childKeys = new String[ganttData.getChildActivityCount(a)];
      for (int i = 0; i < childKeys.length; i++) {
        childKeys[i] = activityKeys.get(ganttData.getChildActivity(a, i));
      }
    }
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query child keys of activity: {0}", activityString(key));
    doQueryDelay();
    return childKeys;
  }

  public static class ActivityRec implements GanttDBRO.ActivityRecord {
    private String key;
    private String name;
    private String id;
    private Date start;
    private Date end;

    private ActivityRec(String key, String id, String name, Date start, Date end) {
      this.key = key;
      this.id = id;
      this.name = name;
      this.start = start;
      this.end = end;
    }

    Override
    public String getKey() {
      return key;
    }

    Override
    public String getName() {
      return name;
    }

    Override
    public String getID() {
      return id;
    }

    Override
    public Date getStart() {
      return start;
    }

    Override
    public Date getEnd() {
      return end;
    }

  }

  // =========================================
  // Resources
  // =========================================

  /**
   * Lookup table of resources, keyed by their lookup key.
   */
  private Map<String, IlvResource> resources;

  /**
   * Reverse lookup table of resource keys, keyed by the resource.
   */
  private Map<IlvResource, String> resourceKeys;

  /**
   * The next assignable resource key value.
   */
  private long nextResourceKey = 0;

  /**
   * Computes and stores the resource lookup keys.
   */
  private void initResourceKeys() {
    resources = new HashMap<String, IlvResource>();
    resourceKeys = new HashMap<IlvResource, String>();
    for (Iterator<IlvResource> i = IlvGanttModelUtil.resourcePreorderIterator(ganttData); i.hasNext();) {
      IlvResource a = i.next();
      String key = "Res" + (nextResourceKey++);
      resources.put(key, a);
      resourceKeys.put(a, key);
    }
  }

  /**
   * Returns a descriptive string for the resource specified by its lookup key.
   */
  protected String resourceString(String key) {
    IlvResource r = resources.get(key);
    StringBuffer buf = new StringBuffer();
    buf.append('[');
    buf.append(key);
    buf.append(',');
    if (r == null)
      buf.append("???");
    else {
      buf.append('"');
      buf.append(r.getName());
      buf.append('"');
    }
    buf.append(']');
    return buf.toString();
  }

  /**
   * Returns the lookup key for the root resource or <code>null</code> if there
   * are no resources.
   */
  Override
  public String queryRootResourceKey() {
    IlvResource root = ganttData.getRootResource();
    String key = (root == null) ? null : resourceKeys.get(root);
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query root resource key: {0}", key);
    doQueryDelay();
    return key;
  }

  /**
   * Returns the <code>Resource</code> record for the resource specified by its
   * lookup key.
   */
  Override
  public GanttDBRO.ResourceRecord queryResource(String key) {
    IlvResource r = resources.get(key);
    ResourceRec rrec;
    if (r == null) {
      rrec = null;
    } else {
      rrec = new ResourceRec(key, r.getID(), r.getName(), r.getQuantity());
    }
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query resource properties: {0}", resourceString(key));
    doQueryDelay();
    return rrec;
  }

  /**
   * Returns the lookup key for the parent of the specified parent resource. If
   * the resource is the root resource, then it has no parent and this method
   * will return <code>null</code>.
   */
  Override
  public String queryResourceParent(String key) {
    IlvResource r = resources.get(key);
    String parentKey;
    if (r == null) {
      parentKey = null;
    } else {
      IlvResource parent = ganttData.getParentResource(r);
      if (parent == null)
        parentKey = null;
      else
        parentKey = resourceKeys.get(parent);
    }
    String[] logParams = { resourceString(key), resourceString(parentKey) };
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query parent key of resource: {0}, parent={1}", logParams);
    doQueryDelay();
    return parentKey;
  }

  /**
   * Returns an array of lookup keys for the children of the specified parent
   * resource.
   */
  Override
  public String[] queryResourceChildren(String key) {
    IlvResource r = resources.get(key);
    String[] childKeys;
    if (r == null) {
      childKeys = new String[0];
    } else {
      childKeys = new String[ganttData.getChildResourceCount(r)];
      for (int i = 0; i < childKeys.length; i++) {
        childKeys[i] = resourceKeys.get(ganttData.getChildResource(r, i));
      }
    }
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query child keys of resource: {0}", resourceString(key));
    doQueryDelay();
    return childKeys;
  }

  public static class ResourceRec implements GanttDBRO.ResourceRecord {
    private String key;
    private String name;
    private String id;
    private float quantity;

    private ResourceRec(String key, String id, String name, float qty) {
      this.key = key;
      this.id = id;
      this.name = name;
      this.quantity = qty;
    }

    Override
    public String getKey() {
      return key;
    }

    Override
    public String getName() {
      return name;
    }

    Override
    public String getID() {
      return id;
    }

    Override
    public float getQuantity() {
      return quantity;
    }

  }

  // =========================================
  // Reservations
  // =========================================

  /**
   * Lookup table of reservations, keyed by their lookup key.
   */
  private Map<String, IlvReservation> reservations;

  /**
   * Reverse lookup table of reservation keys, keyed by the reservation.
   */
  private Map<IlvReservation, String> reservationKeys;

  /**
   * The next assignable reservation key value.
   */
  private long nextReservationKey = 0;

  /**
   * Computes and stores the reservation lookup keys.
   */
  private void initReservationKeys() {
    reservations = new HashMap<String, IlvReservation>();
    reservationKeys = new HashMap<IlvReservation, String>();
    for (Iterator<IlvReservation> i = ganttData.reservationIterator(); i.hasNext();) {
      IlvReservation r = i.next();
      String key = "Rsn" + (nextReservationKey++);
      reservations.put(key, r);
      reservationKeys.put(r, key);
    }
  }

  /**
   * Returns a descriptive string for the reservation specified by its lookup
   * key.
   */
  protected String reservationString(String key) {
    IlvReservation r = reservations.get(key);
    String activityKey = activityKeys.get(r.getActivity());
    String resourceKey = resourceKeys.get(r.getResource());
    StringBuffer buf = new StringBuffer();
    buf.append('[');
    buf.append(key);
    buf.append(',');
    buf.append(activityString(activityKey));
    buf.append(',');
    buf.append(resourceString(resourceKey));
    buf.append(']');
    return buf.toString();
  }

  /**
   * Returns an array of reservation lookup keys derived from all the
   * reservations that are traversed by the specified reservation iterator of
   * the underlying data model.
   */
  private String[] getReservationKeys(Iterator<IlvReservation> reservationIter) {
    List<String> keyList = new ArrayList<String>();
    while (reservationIter.hasNext()) {
      IlvReservation reservation = reservationIter.next();
      String key = reservationKeys.get(reservation);
      keyList.add(key);
    }
    String[] keyArray = new String[keyList.size()];
    keyList.toArray(keyArray);
    return keyArray;
  }

  /**
   * Returns an array of lookup keys for all the reservations.
   */
  Override
  public String[] queryReservations() {
    String[] reservationKeys = getReservationKeys(ganttData.reservationIterator());
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query all reservation keys");
    doQueryDelay();
    return reservationKeys;
  }

  /**
   * Returns an array of lookup keys for all the reservations that are
   * associated with an activity, specified by its lookup key.
   */
  Override
  public String[] queryReservationsForActivity(String activityKey) {
    IlvActivity activity = activities.get(activityKey);
    String[] reservationKeys = getReservationKeys(ganttData.reservationIterator(activity));
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query reservation keys for activity: {0}",
        activityString(activityKey));
    doQueryDelay();
    return reservationKeys;
  }

  /**
   * Returns an array of lookup keys for all the reservations that are
   * associated with a resource, specified by its lookup key.
   */
  Override
  public String[] queryReservationsForResource(String resourceKey) {
    IlvResource resource = resources.get(resourceKey);
    String[] reservationKeys = getReservationKeys(ganttData.reservationIterator(resource));
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query reservation keys for resource: {0}",
        resourceString(resourceKey));
    doQueryDelay();
    return reservationKeys;
  }

  /**
   * Returns an array of lookup keys for all the reservations that are
   * associated with a resource, specified by its lookup key, and that intersect
   * the specified time range.
   */
  Override
  public String[] queryReservationsForResource(String resourceKey, Date start, Date end) {
    IlvResource resource = resources.get(resourceKey);
    IlvTimeInterval interval = new IlvTimeInterval(start, end);
    String[] reservationKeys = getReservationKeys(ganttData.reservationIterator(resource, interval));
    Object[] logParams = { resourceString(resourceKey), start, end };
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query reservation keys for resource: {0}, time span: {1} - {2}",
        logParams);
    doQueryDelay();
    return reservationKeys;
  }

  /**
   * Returns the <code>Reservation</code> record for the resource specified by
   * its lookup key.
   */
  Override
  public GanttDBRO.ReservationRecord queryReservation(String key) {
    IlvReservation r = reservations.get(key);
    ReservationRec rrec;
    if (r == null) {
      rrec = null;
    } else {
      String activityKey = activityKeys.get(r.getActivity());
      String resourceKey = resourceKeys.get(r.getResource());
      rrec = new ReservationRec(key, activityKey, resourceKey);
    }
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query reservation properties: {0}", reservationString(key));
    doQueryDelay();
    return rrec;
  }

  public static class ReservationRec implements GanttDBRO.ReservationRecord {
    private String key;
    private String activityKey;
    private String resourceKey;

    private ReservationRec(String key, String activityKey, String resourceKey) {
      this.key = key;
      this.activityKey = activityKey;
      this.resourceKey = resourceKey;
    }

    Override
    public String getKey() {
      return key;
    }

    Override
    public String getActivityKey() {
      return activityKey;
    }

    Override
    public String getResourceKey() {
      return resourceKey;
    }

  }

  // =========================================
  // Constraints
  // =========================================

  /**
   * Lookup table of constraints, keyed by their lookup key.
   */
  private Map<String, IlvConstraint> constraints;

  /**
   * Reverse lookup table of constraint keys, keyed by the constraint.
   */
  private Map<IlvConstraint, String> constraintKeys;

  /**
   * The next assignable constraint key value.
   */
  private long nextConstraintKey = 0;

  /**
   * Computes and stores the constraint lookup keys.
   */
  private void initConstraintKeys() {
    constraints = new HashMap<String, IlvConstraint>();
    constraintKeys = new HashMap<IlvConstraint, String>();
    for (Iterator<IlvConstraint> i = ganttData.constraintIterator(); i.hasNext();) {
      IlvConstraint r = i.next();
      String key = "Cnt" + (nextConstraintKey++);
      constraints.put(key, r);
      constraintKeys.put(r, key);
    }
  }

  /**
   * Returns a descriptive string for the constraint specified by its lookup
   * key.
   */
  protected String constraintString(String key) {
    IlvConstraint c = constraints.get(key);
    String fromActivityKey = activityKeys.get(c.getFromActivity());
    String toActivityKey = activityKeys.get(c.getToActivity());
    StringBuffer buf = new StringBuffer();
    buf.append('[');
    buf.append(key);
    buf.append(',');
    buf.append(c.getType());
    buf.append(",from=");
    buf.append(activityString(fromActivityKey));
    buf.append(",to=");
    buf.append(activityString(toActivityKey));
    buf.append(']');
    return buf.toString();
  }

  /**
   * Returns an array of constraint lookup keys derived from all the constraints
   * that are traversed by the specified constraint iterator of the underlying
   * data model.
   */
  private String[] getConstraintKeys(Iterator<IlvConstraint> constraintIter) {
    List<String> keyList = new ArrayList<String>();
    while (constraintIter.hasNext()) {
      IlvConstraint constraint = constraintIter.next();
      String key = constraintKeys.get(constraint);
      keyList.add(key);
    }
    String[] keyArray = new String[keyList.size()];
    keyList.toArray(keyArray);
    return keyArray;
  }

  /**
   * Returns an array of lookup keys for all the constraints.
   */
  Override
  public String[] queryConstraints() {
    String[] constraintKeys = getConstraintKeys(ganttData.constraintIterator());
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query all constraint keys");
    doQueryDelay();
    return constraintKeys;
  }

  /**
   * Returns an array of lookup keys for all the constraints that have the
   * specified activity as their source or <i>from</i> activity, specified by
   * its lookup key.
   */
  Override
  public String[] queryConstraintsFromActivity(String activityKey) {
    IlvActivity activity = activities.get(activityKey);
    String[] constraintKeys = getConstraintKeys(ganttData.constraintIteratorFromActivity(activity));
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query all constraint keys from activity: {0}",
        activityString(activityKey));
    doQueryDelay();
    return constraintKeys;
  }

  /**
   * Returns an array of lookup keys for all the constraints that have the
   * specified activity as their target or <i>to</i> activity, specified by its
   * lookup key.
   */
  Override
  public String[] queryConstraintsToActivity(String activityKey) {
    IlvActivity activity = activities.get(activityKey);
    String[] constraintKeys = getConstraintKeys(ganttData.constraintIteratorToActivity(activity));
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query all constraint keys to activity: {0}",
        activityString(activityKey));
    doQueryDelay();
    return constraintKeys;
  }

  /**
   * Returns the <code>Constraint</code> record for the constraint specified by
   * its lookup key.
   */
  Override
  public GanttDBRO.ConstraintRecord queryConstraint(String key) {
    IlvConstraint c = constraints.get(key);
    ConstraintRec crec;
    if (c == null) {
      crec = null;
    } else {
      String fromActivityKey = activityKeys.get(c.getFromActivity());
      String toActivityKey = activityKeys.get(c.getToActivity());
      crec = new ConstraintRec(key, c.getType(), fromActivityKey, toActivityKey);
    }
    Logger.getLogger(logID).log(Level.INFO, "[DB] Query constraint properties: {0}", constraintString(key));
    doQueryDelay();
    return crec;
  }

  public static class ConstraintRec implements GanttDBRO.ConstraintRecord {
    private String key;
    private IlvConstraintType type;
    private String fromActivityKey;
    private String toActivityKey;

    private ConstraintRec(String key, IlvConstraintType type, String fromActivityKey, String toActivityKey) {
      this.key = key;
      this.type = type;
      this.fromActivityKey = fromActivityKey;
      this.toActivityKey = toActivityKey;
    }

    Override
    public String getKey() {
      return key;
    }

    Override
    public IlvConstraintType getType() {
      return type;
    }

    Override
    public String getFromActivityKey() {
      return fromActivityKey;
    }

    Override
    public String getToActivityKey() {
      return toActivityKey;
    }

  }

}