//---------------------------------------------------------------------------------------------
//	リアルタイム・スペクトル・アナライザ
//
//		マイコン側のプログラム：TrG_FFT_Analyzer
//		ボー･レート
//			ポートの検索時：  9,600 baud
//			ポートの検索後：460,800 baud
//
//		マイコンから受け取るデータ：１つのデータ当たり 0 ～ 10,000 に対応する2文字
//
//		作成者：三上直樹，2022/12/21 作成，©三上直樹 2022
//---------------------------------------------------------------------------------------------

using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports;
using System.Drawing.Drawing2D;
using System.Threading;

using Mikami.Application;
using Mikami.WindowsApplication;

namespace CQ_FFT_Analyzer
{
	public partial class Form1 : Form
	{
		private const int BAUD_RATE_ = 460800;	// ボー･レート
		private const int N_Y_ = 501;			// 周波数方向の描画点数
		private const int N_AVR_ = 512;			// バッファのサイズ
		private const int MASK_ = N_AVR_ - 1;	// バッファ用インデックスのマスク

		private const float A1_ = 0.8f;			// 表示の平滑化用 LPF の係数
		private const float B0_ = 1.0f - A1_;	// 表示の平滑化用 LPF の係数

		// シリアルポートと関連して使うクラスのオブジェクト
		private readonly MySerialPort sp_;
		// COM ポートの自動選択で使うクラスのオブジェクト
		private readonly AutoSelect comAuto_;
		private readonly Label[] labels_;		// 周波数表示で使う Label
		private readonly float dx_;				// 横軸描画の際の単位量
		private readonly float yScale_;			// スペクトル表示の縦方向のスケール
		private readonly float orgY_;			// スペクトル表示の y 座標の原点

		private PointF[] pYn_ = new PointF[N_Y_];	// スペクトル表示用
		private float[] xn_ = new float[N_Y_];		// 一時保管用
		private float[] yn_ = new float[N_Y_];		// スペクトル表示用
		private float[,] xnM_ = new float[N_Y_, N_AVR_];	// 巡回形移動平均で使う過去の値
		private Color textColor_ = SystemColors.GrayText;	// Text の色
		private float offset_ = 80;		// 表示に際の縦方向の基準値
		private int nAvr_ = 1;			// 平均する数
		private int index_ = 0;			// バッファのインデックス
		private int count_ = 0;			// 平均した数のカウンタ
		private float oldTrackBarV_ =0;	// トラックバーの以前の値
		private bool peakHold_ = true;	// ピークホールドの有無
		private string rxData_;			// 受信データ用バッファ
		private int nData_;				// データ数
		private bool readOk_ = true;	// RUN のとき true

		public Form1()
		{
			InitializeComponent();

			labels_ = new[]{label1, label2, label3, label4, label5, label6};
			orgY_ = pictureBox1.Size.Height;
			dx_ = pictureBox1.Size.Width/(float)N_Y_;
			yScale_ = (pictureBox1.Size.Height-1)/80.0f;	// スペクトル表示の縦方向のスケール
			Array.Clear(yn_, 0, yn_.Length);
			Array.Clear(xnM_, 0, xnM_.Length);
			label14.Text = "1";
			CommonProc.Disable(panel1, panel2, panel3, button2, trackBar1);
			comboBox1.SelectedIndex = 0;	// 起動時の平均数は 1

			sp_ = new MySerialPort(serialPort1);
			comAuto_ = new AutoSelect("FFTAnalyzer", sp_, toolStripLabel1);
		}

		// マイコン側からのデータ取得実行の開始
		private void button1_Click(object sender, EventArgs e)
		{
			readOk_ = true;
			CommonProc.Disable(button1);
			CommonProc.Enable(button2, trackBar1);
			toolStripLabel1.Text = "RUN";
			RequestXfer();		// データ転送要求を送信
		}

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

		// クリア
		private void button3_Click(object sender, EventArgs e)
		{
			Array.Clear(yn_, 0, yn_.Length);
			Array.Clear(xnM_, 0, xnM_.Length);
			count_ = 0;
		}

		// ピークホールドの有効/無効の切替え
		private void radioButton1_Click(object sender, EventArgs e)
		{
			RadioButton rb = (RadioButton)sender;
			if (rb == radioButton1) peakHold_ = true;
			else					peakHold_ = false;
		}

		// スペクトルの表示レベルの調整
		private void trackBar1_Scroll(object sender, EventArgs e)
		{
			float value = trackBar1.Value/5.0f;
			label16.Text = value.ToString("0.0") + " dB";
			offset_ = value + 80;

			if (!readOk_)	// "STOP" の状態でレベル調整を可能にする
			{
				float dV = value - oldTrackBarV_;	// 変化分
				oldTrackBarV_ = value;
				for (int n=0; n<nData_; n++) pYn_[n].Y -= dV*yScale_;
				pictureBox1.Invalidate();
			}
		}

		// アプリ終了の際の処理
		private void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			readOk_ = false;
			Thread.Sleep(50);	// 50 ms 待つ
			Invoke(() => sp_.Break());
			sp_.Close();
		}

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

		// スペクトルの描画
		private void pictureBox1_Paint(object sender, PaintEventArgs e)
		{
			Graphics gr = e.Graphics;
			gr.SetClip(e.ClipRectangle);	// 描画領域を pictureBox1 のサイズに設定
			gr.SmoothingMode = SmoothingMode.AntiAlias;
			gr.DrawLines(Pens.Blue, pYn_);
			gr.SmoothingMode = SmoothingMode.Default;
		}

		// 平均する数を変えたときの処理
		private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
		{
			groupBox1.Enabled = (comboBox1.SelectedIndex == 0) ? true : false;
			label14.Enabled = !groupBox1.Enabled;

			nAvr_ = int.Parse(comboBox1.SelectedItem.ToString());	// 平均する数
			label14.Text = nAvr_.ToString();
			count_ = 0;

			Array.Clear(yn_, 0, yn_.Length);
			Array.Clear(xnM_, 0, xnM_.Length);
		}

		// データ受信イベントに対応するハンドラ
		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, panel2, panel3));
				textColor_ = SystemColors.ControlText;	// 文字の色を通常の色に設定する
				Invalidate();
			}
			else
				DrawSpectrum();		// "ACK" 受信以外の処理
		}
	}
}
