using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using JDESUNAPQuoteOBJLib;

namespace TickSample
{
    public partial class MainForm : Form
    {
        JDESUNAPQuoteOBJLib.ApplicationJDESUNAPQuote m_app = null;
        string m_strSymbolID = "";
        List<Tick> m_ticks = new List<Tick>(1024);

        // Quote field index
        //
		const int QFIELD_BESTBID1			=  8;
		const int QFIELD_BESTBID2			=  9;
		const int QFIELD_BESTBID3			= 10;
		const int QFIELD_BESTBID4			= 11;
		const int QFIELD_BESTBID5			= 12;
		const int QFIELD_BESTASK1			= 18;
		const int QFIELD_BESTASK2			= 19;
		const int QFIELD_BESTASK3			= 20;
		const int QFIELD_BESTASK4			= 21;
		const int QFIELD_BESTASK5			= 22;
        const int QFIELD_TICKINDEX			= 32;
        const int QFIELD_TICKTIME			= 33;
        const int QFIELD_TICKPRICE			= 34;
        const int QFIELD_TICKBID			= 36;
        const int QFIELD_TICKASK			= 37;
        const int QFILED_TICKVOLUME			= 38;
        const int QFILED_TICKTOTALVOLUME	= 39;
		const int QFILED_DP					= 53;
        
        // Tick field index
        //
        const int TFIELD_INDEX = 0;
        const int TFIELD_TIME = 1;
        const int TFIELD_PRICE = 2;
        const int TFIELD_BID = 4;
        const int TFIELD_ASK = 5;
        const int TFIELD_VOLUME = 6;
        const int TFIELD_TOTALVOLUME = 7;

		static int[] _base10 = new int[] { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
                                
        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            try
            {
                m_app = new JDESUNAPQuoteOBJLib.ApplicationJDESUNAPQuote();
                m_app.OnSymbolSunrise += new IJDESUNAPQuoteNotify_OnSymbolSunriseEventHandler(m_app_OnSymbolSunrise);
                m_app.OnSymbolTick += new IJDESUNAPQuoteNotify_OnSymbolTickEventHandler(m_app_OnSymbolTick);
				m_app.OnSymbolRefresh += new IJDESUNAPQuoteNotify_OnSymbolRefreshEventHandler(m_app_OnSymbolRefresh);
                m_app.OnSystemStatus += new IJDESUNAPQuoteNotify_OnSystemStatusEventHandler(m_app_OnSystemStatus);
                m_app.OnSymbolTickList += new IJDESUNAPQuoteNotify_OnSymbolTickListEventHandler(m_app_OnSymbolTickList);
                
                m_app.Init();
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        void m_app_OnSymbolTickList(string SymbolID, int lStartIdx, int lTotalCount)
        {
            AddLog(true, "SystemTickList change: ID={0}, Start={1}, Count={2}", SymbolID, lStartIdx, lTotalCount);

            List<Tick> ticks = ReadTicks(SymbolID, lStartIdx, lTotalCount);

            // Show the last 10 ticks
            //
            for (int i = Math.Max(ticks.Count - 10, 0); i < ticks.Count; i++)
            {
                Tick tick = ticks[i];
                AddLog(false, FormatTick(tick.Idx-1, tick));
            }
        }

        void m_app_OnSystemStatus(bool bOn)
        {
            AddLog(true, "SystemStatus change: CurrentStatus={0}", bOn);
        }

		void m_app_OnSymbolRefresh(string SymbolID, object val)
		{
			if (SymbolID != m_strSymbolID)
				return;

			try
			{
				UpdateFiveBidAsk((object[])val);
			}
			catch (Exception ex)
			{
				AddLog(true, "OnSymbolRefresh Exception:[{0}]", ex.Message);
			}
		}

        void m_app_OnSymbolTick(string SymbolID, object val)
        {
            if (SymbolID != m_strSymbolID)
                return;

            try
            {
				UpdateFiveBidAsk((object[])val);
                ProcessTickUpdate((object[])val);
            }
            catch(Exception ex)
            {
                AddLog(true, "OnSymbolTick Exception:[{0}]", ex.Message);
            }                
        }

        void m_app_OnSymbolSunrise(string SymbolID, object val)
        {
            if (SymbolID != m_strSymbolID)
                return;

            try
            {
				ClearFiveBidAsk();
                ClearLog();

                AddLog(true, "Symbol Sunrise");

                m_ticks.Clear();

                InitTicks();
            }
            catch(Exception ex)
            {
                AddLog(true, "OnSymbolSunrise Exception:[{0}]", ex.Message);
            }    
        }

		void UpdateFiveBidAsk(object[] qd)
		{
			int nDP = (int)qd[QFILED_DP];
			double dBestBid1 = Convert.ToDouble(qd[QFIELD_BESTBID1]) / _base10[nDP];
			double dBestBid2 = Convert.ToDouble(qd[QFIELD_BESTBID2]) / _base10[nDP];
			double dBestBid3 = Convert.ToDouble(qd[QFIELD_BESTBID3]) / _base10[nDP];
			double dBestBid4 = Convert.ToDouble(qd[QFIELD_BESTBID4]) / _base10[nDP];
			double dBestBid5 = Convert.ToDouble(qd[QFIELD_BESTBID5]) / _base10[nDP];
			double dBestAsk1 = Convert.ToDouble(qd[QFIELD_BESTASK1]) / _base10[nDP];
			double dBestAsk2 = Convert.ToDouble(qd[QFIELD_BESTASK2]) / _base10[nDP];
			double dBestAsk3 = Convert.ToDouble(qd[QFIELD_BESTASK3]) / _base10[nDP];
			double dBestAsk4 = Convert.ToDouble(qd[QFIELD_BESTASK4]) / _base10[nDP];
			double dBestAsk5 = Convert.ToDouble(qd[QFIELD_BESTASK5]) / _base10[nDP];

			BeginInvoke((MethodInvoker)delegate()
			{
				labBestBid1.Text = (dBestBid1 == 0) ? "" : Convert.ToString(dBestBid1);
				labBestBid2.Text = (dBestBid2 == 0) ? "" : Convert.ToString(dBestBid2);
				labBestBid3.Text = (dBestBid3 == 0) ? "" : Convert.ToString(dBestBid3);
				labBestBid4.Text = (dBestBid4 == 0) ? "" : Convert.ToString(dBestBid4);
				labBestBid5.Text = (dBestBid5 == 0) ? "" : Convert.ToString(dBestBid5);
				labBestAsk1.Text = (dBestAsk1 == 0) ? "" : Convert.ToString(dBestAsk1);
				labBestAsk2.Text = (dBestAsk2 == 0) ? "" : Convert.ToString(dBestAsk2);
				labBestAsk3.Text = (dBestAsk3 == 0) ? "" : Convert.ToString(dBestAsk3);
				labBestAsk4.Text = (dBestAsk4 == 0) ? "" : Convert.ToString(dBestAsk4);
				labBestAsk5.Text = (dBestAsk5 == 0) ? "" : Convert.ToString(dBestAsk5);
			});
		}

		void ClearFiveBidAsk()
		{
			BeginInvoke((MethodInvoker)delegate()
			{
				labBestBid1.Text = "";
				labBestBid2.Text = "";
				labBestBid3.Text = "";
				labBestBid4.Text = "";
				labBestBid5.Text = "";
				labBestAsk1.Text = "";
				labBestAsk2.Text = "";
				labBestAsk3.Text = "";
				labBestAsk4.Text = "";
				labBestAsk5.Text = "";
			});
		}

        void AddLog(bool AddTime, string strFormat, params object[] args)
        {
            string strMsg = string.Format(strFormat, args);
            if (AddTime)
            {
                strMsg = string.Format("[{0:HH:mm:ss}] {1}", DateTime.Now, strMsg);
            }
            
            BeginInvoke((MethodInvoker)delegate()
            {
                DisplayLog(txtLog, strMsg);
            });
        }

        void ClearLog()
        {
            BeginInvoke((MethodInvoker)delegate()
            {
                txtLog.Text = "";
            });
        }

        void DisplayLog(TextBox txtBox, string strMsg)
        {
            txtBox.AppendText(strMsg);
            txtBox.AppendText("\r\n");
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            try
            {
                if (m_app != null)
                {
                    m_app = null;
                }
            }
            catch
            {
            }
        }

        private void btnUpdate_Click(object sender, EventArgs e)
        {
            try
            {
                string strSymbolID = txtSymbolID.Text.Trim();
                if (strSymbolID == "")
                    throw new Exception("Please enter a valid symbol ID");
                
                if (strSymbolID.IndexOf('.') <= 0)
                    throw new Exception("Please enter a valid symbol ID");

                if (m_strSymbolID != "")
                {
                    m_app.RemoveSymbolTick(m_strSymbolID);
                }

				ClearFiveBidAsk();
				ClearLog();
                
                m_strSymbolID = strSymbolID;
                m_app.AddSymbolTick(m_strSymbolID);

                InitTicks();
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        void ProcessTickUpdate(object[] qd)
        {
            // Check if qd's TickIndex follows current Tick list
            //
            int idxTick = (int)qd[QFIELD_TICKINDEX];

			//AddLog(true, "TickUpdate Index={0} Mine={1}", idxTick, m_ticks.Count);

			int nDP = (int)qd[QFILED_DP];
            
            if (idxTick < m_ticks.Count)
            {
                // 必須重新讀資料
                //
                AddLog(true, "ResetTicks");
                InitTicks();
            }
            else if (idxTick == m_ticks.Count || idxTick == m_ticks.Count + 1)
            {
                // Update last tick or append one tick
                //
                Tick tick = new Tick();
                tick.Time = (int)qd[QFIELD_TICKTIME];
				tick.Price = Convert.ToDouble(qd[QFIELD_TICKPRICE]) / _base10[nDP];
				tick.Bid = Convert.ToDouble(qd[QFIELD_TICKBID]) / _base10[nDP];
				tick.Ask = Convert.ToDouble(qd[QFIELD_TICKASK]) / _base10[nDP];
                tick.Volume = (int)qd[QFILED_TICKVOLUME];
                tick.TotalVolume = (int)qd[QFILED_TICKTOTALVOLUME];
                if (idxTick == m_ticks.Count)
                    m_ticks.RemoveAt(m_ticks.Count - 1);
                m_ticks.Add(tick);
                AddLog(true, "AddTick {0}", FormatTick(m_ticks.Count - 1, tick));
            }
            else /* idxTick > m_ticks.Count + 1 */
            {
                AddLog(true, "Jump Ticks: Mine=[{0}] New=[{1}]", m_ticks.Count, idxTick);

                // 補出漏掉的ticks
                // 
                int idxLast = m_ticks.Count + 1;    // 1-based
                int nCount = idxTick - idxLast + 1; // count
                object[,] td = (object[,])m_app.GetTick(m_strSymbolID,  idxLast, nCount);
                if (nCount != td.GetLength(0))
                {
                    AddLog(true, "ReadTick has invalid count");
                    InitTicks();
                    return;
                }    
                
                for (int i = 0; i < nCount; i++)
                {
                    Tick tick = new Tick();                    
                    tick.Time = (int)td[i, TFIELD_TIME];
					tick.Price = Convert.ToDouble(td[i, TFIELD_PRICE]) / _base10[nDP];
					tick.Bid = Convert.ToDouble(td[i, TFIELD_BID]) / _base10[nDP];
					tick.Ask = Convert.ToDouble(td[i, TFIELD_ASK]) / _base10[nDP];
                    tick.Volume = (int)td[i, TFIELD_VOLUME];
                    tick.TotalVolume = (int)td[i, TFIELD_TOTALVOLUME];

                    m_ticks.Add(tick);

                    AddLog(true, "AddTick {0}", FormatTick(m_ticks.Count - 1, tick));
                }
            }
        }
        
        void InitTicks()
        {
			object[] qd = (object[])m_app.GetQuote(m_strSymbolID);
			UpdateFiveBidAsk(qd);

            m_ticks = ReadTicks(m_strSymbolID);

            AddLog(true, "Read ticks: count={0}", m_ticks.Count);
            // Show the last 10 ticks
            //
            for (int i = Math.Max(m_ticks.Count - 10, 0); i < m_ticks.Count; i++)
            {
                Tick tick = m_ticks[i];
                AddLog(false, FormatTick(i, tick));
            }
        }

        List<Tick> ReadTicks(string strSymbolID)
        {
            List<Tick> ticks = new List<Tick>(1024);
            // Read quote to get tick count
            //
            object[] qd = (object[])m_app.GetQuote(strSymbolID);
            int idxTick = (int)qd[QFIELD_TICKINDEX];
			int nDP = (int)qd[QFILED_DP];
            if (idxTick > 0)
            {
                object[,] td = (object[,])m_app.GetTick(strSymbolID, 1, idxTick);
                for (int i = 0; i <= td.GetUpperBound(0); i++)
                {
                    Tick tick = new Tick();
                    tick.Time = (int)td[i, TFIELD_TIME];
					tick.Price = Convert.ToDouble(td[i, TFIELD_PRICE]) / _base10[nDP];
					tick.Bid = Convert.ToDouble(td[i, TFIELD_BID]) / _base10[nDP];
					tick.Ask = Convert.ToDouble(td[i, TFIELD_ASK]) / _base10[nDP];
                    tick.Volume = (int)td[i, TFIELD_VOLUME];
                    tick.TotalVolume = (int)td[i, TFIELD_TOTALVOLUME];
                    
                    ticks.Add(tick);
                }
            }
            return ticks;
        }

        List<Tick> ReadTicks(string strSymbolID, int StartIdx, int lCount)
        {
			object[] qd = (object[])m_app.GetQuote(strSymbolID);
			int nDP = (int)qd[QFILED_DP];

            List<Tick> ticks = new List<Tick>(1024);

            object[,] td = (object[,])m_app.GetTick(strSymbolID, StartIdx, lCount);
            for (int i = 0; i <= td.GetUpperBound(0); i++)
            {
                Tick tick = new Tick();
                tick.Idx = (int)td[i, TFIELD_INDEX];
                tick.Time = (int)td[i, TFIELD_TIME];
				tick.Price = Convert.ToDouble(td[i, TFIELD_PRICE]) / _base10[nDP];
				tick.Bid = Convert.ToDouble(td[i, TFIELD_BID]) / _base10[nDP];
				tick.Ask = Convert.ToDouble(td[i, TFIELD_ASK]) / _base10[nDP];
                tick.Volume = (int)td[i, TFIELD_VOLUME];
                tick.TotalVolume = (int)td[i, TFIELD_TOTALVOLUME];

                ticks.Add(tick);
            }

            return ticks;
        }

        string FormatTick(int idxTick, Tick tick)
        {
            return string.Format("[{0}] Time={1} P={2} V={3} TV={4}",
                idxTick,
                FormatTime(tick.Time),
                tick.Price,
                tick.Volume,
                tick.TotalVolume);
        }
        
        string FormatTime(int time)
        {
            if (time < 0)
                return "EMPTY";
                
            string str = time.ToString();
            while (str.Length < 6)
                str = "0" + str;
            
            return str;    
        }

        private void btnClearLog_Click(object sender, EventArgs e)
        {
            try
            {
                ClearLog();
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }                
        }

        private void btnRemove_Click(object sender, EventArgs e)
        {
            try
            {
                if (m_strSymbolID == "")
                    return;
                
                m_app.RemoveSymbolTick(m_strSymbolID);
                m_ticks.Clear();
                m_strSymbolID = "";    
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }

    class Tick
    {
        public int		Time;        // HHmmss
        public int		Idx;
		public double	Price;
		public double	Bid;
		public double	Ask;
        public int		Volume;
        public int		TotalVolume;
    }
}
