using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Aitex.Core.Common;
using Aitex.Core.RT.Routine;
using Aitex.Core.RT.SCCore;
using Aitex.Sorter.Common;
using Aitex.Core.RT.Log;
using Aitex.Core.Util;
using Aitex.Core.RT.DataCenter;
using Aitex.Core.RT.RecipeCenter;
using Aitex.Core.RT.Fsm;

using MECF.Framework.Common.Jobs;
using MECF.Framework.Common.Routine;
using MECF.Framework.Common.Equipment;
using MECF.Framework.Common.SubstrateTrackings;
using MECF.Framework.Common.Schedulers;
using MECF.Framework.Common.DBCore;

using Venus_Core;

using Venus_RT.Modules.Schedulers;
using Venus_RT.Scheduler;
using Venus_Unity;

namespace Venus_RT.Modules
{
    enum ModulePriority
    {
        High,
        Middle,
        Low,
        Stop,
    }

    public enum MovingStatus
    {
        Idle,
        Waiting,
        Moving,
        Moved,

        // PM Status
        WaitProcess,
        StartProcess,
        Processing,

        StartIdleClean,
        IdleClean,

        WaitPreJobClean,
        StartPreJobClean,
        PreJobClean,

        WaitPostJobClean,
        StartPostJobClean,
        PostJobClean,

        WaitWTWClean,
        StartWTWClean,
        WTWClean,

        // Align Status
        WaitAlign,
        StartAlign,
        Aligning,
    }

    class ModuleFlag
    {
        public ModulePriority Priority { get; set; }
        public MovingStatus MovingStatus { get; set; }

        public bool InUsed { get; set; }

        public ModuleFlag(ModulePriority _priority)
        {
            Priority = _priority;
            MovingStatus = MovingStatus.Idle;
            InUsed = true;
        }
    }

    class AutoCycle : IRoutine
    {
        private List<ControlJobInfo> _lstControlJobs = new List<ControlJobInfo>();
        private List<ProcessJobInfo> _lstProcessJobs = new List<ProcessJobInfo>();

        private RState _cycleState = RState.Init;

        Dictionary<ModuleName, SchedulerModule> _atmSchedulers = new Dictionary<ModuleName, SchedulerModule>();
        Dictionary<ModuleName, ModuleFlag> _atmModules = new Dictionary<ModuleName, ModuleFlag>();

        Dictionary<ModuleName, SchedulerModule> _vacSchedulers = new Dictionary<ModuleName, SchedulerModule>();
        Dictionary<ModuleName, ModuleFlag> _vacModules = new Dictionary<ModuleName, ModuleFlag>();


        SchedulerTMRobot _tmRobot = (SchedulerTMRobot)Singleton<TransferModule>.Instance.GetScheduler(ModuleName.TMRobot);
        SchedulerEfemRobot _efemRobot = (SchedulerEfemRobot)Singleton<TransferModule>.Instance.GetScheduler(ModuleName.EfemRobot);

        Dictionary<SlotItem, long> _vacReadyOutSlots = new Dictionary<SlotItem, long>();
        Dictionary<SlotItem, long> _vacReadyInSlots = new Dictionary<SlotItem, long>();
        Dictionary<SlotItem, long> _atmReadyOutSlots = new Dictionary<SlotItem, long>();
        Dictionary<SlotItem, long> _atmReadyInSlots = new Dictionary<SlotItem, long>();

        List<int> _LLAInSlot = new List<int> { 0, 1, 2, 3 };
        List<int> _LLAOutSlot = new List<int> { 0, 1, 2, 3 };
        List<int> _LLBInSlot = new List<int> { 0, 1, 2, 3 };
        List<int> _LLBOutSlot = new List<int> { 0, 1, 2, 3 };
        int _LLASlotNumber = 4;
        int _LLBSlotNumber = 4;
        int _efemRobotSingleArmOption = 0;
        SequenceLLInOutPath _LLInOutPath = SequenceLLInOutPath.DInDOut;
        public SequenceLLInOutPath LLInOutPath { get { return _LLInOutPath; } }

        List<MoveItem> _movingItems = new List<MoveItem>();
        List<MoveItem> _efemMovingItems = new List<MoveItem>();

        Dictionary<SlotItem, Guid> _vacWaferTargets = new Dictionary<SlotItem, Guid>();
        Dictionary<SlotItem, Guid> _atmWaferTargets = new Dictionary<SlotItem, Guid>();

        R_TRIG _vacMoveFinishTrig = new R_TRIG();
        R_TRIG _atmMoveFinishTrig = new R_TRIG();

        private SchedulerFACallback _faCallback;
        private SchedulerDBCallback _dbCallback;

        private bool _isCycleMode;
        private int _cycleSetPoint = 0;
        private int _cycledCount = 0;
        private int _cycledWafer = 0;
        private float _throughput = 0.0f;
        private Dictionary<ModuleName, int> _lpCycleWafer = new Dictionary<ModuleName, int>();
        private Dictionary<ModuleName, int> _lpCycleCount = new Dictionary<ModuleName, int>();
        private Stopwatch _cycleWatch = new Stopwatch();

        private List<ModuleName> _waitPreCleanPMs = new List<ModuleName>();
        private List<ModuleName> _waitPostCleanPMs = new List<ModuleName>();
        private List<ModuleName> _waitWTWCleanPMs = new List<ModuleName>();

        private List<Guid> _lstReturnWafers = new List<Guid>();

        public bool HasJobRunning => _lstControlJobs.Count > 0;

        #region public interface
        public AutoCycle()
        {
            _faCallback = new SchedulerFACallback();
            _dbCallback = new SchedulerDBCallback();

            InitModules();

            _LLASlotNumber = SC.GetValue<int>("LLA.SlotNumber");
            _LLBSlotNumber = SC.GetValue<int>("LLB.SlotNumber");

            _LLAInSlot.RemoveAll(item => item >= _LLASlotNumber);
            _LLAOutSlot.RemoveAll(item => item >= _LLASlotNumber);

            _LLBInSlot.RemoveAll(item => item >= _LLBSlotNumber);
            _LLBOutSlot.RemoveAll(item => item >= _LLBSlotNumber);

            _efemRobotSingleArmOption = SC.GetValue<int>("EFEM.SingleArmOption");

            DATA.Subscribe("Scheduler.CycledCount", () => _cycledCount, SubscriptionAttribute.FLAG.IgnoreSaveDB);
            DATA.Subscribe("Scheduler.CycledWafer", () => _cycledWafer, SubscriptionAttribute.FLAG.IgnoreSaveDB);
            DATA.Subscribe("Scheduler.CycleSetPoint", () => _cycleSetPoint, SubscriptionAttribute.FLAG.IgnoreSaveDB);
            DATA.Subscribe("Scheduler.Throughput", () => _throughput, SubscriptionAttribute.FLAG.IgnoreSaveDB);


            DATA.Subscribe("Scheduler.PjIdList", () => Array.ConvertAll(_lstProcessJobs.ToArray(), x => x.InnerId.ToString()).ToList());
        }

        public RState Start(params object[] objs)
        {
            _isCycleMode = SC.GetValue<bool>("System.IsCycleMode");
            _cycleSetPoint = _isCycleMode ? SC.GetValue<int>("System.CycleCount") : 0;
            _cycledWafer = 0;
            _cycledCount = 0;
            _throughput = 0;
            _cycleWatch.Stop();
            _lpCycleWafer.Clear();
            _lpCycleCount.Clear();

            return RState.Running;
        }

        public RState Monitor()
        {
            if (_cycleState == RState.Running)
            {
                prelude();
                driveAtmSystem();
                driveVacSystem();
                epilogue();
            }

            return _cycleState;
        }

        public void Abort()
        {
        }

        public void CreateJob(Dictionary<string, object> param)
        {
            _isCycleMode = SC.GetValue<bool>("System.IsCycleMode");
            _cycleSetPoint = _isCycleMode ? SC.GetValue<int>("System.CycleCount") : 0;


            string[] slotSequence = (string[])param["SlotSequence"];
            string jobId = (string)param["JobId"];
            string module = (string)param["Module"];
            //bool autoStart = (bool)param["AutoStart"];
            string lotId = jobId;
            if (param.ContainsKey("LotId"))
                lotId = (string)param["LotId"];

            if (slotSequence.Length != SC.GetValue<int>("EFEM.LoadPort.SlotNumber"))
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"slot sequence parameter not valid, length is {slotSequence.Length}, should be {SC.GetValue<int>("EFEM.LoadPort.SlotNumber")}");
                _faCallback.JobCreateFailed(module, lotId, jobId, "");
                return;
            }

            if (!ModuleHelper.IsLoadPort(ModuleHelper.Converter(module)))
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"{module} should be LoadPort");
                _faCallback.JobCreateFailed(module, lotId, jobId, "");
                return;
            }

            if (string.IsNullOrEmpty(jobId))
            {
                jobId = "CJ_Local_" + module;
            }

            if (_lstControlJobs.Exists(x => x.Name == jobId))
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"{jobId} already created");
                _faCallback.JobCreateFailed(module, lotId, jobId, "");
                return;
            }

            SchedulerLoadPort lp = _atmSchedulers[ModuleHelper.Converter(module)] as SchedulerLoadPort;
            if (!lp.CheckReadyRunJob())
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"{module} not ready");
                _faCallback.JobCreateFailed(module, lotId, jobId, "");
                return;
            }

            ControlJobInfo cj = new ControlJobInfo();
            cj.Name = jobId;
            cj.Module = module;
            cj.LotName = lotId;
            cj.LotInnerId = Guid.NewGuid();
            cj.LotWafers = new List<WaferInfo>();
            cj.SetState(EnumControlJobState.WaitingForStart);
            cj.JetState = EnumJetCtrlJobState.Created;

            Dictionary<string, bool[]> seqSlot = new Dictionary<string, bool[]>();
            Dictionary<string, List<Tuple<ModuleName, int>>> seqSlotWafers = new Dictionary<string, List<Tuple<ModuleName, int>>>();
            Dictionary<string, string> indexSequence = new Dictionary<string, string>();

            bool enableGroupBySequence = SC.GetValue<bool>("Scheduler.GroupWaferBySequence");
            for (int i = 0; i < SC.GetValue<int>("EFEM.LoadPort.SlotNumber"); i++)
            {
                if (string.IsNullOrEmpty(slotSequence[i]) || string.IsNullOrEmpty(slotSequence[i].Trim()))
                    continue;

                string groupName = enableGroupBySequence ? slotSequence[i].Trim() : i.ToString();
                indexSequence[groupName] = slotSequence[i];
                if (!seqSlot.ContainsKey(groupName))
                {
                    seqSlot[groupName] = new bool[SC.GetValue<int>("EFEM.LoadPort.SlotNumber")];
                }
                if (!seqSlotWafers.ContainsKey(groupName))
                {
                    seqSlotWafers[groupName] = new List<Tuple<ModuleName, int>>();
                }
                seqSlot[groupName][i] = true;

                if (!WaferManager.Instance.CheckHasWafer(module, i))
                {
                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} not in the carrier");
                    return;
                }
                if (!WaferManager.Instance.CheckWafer(ModuleHelper.Converter(module), i, WaferStatus.Normal))
                {
                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} status is {WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).Status}");
                    _faCallback.JobCreateFailed(module, lotId, jobId, "");
                    return;
                }

                if (WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).ProcessState != EnumWaferProcessStatus.Idle)
                {
                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job wafer: {module} slot {i + 1} process status is {WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).ProcessState}");
                    _faCallback.JobCreateFailed(module, lotId, jobId, "");
                    return;
                }

                cj.LotWafers.Add(WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i));
                WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).SequenceName = slotSequence[i];

                seqSlotWafers[groupName].Add(Tuple.Create(ModuleHelper.Converter(module), i));

                cj.JobWaferSize = WaferManager.Instance.GetWafer(ModuleHelper.Converter(module), i).Size;

                LOG.Write(eEvent.EV_ROUTER, ModuleName.System, $"Assigned wafer job, wafer {module}.{i + 1}, sequence: {slotSequence[i]}");
            }

            if (seqSlotWafers.Count == 0)
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"job has not assign wafer");
                _faCallback.JobCreateFailed(module, lotId, jobId, "");
                return;
            }

            List<ProcessJobInfo> pjs = new List<ProcessJobInfo>();
            string[] seqs = seqSlot.Keys.ToArray();
            for (int i = 0; i < seqs.Length; i++)
            {
                ProcessJobInfo pj = new ProcessJobInfo();
                pj.Name = jobId + "_" + (i + 1);
                pj.Sequence = SequenceInfoHelper.GetInfo(indexSequence[seqs[i]]);
                pj.ControlJobName = cj.Name;
                pj.LotName = lotId;
                pj.SlotWafers = seqSlotWafers[seqs[i]];
                pj.SetState(EnumProcessJobState.Queued);

                if (!CheckSequencePmReady(pj.Sequence, null, out _, out string reason))
                {
                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"no valid chamber for the {reason}");
                    _faCallback.JobCreateFailed(module, lotId, jobId, "");
                    return;
                }

                if (!RouteManager.IsATMMode && !CheckSequenceRecipeFileValid(pj.Sequence, out reason))
                {
                    LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"recipe file not valid in the sequence, {reason}");
                    _faCallback.JobCreateFailed(module, lotId, jobId, "");
                    return;
                }

                pjs.Add(pj);
            }

            _dbCallback.LotUpdate(cj);
            foreach (var pj in pjs)
            {
                cj.ProcessJobNameList.Add(pj.Name);
                _lstProcessJobs.Add(pj);
            }

            _lstControlJobs.Add(cj);
            //AssociatedPMWithLP(cj);

            _faCallback.JobCreated(cj, GetFirstProcessJob(cj));
        }

        public bool CheckAllJobDone()
        {
            foreach (var cj in _lstControlJobs)
            {
                if (cj.State == EnumControlJobState.Executing || cj.State == EnumControlJobState.Paused)
                    return false;
            }

            return true;
        }

        public bool CheckJobJustDone(out string sJobName)
        {
            foreach (var cj in _lstControlJobs)
            {
                if (cj.State == EnumControlJobState.Completed && !cj.BeenPosted)
                {
                    //LP;WaferSize;Lot;Number;Start;End;
                    sJobName = $"{cj.Module};{cj.JobWaferSize};{cj.LotName};{cj.LotWafers.Count};{cj.StartTime:T};{cj.EndTime:T}";
                    cj.BeenPosted = true;
                    return true;
                }
            }

            sJobName = "NULL";
            return false;
        }

        public ProcessJobInfo GetFirstProcessJob(ControlJobInfo cj)
        {
            foreach (var pj in _lstProcessJobs)
            {
                if (pj.ControlJobName == cj.Name)
                    return pj;
            }

            return null;
        }
        public void AbortJob(string jobName)
        {
            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
            if (cj == null)
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"abort job rejected, not found job with id {jobName}");
                return;
            }

            List<ProcessJobInfo> pjAbortList = new List<ProcessJobInfo>();
            foreach (var pj in _lstProcessJobs)
            {
                if (pj.ControlJobName == cj.Name)
                {
                    pj.SetState(EnumProcessJobState.Aborting);
                    pjAbortList.Add(pj);

                    int unprocessed = 0;
                    int aborted = 0;
                    WaferInfo[] wafers = WaferManager.Instance.GetWaferByProcessJob(pj.Name);
                    foreach (var waferInfo in wafers)
                    {
                        waferInfo.ProcessJob = null;
                        waferInfo.NextSequenceStep = 0;
                        if (waferInfo.ProcessState != EnumWaferProcessStatus.Completed)
                            unprocessed++;
                    }
                    JobDataRecorder.EndPJ(pj.InnerId.ToString(), aborted, unprocessed);
                }
            }

            _faCallback.JobAborted(cj, GetFirstProcessJob(cj));
            _dbCallback.LotFinished(cj);
            foreach (var pj in pjAbortList)
            {
                _lstProcessJobs.Remove(pj);
            }

            _lstControlJobs.Remove(cj);
        }

        public void StopJob(string jobName)
        {
            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
            if (cj == null)
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"stop job rejected, not found job with id {jobName}");
                return;
            }

            foreach (var pj in _lstProcessJobs)
            {
                if (pj.ControlJobName == cj.Name)
                {
                    pj.SetState(EnumProcessJobState.Stopping);
                }
            }

            _faCallback.JobStopped(cj, GetFirstProcessJob(cj));
            _dbCallback.LotFinished(cj);
        }

        public void ResumeJob(string jobName)
        {
            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
            if (cj == null)
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"resume job rejected, not found job with id {jobName}");
                return;
            }

            if (cj.State == EnumControlJobState.Paused)
            {
                cj.SetState(EnumControlJobState.Executing);
            }

            _faCallback.JobResumed(cj, GetFirstProcessJob(cj));
        }

        public void PauseJob(string jobName)
        {
            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
            if (cj == null)
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"pause job rejected, not found job with id {jobName}");
                return;
            }

            if (cj.State == EnumControlJobState.Executing)
            {
                cj.SetState(EnumControlJobState.Paused);
            }
            _faCallback.JobPaused(cj, GetFirstProcessJob(cj));
        }


        public void StartJob(string jobName)
        {
            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == jobName);
            if (cj == null)
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"start job rejected, not found job with id {jobName}");
                return;
            }

            if (cj.State == EnumControlJobState.WaitingForStart)
            {
                cj.SetState(EnumControlJobState.Executing);
                //PreJobClean(cj);

                cj.JetState = EnumJetCtrlJobState.Quequed;

                (_atmSchedulers[ModuleHelper.Converter(cj.Module)] as SchedulerLoadPort).NoteJobStart();

                cj.StartTime = DateTime.Now;

                _dbCallback.LotCreated(cj);
                _faCallback.JobStarted(cj, GetFirstProcessJob(cj));

                //if (!_blockingWatcher.IsRunning)
                //    _blockingWatcher.Restart();

                //ResetTraceFlag();
            }

            if (!_cycleWatch.IsRunning)
            {
                _cycleWatch.Restart();
            }

            if (!_lpCycleWafer.Keys.Contains(ModuleHelper.Converter(cj.Module)))
            {
                _lpCycleCount.Add(ModuleHelper.Converter(cj.Module), 0);
                _lpCycleWafer.Add(ModuleHelper.Converter(cj.Module), 0);
            }

            _cycleState = RState.Running;
        }

        public void Clear()
        {
            _vacWaferTargets.Clear();
            _atmWaferTargets.Clear();

            _movingItems.Clear();
            _efemMovingItems.Clear();

            _vacMoveFinishTrig.RST = true;
            _atmMoveFinishTrig.RST = true;

            _efemRobot.ResetTask();
            _tmRobot.ResetTask();
            foreach (var module in _vacSchedulers)
            {
                module.Value.ResetTask();
                _vacModules[module.Key].MovingStatus = MovingStatus.Idle;
            }

            foreach (var module in _atmSchedulers)
            {
                module.Value.ResetTask();
                _atmModules[module.Key].MovingStatus = MovingStatus.Idle;
            }

            _lstControlJobs.Clear();
            _lstProcessJobs.Clear();
            _cycleState = RState.End;
        }

        public bool ManualReturnWafer(object[] objs)
        {
            ModuleName SourceModule = (ModuleName)objs[0];
            int SourceSlot = (int)objs[1];

            if (!_vacSchedulers.Keys.Contains(SourceModule) && !_atmSchedulers.Keys.Contains(SourceModule))
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"Invalid source module {SourceModule} for manual return wafer");
                return false;
            }

            if(WaferManager.Instance.CheckNoWafer(SourceModule, SourceSlot))
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"Can not return wafer as {SourceModule} {SourceSlot} has no wafer");
                return false;
            }

            if(!_vacSchedulers.Keys.Contains(SourceModule) || !_vacSchedulers[SourceModule].IsIdle)
            {
                LOG.Write(eEvent.WARN_ROUTER, ModuleName.System, $"The module: {SourceModule} is not ready for return wafer");
                return false;
            }

            var wafer = WaferManager.Instance.GetWafer(SourceModule, SourceSlot);
            LOG.Write(eEvent.EV_ROUTER, ModuleName.System, $"Manual return  wafer: {wafer.WaferOrigin} at {SourceModule} {SourceSlot} while system is auto running");
            
            _lstReturnWafers.Add(wafer.InnerId);
            return true;
        }


        #endregion

        private void InitModules()
        {
            foreach (var module in new ModuleName[]{ ModuleName.LP1, ModuleName.LP2, ModuleName.LP3,
                ModuleName.Aligner1, ModuleName.Aligner2, ModuleName.Cooling1, ModuleName.Cooling2})
            {
                if (ModuleHelper.IsInstalled(module))
                {
                    if (ModuleHelper.IsLoadPort(module))
                    {
                        _atmSchedulers[module] = Singleton<TransferModule>.Instance.GetScheduler(module);
                        _atmModules[module] = new ModuleFlag(ModulePriority.Middle);
                    }
                    else if (ModuleHelper.IsAligner(module) || ModuleHelper.IsCooling(module))
                    {
                        _atmSchedulers[module] = Singleton<TransferModule>.Instance.GetScheduler(module);
                        _atmModules[module] = new ModuleFlag(ModulePriority.Middle);
                    }
                }
            }

            foreach (var module in new ModuleName[] { ModuleName.PMA, ModuleName.PMB, ModuleName.PMC,
                ModuleName.PMD, ModuleName.PME, ModuleName.PMF})
            {
                if (ModuleHelper.IsInstalled(module))
                {
                    _vacSchedulers[module] = Singleton<TransferModule>.Instance.GetScheduler(module);
                    _vacModules[module] = new ModuleFlag(ModulePriority.Middle);
                }
            }

            foreach (var module in new ModuleName[] { ModuleName.LLA, ModuleName.LLB })
            {
                if (ModuleHelper.IsInstalled(module))
                {
                    var llScheduler = Singleton<TransferModule>.Instance.GetScheduler(module);
                    _vacSchedulers[module] = llScheduler;
                    _vacModules[module] = new ModuleFlag(ModulePriority.Middle);

                    _atmSchedulers[module] = llScheduler;
                    _atmModules[module] = new ModuleFlag(ModulePriority.Middle);
                }
            }
        }

        private void prelude()
        {
        }

        private void epilogue()
        {
            CheckWaferArrived();
            ProcessPMTask();
            ProcessAlignerTask();

            UpdateProcessJobStatus();
            UpdateControlJobStatus();
            //DispatchCtrlJobs();
            UpdateLLInOutPathProperty();
        }

        private void driveVacSystem()
        {
            if (_tmRobot.IsAvailable && _movingItems.Count == 0 && _tmRobot.RobotStatus != RState.Running)
            {
                RoutingInnerPart();
                RuningTMRobotTask();
            }
        }

        #region Vacuum System
        private void PumpingTMRobotTask()
        {
            if (!_tmRobot.IsAvailable || _tmRobot.RobotStatus == RState.Running)
                return;

            foreach (var mod in _vacSchedulers)
            {
                if (mod.Value.IsAvailable && _vacModules[mod.Key].MovingStatus == MovingStatus.Idle)
                {
                    if (ModuleHelper.IsLoadLock(mod.Key))
                    {
                        var inSlots = mod.Key == ModuleName.LLA ? _LLAInSlot : _LLBInSlot;
                        foreach (var slot in inSlots)
                        {
                            if (IsLoadLockReservedByEFEM(mod.Key))
                                continue;

                            if (WaferManager.Instance.CheckHasWafer(mod.Key, slot) && IsVacWaferReadyOut(mod.Key, slot))
                            {
                                _vacReadyOutSlots.Add(new SlotItem(mod.Key, slot), mod.Value.WaferArrivedTicks(slot));
                            }
                        }

                        var outSlots = mod.Key == ModuleName.LLA ? _LLAOutSlot : _LLBOutSlot;
                        foreach (var slot in outSlots)
                        {
                            if (IsLoadLockReservedByEFEM(mod.Key))
                                continue;

                            if (WaferManager.Instance.CheckNoWafer(mod.Key, slot))
                            {
                                _vacReadyInSlots.Add(new SlotItem(mod.Key, slot), mod.Value.WaferArrivedTicks(slot));
                            }
                        }
                    }
                    else
                    {
                        if (WaferManager.Instance.CheckHasWafer(mod.Key, 0) && IsVacWaferReadyOut(mod.Key, 0))  // processed?
                        {
                            _vacReadyOutSlots.Add(new SlotItem(mod.Key, 0), mod.Value.WaferArrivedTicks(0));
                        }
                        else if (WaferManager.Instance.CheckNoWafer(mod.Key, 0))
                        {
                            _vacReadyInSlots.Add(new SlotItem(mod.Key, 0), mod.Value.WaferArrivedTicks(0));
                        }
                    }
                }
            }

            _vacReadyOutSlots = _vacReadyOutSlots.OrderByDescending(item => item.Value).ToDictionary(k => k.Key, v => v.Value);
        }

        bool SearchWaferDestination(SlotItem outSlot, out SlotItem destSlot)
        {
            destSlot = new SlotItem(ModuleName.System, -1);
            var wafer = WaferManager.Instance.GetWafer(outSlot.Module, outSlot.Slot);
            if (wafer.IsEmpty || wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
                return false;

            foreach (var next_module in wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules)
            {
                var validSlots = GetModuleValidSlots(next_module);
                foreach (var nSlot in validSlots)
                {
                    if (_movingItems.Exists(mItem => mItem.DestinationModule == next_module && mItem.DestinationSlot == nSlot))
                        continue;

                    foreach (var inItem in _vacReadyInSlots)
                    {
                        if (inItem.Key.Module == next_module && inItem.Key.Slot == nSlot && _vacModules[next_module].MovingStatus == MovingStatus.Idle && _vacSchedulers[next_module].IsAvailable)
                        {
                            destSlot.Module = next_module;
                            destSlot.Slot = nSlot;
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        bool SearchCanReplaceSlot(SlotItem llSlot, out SlotItem replaceSlot, out SlotItem destSlot)
        {
            replaceSlot = new SlotItem(ModuleName.System, -1);
            destSlot = new SlotItem(ModuleName.System, -1);
            var wafer = WaferManager.Instance.GetWafer(llSlot.Module, llSlot.Slot);
            if (wafer.IsEmpty || wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
                return false;

            foreach (var next_module in wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules)
            {
                if (!ModuleHelper.IsPm(next_module) || WaferManager.Instance.CheckNoWafer(next_module, 0))
                    continue;

                if (SearchWaferDestination(new SlotItem(next_module, 0), out destSlot))
                {
                    replaceSlot.Module = next_module;
                    replaceSlot.Slot = 0;
                    return true;
                }
            }

            return false;
        }

        Queue<int> GetModuleValidSlots(ModuleName mod)
        {
            var validSlot = new Queue<int>();
            if (ModuleHelper.IsLoadLock(mod))
            {
                if (!IsLoadLockReservedByEFEM(mod))
                {
                    var inSlots = mod == ModuleName.LLA ? _LLAInSlot : _LLBInSlot;
                    foreach (var slot in inSlots)
                    {
                        if (WaferManager.Instance.CheckNoWafer(mod, slot) && !_movingItems.Exists(item => item.DestinationModule == mod && item.DestinationSlot == slot))
                            validSlot.Enqueue(slot);

                        if (WaferManager.Instance.CheckHasWafer(mod, slot) && _movingItems.Exists(item => item.SourceModule == mod && item.SourceSlot == slot))
                            validSlot.Enqueue(slot);
                    }
                }
            }
            else if (ModuleHelper.IsPm(mod))
            {
                if (WaferManager.Instance.CheckNoWafer(mod, 0) && !_movingItems.Exists(item => item.DestinationModule == mod))
                    validSlot.Enqueue(0);

                if (WaferManager.Instance.CheckHasWafer(mod, 0) && _movingItems.Exists(item => item.SourceModule == mod))
                    validSlot.Enqueue(0);
            }

            return validSlot;
        }

        private int GetModuleSlotCount(ModuleName mod)
        {
            int nSlotCount = 1;
            if (ModuleHelper.IsLoadLock(mod))
            {
                nSlotCount = mod == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber;
            }
            else if (ModuleHelper.IsLoadPort(mod))
            {
                nSlotCount = SC.GetValue<int>("EFEM.LoadPort.SlotNumber");
            }
            else if (ModuleHelper.IsTMRobot(mod) || ModuleHelper.IsEFEMRobot(mod))
            {
                nSlotCount = 2;
            }

            return nSlotCount;
        }

        List<MoveItem> SearchWaitInSlots(ModuleName inModule)
        {
            List<MoveItem> inSlots = new List<MoveItem>();
            var validSlots = GetModuleValidSlots(inModule);
            foreach (var slot in _vacReadyOutSlots)
            {
                if (slot.Key.Module == inModule || _vacModules[slot.Key.Module].MovingStatus != MovingStatus.Idle)
                    continue;

                if (IsLoadLockReservedByEFEM(slot.Key.Module))
                    continue;

                var wafer = WaferManager.Instance.GetWafer(slot.Key.Module, slot.Key.Slot);

                if (wafer.IsEmpty || wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
                    continue;

                foreach (var next_module in wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules)
                {
                    if (next_module == inModule && validSlots.Count > 0 && !_movingItems.Exists(item => item.SourceModule == slot.Key.Module && item.SourceSlot == slot.Key.Slot))
                    {
                        if (!inSlots.Exists(item => item.SourceModule == slot.Key.Module && item.SourceSlot == slot.Key.Slot))
                        {
                            inSlots.Add(new MoveItem(slot.Key.Module, slot.Key.Slot, inModule, validSlots.Dequeue(), Hand.None));
                        }
                    }
                }
            }

            return inSlots;
        }

        private void UpdateItemMovingStatus(MoveItem moveItem, MovingStatus status = MovingStatus.Moving)
        {
            if (!ModuleHelper.IsTMRobot(moveItem.DestinationModule))
            {
                _vacModules[moveItem.DestinationModule].MovingStatus = status;
            }

            if (!ModuleHelper.IsTMRobot(moveItem.SourceModule))
            {
                _vacModules[moveItem.SourceModule].MovingStatus = status;
            }
        }

        private void UpdateModuleMovingStatus(ModuleName module, MovingStatus status = MovingStatus.Moving)
        {
            if (!ModuleHelper.IsTMRobot(module))
            {
                _vacModules[module].MovingStatus = status;
            }
        }

        private void ProcessTMRobotTask()
        {
            if (_tmRobot.IsAvailable && _tmRobot.RobotStatus != RState.Running)
            {
                if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 0) || WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 1))
                {
                    for (int i = 0; i < 2; i++)
                    {
                        if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, i))
                        {
                            if (SearchWaferDestination(new SlotItem(ModuleName.TMRobot, i), out SlotItem destSlot))
                            {
                                _movingItems.Add(new MoveItem(ModuleName.TMRobot, i, destSlot.Module, destSlot.Slot, (Hand)i));
                                UpdateModuleMovingStatus(destSlot.Module);
                            }
                        }
                    }

                    return;
                }

                foreach (var slot in _vacReadyOutSlots)
                {
                    if (_vacModules[slot.Key.Module].MovingStatus != MovingStatus.Idle)
                        continue;

                    if (ModuleHelper.IsLoadLock(slot.Key.Module))
                    {
                        if (IsLoadLockReservedByEFEM(slot.Key.Module))
                            continue;

                        var llStatus = GetLLProcessStatusCount(slot.Key.Module);
                        if (SearchWaferDestination(slot.Key, out SlotItem destSlot))
                        {
                            _movingItems.Add(new MoveItem(slot.Key.Module, slot.Key.Slot, destSlot.Module, destSlot.Slot, Hand.None));
                            UpdateModuleMovingStatus(slot.Key.Module);
                            UpdateModuleMovingStatus(destSlot.Module);

                            if (llStatus.Item2 > 1)
                            {
                                // check whether match double pick pattern
                                foreach (var item in _vacReadyOutSlots)
                                {
                                    if (item.Key.Module == slot.Key.Module && item.Key.Slot != slot.Key.Slot)
                                    {
                                        if (SearchWaferDestination(item.Key, out SlotItem destSlot_2))
                                        {
                                            _movingItems.Add(new MoveItem(item.Key.Module, item.Key.Slot, destSlot_2.Module, destSlot_2.Slot, Hand.None));
                                            UpdateModuleMovingStatus(destSlot_2.Module);
                                            break;
                                        }
                                    }
                                }

                                if (_movingItems.Count == 1)
                                {
                                    foreach (var item in _vacReadyOutSlots)
                                    {
                                        if (item.Key.Module == slot.Key.Module && item.Key.Slot != slot.Key.Slot)
                                        {
                                            if (SearchCanReplaceSlot(item.Key, out SlotItem replaceSlot, out SlotItem destSlot_2))
                                            {
                                                _movingItems.Add(new MoveItem(item.Key.Module, item.Key.Slot, replaceSlot.Module, replaceSlot.Slot, Hand.None));
                                                _movingItems.Add(new MoveItem(replaceSlot.Module, replaceSlot.Slot, destSlot_2.Module, destSlot_2.Slot, Hand.None));
                                                UpdateModuleMovingStatus(replaceSlot.Module);
                                                UpdateModuleMovingStatus(destSlot.Module);
                                                return;
                                            }
                                        }
                                    }
                                }
                            }


                            // check whether match swap pattern
                            int inCount = 0;
                            var in_slots = SearchWaitInSlots(slot.Key.Module);
                            foreach (var in_slot in in_slots)
                            {
                                if (_vacModules[in_slot.DestinationModule].MovingStatus == MovingStatus.Idle && inCount < 2)
                                {
                                    _movingItems.Add(in_slot);
                                    UpdateItemMovingStatus(in_slot);
                                    inCount++;
                                }
                            }

                            return;
                        }
                    }
                    else // PM
                    {
                        if (SearchWaferDestination(slot.Key, out SlotItem destSlot))
                        {
                            _movingItems.Add(new MoveItem(slot.Key.Module, slot.Key.Slot, destSlot.Module, destSlot.Slot, Hand.None));
                            UpdateModuleMovingStatus(slot.Key.Module);
                            UpdateModuleMovingStatus(destSlot.Module);


                            // check whether match double move pattern
                            int inCount = 0;
                            var same_dest = SearchWaitInSlots(destSlot.Module);
                            foreach (var in_slot in same_dest)
                            {
                                if (_vacModules[in_slot.SourceModule].MovingStatus == MovingStatus.Idle && inCount < 1)
                                {
                                    _movingItems.Add(in_slot);
                                    UpdateItemMovingStatus(in_slot);
                                    inCount++;
                                }
                            }

                            // check whether match swap pattern
                            if (inCount == 0 && !IsPMNeedWTWClean(slot.Key.Module))
                            {
                                int swapCount = 0;
                                var in_slots = SearchWaitInSlots(slot.Key.Module);
                                foreach (var swap_slot in in_slots)
                                {
                                    if (_vacModules[swap_slot.SourceModule].MovingStatus == MovingStatus.Idle && swapCount < 1)
                                    {
                                        _movingItems.Add(swap_slot);
                                        UpdateItemMovingStatus(swap_slot);
                                        swapCount++;
                                    }
                                }
                            }

                            return;
                        }
                    }
                }

                _vacReadyOutSlots.Clear();
                _vacReadyInSlots.Clear();
            }
        }

        bool PushTMRobotWafers()
        {
            if (_tmRobot.IsAvailable && _tmRobot.RobotStatus != RState.Running)
            {
                for (int i = 0; i < 2; i++)
                {
                    if (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, i))
                    {
                        if (SearchWaferDestination(new SlotItem(ModuleName.TMRobot, i), out SlotItem destSlot))
                        {
                            _movingItems.Add(new MoveItem(ModuleName.TMRobot, i, destSlot.Module, destSlot.Slot, (Hand)i));
                            UpdateModuleMovingStatus(destSlot.Module);
                        }
                    }
                }
            }

            return _movingItems.Count > 0;
        }

        bool PushPmWafers()
        {
            if (_tmRobot.IsAvailable && _tmRobot.RobotStatus != RState.Running)
            {
                foreach (var slot in _vacReadyOutSlots)
                {
                    if (_vacModules[slot.Key.Module].MovingStatus != MovingStatus.Idle)
                        continue;

                    if (ModuleHelper.IsPm(slot.Key.Module))
                    {
                        if (SearchWaferDestination(slot.Key, out SlotItem destSlot))
                        {
                            _movingItems.Add(new MoveItem(slot.Key.Module, slot.Key.Slot, destSlot.Module, destSlot.Slot, Hand.None));
                            UpdateModuleMovingStatus(slot.Key.Module);
                            UpdateModuleMovingStatus(destSlot.Module);


                            // check whether match double move pattern
                            int inCount = 0;
                            var same_dest = SearchWaitInSlots(destSlot.Module);
                            foreach (var in_slot in same_dest)
                            {
                                if (_vacModules[in_slot.SourceModule].MovingStatus == MovingStatus.Idle && inCount < 1)
                                {
                                    _movingItems.Add(in_slot);
                                    UpdateItemMovingStatus(in_slot);
                                    inCount++;
                                }
                            }

                            // check whether match swap pattern
                            if (inCount == 0 && !IsPMNeedWTWClean(slot.Key.Module))
                            {
                                int swapCount = 0;
                                var in_slots = SearchWaitInSlots(slot.Key.Module);
                                foreach (var swap_slot in in_slots)
                                {
                                    if (_vacModules[swap_slot.SourceModule].MovingStatus == MovingStatus.Idle && swapCount < 1)
                                    {
                                        _movingItems.Add(swap_slot);
                                        UpdateItemMovingStatus(swap_slot);
                                        swapCount++;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return _movingItems.Count > 0;
        }

        private bool PushLoadlockWafers()
        {
            if (_tmRobot.IsAvailable && _tmRobot.RobotStatus != RState.Running)
            {
                foreach (var slot in _vacReadyOutSlots)
                {
                    if (_vacModules[slot.Key.Module].MovingStatus != MovingStatus.Idle)
                        continue;

                    if (ModuleHelper.IsLoadLock(slot.Key.Module))
                    {
                        if (IsLoadLockReservedByEFEM(slot.Key.Module))
                            continue;

                        var llStatus = GetLLProcessStatusCount(slot.Key.Module);
                        if (SearchWaferDestination(slot.Key, out SlotItem destSlot))
                        {
                            _movingItems.Add(new MoveItem(slot.Key.Module, slot.Key.Slot, destSlot.Module, destSlot.Slot, Hand.None));
                            UpdateModuleMovingStatus(slot.Key.Module);
                            UpdateModuleMovingStatus(destSlot.Module);

                            if (llStatus.Item2 > 1)
                            {
                                // check whether match double pick pattern
                                foreach (var item in _vacReadyOutSlots)
                                {
                                    if (item.Key.Module == slot.Key.Module && item.Key.Slot != slot.Key.Slot)
                                    {
                                        if (SearchWaferDestination(item.Key, out SlotItem destSlot_2))
                                        {
                                            _movingItems.Add(new MoveItem(item.Key.Module, item.Key.Slot, destSlot_2.Module, destSlot_2.Slot, Hand.None));
                                            UpdateModuleMovingStatus(destSlot_2.Module);
                                            break;
                                        }
                                    }
                                }

                                if (_movingItems.Count == 1)
                                {
                                    foreach (var item in _vacReadyOutSlots)
                                    {
                                        if (item.Key.Module == slot.Key.Module && item.Key.Slot != slot.Key.Slot)
                                        {
                                            if (SearchCanReplaceSlot(item.Key, out SlotItem replaceSlot, out SlotItem destSlot_2))
                                            {
                                                _movingItems.Add(new MoveItem(item.Key.Module, item.Key.Slot, replaceSlot.Module, replaceSlot.Slot, Hand.None));
                                                _movingItems.Add(new MoveItem(replaceSlot.Module, replaceSlot.Slot, destSlot_2.Module, destSlot_2.Slot, Hand.None));
                                                UpdateModuleMovingStatus(replaceSlot.Module);
                                                UpdateModuleMovingStatus(destSlot.Module);
                                                return true;
                                            }
                                        }
                                    }
                                }
                            }


                            // check whether match swap pattern
                            int inCount = 0;
                            var in_slots = SearchWaitInSlots(slot.Key.Module);
                            foreach (var in_slot in in_slots)
                            {
                                if (_vacModules[in_slot.DestinationModule].MovingStatus == MovingStatus.Idle && inCount < 2)
                                {
                                    _movingItems.Add(in_slot);
                                    UpdateItemMovingStatus(in_slot);
                                    inCount++;
                                }
                            }

                            return true;
                        }
                    }
                }
            }

            return _movingItems.Count > 0;
        }

        private bool SearchOutLLSlot(ModuleName pm, ModuleName ll, out SlotItem outSlot)
        {
            outSlot = new SlotItem(ModuleName.System, -1);
            var wafer = WaferManager.Instance.GetWafer(pm, 0);
            if (wafer.IsEmpty || wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
                return false;

            if (wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(ll))
            {
                var slotNumber = ll == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber;
                for (int slot = 0; slot < slotNumber; slot++)
                {
                    if ((WaferManager.Instance.CheckNoWafer(ll, slot) && !_movingItems.Exists(item => item.DestinationModule == ll && item.DestinationSlot == slot)) ||
                        (WaferManager.Instance.CheckHasWafer(ll, slot) && _movingItems.Exists(item => item.SourceModule == ll && item.SourceSlot == slot)))
                    {
                        outSlot.Module = ll;
                        outSlot.Slot = slot;
                        return true;
                    }
                }
            }

            return false;
        }

        private bool SearchValidInPM(ModuleName ll, int slot, out SlotItem inSlot)
        {
            inSlot = new SlotItem(ModuleName.System, -1);
            var wafer = WaferManager.Instance.GetWafer(ll, slot);
            if (wafer.IsEmpty || wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
                return false;

            foreach (var mod in _vacModules)
            {
                if (ModuleHelper.IsPm(mod.Key) && _vacSchedulers[mod.Key].IsAvailable && _vacModules[mod.Key].MovingStatus == MovingStatus.Idle)
                {
                    if (wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(mod.Key))
                    {
                        if ((WaferManager.Instance.CheckNoWafer(mod.Key, 0) && !_movingItems.Exists(item => item.DestinationModule == mod.Key)) ||
                            (WaferManager.Instance.CheckHasWafer(mod.Key, 0) && _movingItems.Exists(item => item.SourceModule == mod.Key) && !_movingItems.Exists(item => item.DestinationModule == mod.Key)))
                        {
                            inSlot.Module = mod.Key;
                            inSlot.Slot = 0;
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        private bool ForwardPMsWaferToLL(ModuleName ll)
        {
            int outWaferCount = 0;
            foreach (var module in _vacModules)
            {
                if (outWaferCount >= 2)
                    break;

                if (ModuleHelper.IsPm(module.Key) && _vacSchedulers[module.Key].IsAvailable && _vacModules[module.Key].MovingStatus == MovingStatus.Idle)
                {
                    if (WaferManager.Instance.CheckHasWafer(module.Key, 0))
                    {
                        if (SearchOutLLSlot(module.Key, ll, out SlotItem outSlot))
                        {
                            outWaferCount++;
                            _movingItems.Add(new MoveItem(module.Key, 0, outSlot.Module, outSlot.Slot, Hand.None));
                        }
                    }
                }
            }

            return _movingItems.Count > 0;
        }

        private bool ForwardLLWaferToPMs(ModuleName ll)
        {
            int inWaferCount = 0;
            var slots = (_vacSchedulers[ll] as SchedulerLoadLock).GetOrderedOutSlot();
            foreach (var slot in slots)
            {
                if (slot >= (ll == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber))
                    continue;

                if (inWaferCount >= 2)
                    break;

                if (SearchValidInPM(ll, slot, out SlotItem inSlot))
                {
                    inWaferCount++;
                    _movingItems.Add(new MoveItem(ll, slot, inSlot.Module, inSlot.Slot, Hand.None));
                }
            }

            return _movingItems.Count > 0;
        }

        private bool ExchangeLLWafersWithPMs(ModuleName ll)
        {
            ForwardPMsWaferToLL(ll);
            ForwardLLWaferToPMs(ll);
            return _movingItems.Count > 0;
        }

        private bool ProcessVacManaulReturnWafers()
        {
            if (_lstReturnWafers.Count == 0)
                return false;

            Queue<SlotItem> returnSlots = new Queue<SlotItem>();
            for(int i = 0;  i< 2; i++)
            {
                if(WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, i))
                {
                    var wafer_id = WaferManager.Instance.GetWafer(ModuleName.TMRobot, 0).InnerId;
                    if(_lstReturnWafers.FindIndex(item => item == wafer_id) != -1)
                    {
                        returnSlots.Enqueue(new SlotItem(ModuleName.TMRobot, i));
                    }
                }
            }

            foreach(var mod in _vacModules)
            {
                if(ModuleHelper.IsPm(mod.Key) && WaferManager.Instance.CheckHasWafer(mod.Key, 0) && _vacSchedulers[mod.Key].IsIdle)
                {
                    var wafer_id = WaferManager.Instance.GetWafer(mod.Key, 0).InnerId;
                    if(_lstReturnWafers.FindIndex(item => item == wafer_id) != -1)
                    {
                        returnSlots.Enqueue(new SlotItem(mod.Key, 0));
                    }
                }
            }

            if (returnSlots.Count == 0)
                return false;

            var llaSlotStatus = GetLLProcessStatusCount(ModuleName.LLA);
            var llbSlotStatus = GetLLProcessStatusCount(ModuleName.LLB);

            ModuleName destLL = ModuleName.System;
            if (IsLoadReadyForTM(ModuleName.LLA) && llaSlotStatus.empty > 0 && (!IsLoadReadyForTM(ModuleName.LLB) || (!_vacSchedulers[ModuleName.LLB].IsVac && llaSlotStatus.empty == llbSlotStatus.empty) || llaSlotStatus.empty > llbSlotStatus.empty))
            {
                destLL = ModuleName.LLA;
            }
            else if(IsLoadReadyForTM(ModuleName.LLB) && llbSlotStatus.empty > 0)
            {
                destLL = ModuleName.LLB;
            }

            if(destLL != ModuleName.System)
            {
                int slotNumber = destLL == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber;
                for(int i = 0; i < slotNumber; i++)
                {
                    if(WaferManager.Instance.CheckNoWafer(destLL, i))
                    {
                        if (_movingItems.Count >= 2 || returnSlots.Count == 0)
                            break;

                        var source = returnSlots.Dequeue();
                        _movingItems.Add(new MoveItem(source.Module, source.Slot, destLL, i, Hand.None));
                    }
                }
            }

            return _movingItems.Count > 0;
        }

        private void RoutingInnerPart()
        {
            if (ProcessVacManaulReturnWafers())
                return;

            if (PushTMRobotWafers())
                return;

            var pmSlotStaus = GetPMValidSlotsStatus();
            if (pmSlotStaus.Processed + pmSlotStaus.Empty == 0)
                return;

            var llaSlotStatus = GetLLProcessStatusCount(ModuleName.LLA);
            var llbSlotStatus = GetLLProcessStatusCount(ModuleName.LLB);
            if(LLInOutPath == SequenceLLInOutPath.AInBOut || LLInOutPath == SequenceLLInOutPath.BInAOut)
            {
                ModuleName InModule = LLInOutPath == SequenceLLInOutPath.AInBOut ? ModuleName.LLA : ModuleName.LLB;
                ModuleName OutModule = LLInOutPath == SequenceLLInOutPath.AInBOut ? ModuleName.LLB : ModuleName.LLA;
                int InNumber = Math.Min(InModule == ModuleName.LLA ? llaSlotStatus.unprocessed : llbSlotStatus.unprocessed, pmSlotStaus.Empty);
                int OutNumber = Math.Min(OutModule == ModuleName.LLA ? llaSlotStatus.empty : llbSlotStatus.empty,  pmSlotStaus.Processed);

                if (InNumber + OutNumber == 0)
                    return;

                if(IsLoadReadyForTM(InModule) && (!IsLoadReadyForTM(OutModule) || (!_vacSchedulers[OutModule].IsVac && InNumber == OutNumber) || InNumber > OutNumber))
                {
                    // move in wafer
                    ForwardLLWaferToPMs(InModule);
                }
                else if(IsLoadReadyForTM(OutModule) && OutNumber > 0)
                {
                    // move out wafer
                    ForwardPMsWaferToLL(OutModule);
                }
            }
            else if(LLInOutPath == SequenceLLInOutPath.AInAOut || LLInOutPath == SequenceLLInOutPath.BInBOut)
            {
                ModuleName loadlock = LLInOutPath == SequenceLLInOutPath.AInAOut ? ModuleName.LLA : ModuleName.LLB;
                if(IsLoadReadyForTM(loadlock))
                {
                    // switch wafer
                    ExchangeLLWafersWithPMs(loadlock);
                }
            }
            else // DInDOut or mix pattern
            {
                int llaOutNumber = Math.Min(Math.Min(pmSlotStaus.Processed, 2), llaSlotStatus.empty);
                int llaInNumber = Math.Min(Math.Min(llaSlotStatus.unprocessed, 2), pmSlotStaus.Empty + llaOutNumber);
                int llaExchangeNumber = llaOutNumber + llaInNumber;

                int llbOutNumber = Math.Min(Math.Min(pmSlotStaus.Processed, 2), llbSlotStatus.empty);
                int llbInNumber = Math.Min(Math.Min(llbSlotStatus.unprocessed, 2), pmSlotStaus.Empty + llbOutNumber);
                int llbExchangeNumber = llbOutNumber + llbInNumber;

                if (IsLoadReadyForTM(ModuleName.LLA) && ((!_vacSchedulers[ModuleName.LLB].IsVac && llaExchangeNumber == llbExchangeNumber) || 
                                                                        (llaExchangeNumber > llbExchangeNumber) || 
                                                                        (!IsLoadReadyForTM(ModuleName.LLB) && llaExchangeNumber > 0)))
                {
                    ExchangeLLWafersWithPMs(ModuleName.LLA);
                }
                else if(IsLoadReadyForTM(ModuleName.LLB) && llbExchangeNumber > 0)
                {
                    ExchangeLLWafersWithPMs(ModuleName.LLB);
                }
            }
        }

        private void RuningTMRobotTask()
        {
            if (_movingItems.Count > 0)
            {
                if (_tmRobot.PostMoveItems(_movingItems.ToArray()))
                {

                    foreach (var item in _movingItems)
                    {
                        var wafer = WaferManager.Instance.GetWafer(item.SourceModule, item.SourceSlot);
                        if (wafer.IsEmpty)
                        {
                            // post alarm
                            _cycleState = RState.Failed;
                            LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Cannot run TM moving task as Get {item.SourceModule}{item.SourceSlot} Wafer Info failed");
                            return;
                        }

                        var slot = new SlotItem(item.DestinationModule, item.DestinationSlot);
                        _vacWaferTargets[slot] = wafer.InnerId;
                    }
                }
            }
        }

        private void ProcessPMTask()
        {
            foreach (var mod in _vacModules)
            {
                if (ModuleHelper.IsPm(mod.Key))
                {
                    if (_vacSchedulers[mod.Key].IsAvailable)
                    {
                        switch (mod.Value.MovingStatus)
                        {
                            case MovingStatus.WaitProcess:
                                {
                                    _vacSchedulers[mod.Key].EventWaferArrived?.Invoke(this, new WaferMoveArgs(ModuleName.TMRobot, 0, mod.Key, 0));
                                    mod.Value.MovingStatus = MovingStatus.StartProcess;
                                }
                                break;
                            case MovingStatus.Processing:
                                {
                                    var pmScheduler = _vacSchedulers[mod.Key] as SchedulerPM;
                                    string WTWCleanRecipe = string.Empty;
                                    var wtwJob = _lstControlJobs.Find(cj => cj.JetState == EnumJetCtrlJobState.Processing && IsCtrlJobNeedWTWClean(cj, mod.Key, out WTWCleanRecipe) && !IsAllJobWaferProcessedOrProcessing(cj));
                                    if(wtwJob != null && WTWCleanRecipe.Length > 0) 
                                    {
                                        _waitWTWCleanPMs.Add(mod.Key);
                                    }

                                    mod.Value.MovingStatus = MovingStatus.Idle;
                                }
                                break;
                            case MovingStatus.IdleClean:
                            case MovingStatus.PreJobClean:
                            case MovingStatus.PostJobClean:
                            case MovingStatus.WTWClean:
                                mod.Value.MovingStatus = MovingStatus.Idle;
                                break;
                            case MovingStatus.Idle:
                                {
                                    var pmScheduler = _vacSchedulers[mod.Key] as SchedulerPM;
                                    if (IsPMKeepEmpty(mod.Key))
                                    {
                                        if(_waitPreCleanPMs.Contains(mod.Key))
                                        {
                                            mod.Value.MovingStatus = MovingStatus.WaitPreJobClean;
                                        }
                                        else if(_lstControlJobs.FindIndex(cj => cj.JetState == EnumJetCtrlJobState.PostJobClean) != -1 &&  _waitPostCleanPMs.Contains(mod.Key))
                                        {
                                            mod.Value.MovingStatus = MovingStatus.WaitPostJobClean;
                                        }
                                        else if(pmScheduler.RunIdleCleanTask())  // Check Idle Clean
                                        {
                                            mod.Value.MovingStatus = MovingStatus.StartIdleClean;
                                        }
                                    }
                                }
                                break;
                            case MovingStatus.WaitPreJobClean:
                                {
                                    mod.Value.MovingStatus = MovingStatus.Idle;
                                    var pmScheduler = _vacSchedulers[mod.Key] as SchedulerPM;
                                    if (IsPMKeepEmpty(mod.Key))
                                    {
                                        if (IsPreJobCleanPending(mod.Key, out string preCleanRecipe))
                                        {
                                            if (pmScheduler.RunJobCleanTask(preCleanRecipe))
                                            {
                                                mod.Value.MovingStatus = MovingStatus.StartPreJobClean;
                                            }

                                            _waitPreCleanPMs.Remove(mod.Key);
                                        }
                                    }
                                }
                                break;
                            case MovingStatus.WaitPostJobClean:
                                {
                                    mod.Value.MovingStatus = MovingStatus.Idle;
                                    var pmScheduler = _vacSchedulers[mod.Key] as SchedulerPM;
                                    if (IsPMKeepEmpty(mod.Key))
                                    {
                                        if (IsPostJobCleanPending(mod.Key, out string postCleanRecipe))
                                        {
                                            if (pmScheduler.RunJobCleanTask(postCleanRecipe))
                                            {
                                                mod.Value.MovingStatus = MovingStatus.StartPostJobClean;
                                            }

                                            _waitPostCleanPMs.Remove(mod.Key);
                                        }
                                    }
                                }
                                break;
                            case MovingStatus.WaitWTWClean:
                                {
                                    mod.Value.MovingStatus = MovingStatus.Idle;
                                    var pmScheduler = _vacSchedulers[mod.Key] as SchedulerPM;
                                    if (IsPMKeepEmpty(mod.Key) && _waitWTWCleanPMs.Contains(mod.Key))
                                    {
                                        string WTWCleanRecipe = string.Empty;
                                        var wtwJob = _lstControlJobs.Find(cj => cj.JetState == EnumJetCtrlJobState.Processing && IsCtrlJobNeedWTWClean(cj, mod.Key, out WTWCleanRecipe) && !IsAllJobWaferProcessedOrProcessing(cj));
                                        if (wtwJob != null && WTWCleanRecipe.Length > 0) 
                                        {
                                            if (pmScheduler.RunJobCleanTask(WTWCleanRecipe))
                                            {
                                                mod.Value.MovingStatus = MovingStatus.StartWTWClean;
                                            }

                                            _waitWTWCleanPMs.Remove(mod.Key);
                                        }
                                    }
                                }
                                break;
                        }
                    }
                    else
                    {
                        switch (mod.Value.MovingStatus)
                        {
                            case MovingStatus.StartProcess:
                                mod.Value.MovingStatus = MovingStatus.Processing;
                                break;
                            case MovingStatus.StartIdleClean:
                                mod.Value.MovingStatus = MovingStatus.IdleClean;
                                break;
                            case MovingStatus.StartPreJobClean:
                                mod.Value.MovingStatus = MovingStatus.PreJobClean;
                                break;
                            case MovingStatus.StartPostJobClean:
                                mod.Value.MovingStatus = MovingStatus.PostJobClean;
                                break;
                            case MovingStatus.StartWTWClean:
                                mod.Value.MovingStatus = MovingStatus.WTWClean;
                                break;
                        }
                    }

                }
            }
        }

        private Hand GetTMRobotFreeHand()
        {
            var blade1HasWafer = WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 0);
            var blade2HasWafer = WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 1);
            if (blade1HasWafer && blade2HasWafer)
                return Hand.None;
            else if (!blade1HasWafer && !blade2HasWafer)
                return Hand.Both;
            else if (blade1HasWafer)
                return Hand.Blade2;
            else
                return Hand.Blade1;
        }

        private bool IsVacWaferReadyOut(ModuleName mod, int slot)
        {
            var wafer = WaferManager.Instance.GetWafer(mod, slot);

            if (wafer.IsEmpty)
                return false;

            if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
                return false;

            if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
            {
                // should not go here
                //LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Wafer:{wafer.WaferOrigin} NextSequenceStep {wafer.NextSequenceStep} exceeds {wafer.ProcessJob.Sequence.Name} max steps number");
                return false;
            }

            if (wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(mod))
                return false;

            foreach (var module in wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules)
            {
                if (ModuleHelper.IsLoadLock(module) || ModuleHelper.IsPm(module))
                    return true;
            }

            return false;
        }

        #endregion Vacuum System

        #region Atm System
        private void driveAtmSystem()
        {
            if(_efemMovingItems.Count == 0 && _efemRobot.IsAvailable && _efemRobot.RobotStatus != RState.Running)
            {
                PumpingEFEMRobotTask();
                RuningEFEMRobotTask();
            }
        }

        private void PumpingEFEMRobotTask()
        {
            if (ProcessAtmManaulReturnWafers())
                return;

            var aligners = _atmSchedulers.Where(x => ModuleHelper.IsAligner(x.Key) && x.Value.IsAvailable).ToDictionary(k => k.Key, v => v.Value);
            foreach (var aligner in aligners)
            {
                if (_atmModules[aligner.Key].MovingStatus != MovingStatus.Idle)
                {
                    ProcessAlignerTask();
                    if (_atmModules[aligner.Key].MovingStatus != MovingStatus.Idle)
                        return;
                }

                if (ProcessAlignerEFEMRobotTask(aligner.Key))
                    return;
            }

            var lls = _atmSchedulers.Where(x => ModuleHelper.IsLoadLock(x.Key) && x.Value.IsAvailable && !IsLoadLockReservedByTM(x.Key)).ToDictionary(k => k.Key, v => v.Value);
            foreach(var ll in lls)
            {
                if (ProcessLLEFEMRobotTask(ll.Key))
                    return;
            }

            var lps = _atmSchedulers.Where(x => ModuleHelper.IsLoadPort(x.Key) && x.Value.IsAvailable).ToDictionary(k => k.Key, v => v.Value);
            foreach(var lp in lps)
            {
                if (ProcessLPEFEMRobotTask(lp.Key))
                    return;
            }
        }

        List<int> GetEfemRobotWaferReadyInHands(ModuleName mod)
        {
            var slots = new List<int>();
            for (int i = 0; i < 2; i++)
            {
                if (_efemRobotSingleArmOption != 0 && _efemRobotSingleArmOption != i + 1)
                    continue;

                var wafer = WaferManager.Instance.GetWafer(ModuleName.EfemRobot, i);
                if (wafer.IsEmpty)
                    continue;

                if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.NextSequenceStep == wafer.ProcessJob.Sequence.Steps.Count)
                    continue;

                if (wafer.NextSequenceStep > wafer.ProcessJob.Sequence.Steps.Count)
                {
                    // should not go here
                    LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Wafer:{wafer.WaferOrigin} NextSequenceStep {wafer.NextSequenceStep} exceeds {wafer.ProcessJob.Sequence.Name} max steps number");
                    continue;
                }

                if (wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(mod))
                {
                    slots.Add(i);
                }
            }

            return slots;
        }

        bool IsAtmWaferReadyOut(ModuleName mod, int slot)
        {
            var wafer = WaferManager.Instance.GetWafer(mod, slot);

            if (wafer.IsEmpty)
                return false;

            if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
                return false;

            if (_lstReturnWafers.Contains(wafer.InnerId))
                return false;

            if (wafer.NextSequenceStep == wafer.ProcessJob.Sequence.Steps.Count)
            {
                // need return
                return true;
            }
            else if (wafer.NextSequenceStep > wafer.ProcessJob.Sequence.Steps.Count)
            {
                // should not go here
                LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Wafer:{wafer.WaferOrigin} NextSequenceStep {wafer.NextSequenceStep} exceeds {wafer.ProcessJob.Sequence.Name} max steps number in IsAtmWaferReadyOut()");
                return false;
            }

            if (wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(mod))
                return false;

            if (ModuleHelper.IsLoadLock(mod))
            {
                foreach (var module in wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules)
                {
                    if (ModuleHelper.IsPm(module))
                        return false;
                }
            }

            return true;
        }
        private bool ProcessLLEFEMRobotTask(ModuleName ll)
        {
            if (_vacModules[ll].MovingStatus != MovingStatus.Idle)
                return false;

            var robotSlots = GetEfemRobotWaferReadyInHands(ll);
            var inSlots = ll == ModuleName.LLA ? _LLAInSlot : _LLBInSlot;
            foreach (var slot in inSlots)
            {
                if (robotSlots.Count >= 1)
                {
                    if (WaferManager.Instance.CheckNoWafer(ll, slot))
                    {
                        _efemMovingItems.Add(new MoveItem(ModuleName.EfemRobot, robotSlots.First(), ll, slot, (Hand)robotSlots.First()));
                        robotSlots.RemoveAt(0);
                    }
                }
            }

            var slotNumber = ll == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber;
            Dictionary<int, long> slot_delays = new Dictionary<int, long>();
            for (int i = 0; i < slotNumber; i++)
            {
                if (IsAtmWaferReadyOut(ll, i))
                {
                    slot_delays[i] = _atmSchedulers[ll].WaferArrivedTicks(i);
                }
            }

            slot_delays = slot_delays.OrderByDescending(item => item.Value).ToDictionary(k => k.Key, v => v.Value);
            if (slot_delays.Count > 0)
            {
                var robotHand = GetEFEMRobotFreeHand();
                if (robotHand != Hand.None)
                {
                    _efemMovingItems.Add(new MoveItem(ll, slot_delays.ElementAt(0).Key, ModuleName.EfemRobot, (int)robotHand, robotHand));
                }

                if (slot_delays.Count > 1)
                {
                    var secondHand = GetEFEMRobotFreeHand();
                    if (secondHand != Hand.None)
                    {
                        _efemMovingItems.Add(new MoveItem(ll, slot_delays.ElementAt(1).Key, ModuleName.EfemRobot, (int)secondHand, secondHand));
                    }
                }
            }

            return _efemMovingItems.Count > 0;
        }

        private (int exist, int empty) GetPMWaferExistence()
        {
            int exist = 0;
            int empty = 0;
            List<ModuleName> usedPMs = GetPmUsedInRunningPj();
            foreach (var module in _vacModules)
            {
                if (ModuleHelper.IsPm(module.Key) && ((_vacSchedulers[module.Key].IsOnline && usedPMs.Contains(module.Key)) || _cycleState == RState.End ))
                {
                    if (WaferManager.Instance.CheckHasWafer(module.Key, 0))
                        exist++;
                    else
                        empty++;
                }
            }

            return (exist, empty);
        }

        private (int Processed, int Empty) GetPMValidSlotsStatus()
        {
            int Processed = 0;
            int Empty = 0;
            List<ModuleName> usedPMs = GetPmUsedInRunningPj();
            foreach (var module in _vacModules)
            {
                if (ModuleHelper.IsPm(module.Key) && usedPMs.Contains(module.Key) && _vacSchedulers[module.Key].IsAvailable && _vacModules[module.Key].MovingStatus == MovingStatus.Idle)
                {
                    var wafer = WaferManager.Instance.GetWafer(module.Key, 0);
                    if(wafer.IsEmpty)
                    {
                        Empty++;
                    }
                    else if(wafer.ProcessState == EnumWaferProcessStatus.Completed)
                    {
                        Processed++;
                    }
                }
            }

            return (Processed, Empty);
        }

        private int GetTMRobotWaferCount()
        {
            return (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 0) ? 1 : 0) + (WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, 1) ? 1 : 0);
        }

        private int GetEfemRoborWaferCount()
        {
            return (WaferManager.Instance.CheckHasWafer(ModuleName.EfemRobot, 0) ? 1 : 0) + (WaferManager.Instance.CheckHasWafer(ModuleName.EfemRobot, 1) ? 1 : 0);
        }

        private bool IsLoadLockReservedByEFEM(ModuleName ll)
        {
            List<ModuleName> unfinished = new List<ModuleName>();
            foreach (var item in _atmWaferTargets)
            {
                var moving = _efemMovingItems.Find(m_item => m_item.DestinationModule == item.Key.Module && m_item.DestinationSlot == item.Key.Slot);
                if (!unfinished.Contains(moving.SourceModule))
                    unfinished.Add(moving.SourceModule);

                if (!unfinished.Contains(moving.DestinationModule))
                    unfinished.Add(moving.DestinationModule);
            }

            return unfinished.Contains(ll);
        }

        private bool IsLoadLockReservedByTM(ModuleName ll)
        {
            List<ModuleName> unfinished = new List<ModuleName>();
            foreach (var item in _vacWaferTargets)
            {
                var moving = _movingItems.Find(m_item => m_item.DestinationModule == item.Key.Module && m_item.DestinationSlot == item.Key.Slot);
                if (!unfinished.Contains(moving.SourceModule))
                    unfinished.Add(moving.SourceModule);

                if (!unfinished.Contains(moving.DestinationModule))
                    unfinished.Add(moving.DestinationModule);
            }

            return unfinished.Contains(ll);
        }

        private bool IsLoadReadyForTM(ModuleName ll)
        {
            return _vacSchedulers[ll].IsAvailable && _vacModules[ll].MovingStatus == MovingStatus.Idle && !IsLoadLockReservedByEFEM(ll);
        }

        private bool IsLoadLockReadyForEFEMManualMove(ModuleName ll)
        {
            return ((_vacSchedulers[ll].IsIdle && !_vacSchedulers[ll].IsOnline) || _vacSchedulers[ll].IsAvailable) && _vacModules[ll].MovingStatus == MovingStatus.Idle && !IsLoadLockReservedByTM(ll);
        }

        private bool IsPMKeepEmpty(ModuleName pm)
        {
            return WaferManager.Instance.CheckNoWafer(pm, 0) && _movingItems.FindIndex(item => item.DestinationModule == pm) == -1;
        }

        private bool IsPreJobCleanPending(ModuleName pm, out string preCleanRecipe)
        {
            preCleanRecipe = string.Empty;
            foreach (var cj in _lstControlJobs)
            {
                if (cj.JetState == EnumJetCtrlJobState.PreJobClean && _waitPreCleanPMs.Contains(pm))
                {
                    preCleanRecipe = GetFirstProcessJob(cj).Sequence.PreCleanRecipe;
                    return true;
                }
            }

            return false;
        }

        private bool IsPostJobCleanPending(ModuleName pm, out string postCleanRecipe)
        {
            postCleanRecipe = string.Empty;
            foreach (var cj in _lstControlJobs)
            {
                if (cj.JetState == EnumJetCtrlJobState.PostJobClean && _waitPostCleanPMs.Contains(pm))
                {
                    postCleanRecipe = GetFirstProcessJob(cj).Sequence.PostCleanRecipe;
                    return true;
                }
            }

            return false;
        }

        private bool IsWTWJobCleanPending(ModuleName pm, out string wtwCleanRecipe)
        {
            wtwCleanRecipe = string.Empty;
            foreach (var cj in _lstControlJobs)
            {
                if (cj.JetState == EnumJetCtrlJobState.Processing && _waitWTWCleanPMs.Contains(pm))
                {
                    wtwCleanRecipe = GetFirstProcessJob(cj).Sequence.WTWCleanRecipe;
                    return true;
                }
            }

            return false;
        }

        private (int processed, int unprocessed, int empty) GetLLProcessStatusCount(ModuleName ll)
        {
            int processedCount = 0;
            int unprocessCount = 0;
            int slotCount = ll == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber;
            if (ModuleHelper.IsLoadLock(ll))
            {
                for (int i = 0; i < slotCount; i++)
                {
                    if (WaferManager.Instance.CheckHasWafer(ll, i))
                    {
                        if (CheckWaferNeedProcessAndPMAvailable(ll, i))
                            unprocessCount++;
                        else
                            processedCount++;
                    }
                }
            }

            return (processedCount, unprocessCount, slotCount - processedCount - unprocessCount);
        }


        private int GetAtmInerWaferCount()
        {
            int nCount = 0;
            foreach (var atmModule in _atmModules)
            {
                if (!ModuleHelper.IsEFEMRobot(atmModule.Key) && !ModuleHelper.IsLoadPort(atmModule.Key) && WaferManager.Instance.CheckHasWafer(atmModule.Key, 0))
                    nCount++;
            }

            return nCount;
        }

        private int CanPushInWaferNumber()
        {
            if (_tmRobot.RobotStatus == RState.Running)
                return 0;

            var llaWaferStatus = GetLLProcessStatusCount(ModuleName.LLA);
            var llbWaferStatus = GetLLProcessStatusCount(ModuleName.LLB);
            var pmWaferStatus = GetPMWaferExistence();

            if (_LLInOutPath == SequenceLLInOutPath.AInBOut)
            {
                if (_atmSchedulers[ModuleName.LLB].IsAtm && llbWaferStatus.processed >= 2)
                    return 0;

                int canPushIn = llbWaferStatus.processed == _LLBSlotNumber ? 0 :
                    pmWaferStatus.empty + llaWaferStatus.empty
                    - GetTMRobotWaferCount() - GetAtmInerWaferCount() - GetEfemRoborWaferCount();

                // 修正 单手臂占用影响Throughput的问题
                if (canPushIn == 1/* && llbWaferStatus.processed >= 2*/)
                    return 0;

                return canPushIn;
            }
            else if (_LLInOutPath == SequenceLLInOutPath.BInAOut)
            {
                if (_atmSchedulers[ModuleName.LLA].IsAtm && llaWaferStatus.processed >= 2)
                    return 0;

                int canPushIn = llaWaferStatus.processed == _LLASlotNumber ? 0 :
                    pmWaferStatus.empty + llbWaferStatus.empty
                    - GetTMRobotWaferCount() - GetAtmInerWaferCount() - GetEfemRoborWaferCount();

                if (canPushIn == 1 && llaWaferStatus.processed >= 2)
                    return 0;

                return canPushIn;
            }
            else if (_LLInOutPath == SequenceLLInOutPath.AInAOut || (_LLInOutPath == SequenceLLInOutPath.DInDOut && _vacSchedulers[ModuleName.LLA].IsOnline && !_vacSchedulers[ModuleName.LLB].IsOnline))
            {
                return llaWaferStatus.processed > 0 ? 0 :
                    _LLASlotNumber / 2 + pmWaferStatus.empty - llaWaferStatus.unprocessed
                    - GetTMRobotWaferCount() - GetEfemRoborWaferCount() - GetAtmInerWaferCount();

            }
            else if (_LLInOutPath == SequenceLLInOutPath.BInBOut || (_LLInOutPath == SequenceLLInOutPath.DInDOut && !_vacSchedulers[ModuleName.LLA].IsOnline && _vacSchedulers[ModuleName.LLB].IsOnline))
            {
                return llbWaferStatus.processed > 0 ? 0 :
                    _LLBSlotNumber / 2 + pmWaferStatus.empty - llbWaferStatus.unprocessed
                        - GetTMRobotWaferCount() - GetEfemRoborWaferCount() - GetAtmInerWaferCount();
            }
            else if (_LLInOutPath == SequenceLLInOutPath.DInDOut && _vacSchedulers[ModuleName.LLA].IsOnline && _vacSchedulers[ModuleName.LLB].IsOnline)
            {
                return llaWaferStatus.processed + llbWaferStatus.processed > 1 ? 0 :
                    (llaWaferStatus.empty + llbWaferStatus.empty) / 2 + pmWaferStatus.empty
                    - GetTMRobotWaferCount() - GetEfemRoborWaferCount() - GetAtmInerWaferCount();
            }
            else
            {
                _cycleState = RState.Failed;
                LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, "Both LLA/LLB offline, stop runing.");
                return 0;
            }
        }

        private bool IsForwardPathBlocking(ModuleName mod, int slot)
        {
            var wafer = WaferManager.Instance.GetWafer(mod, slot);
            if (wafer.IsEmpty || wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
                return false;

            foreach (var next_module in wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules)
            {
                var SlotNumber = GetModuleSlotCount(next_module);
                for (int i = 0; i < SlotNumber; i++)
                {
                    if (WaferManager.Instance.CheckNoWafer(next_module, i))
                        return false;
                }
            }

            return true;
        }

        private bool ProcessLPEFEMRobotTask(ModuleName lp)
        {
            // check return
            for (int i = 0; i < 2; i++)
            {
                if (_efemRobotSingleArmOption != 0 && _efemRobotSingleArmOption != i + 1)
                    continue;

                if (WaferManager.Instance.CheckHasWafer(ModuleName.EfemRobot, i))
                {
                    var returnWafer = WaferManager.Instance.GetWafer(ModuleName.EfemRobot, i);
                    if (returnWafer.IsEmpty)
                        continue;

                    if (returnWafer.ProcessJob == null || returnWafer.ProcessJob.Sequence == null)
                        continue;

                    if (returnWafer.NextSequenceStep >= returnWafer.ProcessJob.Sequence.Steps.Count && lp == (ModuleName)returnWafer.OriginStation)  // need return
                    {
                        _efemMovingItems.Add(new MoveItem(ModuleName.EfemRobot, i, lp, returnWafer.OriginSlot, (Hand)i));
                    }
                }
            }

            var canPushInWafers = CanPushInWaferNumber();
            if (_efemMovingItems.Count == 0 && canPushInWafers > 0)
            {
                var outSlots = GetNextWaferInJobQueue(lp);

                var hand = GetEFEMRobotFreeHand();
                if (hand != Hand.None && outSlots.Count > 0 && !IsForwardPathBlocking(lp, outSlots[0]))
                {
                    _efemMovingItems.Add(new MoveItem(lp, outSlots[0], ModuleName.EfemRobot, (int)hand, hand));
                }

                if (canPushInWafers > 1)
                {
                    hand = GetEFEMRobotFreeHand();
                    if (hand != Hand.None && outSlots.Count > 1 && !IsForwardPathBlocking(lp, outSlots[1]))
                    {
                        _efemMovingItems.Add(new MoveItem(lp, outSlots[1], ModuleName.EfemRobot, (int)hand, hand));
                    }
                }
            }

            return _efemMovingItems.Count > 0;
        }

        private List<int> GetNextWaferInJobQueue(ModuleName lp)
        {
            var inSlots = new List<int>();

            foreach (var cj in _lstControlJobs)
            {
                if (lp != ModuleName.System && (cj.Module != lp.ToString()))
                    continue;

                if (cj.State == EnumControlJobState.Executing)
                {
                    foreach (var pj in _lstProcessJobs)
                    {
                        if (pj.ControlJobName == cj.Name && pj.State == EnumProcessJobState.Processing)
                        {
                            foreach (var pjSlotWafer in pj.SlotWafers)
                            {
                                if (CheckWaferNeedProcessAndPMAvailable(pjSlotWafer.Item1, pjSlotWafer.Item2))
                                {
                                    var wafer = WaferManager.Instance.GetWafer(pjSlotWafer.Item1, pjSlotWafer.Item2);
                                    if (!wafer.IsEmpty && wafer.NextSequenceStep == 0)
                                    {
                                        inSlots.Add(pjSlotWafer.Item2);

                                        if (inSlots.Count >= 2)
                                            return inSlots;
                                    }
                                }

                            }
                        }
                    }
                }
            }

            return inSlots;
        }

        private bool ProcessAlignerEFEMRobotTask(ModuleName aligner)
        {
            if (WaferManager.Instance.CheckHasWafer(aligner, 0) && IsAtmWaferReadyOut(aligner, 0) && _atmModules[aligner].MovingStatus == MovingStatus.Idle)
            {
                var hand = GetEFEMRobotFreeHand();
                if (hand != Hand.None)
                {
                    _efemMovingItems.Add(new MoveItem(aligner, 0, ModuleName.EfemRobot, (int)hand, hand));
                }

                //// check with whether another robot arm wafer wait go to aligner
                //var robotSlots = GetEfemRobotWaferReadyInHands(aligner);
                //if (robotSlots.Count > 0)
                //{
                //    _efemMovingItems.Add(new MoveItem(ModuleName.EfemRobot, robotSlots.First(), aligner, 0, (Hand)robotSlots.First()));
                //}
            }
            else if (WaferManager.Instance.CheckNoWafer(aligner, 0))
            {
                var robotSlots = GetEfemRobotWaferReadyInHands(aligner);
                if (robotSlots.Count > 0)
                {
                    _efemMovingItems.Add(new MoveItem(ModuleName.EfemRobot, robotSlots.First(), aligner, 0, (Hand)robotSlots.First()));
                }
            }

            return _efemMovingItems.Count > 0;
        }

        private bool ProcessCoolingEFEMRobotTask(ModuleName cooling)
        {
            if (WaferManager.Instance.CheckHasWafer(cooling, 0) && IsAtmWaferReadyOut(cooling, 0))
            {
                var hand = GetEFEMRobotFreeHand();
                if (hand != Hand.None)
                {
                    _efemMovingItems.Add(new MoveItem(cooling, 0, ModuleName.EfemRobot, (int)hand, hand));
                }
            }
            else
            {
                var robotSlots = GetEfemRobotWaferReadyInHands(cooling);
                if (robotSlots.Count > 0)
                {
                    _efemMovingItems.Add(new MoveItem(cooling, 0, ModuleName.EfemRobot, robotSlots.First(), (Hand)robotSlots.First()));
                }
            }

            return _efemMovingItems.Count > 0;
        }

        private void RuningEFEMRobotTask()
        {
            if (_efemMovingItems.Count > 0)
            {
                if (_efemRobot.PostMoveItems(_efemMovingItems.ToArray()))
                {

                    foreach (var item in _efemMovingItems)
                    {
                        var wafer = WaferManager.Instance.GetWafer(item.SourceModule, item.SourceSlot);
                        if (wafer.IsEmpty)
                        {
                            // post alarm
                            _cycleState = RState.Failed;
                            LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Cannot run EFEM moving task as Get {item.SourceModule}{item.SourceModule} Wafer Info failed");
                            return;
                        }

                        var slot = new SlotItem(item.DestinationModule, item.DestinationSlot);
                        _atmWaferTargets[slot] = wafer.InnerId;

                        if (ModuleHelper.IsLoadLock(item.Module))
                        {
                            _vacModules[item.Module].MovingStatus = MovingStatus.Moving;
                        }
                    }
                }
            }
        }

        private Hand GetEFEMRobotFreeHand()
        {
            for (int i = 0; i < 2; i++)
            {
                if (_efemRobotSingleArmOption != 0 && _efemRobotSingleArmOption != i + 1)
                    continue;

                if ((WaferManager.Instance.CheckNoWafer(ModuleName.EfemRobot, i) &&
                    !_efemMovingItems.Exists(item => item.DestinationModule == ModuleName.EfemRobot && item.DestinationSlot == i)) ||
                (WaferManager.Instance.CheckHasWafer(ModuleName.EfemRobot, i) &&
                _efemMovingItems.Exists(item => item.SourceModule == ModuleName.EfemRobot && item.SourceSlot == i) &&
                !_efemMovingItems.Exists(item => item.DestinationModule == ModuleName.EfemRobot && item.DestinationSlot == i)))
                    return (Hand)i;
            }

            return Hand.None;
        }

        private void ProcessAlignerTask()
        {
            foreach (var align in _atmModules)
            {
                if (ModuleHelper.IsAligner(align.Key))
                {
                    switch (align.Value.MovingStatus)
                    {
                        case MovingStatus.WaitAlign:
                            if (_efemRobot.IsAvailable)
                            {
                                WaferInfo wafer = WaferManager.Instance.GetWafer(align.Key, 0);
                                if (!wafer.IsEmpty)
                                {
                                    int step = wafer.NextSequenceStep;
                                    if (step >= wafer.ProcessJob.Sequence.Steps.Count || !wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(align.Key))
                                    {
                                        step = wafer.NextSequenceStep - 1;
                                    }

                                    string attr = "AlignAngle";
                                    if (wafer.ProcessJob.Sequence.Steps[step].StepParameter.ContainsKey(attr))
                                    {
                                        string angle;
                                        angle = (string)wafer.ProcessJob.Sequence.Steps[step].StepParameter[attr];
                                        if (_efemRobot.Align(float.Parse(angle)))
                                        {
                                            align.Value.MovingStatus = MovingStatus.StartAlign;
                                            return;
                                        }
                                    }
                                }

                                align.Value.MovingStatus = MovingStatus.Idle;
                            }
                            break;
                        case MovingStatus.StartAlign:
                            if (!_efemRobot.IsAvailable)
                            {
                                align.Value.MovingStatus = MovingStatus.Aligning;
                            }
                            break;
                        case MovingStatus.Aligning:
                            {
                                if (_efemRobot.IsAvailable)
                                {
                                    align.Value.MovingStatus = MovingStatus.Idle;
                                }
                            }
                            break;
                    }
                }
            }
        }

        private bool ProcessAtmManaulReturnWafers()
        {
            if (_lstReturnWafers.Count == 0)
                return false;

            for(int i = 0; i < 2; i++)
            {
                if (WaferManager.Instance.CheckHasWafer(ModuleName.EfemRobot, i))
                {
                    var wafer = WaferManager.Instance.GetWafer(ModuleName.EfemRobot, i);
                    if (_lstReturnWafers.FindIndex(id => id == wafer.InnerId) != -1)
                    {
                        _efemMovingItems.Add(new MoveItem(ModuleName.EfemRobot, i, (ModuleName)wafer.OriginStation, wafer.OriginSlot, (Hand)i));
                        _lstReturnWafers.Remove(wafer.InnerId);
                    }
                }
            }

            if (_efemMovingItems.Count > 0)
                return true;

            var lls = _atmSchedulers.Where(x => ModuleHelper.IsLoadLock(x.Key) && IsLoadLockReadyForEFEMManualMove(x.Key)).ToDictionary(k => k.Key, v => v.Value);
            foreach (var ll in lls)
            {
                int slotNumber = ll.Key == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber;
                for(int i = 0; i < slotNumber; i++)
                {
                    if (_efemMovingItems.Count >= 2)
                        return true;

                    if(WaferManager.Instance.CheckHasWafer(ll.Key, i))
                    {
                        var wafer = WaferManager.Instance.GetWafer(ll.Key, i);
                        var robotHand = GetEFEMRobotFreeHand();
                        if (robotHand != Hand.None && _lstReturnWafers.FindIndex(id => id == wafer.InnerId) != -1)
                        {
                            _efemMovingItems.Add(new MoveItem(ll.Key, i, ModuleName.EfemRobot, (int)robotHand, robotHand));
                        }
                    }
                }
            }

            return _efemMovingItems.Count > 0;
        }

        #endregion Atm System

        #region Sequence validation
        private bool CheckSequencePmReady(SequenceInfo seq, List<ModuleName> pmOccupied, out List<ModuleName> pmUsed, out string reason)
        {
            pmUsed = new List<ModuleName>();
            reason = "";
            bool pmIsReady = true;

            for (int i = 0; i < seq.Steps.Count; i++)
            {
                SequenceStepInfo stepInfo = seq.Steps[i];
                bool hasPm = false;
                foreach (var module in stepInfo.StepModules)
                {
                    if (!ModuleHelper.IsPm(module))
                    {
                        hasPm = true;
                        break;
                    }

                    if (ModuleHelper.IsInstalled(module) && (pmOccupied == null || !pmOccupied.Contains(module)))
                    {
                        hasPm = true;
                    }

                    if (!pmUsed.Contains(module))
                        pmUsed.Add(module);
                }

                if (pmIsReady && !hasPm)
                {
                    reason = $"Step {i + 1} no valid PM, " + string.Join("|", stepInfo.StepModules);
                    pmIsReady = false;
                }
            }

            return pmIsReady;
        }

        private bool CheckSequenceRecipeFileValid(SequenceInfo seq, out string reason)
        {
            for (int i = 0; i < seq.Steps.Count; i++)
            {
                SequenceStepInfo stepInfo = seq.Steps[i];
                foreach (var module in stepInfo.StepModules)
                {
                    if (ModuleHelper.IsPm(module))
                    {
                        string attr = $"{module}Recipe";
                        if (stepInfo.StepParameter.ContainsKey(attr)
                            && !string.IsNullOrEmpty((string)stepInfo.StepParameter[attr]))
                        {
                            var recipeName = (string)stepInfo.StepParameter[attr];
                            if (!string.IsNullOrEmpty(recipeName))
                            {
                                var recipeContent =
                                    RecipeFileManager.Instance.LoadRecipe($"{module}", recipeName, false,"Process");
                                if (string.IsNullOrEmpty(recipeContent))
                                {
                                    reason = $"Can not find recipe file{recipeName}";
                                    return false;
                                }
                            }
                        }
                    }
                }
            }

            reason = "";
            return true;
        }

        private bool CheckWaferNeedProcessAndPMAvailable(ModuleName waferModule, int waferSlot, ModuleName processIn = ModuleName.System)
        {
            WaferInfo wafer = WaferManager.Instance.GetWafer(waferModule, waferSlot);

            if (wafer.IsEmpty)
                return false;

            if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
                return false;

            if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
                return false;

            for (int i = wafer.NextSequenceStep; i < wafer.ProcessJob.Sequence.Steps.Count; i++)
            {
                foreach (var stepModule in wafer.ProcessJob.Sequence.Steps[i].StepModules)
                {
                    if (!_vacSchedulers.Keys.Contains(stepModule))
                        continue;

                    if (!_vacSchedulers[stepModule].IsOnline)
                        continue;

                    if (ModuleHelper.IsPm(stepModule))
                    {
                        if (stepModule == processIn || processIn == ModuleName.System)
                            return true;
                    }
                    else
                        break;
                }
            }

            return false;
        }

        private List<ModuleName> GetPmUsedInRunningPj()
        {
            var pmUsed = new List<ModuleName>();
            foreach (var cj in _lstControlJobs)
            {
                if (cj.JetState != EnumJetCtrlJobState.PreJobClean && cj.JetState != EnumJetCtrlJobState.PostJobClean && cj.JetState != EnumJetCtrlJobState.Processing)
                    continue;

                foreach (var pj in _lstProcessJobs)
                {
                    if (pj.ControlJobName == cj.Name && (pj.State == EnumProcessJobState.Processing || pj.State == EnumProcessJobState.Paused))
                    {
                        for (int i = 0; i < pj.Sequence.Steps.Count; i++)
                        {
                            SequenceStepInfo stepInfo = pj.Sequence.Steps[i];
                            foreach (var module in stepInfo.StepModules)
                            {
                                if (ModuleHelper.IsPm(module) && !pmUsed.Contains(module))
                                    pmUsed.Add(module);
                            }
                        }
                    }
                }
            }

            return pmUsed;
        }

        private void UpateSequenceStep(List<MoveItem> items)
        {
            foreach (var item in items)
            {
                if (!ModuleHelper.IsTMRobot(item.DestinationModule) && !ModuleHelper.IsEFEMRobot(item.DestinationModule))
                {
                    var wafer = WaferManager.Instance.GetWafer(item.DestinationModule, item.DestinationSlot);
                    if (!wafer.IsEmpty && wafer.ProcessJob != null)
                    {
                        if (wafer.NextSequenceStep < wafer.ProcessJob.Sequence.Steps.Count && wafer.ProcessJob.Sequence.Steps[wafer.NextSequenceStep].StepModules.Contains(item.DestinationModule))
                            wafer.NextSequenceStep++;
                    }
                    else
                    {
                        // should not go here
                        LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"UpateSequenceStep() failed, location: {item.DestinationModule}:{item.DestinationSlot + 1}, NextSequenceStep: {wafer.NextSequenceStep}");
                    }
                }
            }
        }

        private void CheckWaferArrived()
        {
            if (_cycleState != RState.Running)
                return;

            foreach (var mod in _vacSchedulers.Append(new KeyValuePair<ModuleName, SchedulerModule>(ModuleName.TMRobot, _tmRobot)))
            {
                var tars = _vacWaferTargets.Where(item => item.Key.Module == mod.Key).ToArray();
                foreach (var tar in tars)
                {
                    var wafer = WaferManager.Instance.GetWafer(tar.Key.Module, tar.Key.Slot);
                    if (wafer.IsEmpty || wafer.InnerId.ToString().Length < 10)
                        continue;

                    if (wafer.InnerId == tar.Value && _tmRobot.RobotStatus != RState.Running)
                    {
                        // wafer arrive
                        _vacWaferTargets.Remove(tar.Key);
                        if (!ModuleHelper.IsTMRobot(tar.Key.Module))
                            _vacSchedulers[tar.Key.Module].WaferArrived(tar.Key.Slot);

                        //LOG.Write(eEvent.EV_ROUTER, ModuleName.System, $"wafer {wafer.WaferOrigin}: {wafer.InnerId} arrived {tar.Key.Module}{tar.Key.Slot + 1}");
                        if (ModuleHelper.IsPm(tar.Key.Module))
                        {
                            _vacModules[tar.Key.Module].MovingStatus = MovingStatus.WaitProcess;
                        }
                        else
                        {
                            var curItem = _movingItems.Find(item => item.DestinationModule == tar.Key.Module && item.DestinationSlot == tar.Key.Slot);
                            if(ModuleHelper.IsPm(curItem.SourceModule) && _movingItems.FindIndex(item => item.DestinationModule == curItem.SourceModule) == -1)
                            {
                                if(_waitPostCleanPMs.Contains(curItem.SourceModule))
                                {
                                    _vacModules[curItem.SourceModule].MovingStatus = MovingStatus.WaitPostJobClean;
                                }
                                else if(_waitWTWCleanPMs.Contains(curItem.SourceModule))
                                {
                                    _vacModules[curItem.SourceModule].MovingStatus = MovingStatus.WaitWTWClean;
                                }
                            }
                        }

                        if (!ModuleHelper.IsTMRobot(tar.Key.Module))
                            wafer.NextSequenceStep++;
                    }
                }

                if (ModuleHelper.IsLoadLock(mod.Key))
                {
                    if (!IsLoadLockReservedByTM(mod.Key) && mod.Value.IsAvailable)
                    {
                        _vacModules[mod.Key].MovingStatus = MovingStatus.Idle;
                    }
                }
            }

            _vacMoveFinishTrig.CLK = _vacWaferTargets.Count == 0 && _tmRobot.RobotStatus != RState.Running;
            if (_vacMoveFinishTrig.Q)
            {
                foreach (var item in _movingItems)
                {
                    if (!ModuleHelper.IsTMRobot(item.DestinationModule) && _vacModules[item.DestinationModule].MovingStatus == MovingStatus.Moving)
                    {
                        _vacModules[item.DestinationModule].MovingStatus = MovingStatus.Idle;
                    }

                    if (!ModuleHelper.IsTMRobot(item.SourceModule) && _vacModules[item.SourceModule].MovingStatus == MovingStatus.Moving)
                    {
                        _vacModules[item.SourceModule].MovingStatus = MovingStatus.Idle;
                    }
                }

                //UpateSequenceStep(_movingItems);
                _movingItems.Clear();
            }


            foreach (var mod in _atmSchedulers.Append(new KeyValuePair<ModuleName, SchedulerModule>(ModuleName.EfemRobot, _efemRobot)))
            {
                var tars = _atmWaferTargets.Where(item => item.Key.Module == mod.Key).ToArray();
                foreach (var tar in tars)
                {
                    var wafer = WaferManager.Instance.GetWafer(tar.Key.Module, tar.Key.Slot);
                    if (wafer.IsEmpty || wafer.InnerId.ToString().Length < 10)
                        continue;

                    if (wafer.InnerId == tar.Value && _efemRobot.RobotStatus != RState.Running)
                    {
                        //LOG.Write(eEvent.EV_ROUTER, ModuleName.System, $"wafer {wafer.WaferOrigin}: {wafer.InnerId} arrived {tar.Key.Module}{tar.Key.Slot + 1}");
                        // wafer arrive

                        if (!ModuleHelper.IsLoadPort(tar.Key.Module) && !ModuleHelper.IsEFEMRobot(tar.Key.Module))
                            _atmSchedulers[tar.Key.Module].WaferArrived(tar.Key.Slot);

                        _atmWaferTargets.Remove(tar.Key);

                        if (ModuleHelper.IsAligner(tar.Key.Module))
                        {
                            _atmModules[tar.Key.Module].MovingStatus = MovingStatus.WaitAlign;
                        }

                        if (!ModuleHelper.IsEFEMRobot(tar.Key.Module))
                            wafer.NextSequenceStep++;
                    }
                }

                if (ModuleHelper.IsLoadLock(mod.Key))
                {
                    if (!IsLoadLockReservedByEFEM(mod.Key) && mod.Value.IsAvailable)
                    {
                        _vacModules[mod.Key].MovingStatus = MovingStatus.Idle;
                    }
                }
            }

            //_atmMoveFinishTrig.CLK = _atmWaferTargets.Count == 0 && _efemRobot.RobotStatus != RState.Running;
            if (_atmWaferTargets.Count == 0 && _efemRobot.RobotStatus != RState.Running && _efemMovingItems.Count > 0)
            {
                foreach (var item in _efemMovingItems)
                {
                    if (ModuleHelper.IsLoadLock(item.Module))
                    {
                        _vacModules[item.Module].MovingStatus = MovingStatus.Idle;
                    }
                }

                //UpateSequenceStep(_efemMovingItems);
                _efemMovingItems.Clear();
                //LOG.Write(eEvent.EV_ROUTER, ModuleName.System, $"All EFEM commands complete!");
            }
        }

        private bool ActiveProcessJob(ProcessJobInfo pj)
        {
            foreach (var pjSlotWafer in pj.SlotWafers)
            {
                WaferInfo wafer = WaferManager.Instance.GetWafer(pjSlotWafer.Item1, pjSlotWafer.Item2);

                wafer.ProcessJob = pj;
                wafer.NextSequenceStep = 0;

                WaferDataRecorder.SetPjInfo(wafer.InnerId.ToString(), pj.InnerId.ToString());
            }

            ControlJobInfo cj = _lstControlJobs.Find(x => x.Name == pj.ControlJobName);

            CarrierInfo carrier = CarrierManager.Instance.GetCarrier(cj.Module);
            JobDataRecorder.StartPJ(pj.InnerId.ToString(), carrier.InnerId.ToString(), cj.InnerId.ToString(), pj.Name, cj.Module, cj.Module, pj.SlotWafers.Count);

            pj.SetState(EnumProcessJobState.Processing);

            return true;
        }

        private void DispatchCtrlJobs()
        {
            var quequedJobs = _lstControlJobs.FindAll(job => job.JetState == EnumJetCtrlJobState.Quequed);
            if (quequedJobs.Count > 0)
            {
                var runingJobs = _lstControlJobs.FindAll(job => IsCtrlJobRuning(job));

                if (runingJobs.Count >= 2)
                    return;

                if (runingJobs.Count == 0 || IsCtrlJobEndingState(runingJobs.First()))
                {
                    var startJob = quequedJobs.OrderBy(job => job.StartTime).First();

                    if (IsCtrlJobNeedPreClean(startJob))
                    {
                        startJob.JetState = EnumJetCtrlJobState.PreJobClean;
                        _waitPreCleanPMs.Clear();
                            
                        var PMs = GetWaitPreCleanPMsByCtrlJob(startJob);
                        foreach(var pm in PMs)
                        {
                            _waitPreCleanPMs.Add(pm);
                        }
                    }
                    else
                    {
                        startJob.JetState = EnumJetCtrlJobState.Processing;

                        foreach (var pjName in startJob.ProcessJobNameList)
                        {
                            var pj = _lstProcessJobs.Find(x => x.Name == pjName);
                            if (pj == null)
                            {
                                LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Not find pj named {pjName} in {startJob.Name}");
                                continue;
                            }

                            if (pj.State == EnumProcessJobState.Queued)
                            {
                                ActiveProcessJob(pj);
                                //break;  先将所有的 Process Job全部激活, 不做PreJobClean, PreJobClean 逻辑 引入 ProcessPMTask()
                            }
                        }
                    }
                }
            }

            var preJobClean = _lstControlJobs.Find(job => job.JetState == EnumJetCtrlJobState.PreJobClean);
            if(preJobClean != null && _waitPreCleanPMs.Count == 0)
            {
                preJobClean.JetState = EnumJetCtrlJobState.Processing;

                foreach (var pjName in preJobClean.ProcessJobNameList)
                {
                    var pj = _lstProcessJobs.Find(x => x.Name == pjName);
                    if (pj == null)
                    {
                        LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Not find pj named {pjName} in {preJobClean.Name}");
                        continue;
                    }

                    if (pj.State == EnumProcessJobState.Queued)
                    {
                        ActiveProcessJob(pj);
                        //break;  先将所有的 Process Job全部激活, 不做PreJobClean, PreJobClean 逻辑 引入 ProcessPMTask()
                    }
                }
            }
        }


        private static bool IsWaferNeedGotoModule(WaferInfo wafer, ModuleName module)
        {
            if (wafer.IsEmpty)
                return false;

            if (wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null)
                return false;

            if (wafer.NextSequenceStep >= wafer.ProcessJob.Sequence.Steps.Count)
                return false;

            for (int i = wafer.NextSequenceStep; i < wafer.ProcessJob.Sequence.Steps.Count; i++)
            {
                if (wafer.ProcessJob.Sequence.Steps[i].StepModules
                        .Contains(module))
                    return true;
            }

            return false;
        }

        private bool CheckAllWaferReturned(ProcessJobInfo pj, bool checkAllProcessed)
        {
            bool allWaferReturn = true;
            for (int i = 0; i < pj.SlotWafers.Count; ++i)
            {
                WaferInfo wafer = WaferManager.Instance.GetWafer(pj.SlotWafers[i].Item1, pj.SlotWafers[i].Item2);
                if (wafer.IsEmpty)
                {
                    allWaferReturn = false;
                    continue;
                }

                foreach (var mod in _vacModules)
                {
                    if (ModuleHelper.IsPm(mod.Key))
                    {
                        if (checkAllProcessed && IsWaferNeedGotoModule(wafer, mod.Key))
                        {
                            allWaferReturn = false;
                        }
                    }
                }
            }

            return allWaferReturn;
        }

        private void UpdateProcessJobStatus()
        {
            if (_efemRobot.RobotStatus == RState.Running ||_tmRobot.RobotStatus == RState.Running)
                return;

            foreach (var pj in _lstProcessJobs)
            {
                if (pj.State == EnumProcessJobState.Processing)
                {
                    if (CheckAllWaferReturned(pj, true))
                    {
                        pj.SetState(EnumProcessJobState.ProcessingComplete);
                        JobDataRecorder.EndPJ(pj.InnerId.ToString(), 0, 0);
                    }
                }
                else if (pj.State == EnumProcessJobState.Stopping)
                {
                    if (CheckAllWaferReturned(pj, false))
                    {
                        pj.SetState(EnumProcessJobState.ProcessingComplete);
                        JobDataRecorder.EndPJ(pj.InnerId.ToString(), 0, 0);
                    }
                }
            }
        }

        private void UpdateControlJobStatus()
        {
            if (_lstControlJobs.Count == 0)
                return;

            if (_efemRobot.RobotStatus == RState.Running || _tmRobot.RobotStatus == RState.Running)
                    return;

            bool allControlJobComplete = true;
            List<ControlJobInfo> cjRemoveList = new List<ControlJobInfo>();
            foreach (var cj in _lstControlJobs)
            {
                if(cj.JetState == EnumJetCtrlJobState.Quequed)
                {
                    var runingJobs = _lstControlJobs.FindAll(job => IsCtrlJobRuning(job));
                    if(runingJobs.Count == 0 || (runingJobs.Count == 1 && IsCtrlJobEndingState(runingJobs.First())))
                    {
                        var quequedJobs = _lstControlJobs.FindAll(job => job.JetState == EnumJetCtrlJobState.Quequed);
                        var firstQuequeJob = quequedJobs.OrderBy(job => job.StartTime).First();
                        if(firstQuequeJob.InnerId == cj.InnerId)
                        {
                            if (IsCtrlJobNeedPreClean(cj))
                            {
                                cj.JetState = EnumJetCtrlJobState.PreJobClean;
                                _waitPreCleanPMs.Clear();

                                var PMs = GetWaitPreCleanPMsByCtrlJob(cj);
                                foreach (var pm in PMs)
                                {
                                    _waitPreCleanPMs.Add(pm);
                                }
                            }
                            else
                            {
                                cj.JetState = EnumJetCtrlJobState.Processing;

                                foreach (var pjName in cj.ProcessJobNameList)
                                {
                                    var pj = _lstProcessJobs.Find(x => x.Name == pjName);
                                    if (pj == null)
                                    {
                                        LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Not find pj named {pjName} in {cj.Name}");
                                        continue;
                                    }

                                    if (pj.State == EnumProcessJobState.Queued)
                                    {
                                        ActiveProcessJob(pj);
                                    }
                                }
                            }
                        }
                    }
                }
                else if(IsCtrlJobRuning(cj))
                {
                    if (cj.JetState == EnumJetCtrlJobState.PreJobClean)
                    {
                        if (_waitPreCleanPMs.Count == 0)
                        {
                            cj.JetState = EnumJetCtrlJobState.Processing;

                            foreach (var pjName in cj.ProcessJobNameList)
                            {
                                var pj = _lstProcessJobs.Find(x => x.Name == pjName);
                                if (pj == null)
                                {
                                    LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Not find pj named {pjName} in {cj.Name}");
                                    continue;
                                }

                                if (pj.State == EnumProcessJobState.Queued)
                                {
                                    ActiveProcessJob(pj);
                                }
                            }
                        }
                    }
                    else
                    {
                        if (cj.JetState == EnumJetCtrlJobState.Processing && IsCtrlJobNeedPostClean(cj))
                        {
                            if (IsAllJobWaferProcessedOrProcessing(cj))
                            {
                                cj.JetState = EnumJetCtrlJobState.PostJobClean;
                                _waitPostCleanPMs.Clear();
                                var PMs = GetWaitPreCleanPMsByCtrlJob(cj);
                                foreach (var pm in PMs)
                                {
                                    _waitPostCleanPMs.Add(pm);
                                }
                            }
                        }

                        if (IsAllProcessJobComplete(cj) && (!IsCtrlJobNeedPostClean(cj) || cj.JetState == EnumJetCtrlJobState.PostJobClean && _waitPostCleanPMs.Count == 0))
                        {
                            cj.JetState = EnumJetCtrlJobState.Completed;

                            cj.SetState(EnumControlJobState.Completed);
                            cj.EndTime = DateTime.Now;

                            _faCallback.JobFinished(cj, GetFirstProcessJob(cj));
                            _dbCallback.LotFinished(cj);

                            if (!(_isCycleMode && _cycledCount < _cycleSetPoint))
                                (Singleton<TransferModule>.Instance.GetScheduler(ModuleHelper.Converter(cj.Module)) as SchedulerLoadPort).NoteJobComplete();

                            _lpCycleCount[ModuleHelper.Converter(cj.Module)]++;
                        }
                    }

                    int countProcessed = 0;
                    foreach (var pjName in cj.ProcessJobNameList)
                    {
                        var pj = _lstProcessJobs.Find(x => x.Name == pjName);
                        if (pj == null)
                        {
                            LOG.Write(eEvent.ERR_ROUTER, ModuleName.System, $"Not find pj named {pjName} in {cj.Name}");
                            continue;
                        }

                        // caculate process wafer by process
                        if (_isCycleMode && _cycledCount < (_isCycleMode ? _cycleSetPoint : 0))
                        {
                            foreach (var pjSlotWafer in pj.SlotWafers)
                            {
                                WaferInfo wafer = WaferManager.Instance.GetWafer(pjSlotWafer.Item1, pjSlotWafer.Item2);
                                if (!wafer.IsEmpty && wafer.ProcessJob != null && wafer.ProcessJob.Sequence != null
                                    && !IsWaferNeedGotoModule(wafer, ModuleName.PMA)
                                    && !IsWaferNeedGotoModule(wafer, ModuleName.PMB)
                                    && !IsWaferNeedGotoModule(wafer, ModuleName.PMC)
                                    && !IsWaferNeedGotoModule(wafer, ModuleName.PMD))
                                    countProcessed++;
                            }

                            int lpCycleWafer = _lpCycleCount[ModuleHelper.Converter(cj.Module)] * cj.LotWafers.Count + countProcessed;
                            if (_lpCycleCount[ModuleHelper.Converter(cj.Module)] != _cycledCount || lpCycleWafer != _lpCycleWafer[ModuleHelper.Converter(cj.Module)])
                            {
                                _lpCycleWafer[ModuleHelper.Converter(cj.Module)] = lpCycleWafer;
                                int totolCycleWafer = _lpCycleWafer.Sum(item => item.Value);
                                _cycledCount = _lpCycleCount.Sum(item => item.Value);
                                if (totolCycleWafer != _cycledWafer)
                                {
                                    _cycledWafer = totolCycleWafer;
                                    if (_cycledWafer >= 25)
                                    {
                                        _throughput = (float)(_cycledWafer / _cycleWatch.Elapsed.TotalHours);
                                    }
                                    else
                                    {
                                        _throughput = 0;
                                    }
                                }
                            }
                        }
                    }

                }

                LoadportCassetteState state = (LoadportCassetteState)DATA.Poll($"{cj.Module}.CassetteState");
                if (cj.State == EnumControlJobState.Completed && state != LoadportCassetteState.Normal && cj.JetState == EnumJetCtrlJobState.Completed)
                {
                    cjRemoveList.Add(cj);
                }

                allControlJobComplete = allControlJobComplete && cj.State == EnumControlJobState.Completed;
            }

            if (_isCycleMode && _cycledCount < (_isCycleMode ? _cycleSetPoint : 0))
            {
                if (allControlJobComplete)
                {
                    // _cycledCount++;

                    if (_cycledCount < _cycleSetPoint)
                    {
                        foreach (var cj in _lstControlJobs)
                        {
                            cj.SetState(EnumControlJobState.Executing);
                            cj.JetState = EnumJetCtrlJobState.Quequed;

                            cj.LotInnerId = Guid.NewGuid();

                            _dbCallback.LotCreated(cj);
                        }
                        foreach (var pj in _lstProcessJobs)
                        {
                            pj.SetState(EnumProcessJobState.Queued);
                            pj.InnerId = Guid.NewGuid();
                            foreach (var pjSlotWafer in pj.SlotWafers)
                            {
                                WaferInfo wafer = WaferManager.Instance.GetWafer(pjSlotWafer.Item1, pjSlotWafer.Item2);

                                wafer.ProcessJob = null;
                                wafer.NextSequenceStep = 0;
                                wafer.ProcessState = EnumWaferProcessStatus.Idle;
                            }
                        }
                    }
                    else
                        _cycleState = RState.End;
                }

            }

            foreach (var cj in cjRemoveList)
            {
                List<ProcessJobInfo> pjRemoveList = new List<ProcessJobInfo>();
                foreach (var pj in _lstProcessJobs)
                {
                    if (pj.ControlJobName == cj.Name)
                        pjRemoveList.Add(pj);
                }

                foreach (var pj in pjRemoveList)
                {
                    _lstProcessJobs.Remove(pj);
                }

                _lstControlJobs.Remove(cj);
            }
        }

        private void UpdateLLInOutPathProperty()
        {
            if (_lstControlJobs.Count == 0)
                return;

            List<SequenceLLInOutPath> inOutPaths = new List<SequenceLLInOutPath>();
            foreach (var cj in _lstControlJobs)
            {
                if (cj.State == EnumControlJobState.Executing)
                {
                    bool allPjCompleted = true;
                    foreach (var pjName in cj.ProcessJobNameList)
                    {
                        var pj = _lstProcessJobs.Find(x => x.Name == pjName);
                        if (pj == null || pj.State != EnumProcessJobState.Processing)
                            continue;

                        if (!inOutPaths.Exists(item => item == pj.Sequence.LLInOutPath))
                            inOutPaths.Add(pj.Sequence.LLInOutPath);
                    }
                }
            }

            if (inOutPaths.Count == 1)
            {
                _LLInOutPath = inOutPaths[0];
            }
        }

        private bool IsAllJobWaferProcessedOrProcessing(ControlJobInfo cj)
        {
            List<ModuleName> allModules = _vacModules.Keys.ToList().Union(_atmModules.Keys.ToList()).ToList();
            allModules.Add(ModuleName.EfemRobot);
            allModules.Add(ModuleName.TMRobot);
            int original = (int)ModuleHelper.Converter(cj.Module);
            foreach(var mod in allModules)
            {
                if (ModuleHelper.IsLoadPort(mod) && (int)mod != original)
                    continue;

                var wafers = WaferManager.Instance.GetWafers(mod);
                foreach(var wafer in wafers)
                {
                    if (wafer.IsEmpty || wafer.ProcessJob == null || wafer.ProcessJob.Sequence == null || wafer.OriginStation != original)
                        continue;

                    if (IsWaferNeedGotoModule(wafer, ModuleName.PMA) ||
                        IsWaferNeedGotoModule(wafer, ModuleName.PMB) ||
                        IsWaferNeedGotoModule(wafer, ModuleName.PMC) ||
                        IsWaferNeedGotoModule(wafer, ModuleName.PMD))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        private bool IsAllProcessJobComplete(ControlJobInfo cj)
        {
            foreach(var pj in _lstProcessJobs)
            {
                if(pj.ControlJobName == cj.Name && pj.State != EnumProcessJobState.ProcessingComplete)
                {
                    return false;
                }
            }

            return true;
        }

        private bool IsCtrlJobRuning(ControlJobInfo cj)
        {
            return cj.JetState == EnumJetCtrlJobState.PreJobClean || 
                cj.JetState == EnumJetCtrlJobState.Processing || 
                cj.JetState == EnumJetCtrlJobState.PostJobClean;
        }

        private bool IsCtrlJobEndingState(ControlJobInfo cj)
        {
            return cj.JetState == EnumJetCtrlJobState.PostJobClean || 
                (cj.JetState == EnumJetCtrlJobState.Processing 
                && IsAllJobWaferProcessedOrProcessing(cj) 
                && !IsCtrlJobNeedPostClean(cj));
        }

        private bool IsCtrlJobNeedPreClean(ControlJobInfo cj)
        {
            if (cj.ProcessJobNameList.Count != 1)
                return false;

            var processJob = _lstProcessJobs.Find(pj => pj.Name == cj.ProcessJobNameList.First());
            if (processJob == null || processJob.Sequence == null || processJob.Sequence.PreCleanRecipe == null)
                return false;

            return processJob.Sequence.PreCleanRecipe.Trim().Length > 0;
        }

        private bool IsCtrlJobNeedPostClean(ControlJobInfo cj)
        {
            if (cj.ProcessJobNameList.Count != 1)
                return false;

            var processJob = _lstProcessJobs.Find(pj => pj.Name == cj.ProcessJobNameList.First());
            if (processJob == null || processJob.Sequence == null || processJob.Sequence.PostCleanRecipe == null)
                return false;

            return processJob.Sequence.PostCleanRecipe.Trim().Length > 0;
        }

        private bool IsPMNeedWTWClean(ModuleName pm)
        {
            foreach(var cj in _lstControlJobs)
            {
                if(IsCtrlJobNeedWTWClean(cj, pm, out string wtwClean))
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsCtrlJobNeedWTWClean(ControlJobInfo cj, ModuleName pm,  out string WTWCleanRecipe)
        {
            WTWCleanRecipe = string.Empty;
            if (cj.ProcessJobNameList.Count != 1)
                return false;

            var processJob = _lstProcessJobs.Find(pj => pj.Name == cj.ProcessJobNameList.First());
            if (processJob == null || processJob.Sequence == null || processJob.Sequence.WTWCleanRecipe == null)
                return false;

            WTWCleanRecipe = processJob.Sequence.WTWCleanRecipe.Trim();
            return processJob.Sequence.PMs.Contains(pm) && WTWCleanRecipe.Length > 0;
        }

        private List<ModuleName> GetWaitPreCleanPMsByCtrlJob(ControlJobInfo cj)
        {
            if (cj.ProcessJobNameList.Count != 1)
                return new List<ModuleName>();

            var processJob = _lstProcessJobs.Find(pj => pj.Name == cj.ProcessJobNameList.First());

            return processJob.Sequence.PMs;
        }

        private static WaferInfo GetCloneWafer(ModuleName mod, int slot)
        {
            var Wafer = WaferManager.Instance.GetWafer(mod, slot);
            if (Wafer.IsEmpty || Wafer.ProcessJob == null || Wafer.ProcessJob.Sequence == null)
                return Wafer;

            var cloneWafer = SerializeHelper.DeepCopyJson(Wafer);

            if (Wafer.IsEmpty || Wafer.ProcessJob == null || Wafer.ProcessJob.Sequence == null)
                return Wafer;

            return cloneWafer;
        }

        #endregion

        #region Manual Return All Wafer
        public RState CheckManualReturnWafer()
        {
            if(!_tmRobot.IsIdle && (GetPMWaferExistence().exist + GetTMRobotWaferCount() > 0))
            {
                LOG.Write(eEvent.ERR_ROUTER, ModuleName.TMRobot, $"The TM Robot is not ready for return wafer.");
                return RState.Failed;
            }

            if (!_efemRobot.IsIdle)
            {
                LOG.Write(eEvent.ERR_ROUTER, ModuleName.EfemRobot, $"The EFEM Robot is not ready for return wafer.");
                return RState.Failed;
            }

            var schedulers = _vacSchedulers.Concat(_atmSchedulers);
            foreach(var mod in schedulers)
            {
                if (ModuleHelper.IsLoadPort(mod.Key))
                    continue;

                int nSlotNumber = ModuleHelper.IsLoadLock(mod.Key) ? (mod.Key == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber) : (ModuleHelper.IsTMRobot(mod.Key) || ModuleHelper.IsEFEMRobot(mod.Key) ? 2 : 1);
                for(int slot = 0; slot < nSlotNumber; slot++)
                {
                    var wafer = WaferManager.Instance.GetWafer(mod.Key, slot);
                    if (wafer.IsEmpty)
                        continue;

                    if(!mod.Value.IsIdle)
                    {
                        LOG.Write(eEvent.ERR_ROUTER, mod.Key, $"{mod.Key} is not ready for return wafer.");
                        return RState.Failed;
                    }

                    var destLP = (ModuleName)wafer.OriginStation;
                    if(!_atmSchedulers[destLP].IsAvailable)
                    {
                        LOG.Write(eEvent.ERR_ROUTER, destLP, $"The destination Loadport {destLP} is not ready for return wafer.");
                        return RState.Failed;
                    }
                }
            }

            Clear();
            return RState.Running;
        }

        public RState ReturnAllWafers()
        {
            var llaWaferStatus = GetLLProcessStatusCount(ModuleName.LLA);
            var llbWaferStatus = GetLLProcessStatusCount(ModuleName.LLB);
            var pmWaferStatus = GetPMWaferExistence();
            if (pmWaferStatus.exist + (_LLASlotNumber - llaWaferStatus.empty) + (_LLBSlotNumber - llbWaferStatus.empty) + GetTMRobotWaferCount() + GetAtmInerWaferCount() + GetEfemRoborWaferCount() == 0)
            {
                Clear();
                return RState.End;
            }

            ReturnAtmWafers();
            ReturnVacWafers();
            CheckReturnWafersArrived();

            return RState.Running;
        }

        private void ReturnVacWafers()
        {
            if(_tmRobot.IsAvailable && _tmRobot.RobotStatus != RState.Running && _movingItems.Count == 0)
            {
                var tmRobotWaferCount = GetTMRobotWaferCount();
                if(tmRobotWaferCount > 0 || GetPMWaferExistence().exist > 0)
                {
                    var llaWaferStatus = GetLLProcessStatusCount(ModuleName.LLA);
                    var llbWaferStatus = GetLLProcessStatusCount(ModuleName.LLB);

                    if (IsLoadReadyForTM(ModuleName.LLA) && llaWaferStatus.empty > 0 && (!IsLoadReadyForTM(ModuleName.LLB) || (!_vacSchedulers[ModuleName.LLB].IsVac && llaWaferStatus.empty == llbWaferStatus.empty) || llaWaferStatus.empty > llbWaferStatus.empty))
                    {
                        if (tmRobotWaferCount > 0)
                            ReturnTMRobotWafersToLL(ModuleName.LLA);
                        else
                            ReturnPMWafersToLL(ModuleName.LLA);
                    }
                    else if (IsLoadReadyForTM(ModuleName.LLB) && llbWaferStatus.empty > 0)
                    {
                        if (tmRobotWaferCount > 0)
                            ReturnTMRobotWafersToLL(ModuleName.LLB);
                        else
                            ReturnPMWafersToLL(ModuleName.LLB);
                    }
                }

                RuningTMRobotTask();
            }
        }

        private void ReturnAtmWafers()
        {
            if(_efemRobot.IsAvailable && _efemRobot.RobotStatus != RState.Running && _efemMovingItems.Count == 0)
            {
                var aligners = _atmSchedulers.Where(x => ModuleHelper.IsAligner(x.Key) && x.Value.IsAvailable && WaferManager.Instance.CheckHasWafer(x.Key, 0)).ToDictionary(k => k.Key, v => v.Value);
                var lls = _vacSchedulers.Where(x => ModuleHelper.IsLoadLock(x.Key) && IsLoadLockReadyForEFEMManualMove(x.Key) && GetLLProcessStatusCount(x.Key).processed + GetLLProcessStatusCount(x.Key).unprocessed > 0).ToDictionary(k => k.Key, v => v.Value);
                if (GetEfemRoborWaferCount() > 0)
                {
                    for(int slot = 0; slot < 2; slot++)
                    {
                        var wafer = WaferManager.Instance.GetWafer(ModuleName.EfemRobot, slot);
                        if(!wafer.IsEmpty)
                        {
                            _efemMovingItems.Add(new MoveItem(ModuleName.EfemRobot, slot, (ModuleName)wafer.OriginStation, wafer.OriginSlot, (Hand)slot));
                        }
                    }
                }
                else if(aligners.Count > 0)
                {
                    foreach(var align in aligners)
                    {
                        var wafer = WaferManager.Instance.GetWafer(align.Key, 0);
                        var hand = GetEFEMRobotFreeHand();
                        if (!wafer.IsEmpty && hand != Hand.None)
                        {
                            _efemMovingItems.Add(new MoveItem(align.Key, 0, ModuleName.EfemRobot, (int)hand, hand));
                        }
                    }
                }
                else if(lls.Count > 0)
                {
                    ModuleName source = lls.First().Key;
                    if(lls.Count > 1)
                    {
                        if (GetLLProcessStatusCount(ModuleName.LLB).empty < GetLLProcessStatusCount(ModuleName.LLA).empty)
                            source = ModuleName.LLB;
                        else
                            source = ModuleName.LLA;
                    }

                    int slotNumber = source == ModuleName.LLA ? _LLASlotNumber : _LLBSlotNumber;

                    for(int i = 0; i < slotNumber; i++)
                    {
                        var wafer = WaferManager.Instance.GetWafer(source, i);
                        var hand = GetEFEMRobotFreeHand();
                        if (!wafer.IsEmpty && hand != Hand.None)
                        {
                            _efemMovingItems.Add(new MoveItem(source, i, ModuleName.EfemRobot, (int)hand, hand));
                        }
                    }
                }

                RuningEFEMRobotTask();
            }
        }

        private void ReturnPMWafersToLL(ModuleName ll)
        {
            var validSlots = GetModuleValidSlots(ll);
            foreach(var pm in _vacSchedulers)
            {
                if (!ModuleHelper.IsPm(pm.Key))
                    continue;

                if(pm.Value.IsIdle && WaferManager.Instance.CheckHasWafer(pm.Key, 0) && validSlots.Count > 0)
                {
                    _movingItems.Add(new MoveItem( pm.Key, 0, ll, validSlots.Dequeue(), Hand.None));
                    if (_movingItems.Count >= 2)
                        return;
                }
            }
        }

        private void ReturnTMRobotWafersToLL(ModuleName ll)
        {
            var validSlots = GetModuleValidSlots(ll);
            for(int slot = 0; slot < 2; slot++)
            {
                if(WaferManager.Instance.CheckHasWafer(ModuleName.TMRobot, slot))
                {
                    _movingItems.Add(new MoveItem(ModuleName.TMRobot, slot, ll, validSlots.Dequeue(), (Hand)slot));
                }
            }
        }

        private void CheckReturnWafersArrived()
        {
            foreach (var mod in _vacSchedulers.Append(new KeyValuePair<ModuleName, SchedulerModule>(ModuleName.TMRobot, _tmRobot)))
            {
                var tars = _vacWaferTargets.Where(item => item.Key.Module == mod.Key).ToArray();
                foreach (var tar in tars)
                {
                    var wafer = WaferManager.Instance.GetWafer(tar.Key.Module, tar.Key.Slot);
                    if (wafer.IsEmpty || wafer.InnerId.ToString().Length < 10)
                        continue;

                    if (wafer.InnerId == tar.Value && _tmRobot.RobotStatus != RState.Running)
                    {
                        // wafer arrive
                        _vacWaferTargets.Remove(tar.Key);
                    }
                }

                if (ModuleHelper.IsLoadLock(mod.Key))
                {
                    if (!IsLoadLockReservedByTM(mod.Key) && mod.Value.IsAvailable)
                    {
                        _vacModules[mod.Key].MovingStatus = MovingStatus.Idle;
                    }
                }
            }

            _vacMoveFinishTrig.CLK = _vacWaferTargets.Count == 0 && _tmRobot.RobotStatus != RState.Running;
            if (_vacMoveFinishTrig.Q)
            {
                foreach (var item in _movingItems)
                {
                    if (!ModuleHelper.IsTMRobot(item.DestinationModule) && _vacModules[item.DestinationModule].MovingStatus == MovingStatus.Moving)
                    {
                        _vacModules[item.DestinationModule].MovingStatus = MovingStatus.Idle;
                    }

                    if (!ModuleHelper.IsTMRobot(item.SourceModule) && _vacModules[item.SourceModule].MovingStatus == MovingStatus.Moving)
                    {
                        _vacModules[item.SourceModule].MovingStatus = MovingStatus.Idle;
                    }
                }


                _movingItems.Clear();
            }


            foreach (var mod in _atmSchedulers.Append(new KeyValuePair<ModuleName, SchedulerModule>(ModuleName.EfemRobot, _efemRobot)))
            {
                var tars = _atmWaferTargets.Where(item => item.Key.Module == mod.Key).ToArray();
                foreach (var tar in tars)
                {
                    var wafer = WaferManager.Instance.GetWafer(tar.Key.Module, tar.Key.Slot);
                    if (wafer.IsEmpty || wafer.InnerId.ToString().Length < 10)
                        continue;

                    if (wafer.InnerId == tar.Value && _efemRobot.RobotStatus != RState.Running)
                    {

                        if (!ModuleHelper.IsLoadPort(tar.Key.Module) && !ModuleHelper.IsEFEMRobot(tar.Key.Module))
                            _atmSchedulers[tar.Key.Module].WaferArrived(tar.Key.Slot);

                        _atmWaferTargets.Remove(tar.Key);

                        if (ModuleHelper.IsAligner(tar.Key.Module))
                        {
                            _atmModules[tar.Key.Module].MovingStatus = MovingStatus.WaitAlign;
                        }
                    }
                }

                if (ModuleHelper.IsLoadLock(mod.Key))
                {
                    if (!IsLoadLockReservedByEFEM(mod.Key) && mod.Value.IsAvailable)
                    {
                        _vacModules[mod.Key].MovingStatus = MovingStatus.Idle;
                    }
                }
            }

            if (_atmWaferTargets.Count == 0 && _efemRobot.RobotStatus != RState.Running && _efemMovingItems.Count > 0)
            {
                foreach (var item in _efemMovingItems)
                {
                    if (ModuleHelper.IsLoadLock(item.Module))
                    {
                        _vacModules[item.Module].MovingStatus = MovingStatus.Idle;
                    }
                }

                _efemMovingItems.Clear();
            }
        }
        #endregion
    }
}