/// <summary>
        /// Generates high DPC for new schedule to create new dispatch
        /// This is called when a new schedule has been committed
        /// </summary>
        /// <param name="ScheduleIDs"></param>
        internal static void MarkNewSchedule(IrqHandle IRQ)
        {
            DpcHandle dpchandle;
            Schedule  schedule;
            Int64     scheduleid         = IRQ.ScheduleID;
            DateTime  currentdatetimeutc = DateTime.UtcNow;

            using (DispatchEngineContainer dec = new DispatchEngineContainer())
            {
                schedule = dec.Schedules.Single(s => s.Id == scheduleid);

                // we allow an exception to run once an expired adhoc schedule
                // current assumption is we rely on the scheduling commit method
                // to prevent same schedule from committed more than once
                if (schedule.EndDateTimeUtc > currentdatetimeutc || schedule.IsAdhoc)
                {
                    dpchandle = new DpcHandle()
                    {
                        ScheduleID = scheduleid,
                        Importance = Importance.High,
                        TimeStamp  = currentdatetimeutc
                    };
                    dec.DpcQueue.Add(dpchandle);
                }

                // remove current interrupt
                dec.IrqQueue.Attach(IRQ);
                dec.IrqQueue.Remove(IRQ);

                dec.SaveChanges();
            }
        }
        /// <summary>
        /// Generates high DPC to trigger planning of dispatch for a given schedule
        /// Also removes handle from pool if schedule is expired.
        /// This is called when a schedule changed or when a dispatch has completed, failed & cancelled.
        /// </summary>
        /// <param name="Interrupt"></param>
        internal static void SweepSchedulingPool(IrqHandle IRQ)
        {
            SchedulingHandle schedulinghandle;
            DpcHandle        dpchandle;
            Schedule         schedule;
            Dispatch         dispatch;
            Int64            scheduleid = IRQ.ScheduleID;
            Int64?           dispatchid = IRQ.DispatchID;
            bool             indpcqueue;
            DateTime         currentdatetimeutc = DateTime.UtcNow;

            using (DispatchEngineContainer dec = new DispatchEngineContainer())
            {
                // check if there is already a high DPC for the schedule in queue
                // we don't sweep the pool if there is outstanding work to be done for the schedule
                indpcqueue = dec.DpcQueue.Any(dpc => dpc.ScheduleID == scheduleid && dpc.Importance == Importance.High);
                if (!indpcqueue)
                {
                    // check if dispatch is waiting on callback
                    dispatch = dec.Dispatches.Single(dh => dh.Id == dispatchid);
                    if (dispatch.StatusID != DispatchStatus.Callback)
                    {
                        schedule = dec.Schedules.Single(s => s.Id == scheduleid);

                        // check if schedule has been removed from pool
                        // this can happen when a dispatch is done after the schedule has been expired
                        if (dec.SchedulingPool.Any(sh => sh.ScheduleID == scheduleid))
                        {
                            schedulinghandle = dec.SchedulingPool.Single(sh => sh.ScheduleID == scheduleid);
                            if (schedule.EndDateTimeUtc > currentdatetimeutc)
                            {
                                dpchandle = new DpcHandle()
                                {
                                    ScheduleID = schedule.Id,
                                    DispatchID = schedulinghandle.DispatchID,
                                    Importance = Importance.High,
                                    TimeStamp  = currentdatetimeutc
                                };
                                dec.DpcQueue.Add(dpchandle);
                            }
                            else
                            {
                                dec.SchedulingPool.Remove(schedulinghandle);
                            }
                        }
                    }
                }

                // remove current interrupt
                dec.IrqQueue.Attach(IRQ);
                dec.IrqQueue.Remove(IRQ);

                dec.SaveChanges();
            }
        }
        /// <summary>
        /// Generates high low DPC to trigger runing of a dispatch
        /// This is called after polling
        /// </summary>
        /// <param name="IRQ"></param>
        internal static void MarkDispatchReady(IrqHandle IRQ)
        {
            DpcHandle dpchandle;

            dpchandle = new DpcHandle()
            {
                ScheduleID = IRQ.ScheduleID,
                DispatchID = IRQ.DispatchID,
                Importance = Importance.Low,    // this should be low to run dispatches
                TimeStamp  = DateTime.UtcNow
            };

            using (DispatchEngineContainer dec = new DispatchEngineContainer())
            {
                dec.DpcQueue.Add(dpchandle);

                // remove current interrupt
                dec.IrqQueue.Attach(IRQ);
                dec.IrqQueue.Remove(IRQ);

                dec.SaveChanges();
            }
        }
        /// <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();
            }
        }