//------------------------------------------------------------------------------
// C# ConsoleView Control
// Copyright (C) 2011 Cores Co., Ltd. Japan
//------------------------------------------------------------------------------
// $Id: ConsoleView.cs 88 2011-04-05 11:03:57Z nagasima $
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms;

namespace ConsoleView
{
	public delegate void DataReceiveEventHandlear(object sender, byte[] data);

	public class ConsoleView : Control
	{
		struct ViewData
		{
			public char Data;
			public bool FullWidth;
			public Color ForeColor;
			public Color BackColor;
		}

		private RingBuffer<ViewData> m_ViewDatas;
		private Encoder m_Encoder;
		private Decoder m_Decoder;
		private Color m_CurrentForeColor;
		private Color m_CurrentBackColor;

		enum ParsingState
		{
			Normal,
			CR,
			LF,
			CRLF
		}

		ParsingState m_State;

		private Caret m_Caret;
		private ControlScrollBar m_VerticalScroll;
		private ControlScrollBar m_HorizontalScroll;
		private int m_ColumnIndex = 0;
		private int m_RowIndex = 0;
		private int m_ColumnCount = 80;
		private int m_RowCount = 25;
		private int m_CellWidth = 8;
		private int m_CellHeight = 14;
		private int m_Last;

		//---------------------------------------------------------------------
		/// <summary>
		/// コンストラクタ
		/// </summary>
		//---------------------------------------------------------------------
		public ConsoleView()
		{
			m_ViewDatas = new RingBuffer<ViewData>(80 * 1000);
			m_Encoder = Encoding.Default.GetEncoder();
			m_Decoder = Encoding.Default.GetDecoder();
			m_CurrentForeColor = Color.Black;
			m_CurrentBackColor = Color.White;

			m_Caret = new Caret(this);
			m_HorizontalScroll = new ControlScrollBar(this, ScrollOrientation.HorizontalScroll);
			m_VerticalScroll = new ControlScrollBar(this, ScrollOrientation.VerticalScroll);

			m_HorizontalScroll.Scroll += new ScrollEventHandler(HorizontalScroll_Scroll);
			m_VerticalScroll.Scroll += new ScrollEventHandler(VerticalScroll_Scroll);

			m_HorizontalScroll.Maximum = m_ColumnCount - 1;
			m_VerticalScroll.Maximum = m_RowCount;

			TabStop = true;

			Size = new Size(m_ColumnCount * m_CellWidth, m_RowCount * m_CellHeight);

			NewLine();
		}

		const int WS_VSCROLL = 0x00200000;
		const int WS_HSCROLL = 0x00100000;
		const int CS_VREDRAW = 0x0001;
		const int CS_HREDRAW = 0x0002;
		const int CS_DBLCLKS = 0x0008;

		//---------------------------------------------------------------------
		/// <summary>
		/// 
		/// </summary>
		//---------------------------------------------------------------------
		protected override CreateParams CreateParams
		{
			get
			{
				CreateParams cp = base.CreateParams;

				cp.Style |= WS_VSCROLL | WS_HSCROLL;
				cp.ClassStyle = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;

				return cp;
			}
		}

		protected override void OnCreateControl()
		{
			base.OnCreateControl();

			Rectangle client = ClientRectangle;

			client.Width = m_CellWidth;
			client.Height = m_CellHeight;

			m_Caret.Bounds = client;
			m_Caret.Visible = true;
		}

		protected override bool IsInputKey(Keys keyData)
		{
			return true;// base.IsInputKey(keyData);
		}

		/// <summary>垂直スクロールバー</summary>
		[Browsable(false)]
		public ControlScrollBar HorizontalScroll
		{
			get { return m_HorizontalScroll; }
		}

		/// <summary>水平スクロールバー</summary>
		[Browsable(false)]
		public ControlScrollBar VerticalScroll
		{
			get { return m_VerticalScroll; }
		}

		/// <summary>スクロールした画面の初めに表示される行のインデックス</summary>
		[Browsable(false)]
		public int FirstDisplayedScrollingRowIndex
		{
			get { return m_VerticalScroll.Value; }
			set
			{
				m_VerticalScroll.Value = value;

				Invalidate();
			}
		}

		/// <summary>スクロールした画面の初めに表示される列のインデックス</summary>
		[Browsable(false)]
		public int FirstDisplayedScrollingColumnIndex
		{
			get { return m_HorizontalScroll.Value; }
			set
			{
				m_HorizontalScroll.Value = value;

				Invalidate();
			}
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// Windowメッセージ処理
		/// </summary>
		/// <param name="m"></param>
		//---------------------------------------------------------------------
		protected override void WndProc(ref Message m)
		{
			switch (m.Msg)
			{
				case ControlScrollBar.WM_HSCROLL:
					m_HorizontalScroll.WmScroll(m);
					break;
				case ControlScrollBar.WM_VSCROLL:
					m_VerticalScroll.WmScroll(m);
					break;
			}

			base.WndProc(ref m);
		}

		//--------------------------------------------------------------------------
		/// <summary>フォーカス取得時</summary>
		//--------------------------------------------------------------------------
		protected override void OnGotFocus(EventArgs e)
		{
			base.OnGotFocus(e);
			m_Caret.OnGotFocus();
			this.Invalidate(); // カレント枠描画用
		}

		//--------------------------------------------------------------------------
		/// <summary>フォーカスを失った時 (カレント枠消去用)</summary>
		//--------------------------------------------------------------------------
		protected override void OnLostFocus(EventArgs e)
		{
			base.OnLostFocus(e);
			m_Caret.OnLostFocus();
			this.Invalidate();
		}

		protected override void OnPaintBackground(PaintEventArgs pevent)
		{
			//base.OnPaintBackground(pevent);
			Graphics graphics = pevent.Graphics;
			Rectangle client = ClientRectangle;
			Rectangle cells = new Rectangle(client.Left, client.Top, m_CellWidth * m_HorizontalScroll.PageSize, m_CellHeight * m_VerticalScroll.PageSize);
			Rectangle right = new Rectangle(cells.Right, client.Top, client.Width - cells.Width, cells.Height);
			Rectangle bottom = new Rectangle(client.Left, cells.Bottom, client.Width, client.Height - cells.Height);

			using (Brush brush = new SolidBrush(BackColor))
			{
				graphics.FillRectangle(brush, right);
				graphics.FillRectangle(brush, bottom);
			}
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// コントロールの描画
		/// </summary>
		/// <param name="e"></param>
		//---------------------------------------------------------------------
		protected override void OnPaint(PaintEventArgs e)
		{
			Graphics graphics = e.Graphics;
			Rectangle client = ClientRectangle;
			Rectangle clip = e.ClipRectangle;
			Rectangle cell = new Rectangle(client.Left, client.Top, m_CellWidth, m_CellHeight);
			int rowmin = m_VerticalScroll.Value;
			int colmin = m_HorizontalScroll.Value;
			int rowmax = rowmin + m_VerticalScroll.PageSize;
			int colmax = colmin + m_HorizontalScroll.PageSize;

			for (int row = rowmin; row < rowmax; row++)
			{
				cell.X = client.Left;

				for (int col = colmin; col < colmax; col++)
				{
					if (clip.IntersectsWith(cell))
						OnPaintCell(col, row, graphics, cell);

					cell.X += m_CellWidth;
				}

				cell.Y += m_CellHeight;
			}
		}

		private void OnPaintCell(int col, int row, Graphics graphics, Rectangle cell)
		{
			int index = col + row * m_ColumnCount;

			if (index >= m_ViewDatas.Count)
			{
				using (Brush brush = new SolidBrush(BackColor))
				{
					graphics.FillRectangle(brush, cell);
				}
				return;
			}

			ViewData viewData = m_ViewDatas[index];

			if (viewData.Data == '\0')
			{
				if (!viewData.FullWidth)
					using (Brush brush = new SolidBrush(BackColor))
					{
						graphics.FillRectangle(brush, cell);
					}
				return;
			}

			if (viewData.FullWidth)
				cell = new Rectangle(cell.Left, cell.Top, 2 * cell.Width, cell.Height);

			using (Brush brush = new SolidBrush(viewData.BackColor))
			{
				graphics.FillRectangle(brush, cell);
			}

			using (Brush brush = new SolidBrush(viewData.ForeColor))
			{
				graphics.DrawString(viewData.Data.ToString(), Font, brush, cell);
			}
		}

		protected override void OnResize(EventArgs e)
		{
			UpdatePageSize();

			base.OnResize(e);

			Invalidate();
		}

		private void UpdatePageSize()
		{
			Rectangle rect = ClientRectangle;
			int temp;

			temp = rect.Width / m_CellWidth;
			if (rect.Width - temp * m_CellWidth != 0)
				temp++;

			if (m_CellWidth * m_ColumnCount < rect.Width)
				m_HorizontalScroll.PageSize = m_ColumnCount;
			else
				m_HorizontalScroll.PageSize = temp;

			temp = rect.Height / m_CellHeight;
			if (rect.Height - temp * m_CellHeight != 0)
				temp++;

			if (m_CellHeight * (m_RowCount + 1) < rect.Height)
				m_VerticalScroll.PageSize = m_RowCount + 1;
			else
				m_VerticalScroll.PageSize = temp;
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// 水平スクロールバーWindowメッセージ処理
		/// </summary>
		/// <param name="sender">水平スクロールバー</param>
		/// <param name="e"></param>
		//---------------------------------------------------------------------
		void HorizontalScroll_Scroll(object sender, ScrollEventArgs e)
		{
			OnScroll(e);
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// 垂直スクロールバーWindowメッセージ処理
		/// </summary>
		/// <param name="sender">垂直スクロールバー</param>
		/// <param name="e"></param>
		//---------------------------------------------------------------------
		void VerticalScroll_Scroll(object sender, ScrollEventArgs e)
		{
			OnScroll(e);
		}

		//---------------------------------------------------------------------
		/// <summary>
		/// ユーザーまたはコードによってクライアント領域がスクロールされたときに発生します。
		/// </summary>
		//---------------------------------------------------------------------
		public event ScrollEventHandler Scroll;

		//---------------------------------------------------------------------------
		/// <summary>
		/// 派生先用スクロールイベント（スクロールイベントの呼び出し）
		/// </summary>
		/// <param name="e"></param>
		//---------------------------------------------------------------------------
		protected virtual void OnScroll(ScrollEventArgs e)
		{
			if ((e.ScrollOrientation == ScrollOrientation.VerticalScroll)
				&& (e.Type == ScrollEventType.Last))
				m_Last = e.NewValue;

			if (Scroll != null)
				Scroll(this, e);

			MoveCaret();

			Invalidate();
		}

		public event DataReceiveEventHandlear DataReceived;

		//---------------------------------------------------------------------------
		/// <summary>キーダウン（編集状態遷移と要素削除）</summary>
		/// <param name="e"></param>
		//---------------------------------------------------------------------------
		protected override void OnKeyDown(KeyEventArgs e)
		{
			char[] chars = new char[] { (char)e.KeyData };
			byte[] data = new byte[4];
			int inCount, outCount;
			bool completed;

			m_Encoder.Convert(chars, 0, chars.Length, data, 0, data.Length, false,
				out inCount, out outCount, out completed);

			//if (completed)
			//{
			Array.Resize(ref data, outCount);

			if (DataReceived != null)
				DataReceived(this, data);
			//}

			base.OnKeyDown(e);
		}

		//--------------------------------------------------------------------------
		/// <summary>キーアップ</summary>
		//--------------------------------------------------------------------------
		protected override void OnKeyUp(KeyEventArgs e)
		{
			base.OnKeyUp(e);
		}

		//--------------------------------------------------------------------------
		/// <summary>キープレス</summary>
		//--------------------------------------------------------------------------
		protected override void OnKeyPress(KeyPressEventArgs e)
		{
			base.OnKeyPress(e);
		}

		public void Parse(byte[] data, int offset, int count)
		{
			char[] chars = new char[count];
			int inCount, outCount;
			bool completed;

			m_Decoder.Convert(data, offset, count, chars, 0, chars.Length, false,
				out inCount, out outCount, out completed);

			Array.Resize(ref chars, outCount);

			//if (completed)
			//{
			foreach (char c in chars)
			{
				switch (m_State)
				{
					case ParsingState.Normal:
						switch (c)
						{
							case '\r':
								NewLine();
								m_State = ParsingState.CR;
								break;
							case '\n':
								NewLine();
								m_State = ParsingState.LF;
								break;
							default:
								PutChar(c);
								break;
						}
						break;
					case ParsingState.CR:
						if (c == '\n')
							m_State = ParsingState.CRLF;
						else
						{
							PutChar(c);
							m_State = ParsingState.Normal;
						}
						break;
					case ParsingState.LF:
						PutChar(c);
						m_State = ParsingState.Normal;
						break;
					case ParsingState.CRLF:
						PutChar(c);
						m_State = ParsingState.Normal;
						break;
				}
			}

			bool last = m_VerticalScroll.Value >= m_Last;

			m_RowCount = m_ViewDatas.Count / m_ColumnCount;
			if ((m_ViewDatas.Count - m_RowCount * m_ColumnCount) != 0)
				m_RowCount += 1;
			m_VerticalScroll.Maximum = m_RowCount;

			UpdatePageSize();

			m_Last = m_VerticalScroll.Maximum - m_VerticalScroll.PageSize;
			if (m_Last < 0)
				m_Last = 0;

			if (last)
				m_VerticalScroll.Value = m_Last + 1;
			//}

			Invalidate();
		}

		private void PutChar(char c)
		{
			bool FullWidth = IsFullWidth(c);

			if (m_ColumnIndex >= m_ColumnCount - (FullWidth ? 2 : 1))
				NewLine();

			int index = m_RowIndex * m_ColumnCount + m_ColumnIndex;

			ViewData item = new ViewData();
			item.Data = c;
			item.FullWidth = FullWidth;
			item.ForeColor = m_CurrentForeColor;
			item.BackColor = m_CurrentBackColor;

			m_ViewDatas[index] = item;

			m_ColumnIndex++;

			if (FullWidth)
			{
				item = new ViewData();
				item.Data = '\0';
				item.FullWidth = true;
				item.ForeColor = m_CurrentForeColor;
				item.BackColor = m_CurrentBackColor;

				m_ViewDatas[index + 1] = item;

				m_ColumnIndex++;
			}

			if (m_ColumnIndex == m_ColumnCount - 1)
				NewLine();

			MoveCaret();
		}

		private bool IsFullWidth(char c)
		{
			switch (EastAsianWidthInfo.GetEastAsianWidth(c))
			{
				// おおむね全角
				case EastAsianWidth.Fullwidth:
				case EastAsianWidth.Wide:
					return true;
				// 東アジアでは全角とする
				case EastAsianWidth.Ambiguous:
					return true;
				// 半角（というか普通の文字幅）
				default:
					return false;
			}
		}

		private void NewLine()
		{
			for (int rest = m_ColumnCount - m_ViewDatas.Count % m_ColumnCount; rest > 0; rest--)
			{
				ViewData item = new ViewData();
				item.Data = '\0';
				item.FullWidth = false;
				item.ForeColor = m_CurrentForeColor;
				item.BackColor = m_CurrentBackColor;

				m_ViewDatas.Add(item);
			}
			m_ColumnIndex = 0;
			m_RowIndex = m_ViewDatas.Count / m_ColumnCount - 1;

			MoveCaret();
		}

		private void MoveCaret()
		{
			Rectangle client = ClientRectangle;
			Point pos = new Point();

			pos.X = client.Left + (m_ColumnIndex - m_HorizontalScroll.Value) * m_CellWidth;
			pos.Y = client.Top + (m_RowIndex - m_VerticalScroll.Value) * m_CellHeight;

			m_Caret.Location = pos;
		}
	}
}
