/* A Project is a network of Task and Milestone nodes to
   manage CPM or PERT networks.  A project knows how to
   calculate its critical path, keep track of costs etc.

   Since Project descends from Network, it inherits all of
   Network's instance variables, e.g. start, end, name, desc.
*/!!

inherit(Network, #Project, #(cost      /* sum of all costs */
resources /* used by tasks */
autoCalc  /* boolean is recalc on? */

), 2, nil)!!

now(ProjectClass)!!

now(Project)!!

/* Return only the tasks in the project. */
Def  tasks(self)
{ 
  ^extract(nodes(self),
   {using(node) 
    class(node) <> Milestone;
   });
}!!

/* Returns a collection of lines summarizing useful information. */
Def  getInfoLines(self | tc)
{
  tc := new(OrderedCollection, 6);
  add(tc, loadString(PW_PROJECT) + ": " + field(name, 8)+ "   " + desc);
  add(tc, "");
  add(tc, loadString(PW_PROJT1));
  add(tc, "--------------------------------------------");
  add(tc, loadString(PW_START))
   + field(asString(getEarlyStart(self)), 8) + " "
   + field(asString(getLateStart(self)), 8) + " "
   + field(asString(getCost(self)), 6) + " "
   + field(asString(getTime(self)), 6) + " " 
   + field(asString(getSlack(self)), 4);
  add(tc, loadString(PW_FINISH))
   + field(asString(getEarlyFinish(self)), 8) + " "
   + field(asString(getLateFinish(self)), 8);
  ^tc;
 }!!

/* Return the resource if the name exists in the
   resources collection, otherwise return false and
   display an error message. */
Def  checkResExists(self, resName | res)
{
  if not(res := resExists(self, resName))
    beep();
    errorBox(loadString(PW_RESINVAL),
             loadString(PW_RESOURCE) + " " + resName +
             loadString(PW_NOTEXIST));
  endif;
  ^res;
}!!

/* Return the resources collection. */
Def  resources(self)
{ 
  ^resources;
}!!

/* Return the resource of the name resName if
   it exists, otherwise return false. Use
   checkResExists for error checking. */
Def  resExists(self, resName | res)
{
  ^resName in resources;
}!!

/* Parse a string of resNames and return a set of
   resources.  If the resource doesn't exist, a new
   one is created. */
Def  parseResNames(self, resNames | names, res, aRes)
{
  names := words(resNames);
  res := new(OrderedCollection, 6);
  do(names,
    {using(name) 
     if not(aRes := resExists(self, name))  /* create it */
       aRes := new(Resource);
       setName(aRes, name);
       if editInfo(aRes) == IDOK
         add(resources, getName(aRes), aRes);
         add(res, aRes);
       endif;
     else
       add(res, aRes);
     endif;
  });
  ^res;
}!!

/* Return a sorted collection of activities in the project. 
   Sort by earlyStart, time, name for unique order.  
   This uses early binding and dot notation for speed. */
Def  sortedActivities(self| sorted)
{  
  sorted := new(SortedCollection, 10);
    
  setCompareBlock(sorted, {using(a,b)
                  if a.earlyStart = b.earlyStart
                     if getTime(a) = getTime(b)
                       a.name < b.name
                     else
                       getTime(a) < getTime(b)
                     endif
                  else
                      a.earlyStart < b.earlyStart
                 endif});

  do(nodeNames,
     {using(aNode) 
      add(sorted:SortedCollection, aNode);
   });
  ^sorted;
}!!

/* Return the type of nodes that start and end should be.*/
Def  nodeClass(self)
{
  ^Milestone;
}!!

/* Return the current cost which is always up to date. */
Def  getCost(self)
{
  ^cost;
}!!

/* Get the user set late finish date. */
Def  getUserLateFinish(self)
{ 
  ^getUserLateFinish(end);
}!!

/* Get the user set early start date. */
Def  getUserEarlyStart(self)
{ 
  ^getUserEarlyStart(start);
} !!

/* Return a string to be used as a window caption. */
Def  makeCaption(self)
{
  ^loadString(PW_PROJECT) + ": " + name;
}!!

/* Set the values of a project. 
   Values is an array of name, desc,
   userEarlyStart, userLateFinish. 
   This message is sent after editing. */
Def  setValues(self, values)
{
  name := values[0];
  desc := values[1];
  if getEarlyStart(self) <> values[2]
    setEarlyStart(self, values[2]);
  endif;
  if getLateFinish(self) <> values[3]
    setLateFinish(self, values[3]);
  endif;
}!!

/* Display and edit the project information. 
   The dialog should define methods run() and setEditItem().
*/
Def  editInfo(self | dlg, retValue)
{
  showWaitCurs();
  dlg := new(ProjDialog); 
  setEditItem(dlg, self);
  retValue := run(dlg, ThePort);
  showOldCurs();
  ^retValue;
}!!

/* Project slack is defined as the slack at the end node.
   This could be introduced if the user sets a lateFinish. */
Def  getSlack(self)
{
  ^getSlack(end);
}!!

/* Return the current cost which is always up to date. */
Def  calcCost(self)
{
  ^cost;
}!!

/* Update the cost based on a change to a Task. */
Def  updateCost(self, change)
{ 
  cost := cost + change;
}!!

/* Do the backwards pass only starting from the end node.
   The end node is always critical.
*/
Def  recalc2(self)
{  
   recalc2(end);
   end.critical := true;
}!!

/* Turn automatic calculation on or off.
   Force recalculation if turned on. */
Def  setAutoCalc(self, mode)
{
  if (autoCalc := mode)   /* true or false */
    recalc(self);    
  endif;
}!!

/* Recalculate the entire network. First invalidate all nodes
   so that a full recalc will be forced.  Then do both the
   forward and backwards recalc passes. The forward pass must
   be entirely completed before doing the backwards pass. */
Def  recalc(self)
{
   invalidate(start, new(Set,10)); /* clear all nodes */
   recalc1(start, true);           /* force entire recalc */
   recalc2(self);                  /* backwards pass */
}!!

/* Get the project lateStart date. */
Def  getLateStart(self)
{ 
  ^getLateStart(start);
}!!

/* Get the project earlyStart date. */
Def  getEarlyStart(self)
{ 
  ^getEarlyStart(start);
} !! 

/* Get the project early finish date. */
Def  getEarlyFinish(self)
{ 
  ^getEarlyFinish(end);
}!!

/* Get the project late finish date. */
Def  getLateFinish(self)
{ 
  ^getLateFinish(end);
}!!

/* Return the status of autoCalc mode. */
Def  autoCalc(self)
{
  ^autoCalc;
}!!

/* The user can enter an early start date. */
Def  setEarlyStart(self, aDate)
{ 
  setEarlyStart(start,aDate);
}!!

/* The user can enter a late finish date. */
Def  setLateFinish(self, aDate)
{ 
  setLateFinish(end,aDate);
}!!

/* Calculate the project time. If negative (e.g. project
   isn't fully hooked up yet) return 0. */
Def  getTime(self)
{ ^max(0, getEarlyFinish(self) - getEarlyStart(self));
}!!

/* Initialize a new Project. Uses ancestor init.
   The nodeClass method determines the node type
   for start and end. */
Def  init(self)
{ 
  init(self:Network);         /* use ancestor init */
  cost := 0;
  resources := new(Dictionary, 10);
  setEarlyStart(start,        /* set default project start */
    date(1,1,1988)); 
}!!

