using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Data.SqlServerCe;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.IO;


namespace TCPServer
{
    public partial class MainForm : Form
    {
        const string ENCRYPT_COMMAND = @"--quiet --no-tty --batch --yes --always-trust -a -e -r {0}";
        const string DECRYPT_COMMAND = @"--quiet --no-tty --batch --yes -a";
        const string DELIM = "\r\n";
        const string DELIM2 = "$\r\n";

        ServerData data;
        Users UsersForm;
        Settings SettingsForm;
        Random Generator = new Random();

        readonly string nl = Environment.NewLine;

        #region invoke methods, delegates
        delegate void InvokeDelegate();

        private void SetStatus(string text)
        {
            this.Invoke(
                new InvokeDelegate(delegate(){ ExceptionStripStatusLabel.Text = text; })
            );
        }

        private void AddRow(params string[] args)
        {
            this.Invoke(
                new InvokeDelegate(delegate() { ConnectionsDataGrid.Rows.Add(args); })
            );
        }

        private void DeleteRow(int index)
        {
            this.Invoke(
                new InvokeDelegate(delegate() { ConnectionsDataGrid.Rows.RemoveAt(index); })
            );
        }

        private void SetRowStatus(int index, string status)
        {
            this.Invoke(
                new InvokeDelegate(delegate() { ConnectionsDataGrid.Rows[index].Cells["Status"].Value = status; })
            );
        }

        private void RefreshUsers()
        {
            this.Invoke(
                new InvokeDelegate(delegate() { UsersForm.RefreshUserGrid(); })
            );
        }

        private void RefreshUserRow(int index)
        {
            this.Invoke(
                new InvokeDelegate(delegate() { UsersForm.RefreshUserRow(index); })
            );
        }

        private void RefreshIncomingMail()
        {
            this.Invoke(
                new InvokeDelegate(delegate() { UsersForm.RefreshIngoingGrid(); })
            );
        }

        private void RefreshOutgoingMail()
        {
            this.Invoke(
                new InvokeDelegate(delegate() { UsersForm.RefreshOutgoingGrid(); })
            );
        }

        #endregion

        static int RunCommand(string FileName, string Parameters, string Input, out string Output)
        {
            Output = "";
            Process myProcess = new Process();
            ProcessStartInfo myProcessStartInfo = new ProcessStartInfo(FileName, Parameters);
            myProcessStartInfo.CreateNoWindow = true;
            myProcessStartInfo.UseShellExecute = false;
            myProcessStartInfo.RedirectStandardOutput = true;
            myProcessStartInfo.RedirectStandardInput = true;
            myProcess.StartInfo = myProcessStartInfo;
            myProcess.Start();
            myProcess.StandardInput.Write(Input);
            myProcess.StandardInput.Flush();
            myProcess.StandardInput.Close();
            myProcess.WaitForExit(5000);
            int retcode = myProcess.ExitCode;
            if (retcode == 0)
            {
                StreamReader myStreamReader = myProcess.StandardOutput;
                Output = myStreamReader.ReadToEnd();
            }
            myProcess.Close();
            return retcode;
        }

        static string Encrypt(string Text, string Email)
        {
            //return Text;
            string Output;
            if (RunCommand(ServerData.GPGPath, String.Format(ENCRYPT_COMMAND, Email), Text, out Output) != 0)
                throw new Exception("Failed to Encrypt Text");
            return Output;
        }

        static string Decrypt(string Text)
        {
            //return Text;
            string Output;
            if (RunCommand(ServerData.GPGPath, DECRYPT_COMMAND, Text, out Output) != 0)
                throw new Exception("Failed to Decrypt Text");
            return Output;
        }

        public MainForm()
        {
            InitializeComponent();
            data = new ServerData("BAS.sdf");
        }

        private void MainForm_Shown(object sender, EventArgs e)
        {
            UsersForm = new Users(data);
            SettingsForm = new Settings();
            SettingsForm.OnStartClick+=new EventHandler(SettingsForm_OnStartClick);
            SettingsForm.OnStopClick += new EventHandler(SettingsForm_OnStopClick);
            UsersForm.Show();
            UsersForm.Top = this.Top;
            UsersForm.Left = this.Left + this.Width;
            SettingsForm.Show();
        }

        private void SettingsForm_OnStartClick(object sender, EventArgs e){
            Listen("127.0.0.1", 2222);
        }

        private void SettingsForm_OnStopClick(object sender, EventArgs e)
        {
            ServerData.Server.Stop();
        }

        private void Listen(string IP, int Port)
        {
            IPAddress Addr = IPAddress.Parse(IP);
            ServerData.Server = new TcpListener(Addr, Port);
            ServerData.Server.Start();
            ServerData.Server.BeginAcceptSocket(new AsyncCallback(OnConnect), ServerData.Server);
        }

        private void OnConnect(IAsyncResult ar)
        {
            try
            {
                TcpListener listener = (TcpListener)ar.AsyncState;
                Socket client = listener.EndAcceptSocket(ar);
                Connection state = new Connection();
                state.WorkSocket = client;
                state.TimeStamp = DateTime.Now;
                state.ServerNonce = Generator.Next();
                client.BeginReceive(state.Buffer, 0, Connection.BufferSize, 0, new AsyncCallback(this.OnDataRead), state);
                ServerData.Server.BeginAcceptSocket(new AsyncCallback(OnConnect), ServerData.Server);

                IPEndPoint IP = client.LocalEndPoint as IPEndPoint;
                Monitor.Enter(data.Connections);
                data.Connections.Add(state);
                Monitor.Exit(data.Connections);
                AddRow(client.Handle.ToString(), IP.Address.ToString(), "Connected");
            }
            catch (Exception ex)
            {
            }
        }

        private User GetUser(string email)
        {
            lock (data.Users)
            {
                var res = data.Users.Where(u => u.Email.ToUpper().Trim() == email.ToUpper().Trim());
                return (res.Count<User>() == 0) ? null : res.First<User>();
            }
        }

        private string GetEmail(string rec)
        {
            if (Regex.IsMatch(rec, "<[A-Z0-9@._]+>", RegexOptions.IgnoreCase))
                rec = Regex.Replace(rec, "[^<]*<([^>]+)>.*", "$1");
            return rec.Trim();
        }

        private List<string> ParseEmails(string rec)
        {
            List<string> emails = new List<string>();
            foreach(string r in rec.Split(new char[] {','})){
                emails.Add(GetEmail(r));
            }
            return emails;
        }

        private void OnDataRead(IAsyncResult ar)
        {
            String content = "";
            Connection state = (Connection)ar.AsyncState;
            Socket client = state.WorkSocket;
            try
            {
                int bytesRead = client.EndReceive(ar);
                if (bytesRead > 0)
                {
                    Monitor.Enter(state);
                    state.Data.Append(Encoding.ASCII.GetString(state.Buffer, 0, bytesRead));
                    Monitor.Exit(state);
                    content = state.Data.ToString();
                    if (content.Length > 2048) // too large -> disconnect user
                    {
                        OnDisconnect(client);
                        client.Disconnect(true);
                        return;
                    }
                    if (content.EndsWith(DELIM2)) // koniec spravy
                    {
                        Monitor.Enter(data.Connections);
                        int index = data.Connections.IndexOf(state);
                        Monitor.Exit(data.Connections);
                        Monitor.Enter(state);
                        state.Data.Length = 0;
                        Monitor.Exit(state);

                        foreach (string message in content.Split(new string[] { DELIM2 }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            //string plaintext = message;
                            string plaintext = "";
                            if (message.Length > 512)
                            {
                                try
                                {
                                    plaintext = Decrypt(message);
                                }
                                catch (Exception)
                                { }
                            }
                            Match m;
                            m = Regex.Match(plaintext, "^Auth[\t ]+\"([^\"]+)\"[\t ]+([0-9]+)[\t ]*$", RegexOptions.IgnoreCase);
                            if (m.Success)
                            {
                                Monitor.Enter(state);
                                User user = GetUser(m.Groups[1].Value);
                                if (user != null)
                                {
                                    Monitor.Enter(user);
                                    user.Connection = state;
                                    state.User = user;
                                    state.UserNonce = Convert.ToInt64(m.Groups[2].Value);
                                    Monitor.Exit(user);
                                    state.NextNonce();
                                    client.Send(Encoding.ASCII.GetBytes(Encrypt("DONE " + state.UserNonce + " " + state.ServerNonce, user.Email) + DELIM2));
                                }
                            }
                            m = Regex.Match(plaintext, "^Login[\t ]+\"([^\"]+)\"[\t ]+([0-9]+)[\t ]+([0-9]+)[\t ]*$", RegexOptions.IgnoreCase);
                            if (m.Success)
                            {
                                Monitor.Enter(state);
                                if (!state.CompareNonce(Convert.ToInt64(m.Groups[2].Value),Convert.ToInt64(m.Groups[3].Value)))
                                    throw new Exception("Faulty nonces detected.");
                                User user = GetUser(m.Groups[1].Value);
                                if (user != null)
                                {
                                    Monitor.Enter(user);
                                    user.Connection = state;
                                    state.User = user;
                                    Monitor.Exit(user);
                                    state.NextNonce();
                                    client.Send(Encoding.ASCII.GetBytes(Encrypt("OK " + state.UserNonce + " " + state.ServerNonce, user.Email) + DELIM2));
                                }
                                Monitor.Enter(data.Users);
                                int rowid = data.Users.IndexOf(state.User);
                                Monitor.Exit(data.Users);
                                Monitor.Exit(state);
                                if (rowid == -1)
                                    throw new Exception("Invalid Login");
                                RefreshUserRow(rowid);
                            }
                            m = Regex.Match(plaintext, "^Credits[\t ]+([0-9]+)[\t ]+([0-9]+)[\t ]*$", RegexOptions.IgnoreCase);
                            if (m.Success)
                            {
                                Monitor.Enter(state);
                                if (!state.CompareNonce(Convert.ToInt64(m.Groups[1].Value),Convert.ToInt64(m.Groups[2].Value)))
                                    throw new Exception("Faulty nonces detected.");
                                if (state.User != null)
                                {
                                    Monitor.Enter(state.User);
                                    int credits = state.User.Credits;
                                    state.NextNonce();
                                    client.Send(Encoding.ASCII.GetBytes(Encrypt(credits.ToString() + " " + state.UserNonce + " " + state.ServerNonce, state.User.Email) + DELIM2));
                                    Monitor.Exit(state.User);
                                }
                                Monitor.Exit(state);
                            }
                            m = Regex.Match(plaintext, "^EnoughCredits[\t ]+\"([^\"]+)\"[\t ]+([0-9]+)[\t ]+([0-9]+)[\t ]*$", RegexOptions.IgnoreCase);
                            if (m.Success)
                            {
                                Monitor.Enter(state);
                                if (!state.CompareNonce(Convert.ToInt64(m.Groups[2].Value),Convert.ToInt64(m.Groups[3].Value)))
                                    throw new Exception("Faulty nonces detected.");
                                if (state.User != null)
                                {
                                    Monitor.Enter(state.User);
                                    int usercount = ParseEmails(m.Groups[1].Value).Where<string>(e => GetUser(e) != null).Count<string>();
                                    string status = (usercount <= state.User.Credits)
                                                  ? "ENOUGH"
                                                  : "NOTENOUGH";
                                    state.NextNonce();
                                    client.Send(Encoding.ASCII.GetBytes(Encrypt(status.ToString() + " " + state.UserNonce + " " + state.ServerNonce, state.User.Email) + DELIM2));
                                    Monitor.Exit(state.User);
                                }
                                Monitor.Exit(state);
                            }
                            m = Regex.Match(plaintext, "^Check[\t ]+\"([^\"]+)\"[\t ]+([A-Z0-9]+)[\t ]+([0-9]+)[\t ]+([0-9]+)[\t ]*$", RegexOptions.IgnoreCase);
                            if (m.Success)
                            {
                                string fromemail = GetEmail(m.Groups[1].Value);
                                string stamp = m.Groups[2].Value;
                                Monitor.Enter(data.Users);
                                Monitor.Enter(state);
                                if (!state.CompareNonce(Convert.ToInt64(m.Groups[3].Value),Convert.ToInt64(m.Groups[4].Value)))
                                    throw new Exception("Faulty nonces detected.");
                                User fromuser = GetUser(fromemail);
                                if ((state.User != null))
                                {
                                    Monitor.Enter(state.User);
                                    string msg = "NEQUALS";
                                    if (fromuser != null)
                                    {
                                        if(data.database.CheckMail(fromuser.ID, state.User.ID, stamp))
                                        {
                                            state.User.Credits++;
                                            data.database.DeleteMail(fromuser.ID, state.User.ID, stamp);
                                            data.database.ChangeUser(state.User.ID, state.User.Email, state.User.Credits);
                                            msg = "EQUALS";
                                        }
                                    }
                                    state.NextNonce();
                                    client.Send(Encoding.ASCII.GetBytes(Encrypt(msg + " " + state.UserNonce + " " + state.ServerNonce, state.User.Email) + DELIM2));
                                    Monitor.Exit(state.User);
                                }
                                Monitor.Exit(state);
                                Monitor.Exit(data.Users);
                            }
                            m = Regex.Match(plaintext, "^Send[\t ]+\"([^\"]+)\"[\t ]+([A-Z0-9]+)[\t ]+([0-9]+)[\t ]+([0-9]+)[\t ]*$", RegexOptions.IgnoreCase);
                            if (m.Success)
                            {
                                Monitor.Enter(state);
                                if (!state.CompareNonce(Convert.ToInt64(m.Groups[3].Value),Convert.ToInt64(m.Groups[4].Value)))
                                    throw new Exception("Faulty nonces detected.");
                                if (state.User != null)
                                {
                                    string stamp = m.Groups[2].Value;
                                    foreach (string email in ParseEmails(m.Groups[1].Value))
                                    {
                                        User touser = GetUser(email);
                                        if (touser != null)
                                        {
                                            Monitor.Enter(state.User);
                                            if (state.User.Credits > 0)
                                            {
                                                state.User.Credits--;
                                                data.database.ChangeUser(state.User.ID, state.User.Email, state.User.Credits);
                                                data.database.InsertMail(state.User.ID, touser.ID, stamp);
                                            }
                                            Monitor.Exit(state.User);
                                        }
                                    }
                                }
                                if (state.User != null)
                                {
                                    Monitor.Enter(data.Users);
                                    int rowid = data.Users.IndexOf(state.User);
                                    Monitor.Exit(data.Users);
                                    Monitor.Exit(state);
                                    RefreshUserRow(rowid);
                                    // refresh whole table -> reflect credits change
                                    RefreshIncomingMail();
                                    RefreshOutgoingMail();
                                }
                            }
                        }
                    }
                    client.BeginReceive(state.Buffer, 0, Connection.BufferSize, 0, new AsyncCallback(this.OnDataRead), state);
                }
                else
                {
                    OnDisconnect(client);
                }
            }
            catch (System.Net.Sockets.SocketException es)
            {
                OnDisconnect(client);
                SetStatus(es.Message);
            }
            catch (Exception e)
            {
                SetStatus(e.Message + "(" + e.Source + ")");
                client.BeginReceive(state.Buffer, 0, Connection.BufferSize, 0, new AsyncCallback(this.OnDataRead), state);
            }
        }

        private void DisconnectButton_Click(object sender, EventArgs e)
        {
            if (ConnectionsDataGrid.CurrentRow != null)
            {
                OnDisconnect(data.Connections[ConnectionsDataGrid.CurrentRow.Index].WorkSocket);
            }
        }

        private void OnDisconnect(Socket client)
        {
            Monitor.Enter(data.Connections);
            Connection state = data.Connections.Where(s => s.WorkSocket.Handle == client.Handle).First<Connection>();
            int index = data.Connections.IndexOf(state);
            data.Connections.RemoveAt(index);
            Monitor.Exit(data.Connections);
            DeleteRow(index);
            if (state.User != null)
            {
                Monitor.Enter(state.User);
                state.User.Connection = null;
                Monitor.Exit(state.User);
                RefreshUsers();
            }
        }

    }
}
