using System;
using System.Text;
using System.IO;

using System.Threading;

using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;


namespace Access_Control_Server
{
    public partial class FormMain
    {
        Thread listenThread;

        TcpListener tcpListener = new TcpListener(IPAddress.Any, listenPort);
        private static int listenPort = 45455;

        private delegate Int32 FindTerminal(IPAddress TerminalIP);
        private FindTerminal delegateFindTerminal;

        private delegate Boolean RequiredPIN(Int32 ID);
        private RequiredPIN delegateRequirePIN;

        private delegate Boolean VerifyPIN(Int32 ID, String PIN);
        private VerifyPIN delegateVerifyPIN;

        private delegate Boolean UserToTerminal(Int32 UserID, Int32 TerminalID);
        private UserToTerminal delegateUserToTerminal;

        private delegate Int32 FindCard(String Card);
        private FindCard delegateFindCard;

        private delegate Boolean CardBlocked(Int32 ID);
        private CardBlocked delegateCardBlocked;

        private void StartListenThread()
        {
            listenThread = new Thread(new ThreadStart(ListenForClients));
            listenThread.Start();
        }

        private void ListenForClients()
        {
            delegateFindCard = new FindCard(FindCard_Method);
            delegateFindTerminal = new FindTerminal(FindTerminal_Method);
            delegateUserToTerminal = new UserToTerminal(UserToTerminal_Method);
            delegateRequirePIN = new RequiredPIN(RequirePIN_Method);
            delegateVerifyPIN = new VerifyPIN(VerifyPIN_Method);
            delegateCardBlocked = new CardBlocked(CardBlocked_Method);

            this.tcpListener.Start();

            while (true)
            {
                try
                {
                    //blocks until a client has connected to the server
                    TcpClient client = this.tcpListener.AcceptTcpClient();

                    //create a thread to handle communication 
                    //with connected client
                    Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                    clientThread.Start(client);
                }
                catch { };
            }
        }

        private void HandleClientComm(object client)
        {
            Crypto crypto = new Crypto();

            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();

            byte[] message = new byte[200];
            int bytesRead;

            String[] strArray;
            IPAddress remoteIP;

            Int32 TerminalID;
            Int32 UserID;

            while (true)
            {
                bytesRead = 0;

                try
                {
                    bytesRead = clientStream.Read(message, 0, 200);
                }
                catch
                {
                    break;
                }

                if (bytesRead == 0) break;

                //message has successfully been received
                strArray = tcpClient.Client.RemoteEndPoint.ToString().Split(':');
                remoteIP = IPAddress.Parse(strArray[0]);

                TerminalID = (Int32)Invoke(delegateFindTerminal, new object[] { remoteIP });

                // If no terminal was found
                if (TerminalID < 0)
                {
                    message = crypto.EncryptAES("ERR Unknown terminal\0");
                    clientStream.Write(message, 0, message.Length);
                    break;
                }

                // Split message into parts
                strArray = crypto.DecryptAES(message).Trim('\0').Split(' ');
                
                if (strArray[0] == "CARD") {
                    UserID = (Int32)Invoke(delegateFindCard, new object[] { strArray[1] });
                    // No card found
                    if (UserID < 0)
                    {
                        message = crypto.EncryptAES("ERR Unknown user\0");
                        clientStream.Write(message, 0, message.Length);
                        break;
                    }

                    // Is card blocked
                    if ((Boolean)Invoke(delegateCardBlocked, new object[] { UserID }))
                    {
                        message = crypto.EncryptAES("ERR Card blocked\0");
                        clientStream.Write(message, 0, message.Length);
                        WriteLog(UserID, TerminalID, "Card blocked");
                        break;
                    }

                    // User not verified for terminal
                    if (!(Boolean)Invoke(delegateUserToTerminal, new object[] { UserID, TerminalID }))
                    {
                        message = crypto.EncryptAES("ERR Not registered\0");
                        clientStream.Write(message, 0, message.Length);
                        WriteLog(UserID, TerminalID, "Not registered card");
                        break;
                    }

                    // Not required PIN
                    if (!(Boolean)Invoke(delegateRequirePIN, new object[] { TerminalID }))
                    {
                        message = crypto.EncryptAES("OK\0");
                        clientStream.Write(message, 0, message.Length);
                        WriteLog(UserID, TerminalID, "OK");
                        break;
                    }
                    else
                    {
                        message = crypto.EncryptAES("PIN\0");
                        clientStream.Write(message, 0, message.Length);

                        while (tcpClient.Available == 0);

                        message = new byte[200];
                        try
                        {
                            bytesRead = clientStream.Read(message, 0, 200);
                        }
                        catch
                        {
                            break;
                        }

                        strArray = crypto.DecryptAES(message).Trim('\0').Split(' ');

                        // Test message format
                        if (strArray[0] != "PIN")
                        {
                            message = crypto.EncryptAES("ERR COM ERR\0");
                            clientStream.Write(message, 0, message.Length);
                            WriteLog(UserID, TerminalID, "Bad message format");
                            break;
                        }

                        // Test PIN
                        if (String.Equals("xxxx", strArray[1]))
                        {
                            WriteLog(UserID, TerminalID, "PIN not entered");
                            break;
                        }
                        if (!(Boolean)Invoke(delegateVerifyPIN, new object[] { UserID, strArray[1] }))
                        {
                            message = crypto.EncryptAES("ERR Wrong PIN\0");
                            clientStream.Write(message, 0, message.Length);
                            WriteLog(UserID, TerminalID, "Wrong PIN");
                            break;
                        }

                        message = crypto.EncryptAES("OK\0");
                        clientStream.Write(message, 0, message.Length);
                        WriteLog(UserID, TerminalID, "OK");
                    }
                }
                else
                {
                    message = crypto.EncryptAES("ERR COM ERR\0");
                    clientStream.Write(message, 0, message.Length);
                    break;
                }
            }
            tcpClient.Close();
        }

        private void WriteLog(Int32 UserID, Int32 TerminalID, String Description)
        {
            dataSetList.Logging.AddLoggingRow(UserID, TerminalID, DateTime.Now, Description);
        }

        private Int32 FindTerminal_Method(IPAddress TerminalIP)
        {
            Int32 ID = -1;

            foreach (DataSetTypes.DataSetList.TerminalsRow row in dataSetList.Terminals.Rows) {
                if (String.Equals(row.IP, TerminalIP.ToString())) {
                    ID = row.ID;
                    break;
                }
            }
            return ID;
        }

        private Boolean RequirePIN_Method(Int32 ID)
        {
            foreach (DataSetTypes.DataSetList.TerminalsRow row in dataSetList.Terminals.Rows)
            {
                if (row.ID == ID) return row.RequiredPIN;
            }

            return false;
        }

        private Boolean VerifyPIN_Method(Int32 ID, String PIN)
        {
            foreach (DataSetTypes.DataSetList.UsersRow row in dataSetList.Users.Rows)
            {
                if (row.ID == ID)
                {
                    if (String.Equals(row.PIN, PIN))
                    {
                        row.FailCounter = 0;
                        return true;
                    }
                    else
                    {
                        row.FailCounter++;
                        return false;
                    }
                }
            }

            return false;
        }

        private Boolean CardBlocked_Method(Int32 ID)
        {
            foreach (DataSetTypes.DataSetList.UsersRow row in dataSetList.Users.Rows)
            {
                if (row.ID == ID)
                {
                    if (row.FailCounter >= 3)
                    {
                        row.Blocked = true;
                        row.FailCounter = 0;
                    }
                    return row.Blocked;
                }
            }

            return true;
        }

        private Boolean UserToTerminal_Method(Int32 UserID, Int32 TerminalID)
        {
            foreach (DataSetTypes.DataSetList.UserToTerminalRow row in dataSetList.UserToTerminal.Rows)
            {
                if (row.UserID == UserID && row.TerminalID == TerminalID)
                {
                    return true;
                }
            }

            return false;
        }

        private Int32 FindCard_Method(String Card)
        {
            Int32 ID = -1;
            
            foreach (DataSetTypes.DataSetList.UsersRow row in dataSetList.Users.Rows)
            {
                if (row.CardNo == Card)
                {
                    ID = row.ID;
                    break;
                }
            }

            return ID;
        }
    }
}