//------------------------------------------------------------------------------
//	FrqResponsePlotter クラス
//		周波数特性のグラフを描画する
//
//		作成者：三上直樹，2022/02/25 作成，©三上直樹 2022
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Numerics;
using System.Drawing.Drawing2D;

using static System.Math;
using static System.Numerics.Complex;

namespace Mikami.Application
{
	public class FrqResponsePlotter
	{
		private readonly PictureBox pictureBox_;
		private readonly float dbMin_;		// 表示する dB 値の最小値
		private readonly float dbMax_;		// 表示する dB 値の最大値
		private readonly float x0_;			// 表示する最小周波数の Form 上の位置
		private readonly float y0_;			// 表示する最小 dB 値の PictureBox 上の位置
		private readonly float db1_;		// 1 dB に対応するピクセル数
		private readonly float y0dB_;		// 0 dB に対応する位置
		private readonly float width_;		// グラフの幅（ピクセル数）
		private readonly float offset_;		// x0_ に対するオフセット，Get2nd() で使用
		private readonly float frqMin_;		// 表示する周波数の最小値
		private readonly float frqMax_;		// 表示する周波数の最大値
		private readonly float decade_;		// 横軸の 10 倍に対応するピクセル数
		private readonly float tS_;			// 標本化間隔
		private readonly Complex j2PiTs_;	// j×2π×標本化間隔	

		// コンストラクタ
		public FrqResponsePlotter(PictureBox myPictureBox, float frqMin, float frqMax,
								  float dbMin, float dbMax, float samplingFrq)
		{
			pictureBox_ = myPictureBox;
			dbMin_ = dbMin;
			dbMax_ = dbMax;
			x0_ = pictureBox_.Left;
			y0_ = pictureBox_.ClientSize.Height - 1;
			db1_ = y0_/(dbMax_ - dbMin_);
			y0dB_ = pictureBox_.ClientSize.Height/2;
			width_ = pictureBox_.ClientSize.Width - 1;

			frqMin_ = frqMin;
			frqMax_ = frqMax;
			decade_ = width_/(float)Log10(frqMax_/frqMin_);

			tS_ = 1.0f/samplingFrq;
			j2PiTs_ = ImaginaryOne*2.0*PI*tS_;

			offset_ = x0_;
		}

		// 座標軸の描画
		public void DrawAxis(Graphics gr)
		{
			// 垂直方向の線の描画
			DrawVerticalLines(gr, Pens.Black);
			// 水平方向の線の描画
			for (int n=(int)dbMin_; n<=(int)dbMax_; n+=6)
				gr.DrawLine(Pens.Black, 0, y0dB_-db1_*n, width_, y0dB_-db1_*n);
		}

		// 座標軸の目盛値やタイトルの描画
		public void DrawCharacters(Graphics gr)
		{
			Font myFont = new Font("Meiryo UI", 10);
			SolidBrush myBrush = new SolidBrush(Color.Black);

			float x00 = Get2nd(frqMin_);
			float y00 = pictureBox_.Bottom + 3;

			// 横軸の目盛値，タイトル
			int[] frq = {50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000};
			for (int n=0; n<frq.Length; n++)
				if (frq[n] < 1000)
					gr.DrawString($"{frq[n], 3:D}",
								  myFont, myBrush, Get2nd(frq[n])-14, y00);
				else
					gr.DrawString($"{frq[n]/1000.0f, 2:F0}k",
								  myFont, myBrush, Get2nd(frq[n])-14, y00);

			gr.DrawString("周波数 [Hz]", myFont, myBrush, x0_+(width_-80)/2, y00+20);

			// 縦軸の目盛値，タイトル
			float y0db = pictureBox_.Top + y0dB_ - 10;
			gr.DrawString("-24", myFont, myBrush, x00-27, y0db+db1_*24);
			gr.DrawString("-12", myFont, myBrush, x00-27, y0db+db1_*12);
			gr.DrawString("0", myFont, myBrush, x00-12, y0db-db1_*0);
			gr.DrawString("12", myFont, myBrush, x00-21, y0db-db1_*12);
			gr.DrawString("24", myFont, myBrush, x00-21, y0db-db1_*24);
			
			gr.RotateTransform(-90);
			gr.DrawString("利得 [dB]", myFont, myBrush, -y00+(y0_-58)/2, x00-46);
			gr.ResetTransform();
		}

		// 与えられた周波数に対応する垂直方向の線の描画
		public void DrawVerticalLine(Graphics gr, Pen myPen, float f)
		{
			gr.DrawLine(myPen, Get(f), y0_, Get(f), y0_+(dbMin_-dbMax_)*db1_);
		}

		// 周波数特性の描画
		public void DrawCurve(Graphics gr, Pen myPen, Func<Complex, Complex> Hf)
		{
			if (Hf(0) == null) return;	// 設計がまだの場合

			// アンチエイリアシング処理を有効にしてグラフを描画する
			gr.SmoothingMode = SmoothingMode.AntiAlias;

			float y_dB0 = db1_*dbMax_;
			float df = (float)Exp(Log(frqMax_/frqMin_)/(2.0*width_));
			List<PointF> pt = new List<PointF>();
			for (float f=frqMin_*df; f<=frqMax_; f*=df)
				pt.Add(new PointF(Get(f), -db1_*ToDecibel(Hf, f) + y_dB0));
			gr.DrawLines(myPen, pt.ToArray());

			// アンチエイリアシング処理を無効にする
			gr.SmoothingMode = SmoothingMode.Default;
		}

		// x 座標値を周波数に変換する
		public float ToFrequency(int x)
		{
			return (float)(frqMin_*Pow(10.0, x/decade_));
		}

		// 伝達関数に対応する周波数応答の絶対値を dB に変換
		private float ToDecibel(Func<Complex, Complex> Hf, float f)
		{
			double absV = Abs(Hf(Exp(-f*j2PiTs_)));
			double logAbsV = (absV > 0.0) ? 20.0*Log10(absV) : -100; 
			return (float)logAbsV;
		}

		// 周波数軸（横軸）の数値に対する目盛線の描画
		private void DrawVerticalLines(Graphics gr, Pen aPen)
		{
			int iLogFrqMin = (int)Truncate(Log10(frqMin_));
			int loop = (int)Truncate(Log10(frqMax_)) - iLogFrqMin;

			DrawVerticalLine(gr, aPen, frqMin_);
			float df = (float)Pow(10.0, iLogFrqMin);
			float f1 = (float)(Truncate(frqMin_/df) + 1)*df;
			for (int n=0; n<=loop; n++)
			{
				float fMax = Min(10*df, frqMax_);
				for (float f=f1; f<fMax; f+=df)	DrawVerticalLine(gr, aPen, f);
				df = fMax;
				f1 = df;
			}
			DrawVerticalLine(gr, aPen, frqMax_);
		}

		// Logスケールに変換した値の取り出し
		private float Get(float f)
		{
			return decade_*(float)Log10(f/frqMin_);
		}

		// Logスケールに変換した値の取り出し，オフセット付き
		private float Get2nd(float f)
		{
			return Get(f) + offset_;
		}
	}
}
