using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Web;
using System.IO;
using System.Globalization;
using Microsoft.Win32;

namespace CraneControl
{
    class HTTPServer
    {
        private TcpListener FTCPListener;
        private Thread FListenThread = null;
        private Hashtable FResponseStatus;
        private String FWebFolder;
        public event EventHandler OnNotify = null;
        private ModbusLibrary.ModbusClient momentumClient;
        private Boolean[] dOuts = new Boolean[8];
        private Boolean[] dIns = new Boolean[8];
        private UInt16[] aOuts = new UInt16[4];
        private UInt16[] aIns = new UInt16[6];
        private String FServerName = "HTTPServer";
        private frm Parent;

        public class NotifyEventArgs : EventArgs
        {
            String FMessage;
            public NotifyEventArgs(String Message)
            {
                this.FMessage = Message;
            }
            public String Message
            {
                get { return FMessage; }
            }
        }

        private struct SHTTPRequest
        {
            public string Method;
            public string URL;
            public string Version;
            public Hashtable Args;
            public bool Execute;
            public Hashtable Headers;
            public int BodySize;
            public byte[] BodyData;
            public Hashtable PostData;
        }

        private struct SHTTPResponse
        {
            public int status;
            public string version;
            public Hashtable Headers;
            public int BodySize;
            public byte[] BodyData;
            public FileStream fs;
        }

        enum TParseState
        {
            METHOD, URL, URLPARM, URLVALUE, VERSION,
            HEADERKEY, HEADERVALUE, BODY, POSTPARM, POSTVALUE, OK
        }

        enum TResponseState
        {
            OK = 200,
            BAD_REQUEST = 400,
            NOT_FOUND = 404,
            SERVER_ERROR = 500
        }

        public String WebFolder
        {
            get
            {
                return FWebFolder;
            }
            set
            {
                FWebFolder = value;
            }
        }

        public bool IsAlive
        {
            get
            {
                if (this.FListenThread == null)
                    return false;
                else return this.FListenThread.IsAlive;
            }
        }

        protected virtual void DoOnNotify(EventArgs e)
        {
            if (OnNotify != null)
                OnNotify(this, e);
        }

        public HTTPServer(frm Parent)
        {
            this.Parent = Parent;
            ResonseStatusInit();
            FWebFolder = System.Windows.Forms.Application.StartupPath + "\\Web";
            this.FTCPListener = new TcpListener(IPAddress.Any, 80);
            //momentumClient = new ModbusLibrary.ModbusClient("10.0.0.15", 502);
            momentumClient = new ModbusLibrary.ModbusClient("127.0.0.1", 502);
            momentumClient.OnError += new EventHandler(MomentumErrorEvent);
            UInt16 i = 0;
            for (i = 0; i < 8; i++)
                dIns.SetValue((Boolean)false, i);
            for (i = 0; i < 6; i++)
                aIns.SetValue((UInt16)0, i);
            for (i = 0; i < 8; i++)
                dOuts.SetValue((Boolean)false, i);
            for (i = 0; i < 4; i++)
                aOuts.SetValue((UInt16)0, i);
        }

        private void MomentumErrorEvent(object sender, EventArgs e)
        {
            ModbusLibrary.ErrorEventArgs chyba = (ModbusLibrary.ErrorEventArgs)e;
            DoOnNotify(new NotifyEventArgs(chyba.ErrorCode.ToString() + " - " + chyba.Message));
        }

        ~HTTPServer()
        {
            Stop();
        }

        private void ResonseStatusInit()
        {
            this.FResponseStatus = new Hashtable();

            this.FResponseStatus.Add(200, "200 Ok");
            this.FResponseStatus.Add(201, "201 Created");
            this.FResponseStatus.Add(202, "202 Accepted");
            this.FResponseStatus.Add(204, "204 No Content");

            this.FResponseStatus.Add(301, "301 Moved Permanently");
            this.FResponseStatus.Add(302, "302 Redirection");
            this.FResponseStatus.Add(304, "304 Not Modified");

            this.FResponseStatus.Add(400, "400 Bad Request");
            this.FResponseStatus.Add(401, "401 Unauthorized");
            this.FResponseStatus.Add(403, "403 Forbidden");
            this.FResponseStatus.Add(404, "404 Not Found");

            this.FResponseStatus.Add(500, "500 Internal Server Error");
            this.FResponseStatus.Add(501, "501 Not Implemented");
            this.FResponseStatus.Add(502, "502 Bad Gateway");
            this.FResponseStatus.Add(503, "503 Service Unavailable");
        }

        private void ListenForClients()
        {
            this.FTCPListener.Start();

            while (true)
            {
                TcpClient client = this.FTCPListener.AcceptTcpClient();
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
                clientThread.Start(client);
            }
        }

        private void HandleClient(object acceptedClient)
        {
            TcpClient client = (TcpClient)acceptedClient;
            NetworkStream clientStream = client.GetStream();
            TParseState parseState;
            SHTTPRequest HTTPRequest;
            SHTTPResponse HTTPResponse;

            HTTPRequest.Args = null;
            HTTPRequest.BodyData = null;
            HTTPRequest.BodySize = 0;
            HTTPRequest.Execute = false;
            HTTPRequest.Headers = null;
            HTTPRequest.Method = null;
            HTTPRequest.URL = "";
            HTTPRequest.Version = "";
            HTTPRequest.PostData = null;
            HTTPResponse.BodyData = null;
            HTTPResponse.BodySize = 0;
            HTTPResponse.fs = null;
            HTTPResponse.Headers = null;
            HTTPResponse.status = 0;
            HTTPResponse.version = "";

            byte[] message = new byte[4096];
            String cmplMessage = "";
            int bytesRead = 0;
            Int32 i = 0;
            Int32 k = 0;

            String hValue = "";
            String hKey = "";

            try
            {
                k = 0;
                parseState = TParseState.METHOD;
                do
                {
                    bytesRead = clientStream.Read(message, 0, message.Length);
                    cmplMessage = cmplMessage + Encoding.ASCII.GetString(message, 0, bytesRead);
                    i = 0;
                    do
                    {
                        switch (parseState)
                        {
                            case TParseState.METHOD:
                                if (message[i] != ' ')
                                    HTTPRequest.Method += (char)message[i++];
                                else
                                {
                                    i++;
                                    parseState = TParseState.URL;
                                }
                                break;
                            case TParseState.URL:
                                if (message[i] == '?')
                                {
                                    i++;
                                    hKey = "";
                                    HTTPRequest.Execute = true;
                                    HTTPRequest.Args = new Hashtable();
                                    parseState = TParseState.URLPARM;
                                }
                                else if (message[i] != ' ')
                                    HTTPRequest.URL += (char)message[i++];
                                else
                                {
                                    i++;
                                    HTTPRequest.URL = HttpUtility.UrlDecode(HTTPRequest.URL);
                                    parseState = TParseState.VERSION;
                                }
                                break;
                            case TParseState.URLPARM:
                                if (message[i] == '=')
                                {
                                    i++;
                                    hValue = "";
                                    parseState = TParseState.URLVALUE;
                                }
                                else if (message[i] == ' ')
                                {
                                    i++;
                                    HTTPRequest.URL = HttpUtility.UrlDecode(HTTPRequest.URL);
                                    parseState = TParseState.VERSION;
                                }
                                else
                                {
                                    hKey += (char)message[i++];
                                }
                                break;
                            case TParseState.URLVALUE:
                                if (message[i] == '&')
                                {
                                    i++;
                                    hKey = HttpUtility.UrlDecode(hKey);
                                    hValue = HttpUtility.UrlDecode(hValue);
                                    HTTPRequest.Args[hKey] = HTTPRequest.Args[hKey] != null ? HTTPRequest.Args[hKey] + ", " + hValue : hValue;
                                    hKey = "";
                                    parseState = TParseState.URLPARM;
                                }
                                else if (message[i] == ' ')
                                {
                                    i++;
                                    hKey = HttpUtility.UrlDecode(hKey);
                                    hValue = HttpUtility.UrlDecode(hValue);
                                    HTTPRequest.Args[hKey] = HTTPRequest.Args[hKey] != null ? HTTPRequest.Args[hKey] + ", " + hValue : hValue;

                                    HTTPRequest.URL = HttpUtility.UrlDecode(HTTPRequest.URL);
                                    parseState = TParseState.VERSION;
                                }
                                else
                                {
                                    hValue += (char)message[i++];
                                }
                                break;
                            case TParseState.VERSION:
                                if (message[i] == '\r')
                                    i++;
                                else if (message[i] != '\n')
                                    HTTPRequest.Version += (char)message[i++];
                                else
                                {
                                    i++;
                                    hKey = "";
                                    HTTPRequest.Headers = new Hashtable();
                                    parseState = TParseState.HEADERKEY;
                                }
                                break;
                            case TParseState.HEADERKEY:
                                if (message[i] == '\r')
                                    i++;
                                else if (message[i] == '\n')
                                {
                                    i++;
                                    if (HTTPRequest.Headers["Content-Length"] != null)
                                    {
                                        HTTPRequest.BodySize = Convert.ToInt32(HTTPRequest.Headers["Content-Length"]);
                                        HTTPRequest.BodyData = new byte[HTTPRequest.BodySize];
                                        parseState = TParseState.BODY;
                                    }
                                    else
                                        parseState = TParseState.OK;

                                }
                                else if (message[i] == ':')
                                    i++;
                                else if (message[i] != ' ')
                                    hKey += (char)message[i++];
                                else
                                {
                                    i++;
                                    hValue = "";
                                    parseState = TParseState.HEADERVALUE;
                                }
                                break;
                            case TParseState.HEADERVALUE:
                                if (message[i] == '\r')
                                    i++;
                                else if (message[i] != '\n')
                                    hValue += (char)message[i++];
                                else
                                {
                                    i++;
                                    HTTPRequest.Headers.Add(hKey, hValue);
                                    hKey = "";
                                    parseState = TParseState.HEADERKEY;
                                }
                                break;
                            case TParseState.BODY:
                                Array.Copy(message, i, HTTPRequest.BodyData, k, bytesRead - i);
                                k += bytesRead - i;
                                i = bytesRead;
                                if (HTTPRequest.BodySize <= k)
                                {
                                    parseState = TParseState.OK;
                                }
                                break;

                            //default:
                            //	i++;
                            //	break;
                        }
                    }
                    while (i < bytesRead);

                }
                while (clientStream.DataAvailable);

                if (HTTPRequest.Method == "POST")
                {
                    parseState = TParseState.POSTPARM;
                    HTTPRequest.PostData = new Hashtable();
                }
                i = 0;
                hKey = "";
                hValue = "";
                do
                {
                    switch (parseState)
                    {
                        case TParseState.POSTPARM:
                            if (HTTPRequest.BodyData[i] != '=')
                                hKey += (char)HTTPRequest.BodyData[i++];
                            else
                            {
                                i++;
                                hValue = "";
                                parseState = TParseState.POSTVALUE;
                            }
                            break;
                        case TParseState.POSTVALUE:
                            if ((HTTPRequest.BodyData[i] != '&') && (i < HTTPRequest.BodySize - 1))
                                hValue += (char)HTTPRequest.BodyData[i++];
                            else
                            {
                                if (i == HTTPRequest.BodySize - 1)
                                {
                                    hValue += (char)HTTPRequest.BodyData[i++];
                                    HTTPRequest.PostData.Add(hKey, hValue);
                                    parseState = TParseState.OK;
                                }
                                else
                                {
                                    i++;
                                    HTTPRequest.PostData.Add(hKey, hValue);
                                    hKey = "";
                                    parseState = TParseState.POSTPARM;
                                }
                            }
                            break;
                        case TParseState.OK:
                            i = HTTPRequest.BodySize;
                            break;
                    }
                }
                while (i < HTTPRequest.BodySize);

                //DoOnNotify(new NotifyEventArgs("HTTPServer: Request received."));
                //if (HTTPRequest.BodyData != null)
                //    DoOnNotify(new NotifyEventArgs(Encoding.ASCII.GetString(HTTPRequest.BodyData)));

                HTTPResponse.version = "HTTP/1.1";

                if (parseState != TParseState.OK)
                    HTTPResponse.status = (int)TResponseState.BAD_REQUEST;
                else
                    HTTPResponse.status = (int)TResponseState.OK;

                HTTPResponse.Headers = new Hashtable();
                HTTPResponse.Headers.Add("Server", FServerName);
                HTTPResponse.Headers.Add("Date", DateTime.Now.ToString("r"));

                PrepareResponse(ref HTTPRequest, ref HTTPResponse);

                String HeaderString = HTTPResponse.version + " " + this.FResponseStatus[HTTPResponse.status] + "\n";

                foreach (DictionaryEntry Header in HTTPResponse.Headers)
                {
                    HeaderString += Header.Key + ": " + Header.Value + "\n";
                }

                HeaderString += "\n";
                byte[] bHeadersString = Encoding.ASCII.GetBytes(HeaderString);

                clientStream.Write(bHeadersString, 0, bHeadersString.Length);
                //DoOnNotify(new NotifyEventArgs("HTTPServer: Send header."));

                if (HTTPResponse.BodyData != null)
                    clientStream.Write(HTTPResponse.BodyData, 0, HTTPResponse.BodyData.Length);

                if (HTTPResponse.fs != null)
                    using (HTTPResponse.fs)
                    {
                        byte[] b = new byte[client.SendBufferSize];
                        int bytes;
                        while ((bytes = HTTPResponse.fs.Read(b, 0, b.Length)) > 0)
                        {
                            clientStream.Write(b, 0, bytes);
                        }

                        HTTPResponse.fs.Close();
                    }

            }
            catch (Exception e)
            {
                DoOnNotify(new NotifyEventArgs("HTTPServer: Error: " + e.ToString()));
            }
            finally
            {
                clientStream.Close();
                client.Close();
                if (HTTPResponse.fs != null)
                    HTTPResponse.fs.Close();
                Thread.CurrentThread.Abort();
            }
        }

        private void FillKey(String Key, String Value, String Text)
        {
            Int32 index = Text.IndexOf(Key);
        }

        private void PrepareResponse(ref SHTTPRequest rq, ref SHTTPResponse rp)
        {
            String path = this.FWebFolder + "\\" + rq.URL.Replace("/", "\\");
            String controlPage = "/controlpanel.htm";

            if (rp.status != (int)TResponseState.OK)
                ErrorResponse(ref rp);

            if (Directory.Exists(path))
            {
                if (File.Exists(path + "index.htm"))
                    path += "\\index.htm";
                else
                {
                    String[] dirs = Directory.GetDirectories(path);
                    String[] files = Directory.GetFiles(path);

                    StringBuilder bodyStr = new StringBuilder();
                    bodyStr.Append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">");
                    bodyStr.Append("<HTML><HEAD>");
                    bodyStr.Append("<META http-equiv=Content-Type content=\"text/html; charset=utf-8\">");
                    bodyStr.Append("</HEAD>");
                    bodyStr.Append("<BODY><p>ščřFolder listing, to do not see this add a 'index.htm' document\n<p>");
                    for (int i = 0; i < dirs.Length; i++)
                        bodyStr.Append("<br><a href = \"" + rq.URL + Path.GetFileName(dirs[i]) + "/\">[" + Path.GetFileName(dirs[i]) + "]</a>");
                    for (int i = 0; i < files.Length; i++)
                        bodyStr.Append("<br><a href = \"" + rq.URL + Path.GetFileName(files[i]) + "\">" + Path.GetFileName(files[i]) + "</a>");
                    bodyStr.Append("</BODY></HTML>");

                    rp.BodyData = Encoding.UTF8.GetBytes(bodyStr.ToString());
                    return;
                }
            }

            if (rq.URL == controlPage)
            {
                /*
                UInt16 i = 0;
                if (rq.Method == "POST")
                {
                    for (i = 0; i < 8; i++)
                        if (rq.PostData.ContainsKey("DO" + Convert.ToString(i + 1)))
                        {
                            if (rq.PostData["DO" + Convert.ToString(i + 1)].ToString() == "1")
                                dOuts.SetValue((Boolean)true, i);
                            else
                                dOuts.SetValue((Boolean)false, i);
                        }
                        else
                            dOuts.SetValue((Boolean)false, i);
                    for (i = 0; i < 4; i++)
                        if (rq.PostData.ContainsKey("AO" + Convert.ToString(i + 1)))
                            aOuts.SetValue((UInt16)Convert.ToUInt16(rq.PostData["AO" + Convert.ToString(i + 1)].ToString()), i);
                        else
                            aOuts.SetValue((UInt16)0, i);
                }

                momentumClient.ConnectClient();
                Boolean momentumError = momentumClient.RefreshMomentum(dOuts, aOuts, ref dIns, ref aIns);
                momentumClient.DisconnectClient();
                

                if (momentumError == true)
                {
                    for (i = 0; i < 8; i++)
                        dIns.SetValue((Boolean)false, i);
                    for (i = 0; i < 6; i++)
                        aIns.SetValue((UInt16)0, i);
                    rp.status = (int)TResponseState.SERVER_ERROR;
                    ErrorResponse(ref rp);
                    return;
                    //for (i = 0; i < 8; i++)
                    //    dOuts.SetValue((Boolean)false, i);
                    //for (i = 0; i < 4; i++)
                    //    aOuts.SetValue((UInt16)0, i);
                }
                */

                StreamReader sr = new StreamReader(path, Encoding.UTF7);
                StringBuilder bodyStr = new StringBuilder();
                bodyStr.Append(sr.ReadToEnd());

                bodyStr.Replace("#magnet#", Parent.FIO.Magnet ? " checked='checked'" : "");
                bodyStr.Replace("#pozice#", Parent.FIO.Pozice.ToString());
                bodyStr.Replace("#posuv#", Math.Round(Parent.GetPosuvRychlost()).ToString());
                bodyStr.Replace("#lano#", Math.Round(Parent.GetLanoRychlost()).ToString());

                rp.BodyData = Encoding.Unicode.GetBytes(bodyStr.ToString());
                return;
            }

            if (File.Exists(path))
            {
                RegistryKey regKey = Registry.ClassesRoot.OpenSubKey(Path.GetExtension(path), true);
                String contentType = (String)regKey.GetValue("Content Type");

                rp.fs = File.Open(path, FileMode.Open);
                if (contentType != "")
                    rp.Headers["Content-type"] = contentType;
            }
            else
            {
                rp.status = (int)TResponseState.NOT_FOUND;
                ErrorResponse(ref rp);
            }
        }

        private void ErrorResponse(ref SHTTPResponse rp)
        {
            StringBuilder bodyStr = new StringBuilder();
            bodyStr.Append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">");
            bodyStr.Append("<HTML><HEAD>");
            bodyStr.Append("<META http-equiv=Content-Type content=\"text/html; charset=windows-1252\">");
            bodyStr.Append("</HEAD>");
            bodyStr.Append("<BODY><h1>Error " + rp.status.ToString() + "</h2>" + this.FResponseStatus[rp.status] + "</BODY></HTML>");
            rp.BodyData = Encoding.ASCII.GetBytes(bodyStr.ToString());
        }

        public void Start()
        {
            this.FListenThread = new Thread(new ThreadStart(ListenForClients));
            this.FListenThread.Start();
        }

        public void Stop()
        {
            if (IsAlive == true)
            {
                this.FListenThread.Abort();
                this.FTCPListener.Stop();
                this.FListenThread = null;
            }
        }
    }
}
