using System;
using System.Diagnostics;
using System.Text;
using System.IO;
using System.Net.Sockets;
using System.Net;
namespace IndianHealthService.BMXNet
{
	/// 
	/// BMXNetLib implements low-level socket connectivity to RPMS databases.
	/// The VA RPC Broker must be running on the RPMS server in order for 
	/// BMXNetLib to connect.
	/// 
	public class BMXNetLib
	{
		public BMXNetLib()
		{
			m_sWKID = "XWB";
			m_sWINH = "";
			m_sPRCH = "";
			m_sWISH = "";
			m_cHDR = ADEBHDR(m_sWKID,m_sWINH,m_sPRCH,m_sWISH);
		}
		
		#region Piece Functions
		/// 
		/// Corresponds to M's $L(STRING,DELIMITER)
		/// 
		/// 
		/// 
		/// 
		public static int PieceLength(string sInput, string sDelim)
		{
			char[] cDelim = sDelim.ToCharArray();
			string [] cSplit = sInput.Split(cDelim);
			return cSplit.GetLength(0);
		}
		/// 
		/// Corresponds to M's $$Piece function
		/// 
		/// 
		/// 
		/// 
		/// 
		public static string Piece(string sInput, string sDelim, int nNumber)
		{
			char[] cDelim = sDelim.ToCharArray();
			string [] cSplit = sInput.Split(cDelim);
			int nLength = cSplit.GetLength(0);
			if ((nLength < nNumber)||(nNumber < 1))
				return "";
			return cSplit[nNumber-1];
		}
//		public static string Piece(string[] sInput, string sDelim, int nNumber)
//		{
//			char[] cDelim = sDelim.ToCharArray();
//			int nLength = sInput.GetLength(0);
//			if ((nLength < nNumber)||(nNumber < 1))
//				return "";
//
//			return sInput[nNumber-1];
//
//		}
		public static string Piece(string sInput, string sDelim, int nNumber, int nEnd)
		{
			try 
			{
				if (nNumber < 0)
					nNumber = 1;
				if (nEnd < nNumber)
					return "";
				if (nEnd == nNumber)
					return Piece(sInput, sDelim, nNumber);
				char[] cDelim = sDelim.ToCharArray();
				string [] cSplit = sInput.Split(cDelim);
				int nLength = cSplit.GetLength(0);
				if ((nLength < nNumber)||(nNumber < 1))
					return "";
				//nNumber = 1-based index of the starting element to return
				//nLength = count of elements
				//nEnd = 1-based index of last element to return
				//nCount = number of elements past nNumber to return
				//convert nNumber to 0-based index:
				nNumber--;
				//convert nEnd to 0-based index;
				nEnd--;
				//Calculate nCount;
				int nCount = nEnd - nNumber + 1;
				//Adjust nCount for number of elements
				if (nCount + nNumber >= nLength)
				{
					nCount = nLength - nNumber;
				}
				string sRet =  string.Join(sDelim, cSplit, nNumber, nCount );
				return sRet;
			}
			catch (Exception bmxEx)
			{
				string sMessage =  bmxEx.Message + bmxEx.StackTrace;
				throw new BMXNetException(sMessage);
			}
		}
//		public static string Piece(string[] sInput, string sDelim, int nNumber, int nEnd)
//		{
//			if (nEnd < nNumber)
//				return "";
//
//
//			if (nEnd == nNumber)
//				return Piece(sInput, sDelim, nNumber);
//
//			char[] cDelim = sDelim.ToCharArray();
//			int nLength = sInput.GetLength(0);
//
//			if ((nLength < nNumber)||(nNumber < 1))
//				return "";
//			
//			if (nEnd > nLength)
//				nEnd = nLength;
//
//			return string.Join(sDelim, sInput, nNumber - 1, nEnd - 1);
//		}
		#endregion Piece Functions
		#region RPX Fields
		private string	m_sWKID;
		private string	m_sWISH;
		private string	m_sPRCH;
		private string	m_sWINH;
		private string	m_cHDR;
		private string	m_cVerify;
		private string	m_cAccess;
		private string	m_cDUZ;
		private string	m_cAuthentication;
		private string	m_cAppContext;
		private bool	m_bConnected;
		private int		m_nMServerPort;
		private string	m_cServerAddress;
		private string	m_cDUZ2;
		private string	m_cLoginFacility;
		#endregion RPX Fields
		#region Encryption Keys
		private string[] m_sKey = new string[]
			{
				@"wkEo-ZJt!dG)49K{nX1BS$vH<&:Myf*>Ae0jQW=;|#PsO`'%+rmb[gpqN,l6/hFC@DcUa ]z~R}""V\iIxu?872.(TYL5_3",
				@"rKv`R;M/9BqAF%&tSs#Vh)dO1DZP> *fX'u[.4lY=-mg_ci802N7LTG<]!CWo:3?{+,5Q}(@jaExn$~p\IyHwzU""|k6Jeb",
				@"\pV(ZJk""WQmCn!Y,y@1d+~8s?[lNMxgHEt=uw|X:qSLjAI*}6zoF{T3#;ca)/h5%`P4$r]G'9e2if_>UDKb7H=CT8S!",
				@"NZW:1}K$byP;jk)7'`x90B|cq@iSsEnu,(l-hf.&Y_?J#R]+voQXU8mrV[!p4tg~OMez CAaGFD6H53%L/dT2<*>""{\wI=",
				@"vCiJ[D_0xR32c*4.P""G{r7}E8wUgyudF+6-:B=$(sY,LkbHa#'@Q",
				@"hvMX,'4Ty;[a8/{6l~F_V""}qLI\!@x(D7bRmUH]W15J%N0BYPkrs&9:$)Zj>u|zwQ=ieC-oGA.#?tfdcO3gp`S+En K2*<",
				@"jd!W5[];4'?ghBzIFN}fAK""#`p_TqtD*1E37XGVs@0nmSe+Y6Qyo-aUu%i8c=H2vJ\) R:MLb.9,wlO~P",
				@"2ThtjEM+!=xXb)7,ZV{*ci3""8@_l-HS69L>]\AUF/Q%:qD?1~m(yvO0e'<#o$p4dnIzKP|`NrkaGg.ufCRB[; sJYwW}5&",
				@"vB\5/zl-9y:Pj|=(R'7QJI *&CTX""p0]_3.idcuOefVU#omwNZ`$Fs?L+1Sk<,b)hM4A6[Y%aDrg@~KqEW8t>H};n!2xG{",
				@"sFz0Bo@_HfnK>LR}qWXV+D6`Y28=4Cm~G/7-5A\b9!a#rP.l&M$hc3ijQk;),TvUd<[:I""u1'NZSOw]*gxtE{eJp|y (?%",
				@"M@,D}|LJyGO8`$*ZqH .j>c~hanG",
				@"xVa1']_GU#zm+:5b@06O3Ap8=*7ZFY!H-uEQk; .q)i&rhd",
				@"I]Jz7AG@QX.""%3Lq>METUo{Pp_ |a6<0dYVSv8:b)~W9NK`(r'4fs&wim\kReC2hg=HOj$1B*/nxt,;c#y+![?lFuZ-5D}",
				@"Rr(Ge6F Hx>q$m&C%M~Tn,:""o'tX/*yP.{lZ!YkiVhuw_y|m};d)-7DZ""Fe/Y9 WidFN,1KsmwQ)GJM{I4:C%}#Ep(?HB/r;t.&U8o|l['Lg""2hRDyZ5`nbf]qjc0!zS-TkYO<_=76a\X@$Pe3+xVvu",
				@"yYgjf""5VdHc#uA,W1i+v'6|@pr{n;DJ!8(btPGaQM.LT3oe?NB/&9>Z`-}02*%x<7lsqz4OS ~E$\R]KI[:UwC_=h)kXmF",
				@"5:iar.{YU7mBZR@-K|2 ""+~`M%8sq4JhPo<_X\Sg3WC;Tuxz,fvEQ1p9=w}FAI&j/keD0c?)LN6OHV]lGy'$*>nd[(tb!#",
		};
		#endregion Encryption Keys
		
		#region RPX Functions
		/// 
		/// Given strInput = "13" builds "013" if nLength = 3.  Default for nLength is 3.
		/// 
		/// 
		/// 
		private string ADEBLDPadString(string strInput)
		{
			return ADEBLDPadString(strInput, 3);
		}
		/// 
		/// Given strInput = "13" builds "013" if nLength = 3  Default for nLength is 3.
		/// 
		/// 
		/// Default = 3
		/// 
		private string ADEBLDPadString(string strInput, int nLength /*=3*/)
		{
			return strInput.PadLeft(nLength, '0');
		}
		/// 
		/// Concatenates zero-padded length of sInput to sInput
		/// Given "Hello" returns "004Hello"
		/// If nSize = 5, returns "00004Hello"
		/// Default for nSize is 3.
		/// 
		/// 
		/// 
		/// 
		private string ADEBLDB(string sInput)
		{
			return ADEBLDB(sInput, 3);
		}
		/// 
		/// Concatenates zero-padded length of sInput to sInput
		/// Given "Hello" returns "004Hello"
		/// If nSize = 5, returns "00004Hello"
		/// Default for nSize is 3.
		/// 
		/// 
		/// 
		/// 
		private string ADEBLDB(string sInput, int nSize /*=3*/)
		{
			int nLen = sInput.Length;
			string sLen = this.ADEBLDPadString(nLen.ToString(), nSize);
			return sLen + sInput;
		}
		/// 
		/// Build protocol header
		/// 
		/// 
		/// 
		/// 
		/// 
		/// 
		private string ADEBHDR(string sWKID, string sWINH, string sPRCH, string sWISH)
		{
			string strResult;
			strResult = sWKID+";"+sWINH+";"+sPRCH+";"+sWISH+";";
			strResult = ADEBLDB(strResult);
			return strResult;
		}
		private string ADEBLDMsg(string cHDR, string cRPC, string cParam, string cMult)
		{
			//Builds RPC message
			//Automatically splits parameters longer than 200 into subscripted array
			string cMSG;
			string sBuild = "";
			string sPiece = "";
			string sBig = "";
			int l;
			int nLength;
			if (cParam == "") 
			{
				cMSG = "0" + cRPC ;
			}
			else
			{
				l = PieceLength(cParam, "^");
				for (int j=1; j <= l; j++) 
				{
					sPiece = Piece(cParam, "^", j);
					if ((j == l) && (sPiece.Length > 200)) 
					{
						//Split up long param into array pieces
						sBig = sPiece;
						sPiece = ".x";
						nLength = sPiece.Length + 1;
						sPiece = ADEBLDPadString(nLength.ToString()) + "2" + sPiece;
						sBuild = sBuild + sPiece;
						int nSubscript = 1;
						string sSubscript ="";
						int nSubLen = 0;
						string sSubLen ="";
						int nBigLen = sBig.Length;
						string sHunk ="";
						int nHunkLen = 0;
						string sHunkLen ="";
						do 
						{
							sHunk = sBig.Substring(0,200);
							sBig = sBig.Substring(201, sBig.Length + 1);
							nBigLen = sBig.Length;
							sSubscript = nSubscript.ToString();
							nSubLen = sSubscript.Length;
							sSubLen = nSubLen.ToString();
							sSubLen = ADEBLDPadString(sSubLen);
							nHunkLen = sHunk.Length;
							sHunkLen = nHunkLen.ToString();
							sHunkLen = ADEBLDPadString(sHunkLen);
							cMult = cMult + sSubLen + sSubscript + sHunkLen + sHunk;
							nSubscript++;
						} while (nBigLen > 0);
					}
					else
					{
						nLength = sPiece.Length +1;
						sPiece = ADEBLDPadString(nLength.ToString()) + "0" + sPiece;
						sBuild = sBuild + sPiece;
					}
				}
				nLength = sBuild.Length;
				string sTotLen = ADEBLDPadString(nLength.ToString(),5);
				if (cMult.Length > 0) 
				{
					cMSG = "1"+ cRPC + "^" +sTotLen + sBuild;
				}
				else
				{
					cMSG = "0"+ cRPC + "^" +sTotLen + sBuild;
				}
			}
			cMSG = ADEBLDB(cMSG, 5);
			cMSG = cHDR + cMSG;
			return cMSG;
		}
		private string ADEEncryp(string sInput)
		{
			//Encrypt a string
			string strResult;
			string strPercent;
			string strAssoc;
			string strIdix;
			int nPercent;
			int nAssocix;
			int nIdix;
			Debug.Assert(sInput != "");
			System.Random rRand = new Random(DateTime.Now.Second);
			nPercent = rRand.Next(0,10000);
			nPercent += 72439;
			nAssocix = nPercent % 20;
			nAssocix++;
			Debug.Assert(nAssocix < 21);
			strPercent = nPercent.ToString();
			strPercent = strPercent.Substring(1,2);
			nIdix = Convert.ToInt32(strPercent);
			nIdix = nIdix % 20;
			nIdix++;
			Debug.Assert(nIdix < 21);
			const int nEncryptBase = 101;
			strAssoc = LoadKey(nEncryptBase + nAssocix);
			Debug.Assert(strAssoc.Length == 94);
			strIdix = LoadKey(nEncryptBase + nIdix);
			Debug.Assert(strIdix.Length == 94);
			string sEncrypted = "";
			
			foreach (char c in sInput)
			{
				string d = c.ToString();
				int nFindChar = FindChar(strIdix, c);
				if (nFindChar > -1)
				{
					d = strAssoc.Substring(nFindChar,1);
				}
				sEncrypted += d;
			}
			strResult = (char) (nIdix + 31) + sEncrypted + (char) (nAssocix + 31);
			return strResult;
		}
		private int FindChar(byte[] c, char y)
		{
			int n = 0;
			int nRet = -1;
			for (n=0; n < c.Length; n++)
			{
				if (y == (char) c[n])
				{
					nRet = n;
					break;
				}
			}
			return nRet;
		}
		
		private int FindChar(string s, char y)
		{
			int n = 0;
			int nRet = -1;
			foreach (char c in s)
			{
				if (y == c)
				{
					nRet = n;
					break;
				}
				n++;
			}
			return nRet;
		}
		private string LoadKey(int nID)
		{
			nID -= 102;
			Debug.Assert( nID < 20);
			return m_sKey[nID];
		}
		//		private string GetLocalAddress()
		//		{
		//			return "";
		//		}
	
		public bool OpenConnection(string sServerAddress, string sAccess, string sVerify)
		{
			try
			{
				m_cServerAddress = sServerAddress;
				m_cAccess = sAccess;
				m_cVerify = sVerify;
				//Get the local host address and available port;
				IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
				if (ipHostInfo.AddressList.Length < 1)
				{
					throw new BMXNetException("BMXNetLib.OpenConnection unable to find IP Address.");
				}
				//Start the listener
				IPAddress ipAddress = ipHostInfo.AddressList[0];
				IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 0);
				TcpListener listener = new TcpListener(localEndPoint);
				listener.Start();
				IPEndPoint ipEp = (IPEndPoint) listener.LocalEndpoint;
				int nLocalPort = ipEp.Port;
				string sLocalAddress = ipAddress.ToString();
				//Connect with the server
				TcpClient connector = new TcpClient();
				try 
				{
					connector.Connect(m_cServerAddress, m_nMServerPort);
				}
				catch (SocketException exSocket)
				{
					string s = exSocket.Message + exSocket.StackTrace;
					throw new BMXNetException(s);
				}
			
				//Prepare & send the connect message
				string cSend = "TCPconnect^" + sLocalAddress + "^" + nLocalPort.ToString() + "^";
				int nLen = cSend.Length;
				string sLen = nLen.ToString();
				sLen = sLen.PadLeft(5, '0');
				cSend = "{XWB}" + sLen + cSend;
				NetworkStream ns = connector.GetStream();
				byte[] sendBytes = Encoding.ASCII.GetBytes(cSend);
				ns.Write(sendBytes,0,sendBytes.Length);
				//Accept connection from server
				DateTime dStartTime = DateTime.Now;
				DateTime dEndTime;
				bool bPending = false;
				while (bPending == false)
				{
					if (listener.Pending() == true)
					{
						m_pCommSocket = listener.AcceptTcpClient();
						bPending = true;
						this.m_bConnected = true;
						break;
					}
					dEndTime = DateTime.Now;
					TimeSpan ds = dEndTime - dStartTime;
					if (ds.TotalSeconds > 10) //TODO: Parameterize this timeout value
					{
						throw new Exception("AcceptTcpClient failed.");
					}
				}
				try
				{
					bool bSecurity = SendSecurityRequest();
				}
				catch (Exception ex)
				{
					//Close the connection
					SendString(m_pCommSocket, "#BYE#");
					m_pCommSocket.Close();
					m_bConnected = false;
					m_cServerAddress = "";
					throw ex;
				}
				
				m_bConnected = true;
//				if ( bSecurity != true) 
//				{
//					//Close the connection
//					SendString(m_pCommSocket, "#BYE#");
//					m_pCommSocket.Close();
//					m_bConnected = false;
//					m_cServerAddress = "";
//				}
//				else
//				{
//					m_bConnected = true;
//				}
				return m_bConnected;
			}
			catch (BMXNetException bmxEx)
			{
				throw bmxEx;
			}
			catch (Exception ex)
			{
				string s = ex.Message + ex.StackTrace;
				throw new BMXNetException(s);
			}
		}
		private void SendString(TcpClient tcpClient, string cSendString)
		{
			string sMult = "";
			SendString(tcpClient, cSendString, sMult);
		}
		private void SendString(TcpClient tcpClient, string cSendString, string cMult)
		{
			int nLen = cSendString.Length;
			string sLen = nLen.ToString();
			sLen = sLen.PadLeft(5, '0');
			cSendString = sLen + cSendString;
			nLen += 15;
			sLen = nLen.ToString();
			sLen = sLen.PadLeft(5, '0');
			cSendString = "{XWB}" + sLen + cSendString;
			cSendString = cSendString + cMult;
			NetworkStream ns = tcpClient.GetStream();
			byte[] sendBytes = Encoding.ASCII.GetBytes(cSendString);
			ns.Write(sendBytes,0,sendBytes.Length);
			return;
		}
		private string ReceiveString(TcpClient tcpClient)
		{
			NetworkStream ns = tcpClient.GetStream();
			do
			{
			}while (ns.DataAvailable == false);
			byte[] bReadBuffer = new byte[1024];
			string sReadBuffer = "";
			StringBuilder sbAll = new StringBuilder("", 1024);
			int numberOfBytesRead = 0;
			// Incoming message may be larger than the buffer size.
			numberOfBytesRead = ns.Read(bReadBuffer, 0, 2); //first two bytes are 0
			bool bFinished = false;
			int nFind = -1;
			do
			{
				numberOfBytesRead = ns.Read(bReadBuffer, 0, bReadBuffer.Length); 
				nFind = FindChar(bReadBuffer, (char) 4);
				if (nFind > -1)
					bFinished = true;
				sReadBuffer = Encoding.ASCII.GetString(bReadBuffer, 0, numberOfBytesRead);
				if (nFind > -1)
				{
					sbAll.Append(sReadBuffer, 0, numberOfBytesRead -1);
				}
				else 
				{
					sbAll.Append(sReadBuffer);
				}
			}
			while(bFinished == false);
			return sbAll.ToString();
			
		}
		private bool SendSecurityRequest()
		{
			string		strReceive = "";
			string		cAccess;
			string		cVerify;
			//Setup Signon Environment
			string cMSG = this.ADEBLDB("0XUS SIGNON SETUP^00000", 5);
			cMSG = m_cHDR + cMSG;
			SendString(m_pCommSocket, cMSG);
			strReceive = ReceiveString(m_pCommSocket);
			cAccess = m_cAccess.ToUpper();
			cVerify = m_cVerify.ToUpper();
		
			//Build AV Call
			string strAV = cAccess + ";" + cVerify;
			strAV = ADEEncryp(strAV);
			cMSG = ADEBLDMsg(m_cHDR, "XUS AV CODE", strAV,"");
			SendString(m_pCommSocket, cMSG);
			strReceive = ReceiveString(m_pCommSocket);
			char[] cDelim = {(char) 13,(char) 10,(char) 0};
			m_cAuthentication = strReceive;
			string sDelim = new string(cDelim);
			int nPiece = 1;
			m_cDUZ = Piece(m_cAuthentication, sDelim , nPiece);
			if ((m_cDUZ == "0")||(m_cDUZ == ""))
			{
				m_cAccess = "";
				m_cVerify = "";	
				string sReason = Piece(m_cAuthentication, sDelim, 7);
				throw new Exception(sReason);
			}
			m_cAccess = cAccess;
			m_cVerify = cVerify;
			
			//Set up context
			if (m_cAppContext == null)
				m_cAppContext = "XUS SIGNON";
			SendString(m_pCommSocket, ADEBLDMsg(m_cHDR, "XWB CREATE CONTEXT", ADEEncryp(m_cAppContext), ""));
			m_cAuthentication = ReceiveString(m_pCommSocket);
			return true;		
		}
		public void CloseConnection()
		{
			if (!m_bConnected) 
			{
				return;
			}
			SendString(m_pCommSocket, "#BYE#");
			m_pCommSocket.Close();
			m_bConnected = false;
			m_cServerAddress = "";
			m_cAuthentication = "";
			m_cDUZ2 = "";
			m_cAccess = "";
			m_cVerify = "";
			m_cDUZ = "";
		}
		public string TransmitRPC(string sRPC, string sParam)
		{
			if (m_bConnected == false) 
			{
				//Raise Exception
				throw new BMXNetException("BMXNetLib.TransmitRPC failed because BMXNetLib is not connected to RPMS.");
			}
			Debug.Assert(m_cDUZ != "");
			Debug.Assert(m_pCommSocket != null);
			string sMult = "";
			string sSend = ADEBLDMsg(m_cHDR, sRPC, sParam, sMult);
			SendString(m_pCommSocket, sSend, sMult);
			string strResult = ReceiveString(m_pCommSocket);
			return strResult;
		}
		public string GetLoginFacility()
		{
			try
			{
				if (m_bConnected == false) 
				{
					throw new BMXNetException("BMXNetLib is not connected to RPMS");
				}
				if (m_cLoginFacility != "") 
				{
					return m_cLoginFacility;
				}
				Debug.Assert(m_pCommSocket != null);
				Debug.Assert(m_cDUZ != "");
				SendString(m_pCommSocket, ADEBLDMsg(m_cHDR, "BMXGetFac", m_cDUZ, ""));
				string sFac = ReceiveString(m_pCommSocket);
				m_cLoginFacility = sFac;
				return sFac;
			}
			catch (BMXNetException bmxEx)
			{
				string sMessage =  bmxEx.Message + bmxEx.StackTrace;
				throw new BMXNetException(sMessage);
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		TcpClient m_pCommSocket;
		#endregion RPX Functions
		#region RPX Properties
		
		public string WKID
		{
			get
			{
				return m_sWKID;
			}
			set
			{
				m_sWKID = value;
			}
		}
		public string PRCH
		{
			get
			{
				return m_sPRCH;
			}
			set
			{
				m_sPRCH = value;
			}
		}
		public string WINH
		{
			get
			{
				return m_sWINH;
			}
			set
			{
				m_sWINH = value;
			}
		}
		public string WISH
		{
			get
			{
				return m_sWISH;
			}
			set
			{
				m_sWISH = value;
			}
		}
		public string AppContext
		{
			get
			{
				return m_cAppContext;
			}
			set
			{
				m_cAppContext = value;
				//TODO: send the changed context to RPMS
			}
		}
		public bool Connected
		{
			get
			{
				return m_bConnected;
			}
		}
		public string DUZ
		{
			get
			{
				return m_cDUZ;
			}
		}
//		public string Error
//		{
//			get
//			{
//				return "";
//			}
//		}
		public string DUZ2
		{
			get
			{
				return m_cDUZ2;
			}
			set
			{
				m_cDUZ2 = value;
				//TODO: send the changed context to RPMS
			}
		}
		public string MServerAddress
		{
			get
			{
				return m_cServerAddress;
			}
		}
		public int MServerPort
		{
			get
			{
				return m_nMServerPort;
			}
			set
			{
				m_nMServerPort = value;
			}
		}
		#endregion RPX Properties
	}
}