context: /*
      Copyright 2014 Travel Modelling Group, Department of Civil Engineering, University of Toronto
  
      This file is part of XTMF.
  
      XTMF is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      XTMF is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with XTMF.  If not, see <http://www.gnu.org/licenses/>.
  */
  using System;
  using Tasha.Common;
  using XTMF;
  
  namespace Tasha.Scheduler
  {
      internal sealed class ProjectSchedule : Schedule
      {
          public override bool CheckEpisodeInsert(IEpisode episode, ref TimeWindow feasibleWindow)
          {
              throw new NotImplementedException();
          }
  
          public override Time CheckOverlap(Episode episode)
          {
              Time total = Time.Zero;
              for(int i = 0; i < EpisodeCount; i++)
              {
                  if(Episodes[i].StartTime <= episode.StartTime
                      && Episodes[i].EndTime >= episode.EndTime)
                  {
                      // this [i] completely covers the given episode
                      total += episode.Duration;
                  }
                  else if(Episodes[i].StartTime >= episode.StartTime
                      && Episodes[i].EndTime >= episode.EndTime)
                  {
                      // if the episode happens before we start, but we end after
                      total += episode.EndTime - Episodes[i].StartTime;
                  }
                  else if(Episodes[i].StartTime <= episode.StartTime
                      && Episodes[i].EndTime <= episode.EndTime)
                  {
                      //if we started before this episode, but we finished first
                      total += Episodes[i].EndTime - episode.StartTime;
                  }
                  else if(Episodes[i].StartTime >= episode.StartTime
                      && Episodes[i].EndTime <= episode.EndTime)
                  {
                      // if the episode is larger and 100% covering this [i]
                      total += Episodes[i].Duration;
                  }
              }
              return total;
          }
  
          public override int CleanUp(Time minPrimaryWorkLength)
          {
              throw new NotImplementedException();
          }
  
          public override bool ForcedEpisodeInsert(Episode ep)
          {
              throw new NotImplementedException();
          }
  
          public override void GenerateTrips(ITashaHousehold household, int householdIteration, Time minimumAtHomeTime)
          {
              throw new NotImplementedException();
          }
  
          public override bool Insert(Episode ep, Random random)
          {
              /* This is where episodes are first put near their other common types
               * RULES:
               * 1) Unless it is a Work Business episode, we are not allowed to have a "split" conflict type
               * 2) We are not allowed to squish things past the threshold allowed (50% by default)
              */
              // Learn what type of case we are going to be in
              ConflictReport conflict = InsertCase(null, ep, false);
              switch(conflict.Type)
              {
                  case ScheduleConflictType.NoConflict:
                      {
                          InsertAt(ep, conflict.Position);
                          return true;
                      }
                  case ScheduleConflictType.Split:
                      {
                          if((ep.ActivityType != Activity.WorkBasedBusiness) & (ep.ActivityType != Activity.ReturnFromWork)) return false;
                          if(Episodes[conflict.Position].ActivityType != Activity.PrimaryWork) return false;
                          // Since it is a primary work episode we need to split it
                          var postEp = new ActivityEpisode(new TimeWindow(ep.EndTime, Episodes[conflict.Position].EndTime), Activity.PrimaryWork,
                               Episodes[conflict.Position].Owner)
                          {
                              Zone = Episodes[conflict.Position].Zone
                          };
                          ((Episode)Episodes[conflict.Position]).EndTime = ep.StartTime;
                          InsertAt(ep, conflict.Position + 1);
                          InsertAt(postEp, conflict.Position + 2);
                          return true;
                      }
                  case ScheduleConflictType.Posterior:
                      {
                          // the given position is the element we need to go after
                          Time earlyTimeBound = Time.StartOfDay;
                          Time lateTimeBound = Time.EndOfDay;
                          Episode prior = (Episode)Episodes[conflict.Position];
                          Episode middle = ep;
                          Episode post = (Episode)((conflict.Position < EpisodeCount - 1)
                              ? Episodes[conflict.Position + 1] : null);
                          if(conflict.Position >= 1)
                          {
                              earlyTimeBound = Episodes[conflict.Position - 1].EndTime;
                          }
                          if(EpisodeCount - conflict.Position > 2)
                          {
                              lateTimeBound = Episodes[conflict.Position + 2].StartTime;
                          }
                          if(Insert(earlyTimeBound, prior, middle, post, lateTimeBound))
                          {
                              InsertAt(ep, conflict.Position + 1);
                              return true;
                          }
                          return false;
                      }
                  case ScheduleConflictType.Prior:
                      {
                          // The given position is the element we need to go before
                          Time earlyTimeBound = Time.StartOfDay;
                          Time lateTimeBound = Time.EndOfDay;
                          Episode prior = (Episode)(conflict.Position > 0 ? Episodes[conflict.Position - 1] : null);
                          Episode middle = ep;
                          Episode post = (Episode)Episodes[conflict.Position];
                          if(conflict.Position >= 2)
                          {
                              earlyTimeBound = Episodes[conflict.Position - 2].EndTime;
                          }
                          if(EpisodeCount - conflict.Position > 1)
                          {
                              lateTimeBound = Episodes[conflict.Position + 1].StartTime;
                          }
                          if(Insert(earlyTimeBound, prior, middle, post, lateTimeBound))
                          {
                              InsertAt(ep, conflict.Position);
                              return true;
                          }
                          return false;
                      }
                  case ScheduleConflictType.CompleteOverlap:
                      {
                          // There are no cases where a complete overlap is allowed
                          return false;
                      }
                  default:
                      {
                          // We came across a type of conflict that we do not know how to handle!
                          throw new NotImplementedException(String.Format("This conflict type \"{0}\" has not been coded for yet!",
                              Enum.GetName(typeof(ScheduleConflictType), conflict.Type)));
                      }
              }
          }
  
          public override bool TestInsert(Episode episode)
          {
              throw new NotImplementedException();
          }
  
          private static bool FillInGaps(Episode middle, ref Time priorOverlap, ref Time postOverlap)
          {
              if(priorOverlap <= Time.Zero)
              {
                  // check to see if there is enough time to move the middle closer to the prior
                  if(postOverlap <= -priorOverlap)
                  {
                      Relocate(middle, (middle.StartTime - postOverlap));
                      return true;
                  }
                  // prior overlap < 0, so just add it
                  Relocate(middle, (middle.StartTime + priorOverlap));
                  // subtract out the reduced time
                  postOverlap += priorOverlap;
              }
              if(postOverlap <= Time.Zero)
              {
                  // check to see if there is enough time to move the middle closer to the prior
                  if(priorOverlap <= -postOverlap)
                  {
                      Relocate(middle, (middle.StartTime + priorOverlap));
                      return true;
                  }
                  // prior overlap < 0, so subtract it
  
gt:                 Relocate(middle, (middle.StartTime - postOverlap));