using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace AutoSellerNS
{
	public class CybosHelper
	{
		AutoSeller m_Listener = null;

		CPUTILLib.CpCybos m_CPCybos = new CPUTILLib.CpCybos();
		CPFORETRADELib.CpForeTdUtil m_CPUtil = new CPFORETRADELib.CpForeTdUtil();
		DSCBO1Lib.CpConclusion m_CpConclusion = new DSCBO1Lib.CpConclusion();

		Dictionary<string, STOCK_CUR_ITEM> m_aStockCur = new Dictionary<string, STOCK_CUR_ITEM>();

		object m_RequestRQLock = new object();
		object m_RequestTRLock = new object();

		CPTRADELib.CpTd6033 m_CP6033 = new CPTRADELib.CpTd6033();
		CPTRADELib.CpTd5339 m_CP5339 = new CPTRADELib.CpTd5339();
		CPTRADELib.CpTd5342 m_CP5342 = new CPTRADELib.CpTd5342();

		ConcurrentDictionary<string, int> m_SellingCode = new ConcurrentDictionary<string, int>();
		ConcurrentDictionary<int, string> m_ItemsInCorrection = new ConcurrentDictionary<int, string>();

		public CybosHelper(AutoSeller Listener)
		{
			m_Listener = Listener;
			m_CP6033.Received += CP6033_Received;
			m_CP5339.Received += CP5339_Received;
		}

        public void InitCybos()
        {
            short iResult = m_CPUtil.TradeInit();
            switch (iResult)
            {
                case -1:
                    Util.Log(Util.LOG_TYPE.ERROR, "[TradeInit] 오류");
                    break;

                case 0:
                    Util.Log(Util.LOG_TYPE.VERBOSE, "[TradeInit] 로그인 되었습니다");
                    break;

                case 1:
                    Util.Log(Util.LOG_TYPE.ERROR, "[TradeInit] 업무 키 입력 잘못됨");
                    break;

                case 2:
                    Util.Log(Util.LOG_TYPE.ERROR, "[TradeInit] 계좌 비밀번호가 잘못되었습니다");
                    break;

                case 3:
                    Util.Log(Util.LOG_TYPE.ERROR, "[TradeInit] 취소되었습니다");
                    break;
            }

			m_Listener.SetAccountList(m_CPUtil.AccountNumber);
            m_CpConclusion.Received += CpConclusion_Received;
            m_CpConclusion.Subscribe();
        }

		public async Task InitCybosAsync()
		{
			await Task.Run(() =>
			{
				InitCybos();
			});
		}

		public void SavePrice(STOCK_CUR_ITEM item)
		{
			if (Directory.Exists(Util.GetLogPath()) == false)
				Directory.CreateDirectory(Util.GetLogPath());

			string strToday = DateTime.Now.ToString("yyyy-MM-dd");
			string strFilePath = Util.GetLogPath() + "/price-" + strToday + item.m_strCodeName + ".csv";
			string strMessage = "시간, 동시호가, 현재가, 매도호가, 매수호가, 거래량, 순간체결량, 평균, 표준편차, 매도 제한, 매도 수, 시가 대비";
			File.AppendAllText(strFilePath, strMessage + Environment.NewLine, new UTF8Encoding(true));
			lock(item.m_PriceList)
			{
				foreach (var node in item.m_PriceList)
				{
					strMessage = string.Format("{0}:{1}:{2}, , {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}, {11}, {12:0.00}",
						node.m_iTime / 10000, (node.m_iTime / 100) % 100, node.m_iTime % 100,
						node.m_iPrice, node.m_iAskPrice, node.m_iBidPrice,
						node.m_iAccDealCnt, node.m_iDealCnt,
						node.m_dAvg, node.m_dSD, node.m_dSellLimit, node.m_iSellCnt, (node.m_iPrice - item.m_PriceList[0].m_iPrice) * 100 / (double)item.m_PriceList[0].m_iPrice);
					File.AppendAllText(strFilePath, strMessage + Environment.NewLine, new UTF8Encoding(true));
				}
			}
		}

		int iDibWaitCnt = 0;
		void BlockRequest(DSCBO1Lib.IDib dib)
		{
			//lock (m_RequestRQLock)
			{
				while (GetLimitRemainCountRQ() < 2)
				{
					iDibWaitCnt++;
					Thread.Sleep(500);
					iDibWaitCnt--;
				}

				var ret = dib.BlockRequest2(0);
				if (ret == -1)
					Util.Log(Util.LOG_TYPE.DEBUG, "BlockRequest RQ 오류");
				else if (ret == 1)
					Util.Log(Util.LOG_TYPE.DEBUG, "BlockRequest RQ 수신대기");
			}
		}

		int iTdDibWaitCnt = 0;
		ConcurrentQueue<CPTRADELib.ICpTdDib> m_TRQueue = new ConcurrentQueue<CPTRADELib.ICpTdDib>();
		void BlockRequest(CPTRADELib.ICpTdDib dib)
		{
			m_TRQueue.Enqueue(dib);

			CPTRADELib.ICpTdDib tr;
			bool bContinue = true;
			while(bContinue == true && m_TRQueue.TryDequeue(out tr) == true)
			{
				var ret = tr.BlockRequest2(0);
				if (ret == -1)
					Util.Log(Util.LOG_TYPE.DEBUG, "BlockRequest TR 오류");
				else if (ret == 1)
					Util.Log(Util.LOG_TYPE.DEBUG, "BlockRequest TR 수신대기");
				bContinue = (ret == 0);
			}

			////lock (m_RequestTRLock)
			//{
			//	while (GetLimitRemainCountTrade() < 2)
			//	{
			//		iTdDibWaitCnt++;
			//		Thread.Sleep(500);
			//		iTdDibWaitCnt--;
			//	}

			//	var ret = dib.BlockRequest2(0);
			//	if (ret == -1)
			//		Util.Log(Util.LOG_TYPE.DEBUG, "BlockRequest TR 오류");
			//	else if (ret == 1)
			//		Util.Log(Util.LOG_TYPE.DEBUG, "BlockRequest TR 수신대기");
			//}
		}

		
		public void UpdateItems()
		{
			if (Config.GetAccount() == "")
				return;

			m_CP6033.SetInputValue(0, Config.GetAccount());
			m_CP6033.SetInputValue(1, Config.GetSubAccount());
			m_CP6033.SetInputValue(2, 50);

			string Msg1 = m_CP6033.GetDibMsg1();
			string Msg2 = m_CP6033.GetDibMsg2();
			int iStatus = m_CP6033.GetDibStatus();
			if (iStatus == 1)
				return;

			m_CP6033.Request();
		}

		private void CP6033_Received()
		{
			List<AutoSeller.ITEM> aItems = new List<AutoSeller.ITEM>();
			string strCodeList = "";

			bool bContinue = true;
			while (bContinue == true)
			{
				int iCount = m_CP6033.GetHeaderValue(7);
				for (int i = 0; i < iCount; i++)
				{
					AutoSeller.ITEM Item = new AutoSeller.ITEM();

					Item.m_strCodeName = m_CP6033.GetDataValue(0, i);
					if (Item.m_strCodeName != "")
					{
						Item.m_strCode = m_CP6033.GetDataValue(12, i);
						Item.m_iItemCnt = m_CP6033.GetDataValue(7, i);
						Item.m_iAssessedValue = m_CP6033.GetDataValue(9, i) / 1000;
						Item.m_iValuationGains = m_CP6033.GetDataValue(10, i) / 1000;
						Item.m_dYield = m_CP6033.GetDataValue(11, i);
						Item.m_iAvailableQuantity = m_CP6033.GetDataValue(15, i);
						Item.m_dBookUnitPrice = m_CP6033.GetDataValue(17, i);
						Item.m_iProfitUnitPrice = m_CP6033.GetDataValue(18, i);

						if (strCodeList.Length > 0)
							strCodeList += ",";
						strCodeList += Item.m_strCode;

						aItems.Add(Item);
					}
				}

				bContinue = (m_CP6033.Continue != 0);
				bContinue = false;
				if (bContinue == true)
					BlockRequest(m_CP6033);
			}

			if (strCodeList.Length > 0)
			{
				DSCBO1Lib.StockMst2 StockMst = new DSCBO1Lib.StockMst2();
				StockMst.SetInputValue(0, strCodeList);
				BlockRequest(StockMst);
				int iCnt = StockMst.GetHeaderValue(0);
				for (int i = 0; i < iCnt; i++)
				{
					string strCode = StockMst.GetDataValue(0, i);
					int iPrice = StockMst.GetDataValue(3, i);
					AutoSeller.ITEM Item = aItems.FirstOrDefault(s => s.m_strCode == strCode);
					if (Item != default(AutoSeller.ITEM))
						Item.m_iCurPrice = iPrice;
				}
			}

			// sync
			List<string> aRemoveKeys = new List<string>();
			lock (m_aStockCur)
			{
				foreach (var Item in m_aStockCur)
				{
					if (aItems.Any(s => s.m_strCode == Item.Key) == false)
					{
						lock (m_aStockCur[Item.Key])
						{
							m_aStockCur[Item.Key].m_StockCur.Unsubscribe();
							m_aStockCur[Item.Key].m_Jpbid.Unsubscribe();
							SavePrice(m_aStockCur[Item.Key]);
							aRemoveKeys.Add(Item.Key);
						}
					}
				}

				foreach (var key in aRemoveKeys)
					m_aStockCur.Remove(key);
			}

			m_Listener.UpdateItemCallback(aItems);
		}

		public void UpdateNC()
		{
			m_CP5339.SetInputValue(0, Config.GetAccount());
			m_CP5339.SetInputValue(1, Config.GetSubAccount());

			string Msg1 = m_CP5339.GetDibMsg1();
			string Msg2 = m_CP5339.GetDibMsg2();
			int iStatus = m_CP5339.GetDibStatus();
			if (iStatus == 1)
				return;

			m_CP5339.Request();
		}

		private void CP5339_Received()
		{
			List<AutoSeller.NCITEM> NCItems = new List<AutoSeller.NCITEM>();

			bool bContinue = true;
			while (bContinue)
			{
				int iCnt = m_CP5339.GetHeaderValue(5);
				for (int i = 0; i < iCnt; i++)
				{
					AutoSeller.NCITEM Item = new AutoSeller.NCITEM();

					Item.m_strCode = m_CP5339.GetDataValue(3, i);
					Item.m_strCodeName = m_CP5339.GetDataValue(4, i);
					Item.m_strDesc = m_CP5339.GetDataValue(5, i);
					Item.m_iOrderPrice = m_CP5339.GetDataValue(7, i);
					Item.m_iRemainCnt = m_CP5339.GetDataValue(11, i);
					Item.m_bAsk = (m_CP5339.GetDataValue(13, i) == "1");
					Item.m_iOrderNo = m_CP5339.GetDataValue(1, i);
					Item.m_iOrgOrderNo = m_CP5339.GetDataValue(2, i);
					if (Item.m_iOrgOrderNo == 0)
						Item.m_iOrgOrderNo = Item.m_iOrderNo;

					NCItems.Add(Item);
				}

				bContinue = (m_CP5339.Continue != 0);
				if (bContinue)
					BlockRequest(m_CP5339);
			}

			m_Listener.UpdateNCItemCallback(NCItems);
		}

		private void CpConclusion_Received()
		{
			string strType = m_CpConclusion.GetHeaderValue(14);
			int iType = 0;
			int.TryParse(strType, out iType);
			bool bBid = (m_CpConclusion.GetHeaderValue(12) == "2");

			string strCode = m_CpConclusion.GetHeaderValue(9);
			string strCodeName = m_CpConclusion.GetHeaderValue(2);
			int iConclusionCnt = m_CpConclusion.GetHeaderValue(3);
			int iPrice = m_CpConclusion.GetHeaderValue(4);
			int iOrderNumber = m_CpConclusion.GetHeaderValue(5);
			int iOrgOrderNumber = m_CpConclusion.GetHeaderValue(6);
			string strCancel = m_CpConclusion.GetHeaderValue(16);
			string strOrderCondition = m_CpConclusion.GetHeaderValue(19);
			int iBookValue = m_CpConclusion.GetHeaderValue(21);
			int iRemainCnt = m_CpConclusion.GetHeaderValue(23);
			string strBidOrAsk = bBid ? "매수" : "매도";

			if (strOrderCondition == "0")
				strOrderCondition = "없음";
			else if (strOrderCondition == "1")
				strOrderCondition = "IOC";
			else if (strOrderCondition == "2")
				strOrderCondition = "FOK";

			switch (iType)
			{
				case 1: // 체결
					m_Listener.UpdateItem();
					m_Listener.UpdateNCItem();
					Util.Log(bBid?Util.LOG_TYPE.BUY_CONCLUSION:Util.LOG_TYPE.SELL_CONCLUSION, string.Format("{0}:{1} {2} 체결 ({3:n0}원 {4}주) - {5}", strCodeName, strCode, strBidOrAsk, iPrice, iConclusionCnt, strOrderCondition));
					break;

				case 2: // 확인
					Util.Log(Util.LOG_TYPE.VERBOSE, string.Format("{0}:{1} {2} 확인 - {3} ({4}:{5})", strCodeName, strCode, strBidOrAsk, strOrderCondition, iOrderNumber, iOrgOrderNumber));
					break;

				case 3: // 거부
					Util.Log(Util.LOG_TYPE.VERBOSE, string.Format("{0}:{1} {2} 거부", strCodeName, strCode, strBidOrAsk));
					break;

				case 4: // 접수
					m_Listener.UpdateNCItem();
					Util.Log(bBid ? Util.LOG_TYPE.BUY : Util.LOG_TYPE.SELL, string.Format("{0}:{1} {2} 접수 ({3:n0}원 {4}주) - {5}", strCodeName, strCode, strBidOrAsk, iPrice, iConclusionCnt, strOrderCondition));
					if (bBid == false)
					{
						if(m_aStockCur.ContainsKey(strCode) == true)
						{
							lock(m_aStockCur[strCode].m_PriceList)
							{
								var priceList = m_aStockCur[strCode].m_PriceList;
								List<Tuple<int, int>> PriceDealCntList = new List<Tuple<int, int>>();
								for (int i = 0; i < Math.Min(5, priceList.Count); i++)
									PriceDealCntList.Add(new Tuple<int, int>(priceList[i].m_iPrice, priceList[i].m_iDealCnt));

								SimulationHelper.InsertSimulationLog(strCode, strCodeName, PriceDealCntList);
							}
						}	
					}
						
					break;
			}
		}

		public int GetLimitRemainCountTrade()
		{
			return m_CPCybos.GetLimitRemainCount(CPUTILLib.LIMIT_TYPE.LT_TRADE_REQUEST);
		}

		public int GetLimitRemainCountRQ()
		{
			return m_CPCybos.GetLimitRemainCount(CPUTILLib.LIMIT_TYPE.LT_NONTRADE_REQUEST);
		}

		public int GetLimitRemainCountSB()
		{
			return m_CPCybos.GetLimitRemainCount(CPUTILLib.LIMIT_TYPE.LT_SUBSCRIBE);
		}

		public bool IsConnected()
		{
			return (m_CPCybos.IsConnect==1);
		}

		public STOCK_CUR_ITEM GetItem(string strCode)
		{
			if (m_aStockCur.ContainsKey(strCode) == false)
				return null;

			return m_aStockCur[strCode];
		}

		public void Subscribe(AutoSeller.ITEM Item)
		{
			if(m_aStockCur.Any(s => s.Key == Item.m_strCode) == false)
			{
				lock (m_aStockCur)
				{
					STOCK_CUR_ITEM StockCur = new STOCK_CUR_ITEM();
					StockCur.m_Listener = m_Listener;
					StockCur.m_CybosHelper = this;
					StockCur.m_strCode = Item.m_strCode;
					StockCur.m_strCodeName = Item.m_strCodeName;

					DSCBO1Lib.StockJpbid2 JpBid = new DSCBO1Lib.StockJpbid2();
					JpBid.SetInputValue(0, Item.m_strCode);
					BlockRequest(JpBid);
					for (int i = 0; i < 10; i++)
					{
						StockCur.m_aiAskPrice[i] = JpBid.GetDataValue(0, i);
						StockCur.m_aiAskCount[i] = JpBid.GetDataValue(2, i);
						StockCur.m_aiBidPrice[i] = JpBid.GetDataValue(1, i);
						StockCur.m_aiBidCount[i] = JpBid.GetDataValue(3, i);
					}

					StockCur.m_StockCur = new DSCBO1Lib.StockCur();
					StockCur.m_StockCur.SetInputValue(0, Item.m_strCode);
					StockCur.m_StockCur.Received += StockCur.OnRecievedPrice;
					StockCur.m_StockCur.Subscribe();
					StockCur.m_Jpbid = new DSCBO1Lib.StockJpbid();
					StockCur.m_Jpbid.SetInputValue(0, Item.m_strCode);
					StockCur.m_Jpbid.Received += StockCur.OnReceivedCall;
					StockCur.m_Jpbid.SubscribeLatest();
					StockCur.m_iCurPrice = Item.m_iCurPrice;
					StockCur.m_iMaxPrice = Item.m_iCurPrice;
					m_aStockCur.Add(Item.m_strCode, StockCur);
				}
			}
		}

		public int GetUnitValue(int iValue)
		{
			int iUnit = 1;
			if(iValue < 1000)
				iUnit = 1;
			else if(iValue < 5000)
				iUnit = 5;
			else if(iValue < 10000)
				iUnit = 10;
			else if(iValue < 50000)
				iUnit = 50;
			else if(iValue < 100000)
				iUnit = 100;
			else if(iValue < 500000)
				iUnit = 500;
			else
				iUnit = 1000;

			return iUnit;
		}

		int GetCallUnitValue(int iValue)
		{
			int iUnit = GetUnitValue(iValue);
			return (iValue/iUnit)*iUnit;
		}

		public async Task SellItem(string strCode, int iCnt, int iAskPrice)
		{
			if (iCnt <= 0 || m_SellingCode.ContainsKey(strCode) == true)
				return;

			m_SellingCode.TryAdd(strCode, iAskPrice);

			await Task.Run(() =>
			{
				int iSellPrice = iAskPrice - GetUnitValue(iAskPrice);

				CPTRADELib.CpTd0311 Td0311 = new CPTRADELib.CpTd0311();
				Td0311.SetInputValue(0, "1");
				Td0311.SetInputValue(1, Config.GetAccount());
				Td0311.SetInputValue(2, Config.GetSubAccount());
				Td0311.SetInputValue(3, strCode);
				Td0311.SetInputValue(4, iCnt);
				Td0311.SetInputValue(5, iSellPrice);
				Td0311.SetInputValue(8, "01");
				BlockRequest(Td0311);

				int val;
				m_SellingCode.TryRemove(strCode, out val);
			});
		}

		public async Task SellItem(string strCode, int iCnt)
		{
			if (iCnt <= 0)
				return;

			await Task.Run(() =>
			{
				CPTRADELib.CpTd0311 Td0311 = new CPTRADELib.CpTd0311();
				Td0311.SetInputValue(0, "1");
				Td0311.SetInputValue(1, Config.GetAccount());
				Td0311.SetInputValue(2, Config.GetSubAccount());
				Td0311.SetInputValue(3, strCode);
				Td0311.SetInputValue(4, iCnt);
				Td0311.SetInputValue(8, "03");
				BlockRequest(Td0311);
			});
		}

		public async Task CancelItem(string strCode, int iOrgOrderNo)
		{
			await Task.Run(() =>
			{
				CPTRADELib.CpTd0314 Td0314 = new CPTRADELib.CpTd0314();
				Td0314.SetInputValue(1, iOrgOrderNo);
				Td0314.SetInputValue(2, Config.GetAccount());
				Td0314.SetInputValue(3, Config.GetSubAccount());
				Td0314.SetInputValue(4, strCode);
				Td0314.SetInputValue(5, 0);
				BlockRequest(Td0314);
			});
		}

		public async Task CorrectionItem(string strCode, int iOrgOrderNo, int iCnt, int iAskPrice)
		{
			if (m_ItemsInCorrection.ContainsKey(iOrgOrderNo) == true)
				return;

			m_ItemsInCorrection.TryAdd(iOrgOrderNo, strCode);

			if (Config.GetMockTrading() == true)
			{
				await Task.Run(() =>
				{
					CancelItem(strCode, iOrgOrderNo).Wait();
					SellItem(strCode, iCnt, iAskPrice).Wait();

					string strCode2;
					m_ItemsInCorrection.TryRemove(iOrgOrderNo, out strCode2);
				});
			}
			else
			{
				await Task.Run(() =>
				{
					int iSellPrice = iAskPrice;

					CPTRADELib.CpTd0313 Td0313 = new CPTRADELib.CpTd0313();
					Td0313.SetInputValue(1, iOrgOrderNo);
					Td0313.SetInputValue(2, Config.GetAccount());
					Td0313.SetInputValue(3, Config.GetSubAccount());
					Td0313.SetInputValue(4, strCode);
					Td0313.SetInputValue(5, 0);
					Td0313.SetInputValue(6, iSellPrice);
					BlockRequest(Td0313);

					string strCode2;
					m_ItemsInCorrection.TryRemove(iOrgOrderNo, out strCode2);
				});
			}
		}

		public async void GetConclusion()
		{
			List<CONCLUSION> data = new List<CONCLUSION>();

			await Task.Run(() => {
				m_CP5342.SetInputValue(0, Config.GetAccount());
				m_CP5342.SetInputValue(1, Config.GetSubAccount());
				m_CP5342.SetInputValue(2, 20);
				m_CP5342.SetInputValue(3, "1");
				BlockRequest(m_CP5342);

				int iCnt = m_CP5342.GetHeaderValue(8);
				bool bContinue = true;
				while (bContinue)
				{
					for (int i = 0; i < Math.Min(iCnt, 20); i++)
					{
						var newData = new CONCLUSION
						{
							strCode = m_CP5342.GetDataValue(0, i),
							strCodeName = m_CP5342.GetDataValue(1, i),
							iConclusionCnt = (int)m_CP5342.GetDataValue(3, i),
							iFee = (int)m_CP5342.GetDataValue(4, i),
							iSpecialTax = (int)m_CP5342.GetDataValue(5, i),  // 농특세
							strSellBuy = m_CP5342.GetDataValue(10, i),
							iTransactionTax = (int)m_CP5342.GetDataValue(12, i), // 거래세
							strDate = m_CP5342.GetDataValue(13, i),
							iCommitedAmount = (int)m_CP5342.GetDataValue(22, i), // 약정금액
							iSettlementAmount = (int)m_CP5342.GetDataValue(24, i),   // 정산금액
						};

						int iIdx = data.FindLastIndex(t => t.strCode == newData.strCode);
						if (iIdx < 0)
							data.Add(newData);
						else
							data.Insert(iIdx + 1, newData);
					}
					iCnt -= 20;

					bContinue = (iCnt > 0);
					if (bContinue)
						BlockRequest(m_CP5342);
				}

				m_Listener.UpdateConclusionCallback(data);
			});
		}

		public class CONCLUSION
		{
			public string strCode;
			public string strCodeName;
			public int iConclusionCnt;
			public int iFee;
			public int iSpecialTax;
			public string strSellBuy;
			public int iTransactionTax;
			public string strDate;
			public int iCommitedAmount;
			public int iSettlementAmount;

			public override string ToString()
			{
				return $"{strCodeName} {strSellBuy} {iSettlementAmount:n}";
			}
		}

	}
}
