//******************************************************************************
//  タイトル  : Analog wave monitor
//  ファイル  : FormMain.cs
//  機能概要  : フリーラン波形を表示するプログラム
//------------------------------------------------------------------------------
//  使用言語  : C#
//  ランタイム: Microsoft .NET framework Version 4.5.51641
//  ビルド環境: Microsoft Visual Studio Express 2013 for Windows Desktop
//              Version 12.0.21005.1 REL
//------------------------------------------------------------------------------
//  変更履歴  :
//  Ver 1.0.0.0  2015/02/23  Tetsuro Tatsuoka  新規作成
//******************************************************************************
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;

namespace AnalogWaveMonitor
{
	public partial class FormMain : Form
	{
		#region 定数
		//***********************************************************************************

		// 画面レイアウト関連
        private const int waveformIndex = 0;                // 波形用波形描画オブジェクトインデックス
		private const int waveAreaOffsetX = 40;				// 波形エリアの左端マージン
		private const int waveAreaOffsetY = 40;				// 波形エリアの上端マージン
		private const int waveAreaInterval = 5;			    // 波形エリアの間隔
		private const int waveAreaSizeX = 1000;				// 波形エリアの横サイズ
		private const int waveAreaSizeY = 256;				// 波形エリアの縦サイズ
        private const int waveAreaNum = 1;					// 表示波形の数
		private const string waveControlName = "drawWave";	// 波形描画オブジェクトのName
		private static string[] waveCaption					// 波形のキャプション
			= new string[]{ "" };

		// 受信データ関連
        private enum RECV_STATE { RDY, START, CNTR_WV, SIZE_WV, WAVDAT };
        private RECV_STATE recvState = RECV_STATE.RDY;		// 受信ステート
        private const int packetSize = 4;					// 送信パケットのサイズ
        private const int headerSize = 2;					// ヘッダのサイズ
        private const byte packetHeader = 0xAA;				// ヘッダ値(1byte目)
        private const byte waveformId = 0x01;	    		// 波形ID(2byte目)
        private const byte packetCntrMax = 99;				// パケットカウンタの最大値

		// シリアルポート関連
		private const string defaultComPort = "COM1";		// ポートの初期値
		private const int defaultBaudRate = 115200;			// ボーレートの初期値

		//***********************************************************************************
		#endregion

		#region フィールド
		//***********************************************************************************

		// 画面レイアウト関連
		private DrawWave[] wp;								// 波形描画オブジェクト配列

		// データバッファ
		private int rxBufSize;								// Rxバッファサイズ
		private byte[] rxBuf;								// 受信データ格納用バッファ
        private short tempWavBuf;						    // 波形データ用のテンポラリバッファ
        private byte prevCounterNum;				        // パケットカウンタの前回の値
        private int dataByteCounter;                        // データカウンタ
        private int timeScaleCounter;                       // 時間スケールカウンタ
        private short maxDataVal;                           // 描画データ最大値
        private short minDataVal;                           // 描画データ最小値
        System.Diagnostics.Stopwatch sw;                    // for debug

#if TEST_MODE
		private int debugCntr;
#endif

		// 同期処理関連
		delegate void DrawWaveDelegate(short val);
		private DrawWaveDelegate DrawWaveHandlar;

		//***********************************************************************************
		#endregion

		#region メソッド
		//***********************************************************************************

		/// <summary>
		/// コンストラクタ
		/// </summary>
		public FormMain()
		{
			// コントロールの初期化
			InitializeComponent();

			#region 波形描画コントロールの初期化

			wp = new DrawWave[waveAreaNum];

            // For waveform (signed)
            wp[waveformIndex] = new DrawWave(waveAreaSizeX, waveAreaSizeY, false);
            ((System.ComponentModel.ISupportInitialize)(wp[waveformIndex])).BeginInit();

			this.SuspendLayout();

            // For waveform
            this.wp[waveformIndex].Location
                = new System.Drawing.Point(waveAreaOffsetX, waveAreaOffsetY);
            this.wp[waveformIndex].Width = waveAreaSizeX;
            this.wp[waveformIndex].Height = waveAreaSizeY;
            this.wp[waveformIndex].DividerFlag = true;

            for (int i = 0; i < waveAreaNum; i++)
			{
				this.wp[i].Name = waveControlName + i.ToString();
				this.wp[i].WaveformLabel = (i < waveCaption.Length) ? waveCaption[i] : (i + 1).ToString();
				this.wp[i].TabStop = false;
				this.Controls.Add(wp[i]);
				((System.ComponentModel.ISupportInitialize)(wp[i])).EndInit();
			}

			this.ResumeLayout(false);

			#endregion

			// コントロールの初期化（波形描画コントロール以外）
			comboBoxPort.SelectedItem = defaultComPort;

			// フィールドの初期化
			rxBufSize = serialPortObj.ReadBufferSize;
			rxBuf = new byte[rxBufSize];
			tempWavBuf = 0;
            prevCounterNum = packetCntrMax;
            dataByteCounter = 0;
            timeScaleCounter = 0;
            maxDataVal = 0;
            minDataVal = 0; 
            sw = System.Diagnostics.Stopwatch.StartNew();

#if TEST_MODE
			debugCntr = 0;
#endif

			// 現在のスレッドの優先度を設定
			Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
		}

		/// <summary>
		/// ポートオープンボタンのON/OFFイベント
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void checkBoxOpen_CheckedChanged(object sender, EventArgs e)
		{
			if (((CheckBox)sender).Checked == true)
			{
				serialPortObj.PortName = comboBoxPort.SelectedItem.ToString();
				switchControlEnable(true);
				if (serialPortObj.IsOpen == false)
				{
					try
					{
						serialPortObj.Open();
					}
					catch(Exception ex)
					{
						MessageBox.Show("ポートをオープンできません。\n" + ex.Message, "エラー",
							MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
						((CheckBox)sender).Checked = false;
					}
				}
			}
			else
			{
				switchControlEnable(false);
				if (serialPortObj.IsOpen == true)
				{
					try
					{
						serialPortObj.Close();
					}
					catch (Exception ex)
					{
						MessageBox.Show(ex.Message, "エラー",
							MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
					}
				}
			}
		}

		/// <summary>
		/// コントロールの表示/非表示の切り替え
		/// </summary>
		/// <param name="portOpen">ポートがオープンの状態か</param>
		private void switchControlEnable(bool portOpen)
		{
			comboBoxPort.Enabled = !portOpen;
			checkBoxOpen.Text = (portOpen) ? "Close" : "Open";
		}

		/// <summary>
		/// メインフォームオープンイベント
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void FormMain_Load(object sender, EventArgs e)
		{
			// イベントハンドラをセット
			DrawWaveHandlar = drawWaveProc;
		}

		/// <summary>
		/// メインフォームクローズイベント
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void FormMain_FormClosed(object sender, FormClosedEventArgs e)
		{
			try
			{
				if (serialPortObj.IsOpen == true)
				{
					Thread.Sleep(50);
					serialPortObj.DiscardInBuffer();
					Thread.Sleep(50);
					serialPortObj.Close();
				}
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
		}

		/// <summary>
		/// 波形データを描画する
		/// </summary>
		/// <param name="val">波形データ値</param>
        private void drawWaveProc(short val)
        {
            if ((int)numericUpDownTimeScale.Value == 1)
            {
                // 波形描画（タイムスケール1倍のときは常に更新）
                wp[waveformIndex].SetWavePoint(clipShort((int)val * (int)numericUpDownVirticalGain.Value, false),
                                   clipShort((int)val * (int)numericUpDownVirticalGain.Value, false));
                timeScaleCounter = 0;
            }
            else
            {
                // 波形描画（タイムスケール1倍以外のときは間引いて更新）
                if (timeScaleCounter == 0)
                {
                    // 最大、最小値を初期化
                    maxDataVal = val;
                    minDataVal = val;
                }
                else
                {
                    // 最大、最小値を更新
                    maxDataVal = (maxDataVal < val) ? val : maxDataVal;
                    minDataVal = (minDataVal > val) ? val : minDataVal;
                }
                // 間引きカウンタが最大数に達したら波形を描画
                timeScaleCounter++;
                if (timeScaleCounter >= (int)numericUpDownTimeScale.Value)
                {
                    // 波形描画
                    wp[waveformIndex].SetWavePoint(clipShort((int)maxDataVal * (int)numericUpDownVirticalGain.Value, false),
                                       clipShort((int)minDataVal * (int)numericUpDownVirticalGain.Value, false));
                    timeScaleCounter = 0;
                }
            }
        }

		/// <summary>
		/// short型の範囲でクリップする
		/// </summary>
		/// <param name="val"></param>
        /// <param name="unsignedMode">符号なしモード</param>
		/// <returns></returns>
        private int clipShort(int val, bool unsignedMode)
        {
            int retVal = val;
            int maxVal;
            int minVal;

            if (unsignedMode)
            {
                maxVal = ushort.MaxValue;
                minVal = ushort.MinValue;
            }
            else
            {
                maxVal = short.MaxValue;
                minVal = short.MinValue;
            }

            retVal = (retVal > maxVal) ? maxVal : retVal;
            retVal = (retVal < minVal) ? minVal : retVal;

            return retVal;
        }

		/// <summary>
		/// シリアルポート受信イベント
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void serialPortObj_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
		{
            sw.Stop();
            Trace.WriteLine(sw.Elapsed);    // 受信頻度をデバッグウィンドウに出力
            sw.Restart();

			int readLen = 0;

			try
			{
				readLen = serialPortObj.Read(rxBuf, 0, rxBufSize);
			}
			catch
			{
				return;
			}

			if (readLen > 0)
            {
				for (int i = 0; i < readLen; i++)
				{
					byte rxVal = rxBuf[i];
                    
                    switch (recvState)
                    {
                        // 待機中
                        case RECV_STATE.RDY:
                            if (rxVal == packetHeader)
                            {
                                recvState = RECV_STATE.START;
                            }
                            break;
                        // パケット開始
                        case RECV_STATE.START:
                            if (rxVal == waveformId)
                            {
                                recvState = RECV_STATE.CNTR_WV; // 波形パケットカウンタへ（波形モード）
                            }
                            else
                            {
                                Trace.WriteLine("パケットIDが不正です。取得値：" + rxVal.ToString());
                                recvState = RECV_STATE.RDY;
                            }
                            break;
                        // 波形パケットカウンタ
                        case RECV_STATE.CNTR_WV:
                            bool packetCntrErrFlag = false;
                            if (rxVal == 0)
                            {
                                if (prevCounterNum != packetCntrMax)
                                {
                                    packetCntrErrFlag = true;
                                }
                            }
                            else if ((rxVal - prevCounterNum) != 1)
                            {
                                packetCntrErrFlag = true;
                            }
                            if (packetCntrErrFlag)
                            {
                                Trace.WriteLine("パケットカウンタの値が不連続です。現在値：" + rxVal.ToString()
                                    + "前回値" + prevCounterNum.ToString());
                            }
                            prevCounterNum = rxVal;

                            recvState = RECV_STATE.SIZE_WV;
                            break;
                        // 波形データサイズ
                        case RECV_STATE.SIZE_WV:
                            dataByteCounter = (int)rxVal;
                            recvState = RECV_STATE.WAVDAT;
                            break;
                        // 波形データ
                        case RECV_STATE.WAVDAT:
                            if ((dataByteCounter % 2)==0)
                            {
                                tempWavBuf = (short)(rxVal << 8);
                            }
                            else
                            {
                                tempWavBuf = (short)(tempWavBuf + rxVal);
                                this.BeginInvoke(DrawWaveHandlar, new object[] { tempWavBuf });
                            }
                            dataByteCounter--;
                            if (dataByteCounter <= 0)
                            {
                                recvState = RECV_STATE.RDY;
                            }
                            break;
                        default:
                            break;
                    }
				}
			}
		}

        /// <summary>
        /// シリアルポートエラー受信イベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
		private void serialPortObj_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
		{
			Trace.WriteLine("シリアルポートでエラーが発生しました。\nメッセージ：" + e.ToString());
		}

        /// <summary>
        /// Enter キーで PortOpen チェックボックスをON/OFFさせるため
        /// （buttonDummy はフォームのAcceptButtonに登録され、チェックボックスの背面に隠してある）
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonDummy_Click(object sender, EventArgs e)
        {
            checkBoxOpen.Checked = !checkBoxOpen.Checked;
        }

		//***********************************************************************************
		#endregion

	}
}
