public override string ReadLine()
        {
            // Send any commands that are queue before moving on to the internal stream.
            string nextCommand = queuedCommands.ReadLine();

            if (nextCommand != null)
            {
                return(nextCommand);
            }

            switch (RecoveryState)
            {
            // heat the extrude to remove it from the part
            case RecoveryState.RemoveHeating:
                // TODO: make sure we heat up all the extruders that we need to (all that are used)
                queuedCommands.Add("G21; set units to millimeters");
                queuedCommands.Add("M107; fan off");
                queuedCommands.Add("T0; set the active extruder to 0");
                queuedCommands.Add("G90; use absolute coordinates");
                queuedCommands.Add("G92 E0; reset the expected extruder position");
                queuedCommands.Add("M82; use absolute distance for extrusion");

                bool   hasHeatedBed = printer.Settings.GetValue <bool>(SettingsKey.has_heated_bed);
                double bedTemp      = printer.Settings.GetValue <double>(SettingsKey.bed_temperature);
                if (hasHeatedBed && bedTemp > 0)
                {
                    // start heating the bed
                    queuedCommands.Add($"M140 S{bedTemp}");
                }

                // heat up the extruder
                queuedCommands.Add("M109 S{0}".FormatWith(printer.Settings.Helpers.ExtruderTemperature(0)));

                if (hasHeatedBed && bedTemp > 0)
                {
                    // finish heating the bed
                    queuedCommands.Add($"M190 S{bedTemp}");
                }

                RecoveryState = RecoveryState.Raising;
                return("");

            // remove it from the part
            case RecoveryState.Raising:
                // We don't know where the printer is for sure (it make have been turned off). Disable leveling until we know where it is.
                PrintLevelingStream.AllowLeveling = false;
                queuedCommands.Add("M114 ; get current position");
                queuedCommands.Add("G91 ; move relative");
                queuedCommands.Add("G1 Z10 F{0}".FormatWith(printer.Settings.ZSpeed()));
                queuedCommands.Add("G90 ; move absolute");
                RecoveryState = RecoveryState.Homing;
                return("");

            // if top homing, home the extruder
            case RecoveryState.Homing:
                if (printer.Settings.GetValue <bool>(SettingsKey.z_homes_to_max))
                {
                    queuedCommands.Add("G28");
                }
                else
                {
                    // home x
                    queuedCommands.Add("G28 X0");
                    // home y
                    queuedCommands.Add("G28 Y0");
                    // move to the place we can home z from
                    Vector2 recoveryPositionXy = printer.Settings.GetValue <Vector2>(SettingsKey.recover_position_before_z_home);
                    queuedCommands.Add("G1 X{0:0.###}Y{1:0.###}F{2}".FormatWith(recoveryPositionXy.X, recoveryPositionXy.Y, printer.Settings.XSpeed()));
                    // home z
                    queuedCommands.Add("G28 Z0");
                }
                // We now know where the printer is re-enable print leveling
                PrintLevelingStream.AllowLeveling = true;
                RecoveryState = RecoveryState.FindingRecoveryLayer;
                return("");

            // This is to recover printing if an out a filament occurs.
            // Help the user move the extruder down to just touching the part
            case RecoveryState.FindingRecoveryLayer:
                if (false)                         // help the user get the head to the right position
                {
                    // move to above the completed print
                    // move over a know good part of the model at the current top layer (extrude vertex from gcode)
                    // let the user move down until they like the height
                    // calculate that position and continue
                }
                else                         // we are resuming because of disconnect or reset, skip this
                {
                    RecoveryState = RecoveryState.SkippingGCode;
                    goto case RecoveryState.SkippingGCode;
                }

            case RecoveryState.SkippingGCode:
                // run through the gcode that the device expected looking for things like temp
                // and skip everything else until we get to the point we left off last time
                int commandCount = 0;
                boundsOfSkippedLayers = RectangleDouble.ZeroIntersection;
                while (internalStream.GCodeFile.PercentComplete(internalStream.LineIndex) < percentDone)
                {
                    string line = internalStream.ReadLine();
                    if (line == null)
                    {
                        break;
                    }
                    commandCount++;

                    // make sure we don't parse comments
                    if (line.Contains(";"))
                    {
                        line = line.Split(';')[0];
                    }
                    lastDestination = GetPosition(line, lastDestination);

                    if (commandCount > 100)
                    {
                        boundsOfSkippedLayers.ExpandToInclude(lastDestination.position.Xy);
                    }

                    // check if the line is something we want to send to the printer (like a temp)
                    if (line.StartsWith("M109") ||                          // heat and wait extruder
                        line.StartsWith("M104") ||                                 // heat extruder
                        line.StartsWith("M190") ||                                 // heat and wait bed
                        line.StartsWith("M140") ||                                 // heat bed
                        line.StartsWith("T") ||                                 // switch extruder
                        line.StartsWith("M106") ||                                 // fan on
                        line.StartsWith("M107") ||                                 // fan off
                        line.StartsWith("G92"))                                    // set position
                    {
                        return(line);
                    }
                }

                RecoveryState = RecoveryState.PrimingAndMovingToStart;

                // make sure we always- pick up the last movement
                boundsOfSkippedLayers.ExpandToInclude(lastDestination.position.Xy);
                return("");

            case RecoveryState.PrimingAndMovingToStart:
            {
                if (printer.Settings.GetValue("z_homes_to_max") == "0")                                 // we are homed to the bed
                {
                    // move to the height we can recover printing from
                    Vector2 recoverPositionXy = printer.Settings.GetValue <Vector2>(SettingsKey.recover_position_before_z_home);
                    queuedCommands.Add(CreateMovementLine(new PrinterMove(new VectorMath.Vector3(recoverPositionXy.X, recoverPositionXy.Y, lastDestination.position.Z), 0, printer.Settings.ZSpeed())));
                }

                double extruderWidth = printer.Settings.GetValue <double>(SettingsKey.nozzle_diameter);
                // move to a position outside the printed bounds
                queuedCommands.Add(CreateMovementLine(new PrinterMove(
                                                          new Vector3(boundsOfSkippedLayers.Left - extruderWidth * 2, boundsOfSkippedLayers.Bottom + boundsOfSkippedLayers.Height / 2, lastDestination.position.Z),
                                                          0, printer.Settings.XSpeed())));

                // let's prime the extruder
                queuedCommands.Add("G1 E10 F{0}".FormatWith(printer.Settings.EFeedRate(0))); // extrude 10
                queuedCommands.Add("G1 E9");                                                 // and retract a bit

                // move to the actual print position
                queuedCommands.Add(CreateMovementLine(new PrinterMove(lastDestination.position, 0, printer.Settings.XSpeed())));

                /// reset the printer to know where the filament should be
                queuedCommands.Add("G92 E{0}".FormatWith(lastDestination.extrusion));
                RecoveryState = RecoveryState.PrintingSlow;
            }
                return("");

            case RecoveryState.PrintingSlow:
            {
                string lineToSend = internalStream.ReadLine();
                if (lineToSend == null)
                {
                    return(null);
                }

                if (!GCodeFile.IsLayerChange(lineToSend))
                {
                    // have not seen the end of this layer so keep printing slow
                    if (LineIsMovement(lineToSend))
                    {
                        PrinterMove currentMove = GetPosition(lineToSend, lastDestination);
                        PrinterMove moveToSend  = currentMove;

                        moveToSend.feedRate = recoverFeedRate;

                        lineToSend      = CreateMovementLine(moveToSend, lastDestination);
                        lastDestination = currentMove;
                        return(lineToSend);
                    }

                    return(lineToSend);
                }
            }

                // we only fall through to here after seeing the next "; Layer:"
                RecoveryState = RecoveryState.PrintingToEnd;
                return("");

            case RecoveryState.PrintingToEnd:
                return(internalStream.ReadLine());
            }

            return(null);
        }
        public override string ReadLine()
        {
            string nextCommand = queuedCommands.ReadLine();

            if (nextCommand != null)
            {
                return(nextCommand);
            }

            switch (resumeState)
            {
            // heat the extrude to remove it from the part
            case ResumeState.RemoveHeating:
                // TODO: make sure we heat up all the extruders that we need to (all that are used)
                queuedCommands.Add("G21; set units to millimeters");
                queuedCommands.Add("M107; fan off");
                queuedCommands.Add("T0; set the active extruder to 0");
                queuedCommands.Add("G90; use absolute coordinates");
                queuedCommands.Add("G92 E0; reset the expected extruder position");
                queuedCommands.Add("M82; use absolute distance for extrusion");
                queuedCommands.Add("M109 S{0}".FormatWith(ActiveSliceSettings.Instance.ExtruderTemperature(0)));

                resumeState = ResumeState.Raising;
                return("");

            // remove it from the part
            case ResumeState.Raising:
                queuedCommands.Add("M114 ; get current position");
                queuedCommands.Add("G91 ; move relative");
                queuedCommands.Add("G1 Z10 F{0}".FormatWith(MovementControls.ZSpeed));
                queuedCommands.Add("G90 ; move absolute");
                resumeState = ResumeState.Homing;
                return("");

            // if top homing, home the extruder
            case ResumeState.Homing:
                if (ActiveSliceSettings.Instance.ActiveValue("z_homes_to_max") == "1")
                {
                    queuedCommands.Add("G28");
                }
                else
                {
                    // home x
                    queuedCommands.Add("G28 X0");
                    // home y
                    queuedCommands.Add("G28 Y0");
                    // move to the place we can home z from
                    Vector2 resumePositionXy = ActiveSliceSettings.Instance.ActiveVector2("resume_position_before_z_home");
                    queuedCommands.Add("G1 X{0:0.000}Y{1:0.000}F{2}".FormatWith(resumePositionXy.x, resumePositionXy.y, MovementControls.XSpeed));
                    // home z
                    queuedCommands.Add("G28 Z0");
                }
                resumeState = ResumeState.FindingResumeLayer;
                return("");

            // This is to resume printing if an out a filament occurs.
            // Help the user move the extruder down to just touching the part
            case ResumeState.FindingResumeLayer:
                if (false)                         // help the user get the head to the right position
                {
                    // move to above the completed print
                    // move over a know good part of the model at the current top layer (extrude vertex from gcode)
                    // let the user move down until they like the height
                    // calculate that position and continue
                }
                else                         // we are resuming because of disconnect or reset, skip this
                {
                    resumeState = ResumeState.SkippingGCode;
                    goto case ResumeState.SkippingGCode;
                }

            case ResumeState.SkippingGCode:
                // run through the gcode that the device expected looking for things like temp
                // and skip everything else until we get to the point we left off last time
                while (internalStream.FileStreaming.PercentComplete(internalStream.LineIndex) < percentDone)
                {
                    string line = internalStream.ReadLine();

                    lastDestination = GetPosition(line, lastDestination);

                    // check if the line is something we want to send to the printer (like a temp)
                    if (line.StartsWith("M109") ||
                        line.StartsWith("M104") ||
                        line.StartsWith("T") ||
                        line.StartsWith("M106") ||
                        line.StartsWith("M107"))
                    {
                        return(line);
                    }
                }

                resumeState = ResumeState.PrimingAndMovingToStart;
                return("");

            case ResumeState.PrimingAndMovingToStart:
            {
                // let's prime the extruder, move to a good position over the part, then start printing
                queuedCommands.Add("G1 E5");
                queuedCommands.Add("G1 E4");
                if (ActiveSliceSettings.Instance.ActiveValue("z_homes_to_max") == "0")                                 // we are homed to the bed
                {
                    // move to the height we can resume printing from
                    Vector2 resumePositionXy = ActiveSliceSettings.Instance.ActiveVector2("resume_position_before_z_home");
                    queuedCommands.Add(CreateMovementLine(new PrinterMove(new VectorMath.Vector3(resumePositionXy.x, resumePositionXy.y, lastDestination.position.z + 5), 0, MovementControls.ZSpeed)));
                    // move just above the actual print position
                    queuedCommands.Add(CreateMovementLine(new PrinterMove(lastDestination.position + new VectorMath.Vector3(0, 0, 5), 0, MovementControls.XSpeed)));
                    // move down to part
                    queuedCommands.Add(CreateMovementLine(new PrinterMove(lastDestination.position, 0, MovementControls.ZSpeed)));
                }
                else
                {
                    // move to the actual print position
                    queuedCommands.Add(CreateMovementLine(new PrinterMove(lastDestination.position, 0, MovementControls.ZSpeed)));
                }
                // extrude back to our filament start
                queuedCommands.Add("G1 E5");
                /// reset the printer to know where the filament should be
                queuedCommands.Add("G92 E{0}".FormatWith(lastDestination.extrusion));
                resumeState = ResumeState.PrintingSlow;
            }
                return("");

            case ResumeState.PrintingSlow:
            {
                string lineToSend = internalStream.ReadLine();
                if (lineToSend != null &&
                    lineToSend.StartsWith("; LAYER:"))
                {
                    if (lineToSend != null &&
                        LineIsMovement(lineToSend))
                    {
                        PrinterMove currentMove = GetPosition(lineToSend, lastDestination);
                        PrinterMove moveToSend  = currentMove;

                        double feedRate;

                        string firstLayerSpeed = ActiveSliceSettings.Instance.ActiveValue("resume_first_layer_speed");
                        if (!double.TryParse(firstLayerSpeed, out feedRate))
                        {
                            feedRate = 10;
                        }
                        feedRate *= 60;

                        moveToSend.feedRate = feedRate;

                        lineToSend      = CreateMovementLine(moveToSend, lastDestination);
                        lastDestination = currentMove;
                        return(lineToSend);
                    }

                    return(lineToSend);
                }
            }

                resumeState = ResumeState.PrintingToEnd;
                return("");

            case ResumeState.PrintingToEnd:
                return(internalStream.ReadLine());
            }

            return(null);
        }