/// <summary>
        /// Create a new Dispatch and add to scheduling pool for new schedules. Otherwise swap existing dispatch out
        /// This is called after a schedule change or dispatch has completed
        /// </summary>
        /// <param name="ScheduleID"></param>
        internal static void PlanSchedulingPool(DpcHandle DPC)
        {
            SchedulingHandle schedulinghandle;
            Dispatch         olddispatch;
            Dispatch         newdispatch;
            Schedule         schedule;
            Tool             tool;
            CallbackDevice   callbackdevice;
            bool             inschedulingpool;
            DateTime         nextdispatchdatetimeutc;
            DateTime         currentdatetimeutc = DateTime.UtcNow;

            using (DispatchEngineContainer dec = new DispatchEngineContainer())
            {
                schedule = dec.Schedules.Single(s => s.Id == DPC.ScheduleID);
                dec.Entry(schedule).Reference(r => r.Tool).Load();
                // current implementation only supports one tool per schedule
                tool = schedule.Tool;

                nextdispatchdatetimeutc = GetTargetDispatchDateTimeUtc(schedule);

                newdispatch = new Dispatch()
                {
                    CreatedDateTimeUtc      = currentdatetimeutc,
                    LastModifiedDateTimeUtc = currentdatetimeutc,
                    StatusID         = DispatchStatus.Pending,
                    RetryCount       = 0,
                    ScheduleID       = DPC.ScheduleID,
                    StartDateTimeUtc = nextdispatchdatetimeutc,
                    ToolID           = schedule.Tool.Id
                };

                // new schedule will not have a dispatch in pool
                inschedulingpool = dec.SchedulingPool.Any(dh => dh.ScheduleID == DPC.ScheduleID);
                if (inschedulingpool)
                {
                    // check if dispatch is waiting for callback
                    // there should always be a dispatch in pool if this is not a new schedule
                    olddispatch = dec.Dispatches.Single(dh => dh.Id == DPC.DispatchID);
                    if (olddispatch.StatusID != DispatchStatus.Callback)
                    {
                        schedulinghandle = dec.SchedulingPool.Single(dh => dh.ScheduleID == DPC.ScheduleID);

                        // set old dispatch to cancel only if it is in pending status
                        if (olddispatch.StatusID == DispatchStatus.Pending)
                        {
                            olddispatch.StatusID = DispatchStatus.Cancelled;
                            olddispatch.LastModifiedDateTimeUtc = currentdatetimeutc;
                        }

                        // swap out old dispatch if schedule is dispatchable
                        if (schedule.EndDateTimeUtc > nextdispatchdatetimeutc)
                        {
                            dec.Dispatches.Add(newdispatch);
                            dec.SaveChanges();
                            dec.Entry(newdispatch).Reload();

                            schedulinghandle.DispatchID = newdispatch.Id;
                        }
                    }
                }
                else
                {
                    // this case it is a new schedule, we need to add to the schedule pool as well
                    dec.Dispatches.Add(newdispatch);
                    dec.SaveChanges();
                    dec.Entry(newdispatch).Reload();

                    // add devices associated to schedule to CallbackDevices table that is specifically maintained by engine
                    dec.Entry(schedule).Collection(c => c.Devices).Load();
                    if (schedule.Devices.Count > 0)
                    {
                        foreach (var device in schedule.Devices)
                        {
                            callbackdevice = new CallbackDevice()
                            {
                                DispatchID = newdispatch.Id,
                                DeviceID   = device.Id
                            };
                            dec.CallbackDevices.Add(callbackdevice);
                        }
                    }

                    schedulinghandle = new SchedulingHandle()
                    {
                        ScheduleID = DPC.ScheduleID,
                        DispatchID = newdispatch.Id
                    };
                    dec.SchedulingPool.Add(schedulinghandle);
                }

                // remove current DPC
                dec.DpcQueue.Attach(DPC);
                dec.DpcQueue.Remove(DPC);

                dec.SaveChanges();
            }
        }
        /// <summary>
        /// Runs simplex/duplex tool and update dispatch status accordingly. Also generates IRQ to notify dispatch is done
        /// This is called when there are items in dispatchready pool
        /// </summary>
        /// <param name="DispatchID"></param>
        internal static void RunDispatch(DpcHandle DPC)
        {
            Schedule         schedule;
            Dispatch         dispatch;
            IrqHandle        irqhandle;
            Tool             tool;
            List <DeviceDTO> devices            = new List <DeviceDTO>();
            Int64            scheduleid         = DPC.ScheduleID;
            Int64?           dispatchid         = DPC.DispatchID;
            DateTime         currentdatetimeutc = DateTime.UtcNow;

            using (DispatchEngineContainer dec = new DispatchEngineContainer())
            {
                dispatch = dec.Dispatches.Single(d => d.Id == dispatchid);
                schedule = dec.Schedules.Single(s => s.Id == scheduleid);
                tool     = dec.Tools.Single(t => t.Id == dispatch.ToolID);

                if (dec.CallbackDevices.Any(cd => cd.DispatchID == dispatchid))
                {
                    devices = (from d in dec.Devices
                               where (from cd in dec.CallbackDevices
                                      where cd.DispatchID == dispatchid
                                      select cd.DeviceID).Contains(d.Id)
                               select new DeviceDTO()
                    {
                        Id = d.Id,
                        Name = d.Name,
                        IPAddress = d.IPAddress
                    }).ToList();
                }
            }

            // we check here to make sure schedule change or cancellation is honored
            // which would hvae set the dispatch to cancelled
            if (dispatch.StatusID != DispatchStatus.Cancelled)
            {
                RunTool(ref dispatch, tool, devices);
            }

            // prevent dispatch from further running if it has retried at least 5 times
            if (dispatch.RetryCount >= 5)
            {
                dispatch.StatusID = DispatchStatus.Failed;
            }

            // update the last modified of dispatch
            dispatch.LastModifiedDateTimeUtc = currentdatetimeutc;

            using (DispatchEngineContainer dec = new DispatchEngineContainer())
            {
                dec.Entry(dispatch).State = System.Data.EntityState.Modified;

                // generate an interrupt to notify completion
                if (dispatch.StatusID == DispatchStatus.Completed || dispatch.StatusID == DispatchStatus.Failed)
                {
                    irqhandle = new IrqHandle()
                    {
                        ScheduleID = dispatch.ScheduleID,
                        DispatchID = dispatch.Id,
                        Level      = IRQL.Dispatch,
                        TimeStamp  = currentdatetimeutc
                    };
                    dec.IrqQueue.Add(irqhandle);
                }

                // remove current DPC
                dec.DpcQueue.Attach(DPC);
                dec.DpcQueue.Remove(DPC);

                dec.SaveChanges();
            }
        }