/// <summary>
        /// 同期キャラクタ検出を行う。キャラクタ検出を行ったら、その完了ポイントへの、インデックスを返す。
        /// 同期キャラクタは、読み出しバッファに収納される。
        /// キャラクタ検出できなければ、インデックスはバッファ長と同じ。
        /// </summary>
        int synchronation(int[] waveform, int offset)
        {
            byte data = 0;
            int cnt   = 16;
            int index = offset;
            for (; index < waveform.Length; index+=4)
            {
                // char sync
                if (waveform[index] > 0)
                    data |= 0x80;

                if (data == _syncCharactor)
                {
                    System.Diagnostics.Debug.WriteLine(String.Format("Sync: 0x{0:X}.", data));

                    _recoveryData   = 0;
                    _recoveryBitPos = 0;
                    _state = RecoveryState.DATA_EDGE;
                    return (index + 4);
                }
                // nosignal
                cnt--;
                if (cnt < 0)
                {
                    _state = RecoveryState.SYNC_EDGE;
                    return index;
                }
                data >>= 1;
            }
            return index;
        }
 /// <summary>
 /// データをリカバリします。これを読み出したあとは、HasReceivedが有効ならば、データバッファを読み出してください。次に呼び出したとき、このバッファは破壊されています。
 /// </summary>
 /// <param name="waveform"></param>
 public void Recovery(int[] waveform)
 {
     int index = _indexOffset;
     while (index < waveform.Length)
     {
         switch (_state)
         {
             case RecoveryState.SYNC_EDGE:
                 index = syncEdgeDetection(waveform, index);
                 break;
             case RecoveryState.SYNC:
                 index = synchronation(waveform, index);
                 break;
             case RecoveryState.DATA_EDGE:
                 index = dataEdgeDetection(waveform, index);
                 break;
             case RecoveryState.DATA:
                 index = dataRecovery(waveform, index);
                 break;
             default: _state = RecoveryState.SYNC_EDGE;
                 return;
         }
     }
     _indexOffset = index - waveform.Length;
 }
 /// <summary>
 /// エッジ検出。
 /// </summary>
 int syncEdgeDetection(int[] waveform, int offset)
 {
     int index = offset;
     for (; index < waveform.Length; index++)
     {
         // edge detection
         if (waveform[index] > 0)
         {
             _state = RecoveryState.SYNC;
             return (index + 1 + 4);
         }
     }
     return index;
 }
        /// <summary>
        /// データ再生を行う。
        /// </summary>
        int dataRecovery(int[] waveform, int offset)
        {
            int index = offset;
            for (; index < waveform.Length; index += 4)
            {
                // char sync
                if (waveform[index] > 0)
                    _recoveryData |= 0x80;

                if (_recoveryBitPos >= 7)
                {
                    System.Diagnostics.Debug.WriteLine(String.Format("Receiving: 0x{0:X}.", _recoveryData));
                    _rcvBuf.Add(_recoveryData);
                    _recoveryData = 0;
                    _recoveryBitPos = 0;
                    _state = RecoveryState.DATA_EDGE;
                    return (index + 4);
                }
                _recoveryBitPos++;
                _recoveryData >>= 1;
            }
            return index;
        }
 int dataEdgeDetection(int[] waveform, int offset)
 {
     int index = offset;
     int cnt   = 4;
     for (; index < waveform.Length; index++)
     {
         // edge detection
         if (waveform[index] > 0)
         {
             _recoveryData = 0;
             _state = RecoveryState.DATA;
             return (index + 1 + 4);
         }
         cnt--;
         if (cnt < 0)
         {
             _queue.Enqueue(_rcvBuf.ToArray());
             _rcvBuf.Clear();
             _state = RecoveryState.SYNC_EDGE;
             return index;
         }
     }
     return index;
 }
 /// <summary>
 /// 強制的に同期状態に遷移します
 /// </summary>
 public void Resynchronize()
 {
     _rcvBuf.Clear();
     _state = RecoveryState.SYNC_EDGE;
 }
		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");
					queuedCommands.Add("M109 S{0}".FormatWith(ActiveSliceSettings.Instance.Helpers.ExtruderTemperature(0)));

					recoveryState = RecoveryState.Raising;
					return "";

				// remove it from the part
				case RecoveryState.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");
					recoveryState = RecoveryState.Homing;
					return "";

				// if top homing, home the extruder
				case RecoveryState.Homing:
					if (ActiveSliceSettings.Instance.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 = ActiveSliceSettings.Instance.GetValue<Vector2>(SettingsKey.recover_position_before_z_home);
						queuedCommands.Add("G1 X{0:0.###}Y{1:0.###}F{2}".FormatWith(recoveryPositionXy.x, recoveryPositionXy.y, MovementControls.XSpeed));
						// home z
						queuedCommands.Add("G28 Z0");
					}
					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.FileStreaming.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);
							if (boundsOfSkippedLayers.Bottom < 10)
							{
								int a = 0;
							}
						}

						// 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;
						}
					}
					
					recoveryState = RecoveryState.PrimingAndMovingToStart;

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

				case RecoveryState.PrimingAndMovingToStart:
					{

						if (ActiveSliceSettings.Instance.GetValue("z_homes_to_max") == "0") // we are homed to the bed
						{
							// move to the height we can recover printing from
							Vector2 recoverPositionXy = ActiveSliceSettings.Instance.GetValue<Vector2>(SettingsKey.recover_position_before_z_home);
							queuedCommands.Add(CreateMovementLine(new PrinterMove(new VectorMath.Vector3(recoverPositionXy.x, recoverPositionXy.y, lastDestination.position.z), 0, MovementControls.ZSpeed)));
						}

						double extruderWidth = ActiveSliceSettings.Instance.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, MovementControls.XSpeed)));
						
						// let's prime the extruder
						queuedCommands.Add("G1 E10 F{0}".FormatWith(MovementControls.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, MovementControls.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;
		}