using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Numerics;

//VERSION_0.2

namespace WIN_CLIENT_RPI
{
    class IPandSocket
    {
        public TcpClientSifrovany Client;
        public string IP;
        public int port;
        public IPandSocket(string iIP,int iport, TcpClientSifrovany iClient)
        {
            port = iport;
            IP = iIP; Client = iClient;
        }
    }
    class TcpListenerSifrovany
    {
        TcpListener listener;
        TcpClientSifrovany client;
        EventHandler Sluzba_pre_priate_data;
        private TcpListenerSifrovany() { }
        public TcpListenerSifrovany(int port, EventHandler iSluzba_pre_priate_data)
        {
            Sluzba_pre_priate_data = iSluzba_pre_priate_data;
            listener = new TcpListener(System.Net.IPAddress.Any, port);
            //listener.Start();
            //listener.BeginAcceptTcpClient(asyn_accept_listener,this);

        }
        public void Start()
        {
            listener.Start();
            listener.BeginAcceptTcpClient(asyn_accept_listener, this);
        }
        private void asyn_accept_listener(IAsyncResult ar)
        {
            TcpListenerSifrovany l = (TcpListenerSifrovany)ar.AsyncState;
            TcpClient tcp = l.listener.EndAcceptTcpClient(ar);
            l.listener.BeginAcceptTcpClient(asyn_accept_listener, l);
            l.client = new TcpClientSifrovany(tcp);
            l.client.BeginRead(l.Sluzba_pre_priate_data);
        }
    }
    class TcpClientSifrovany
    {
        public class Receive
        {
            public TcpClientSifrovany client;
            public byte[] mesage;
            public int mesage_lenght;
        }
        private class sluzba_tcp_mesage
        {
            public byte[] mesage;
            public TcpClientSifrovany client;
            public EventHandler sluzba;
        }
        TcpClient TcpCl;
        UInt32 heslo;
        Boolean secured = false;
        bool Closed = false;
        /// <summary>
        /// Použitie pre priate TCP spojenia s liseneru a pre jeho následné zašifrovanie.
        /// </summary>
        /// <param name="iTcpClient"></param>
        public TcpClientSifrovany(TcpClient iTcpClient, Boolean receive = true)
        {
            TcpCl = iTcpClient;
            heslo = 0;
            if (receive)
            {
                byte[] mesage = new byte[12];
                //TcpCl.GetStream().ReadTimeout = 1000;
                TcpCl.GetStream().Read(mesage, 0, 12);
                UInt32 p = BitConverter.ToUInt32(mesage, 0);
                UInt32 q = BitConverter.ToUInt32(mesage, 4);
                UInt32 N = BitConverter.ToUInt32(mesage, 8);
                Random Rand = new Random(System.DateTime.Now.Millisecond); //random zo semienkom 
                UInt32 B = (UInt32)((Math.Abs(Rand.NextDouble() - 0.5) + 0.5) * UInt32.MaxValue);
                UInt32 M = (UInt32)System.Numerics.BigInteger.ModPow(q, B, p);
                heslo = (UInt32)System.Numerics.BigInteger.ModPow(N, B, p);
                secured = true;
                BitConverter.GetBytes(M).CopyTo(mesage, 4);
                TcpCl.Client.SendTimeout = 100;
                TcpCl.Client.SendTimeout = 100;
                TcpCl.GetStream().WriteTimeout = 100;
                TcpCl.GetStream().Write(mesage, 0, 12);

            }
        }
        public void BeginRead(EventHandler sluzba_pre_data)
        {
            byte[] mesage = new byte[1024];
            sluzba_tcp_mesage vstup = new sluzba_tcp_mesage();
            vstup.client = this;
            vstup.sluzba = sluzba_pre_data;
            vstup.mesage = mesage;
            this.TcpCl.GetStream().BeginRead(mesage, 0, 1024, priatie_a_desifrovanie, vstup);
        }
        public int Read(byte[] mesage, int ocakavane_data = 0, int timeout = 300)
        {
            if (ocakavane_data > mesage.Length) return -1;
            int ret = 0;

            GetStream().ReadTimeout = timeout;
            if (ocakavane_data == 0)
            {
                try
                {
                    ret = this.GetStream().Read(mesage,0, mesage.Length);
                    odhesluj(mesage, heslo, 0, ret);
                    return ret;
                }
                catch (Exception)
                {
                    Closed = true;
                    return -1;
                }
            }
            else
            {
                while (ret < ocakavane_data)
                {
                    try
                    {
                        ret += this.GetStream().Read(mesage, ret, mesage.Length - ret);
                    }
                    catch (Exception)
                    {
                        Closed = true;
                        return -1;
                    }
                }
                odhesluj(mesage, heslo, 0, ret);
                return ret;
            }


        }
        private void priatie_a_desifrovanie(IAsyncResult ar)
        {
            sluzba_tcp_mesage s = (sluzba_tcp_mesage)ar.AsyncState;
            int nacitane = 0;
            try
            {
                nacitane = s.client.GetStream().EndRead(ar);
                if (nacitane == 0)
                    return;
            }
            catch (Exception) { return; }
            Receive vstup = new Receive();
            vstup.client = this;
            vstup.mesage = s.client.odhesluj(s.mesage, s.client.heslo, 0, nacitane);
            vstup.mesage_lenght = nacitane;
            s.sluzba(vstup, (EventArgs)null);
            s.client.TcpCl.GetStream().BeginRead(s.mesage, 0, 1024, priatie_a_desifrovanie, s);
        }
        public TcpClientSifrovany()
        {
            TcpCl = new TcpClient();
            heslo = 0;
        }
        internal void Cancel()
        {
            this.TcpCl.Close();
            Closed = true;
            this.secured = false;
        }
        public void Conect_nonbloking(string hostname, int port)
        {
            if (Closed == false)
            {
                IPandSocket VSTUP = new IPandSocket(hostname, port, this);
                TcpCl.BeginConnect(hostname, port, Connected_to_server, VSTUP);
            }
            else
                throw new InvalidOperationException();

        }

        private void EndConnect(IAsyncResult ar)
        {
            TcpCl.EndConnect(ar);
        }
        private NetworkStream GetStream()
        {
            try
            {
                return TcpCl.GetStream();
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.ToString(), "EXCEPTION");
            }
            return null;
        }
        private IAsyncResult BeginConnect(string hostname, int port, AsyncCallback calback, object obj)
        {
            if (Closed==false)
                return TcpCl.BeginConnect(hostname, port, calback, obj);
            return null;
        }

        private static void Connected_to_server(IAsyncResult ar)
        {
            IPandSocket s = (IPandSocket)ar.AsyncState;
            //TcpClient s = (TcpClient)ar.AsyncState;

            if (s.Client.Connected == true && s.Client.Connected == true)
            {
                s.Client.EndConnect(ar);
                // priate spojenie ... priprav heslo
                Random Rand = new Random(System.DateTime.Now.Millisecond); //random zo semienkom 
                UInt32 p = (UInt32)((Math.Abs(Rand.NextDouble() - 0.5) + 0.5) * UInt32.MaxValue);
                UInt32 q = (UInt32)((Math.Abs(Rand.NextDouble() - 0.5) + 0.5) * UInt32.MaxValue);
                UInt32 A = (UInt32)((Math.Abs(Rand.NextDouble() - 0.5) + 0.5) * UInt32.MaxValue);
                UInt32 N = (UInt32)System.Numerics.BigInteger.ModPow(q, A, p);
                byte[] mesage = new byte[12];
                BitConverter.GetBytes(p).CopyTo(mesage, 0);
                BitConverter.GetBytes(q).CopyTo(mesage, 4);
                BitConverter.GetBytes(N).CopyTo(mesage, 8);

                s.Client.GetStream().Write(mesage, 0, 12);
                int count = 0;
                //System.Threading.Thread.Sleep(3000);
                while (count++ < 30)
                {
                    if (s.Client.Available > 0)
                    {
                        int readed = s.Client.GetStream().Read(mesage, 0, 12);
                        if (readed > 11)
                        {
                            UInt32 M = BitConverter.ToUInt32(mesage, 4);
                            s.Client.heslo = (UInt32)System.Numerics.BigInteger.ModPow(M, A, p);
                            s.Client.secured = true;
                            count = 40;
                        }
                    }
                    else
                    {
                        System.Threading.Thread.Sleep(100);

                    }
                }
                if (count == 30)
                {
                    // problem
                }

            }
            else
            {
                Boolean end = false;
                do
                {
                    try
                    {
                        System.Threading.Thread.Sleep(3000);
                        s.Client.BeginConnect(s.IP, s.port, Connected_to_server, s);
                        end = true;
                    }
                    catch (System.Net.Sockets.SocketException)
                    {
                        end = false;
                    }
                } while (end == false);
            }
        }
        public Boolean Write(string sprava)
        {
            byte[] msg = ASCIIEncoding.UTF8.GetBytes(sprava);
            return Write(msg, 0, msg.Length);
        }
        public Boolean Write(byte[] mesage, int ofset, int size)
        {
            if (TcpCl.Connected == true)
            {
                try
                {
                    this.TcpCl.GetStream().Write(zahesluj(mesage, this.heslo, 0, size), 0, size);
                    return true;
                }
                catch (Exception) { 
                    return false; }
            }
            else
                return false;
        }

        #region sifrovanie
        private byte[] zahesluj(byte[] vstup, UInt32 heslo)
        {
            byte[] rot = new byte[32];
            for (int i = 0; i < 32; i++)
                rot[i] = (byte)(((heslo >> i) % 7) + 1);

            for (int j = 0; j < 32; j += 2)
            {
                byte pomocna = vstup[vstup.Length - 1];
                for (int i = vstup.Length - 1; i >= 0; i--)
                {
                    if (i == 0)
                        vstup[i] = (byte)((pomocna << (8 - rot[j])) | (vstup[i] >> rot[j]));
                    else
                        vstup[i] = (byte)((vstup[i - 1] << (8 - rot[j])) | (vstup[i] >> rot[j]));
                }
                for (int i = 0; i < vstup.Length; i++)
                {
                    vstup[i] = (byte)((vstup[i] << (8 - rot[j + 1])) | (vstup[i] >> rot[j + 1]));
                }
            }
            return vstup;
        }
        private byte[] zahesluj(byte[] vstup, UInt32 heslo, int start_index, int lenght)
        {
            byte[] rot = new byte[32];
            for (int i = 0; i < 32; i++)
                rot[i] = (byte)(((heslo >> i) % 7) + 1);

            for (int j = 0; j < 32; j += 2)
            {
                byte pomocna = vstup[start_index + lenght - 1];
                for (int i = start_index + lenght - 1; i >= start_index; i--)
                {
                    if (i == start_index)
                        vstup[i] = (byte)((pomocna << (8 - rot[j])) | (vstup[i] >> rot[j]));
                    else
                        vstup[i] = (byte)((vstup[i - 1] << (8 - rot[j])) | (vstup[i] >> rot[j]));
                }
                for (int i = start_index; i < start_index + lenght; i++)
                {
                    vstup[i] = (byte)((vstup[i] << (8 - rot[j + 1])) | (vstup[i] >> rot[j + 1]));
                }
            }
            return vstup;
        }
        private byte[] odhesluj(byte[] vstup, UInt32 heslo)
        {
            byte[] rot = new byte[32];
            for (int i = 0; i < 32; i++)
                rot[i] = (byte)(((heslo >> i) % 7) + 1);

            for (int j = 31; j >= 0; j -= 2)
            {
                for (int i = 0; i < vstup.Length; i++)
                {
                    vstup[i] = (byte)((vstup[i] >> (8 - rot[j])) | (vstup[i] << rot[j]));
                }
                byte pomocna = vstup[0];
                for (int i = 0; i < vstup.Length; i++)
                {
                    if (i == (vstup.Length - 1))
                        vstup[i] = (byte)((pomocna >> (8 - rot[j - 1])) | (vstup[i] << rot[j - 1]));
                    else
                        vstup[i] = (byte)((vstup[i + 1] >> (8 - rot[j - 1])) | (vstup[i] << rot[j - 1]));
                }

            }
            return vstup;
        }
        private byte[] odhesluj(byte[] vstup, UInt32 heslo, int start_index, int lenght)
        {
            byte[] rot = new byte[32];
            for (int i = 0; i < 32; i++)
                rot[i] = (byte)(((heslo >> i) % 7) + 1);

            for (int j = 31; j >= 0; j -= 2)
            {
                for (int i = start_index; i < start_index + lenght; i++)
                {
                    vstup[i] = (byte)((vstup[i] >> (8 - rot[j])) | (vstup[i] << rot[j]));
                }
                byte pomocna = vstup[start_index];
                for (int i = start_index; i < start_index + lenght; i++)
                {
                    if (i == (start_index + lenght - 1))
                        vstup[i] = (byte)((pomocna >> (8 - rot[j - 1])) | (vstup[i] << rot[j - 1]));
                    else
                        vstup[i] = (byte)((vstup[i + 1] >> (8 - rot[j - 1])) | (vstup[i] << rot[j - 1]));
                }

            }
            return vstup;
        }
        #endregion
        public bool Connected
        {
            
            get {
                if (Closed == false)
                    return TcpCl.Connected;
                else return false;
        }

        }
        public int Available
        {
            get { return TcpCl.Available; }
        }
        public int WriteTimeout
        {
            get
            {
                try
                {
                    int hodnota = TcpCl.GetStream().WriteTimeout;
                    return hodnota;
                }
                catch (Exception) { return -1; }
            }
            set
            {
                try
                {
                    TcpCl.GetStream().WriteTimeout = value;
                }
                catch (Exception) { }
            }
        }

        public bool Secured { get { return secured; } }

    }
}
