//---------------------------------------------------------------------------------------------
//	オシロスコープ
//
//		マイコン側のプログラム：TrG_Oscilloscope
//		ボー･レート
//			ポートの自動選択時：	  9,600 baud
//			ポートの選択後：		460,800 baud
//
//		作成者：三上直樹，2022/04/29 作成，©三上直樹 2022
//---------------------------------------------------------------------------------------------

using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports;
using Mikami.Application;
using Mikami.WindowsApplication;

namespace CQ_Oscilloscope
{
	public partial class Form1 : Form
	{
		private const int BAUD_RATE_ = 460800;	// ボー･レート
		private const int N_T_ = 1000;		// 受信するデータ数（１チャンネル当たり）
		private const int N_DISP_ = N_T_/2;	// 表示するデータ数（１チャンネル当たり）

		// シリアルポートと関連して使うクラスのオブジェクト
		private readonly MySerialPort sp_;
		// COM ポートの自動選択で使うクラスのオブジェクト
		private readonly AutoSelect comAuto_;

		private readonly Pen ch1Pen_ = Pens.Magenta;	// CH1 で使う Pen
		private readonly Pen ch2Pen_ = Pens.Blue;		// CH2 で使う Pen
		private readonly float unit_;		// 倍率１倍で最大振幅に対応するピクセル数
		private readonly int maxDisp_;		// 多重表示の最大表示数
		private readonly Label[] labels_;	// 横軸の目盛りで使用
		private readonly float[] y1Buf_ = new float[N_T_];
		private readonly float[] y2Buf_ = new float[N_T_];

		private int currentFrame_ = 0;
		private int traceN_ = 1;		// 描画する波形の数，最小は 1
		private string rxData_ = "";	// 受信データ用バッファ
		private int trgCh1_ = 100;		// CH1 のトリガに対応する trackBar1 の値
		private int trgCh2_ = -100;		// CH2 のトリガに対応する trackBar1 の値
		private bool single_ = false;	// Single モードのとき true
		private bool singleOk_ = false;	// Single モードでトリガがかかったとき true
		private bool readOk_ = false;	// シリアルポート読み込み可のとき true

		private float orgY1_, orgY2_;	// 縦軸の 0 の位置: CH1, CH2
		private float orgY1Old_, orgY2Old_;
		private PointF[][] pYn1_;		// CH1 の波形表示用
		private PointF[][] pYn2_;		// CH2 の波形表示用
		private int nData_;				// 全データ数
		private int chTrg_;				// トリガ･ソース
		private int mag1_, mag2_;		// 倍率: CH1, CH2

		// マイコンに送るパラメータ
		private int fs_;		// 標本化周波数 [kHz]

		public Form1()
		{
			InitializeComponent();

			CommonProc.Do(c => c.ForeColor = ch1Pen_.Color,
						  label3, label5, radioButton1, checkBox1);
			CommonProc.Do(c => c.ForeColor = ch2Pen_.Color,
						  label4, label6, radioButton2, checkBox2);
			CommonProc.Disable(button2, button3, panel1,
							   trackBar1, trackBar2, trackBar3,
							   label1, label5, label6, label7, label14, label16);
			labels_ = new[]{label8, label9, label10, label11, label12, label13};
			CommonProc.Disable(labels_);

			unit_ = pictureBox1.Size.Height/4;
			float height2 = pictureBox1.Size.Height/2.0f;
			orgY1_ = orgY1Old_ = height2 - unit_;	// 起動時の CH1 の原点
			orgY2_ = orgY2Old_ = height2 + unit_;	// 起動時の CH2 の原点

			chTrg_ = radioButton1.Checked ? 1 : 2;	// 起動時のトリガ･ソース

			comboBox1.SelectedIndex = 3;			// 起動時の標本化周波数等の設定
			FsXAxis();

			comboBox2.SelectedIndex = 0;			// 起動時の CH1 の倍率設定
			mag1_ = GetMagnification(comboBox2);
			comboBox3.SelectedIndex = 0;			// 起動時の CH2 の倍率設定
			mag2_ = GetMagnification(comboBox3);

			comboBox4.SelectedIndex = 0;			// 起動時の多重表示は１
			maxDisp_ = comboBox4.Items.Count;		// 最大多重表示数を取得

			pYn1_ = new PointF[maxDisp_][];
			for (int n=0; n<maxDisp_; n++) pYn1_[n] = new PointF[N_DISP_];
			pYn2_ = new PointF[maxDisp_][];
			for (int n=0; n<maxDisp_; n++) pYn2_[n] = new PointF[N_DISP_];

			sp_ = new MySerialPort(serialPort1);
			comAuto_ = new AutoSelect("Oscilloscope", sp_, toolStripLabel1);
			readOk_ = true;
		}

		// マイコン側からのデータ取得実行の開始
		private void button1_Click(object sender, EventArgs e)
		{
			readOk_ = true;
			button1.Enabled = false;
			button2.Enabled = true;
			toolStripLabel1.Text = "RUN";

			RequestXfer();		// データ転送を要求
		}

		// 停止
		private void button2_Click(object sender, EventArgs e)
		{
			readOk_ = false;
			button1.Enabled = true;
			button2.Enabled = false;
			toolStripLabel1.Text = "STOP";
		}

		// Single モードの際のトリガのリセット
		private void button3_Click(object sender, EventArgs e)
		{
			CommonProc.Disable(label16, button3);
			singleOk_ = true;
			pictureBox1.Invalidate();	// リセットした際に波形を消去するため
		}

		// アプリ終了の際の処理
		private void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			sp_.Break();
			sp_.Close();
		}

		// 座標軸等の描画
		private void Form1_Paint(object sender, PaintEventArgs e)
		{
			DrawAxis(e.Graphics, trackBar1.BackColor);
		}

		// 波形の表示
		private void pictureBox1_Paint(object sender, PaintEventArgs e)
		{
			if (singleOk_) return;	// Single モードに対応するため

			e.Graphics.SetClip(e.ClipRectangle);	// 描画領域を pictureBox1 のサイズに設定

			if (checkBox1.Checked) MultiDisplay(e.Graphics, ch1Pen_, pYn1_);
			if (checkBox2.Checked) MultiDisplay(e.Graphics, ch2Pen_, pYn2_);
		}

		// トリガ･レベル調整用トラックバーの値の保存
		private void trackBar1_Scroll(object sender, EventArgs e)
		{
			if (radioButton1.Checked) trgCh1_ = trackBar1.Value;
			else					  trgCh2_ = trackBar1.Value;
		}

		// チャンネル1の表示の位置を変更
		private void trackBar2_Scroll(object sender, EventArgs e)
		{
			VerticalPos(trackBar2, radioButton1,
						ref orgY1_, ref orgY1Old_, ref pYn1_);
		}

		// チャンネル2の表示の位置を変更
		private void trackBar3_Scroll(object sender, EventArgs e)
		{
			VerticalPos(trackBar3, radioButton2,
						ref orgY2_, ref orgY2Old_, ref pYn2_);
		}

		// comboBox1 のドロップダウン･リストが閉じたときに標本化周波数を設定する
		private void comboBox1_SelectionChangeCommitted(object sender, EventArgs e)
		{
			FsXAxis();
		}

		// comboBox2 のドロップダウン･リストが閉じたときにチャンネル 1 の倍率を設定する
		private void comboBox2_SelectionChangeCommitted(object sender, EventArgs e)
		{
			mag1_ = GetMagnification(comboBox2);
			label5.Text = "CH1 ： ×" + mag1_.ToString();
		}

		// comboBox3 のドロップダウン･リストが閉じたときにチャンネル 2 の倍率を設定する
		private void comboBox3_SelectionChangeCommitted(object sender, EventArgs e)
		{
			mag2_ = GetMagnification(comboBox3);
			label6.Text = "CH2 ： ×" + mag2_.ToString();
		}

		// comboBox4 のドロップダウン･リストが閉じたときに描画する波形の数を設定する
		private void comboBox4_SelectionChangeCommitted(object sender, EventArgs e)
		{
			traceN_ = int.Parse(comboBox4.Text);
		}

		// 表示チャンネルに対応する RadioButton, Trackbar の enable/disable のコントロール
		private void checkBox1_Click(object sender, EventArgs e)
		{
			radioButton1.Enabled = trackBar2.Enabled = checkBox1.Checked;
			radioButton2.Enabled = trackBar3.Enabled = checkBox2.Checked;

			if (checkBox1.Checked != checkBox2.Checked)
			{
				trackBar1.Value = checkBox1.Checked ? trgCh1_ : trgCh2_;
				if (checkBox1.Checked) radioButton1.Checked = true;
				if (checkBox2.Checked) radioButton2.Checked = true;
				chTrg_ = checkBox1.Checked ? 1 : 2;	// トリガ･ソース
			}
		}

		// トリガ･ソースの切り替え
		private void radioButton1_Click(object sender, EventArgs e)
		{
			trackBar1.Value = ((RadioButton)sender == radioButton1) ? trgCh1_ : trgCh2_;
			chTrg_ = radioButton1.Checked ? 1 : 2;	// トリガ･ソース
		}

		// トリガ･モードの切り替え
		private void radioButton3_Click(object sender, EventArgs e)
		{
			single_ = (RadioButton)sender == radioButton4;
			if (single_)	// Single モード
			{
				CommonProc.Disable(label15, comboBox4);
				traceN_ = 1;
				singleOk_ = true;		// Single モードのとき準備 OK
				pictureBox1.Invalidate();
			}
			else			// Auto モード
			{
				CommonProc.Enable(label15, comboBox4);
				traceN_ = int.Parse(comboBox4.Text);
			}
		}

		// データ受信イベントに対応するハンドラ
		private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			if (!readOk_) return;	// 終了時に ReadLine() で例外を発生しないようにするため

			rxData_ = sp_.ReadLine();
			if (rxData_ == "ACK")
			{
				comAuto_.StopTimer();	// タイムアウト監視タイマを停止
				toolStripLabel1.Text = "マイコン側の準備ができました．";
				toolStripLabel2.Text = " " + sp_.PortName + " ";
				sp_.BaudRate = BAUD_RATE_;		// ボー･レートを高速に設定する
				readOk_ = false;
				Invoke(() => CommonProc.Enable(panel1, trackBar1, trackBar2, trackBar3,
											   label1, label5, label6, label7, label14));
				Invoke(() => CommonProc.Enable(labels_));
			}
			else
				PreDisplay();	// "ACK" 受信以外の処理
		}
	}
}
