using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;
using System.IO;
using WiPoMServer;
using System.Globalization;
using System.Windows.Forms;

namespace WebServer
{
    public class Server : IDisposable
    {
        public enum HTTP_STATUS_CODE
	    {
	        OK = 200,
            ERROR = 400,
            NOT_FOUND = 404,
            ERROR_SERVER = 500
	    }

        public enum HTTP_CONNECTION
        {
            CLOSE,
            KEEP_ALIVE
        }

        public Dictionary<int, string> STATUS_DICT = new Dictionary<int, string>();
        public Dictionary<string, string> MIME_DICT = new Dictionary<string, string>();

        private CultureInfo culture = CultureInfo.InvariantCulture;
        private string ServerName = "CarControl (Devinyl)";
        private string WWWRoot = string.Empty;

        private string responseBegin = "HTTP/1.1 ";
        private string charSetDef = "; charset=UTF-8";

        private string response = @"HTTP/1.1 200 OK
Date: Wed, 10 Mar 2010 13:21:25 GMT
Server: Apache/2.2.4 (Linux/SUSE)
Vary: Cookie,Accept-Encoding,User-Agent
Content-Type: text/html; charset=utf-8
Via: 1.1 ironport.hslu.ch:80 (IronPort-WSA/6.3.3-015)
Connection: keep-alive

";
        //Content-Encoding: gzip
        //Transfer-Encoding: chunked
        //X-Junk: xxxxxxxxxxxxxxxx

        private string website = @"HTTP/1.1 200 OK
Date: Wed, 10 Mar 2010 14:30:25 GMT
Server: Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1
Last-Modified: Wed, 10 Mar 2010 13:28:18 GMT
ETag: ""1000000006158-88e-48172456f0080""
Accept-Ranges: bytes
Content-Length: ";

        private string websiteEnd = @"
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

";

        private string apacheResponse = @"HTTP/1.1 200 OK
Date: Thu, 11 Mar 2010 07:42:52 GMT
Server: Apache/2.2.14 (Win32) DAV/2 mod_ssl/2.2.14 OpenSSL/0.9.8l mod_autoindex_color PHP/5.3.1 mod_apreq2-20090110/2.7.1 mod_perl/2.0.4 Perl/v5.10.1
Vary: accept-language,accept-charset
Accept-Ranges: bytes
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Content-Type: text/html; charset=iso-utf-8
Content-Language: de
Content-Length: ";

        private string GetValueString = "getValue";
        private string SetValueString = "setValue";

        private string NewLine = "\r\n";
        private byte[] ending = Encoding.UTF8.GetBytes("\r\n\r\n");
        byte[] websiteheader1;
        byte[] websiteheader2;

        byte[] responseData;
        byte[] apacheResponseData;

        private TcpListener tcpListener;
        private Thread listenThread;

        private IWiPoMInterface controller;

        private int port;
        private bool isRunning = true;

        private List<Thread> ThreadList;

        public Server(int port, IWiPoMInterface controlInterface)
        {
            this.MIME_DICT.Add("html", "text/html");
            this.MIME_DICT.Add("gif", "image/gif");
            this.MIME_DICT.Add("jpg", "image/jpEg");
            this.MIME_DICT.Add("png", "image/png");
            this.MIME_DICT.Add("bmp", "image/bmp");

            this.MIME_DICT.Add("js", "application/x-javascript");
            this.MIME_DICT.Add("mp3", "audio/mpeg3");
            this.MIME_DICT.Add("wav", "audio/wav");
            this.MIME_DICT.Add("flv", "video/x-flv");
            this.MIME_DICT.Add("3gp", "video/3gpp");
            this.MIME_DICT.Add("avi", "video/avi");
            this.MIME_DICT.Add("mpg", "video/mpeg");
            this.MIME_DICT.Add("mpeg", "video/mpeg");

            this.MIME_DICT.Add("txt", "text/plain");
            this.MIME_DICT.Add("xaml", "application/xaml+xml");
            this.MIME_DICT.Add("xap", "application/x-silverlight-app");
            this.MIME_DICT.Add("xbap", "application/x-ms-xbap");

            this.STATUS_DICT.Add(200, "200 OK");
            this.STATUS_DICT.Add(400, "400 ERROR");
            this.STATUS_DICT.Add(404, "404 Not Found");
            this.STATUS_DICT.Add(500, "500 Error");
                
            this.controller = controlInterface;

            this.ThreadList = new List<Thread>();

            this.port = port;

            this.tcpListener = new TcpListener(IPAddress.Any, this.port);
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.listenThread.Start();

            //Create prepared Data Arrays
            websiteheader1 = Encoding.UTF8.GetBytes(this.website);
            websiteheader2 = Encoding.UTF8.GetBytes(this.websiteEnd);

            responseData = Encoding.UTF8.GetBytes(this.response);
            apacheResponseData = Encoding.UTF8.GetBytes(this.apacheResponse);

            this.WWWRoot = Path.Combine(Environment.CurrentDirectory, WiPoMServer.Properties.Settings.Default.wwwRoot);
        }

        public void Stop()
        {
            this.isRunning = false;
            this.Dispose();
        }

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

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

                    Debug.WriteLine("Client connected from:\t" + ((IPEndPoint)client.Client.RemoteEndPoint).Address);
                    //create a thread to handle communication
                    //with connected client
                    Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                    this.ThreadList.Add(clientThread);
                    clientThread.Start(client);
                }
                catch (Exception e)
                {
                    Debug.WriteLine(e.Message);
                }
            }
        }

        private void HandleClientComm(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();
            clientStream.ReadTimeout = 10000;

            while (true)
            {
                try
                {
                    string request = this.readRequest(clientStream);
                    if (!string.IsNullOrEmpty(request))
                        if (!this.handleRequest(request, clientStream))
                            break;
                        else
                            break;
                }
                catch (Exception e)
                {
                    //a socket error has occured
                    Debug.WriteLine("Error at loop HandleClientCom:\t" + e.Message +"\r\n"+e.StackTrace);
                    break;
                }
            }

            tcpClient.Close();
        }
        /// <summary>
        /// Handles request from client,
        /// returns true or false if connection should be closed
        /// </summary>
        /// <param name="request"></param>
        /// <param name="clientStream"></param>
        /// <returns></returns>
        private bool handleRequest(string request, NetworkStream clientStream)
        {
            //Extracts string after wwwAddress to decode.
            string address = this.extractRequestedAddress(request);

#if DEBUG
            Debug.WriteLine(address);
#endif

            //Browser wants special data from us ( ajax?)
            if (address.Contains(this.GetValueString))
            {
                //Send Data from interface or other stuff
                this.sendValue(clientStream);
                //this.sendResponseHeader(clientStream, HTTP_STATUS_CODE.OK, 0, string.Empty);
            }
            else if (address.Contains(this.SetValueString))
            {
                //Set value on Interface
                this.decodeValue(address, clientStream);
                //this.sendResponseHeader(clientStream, HTTP_STATUS_CODE.OK, 0, string.Empty);
            }
            else 
            //If adress contains a dot, we assume that requesting browser wants a file to download.
            if (address.Contains("."))
            {
                this.sendFile(clientStream, address);
            }
            //If requesting address is only a slash, we deliver him our "homepage"
            else if (address == "/")
            {
                this.sendWebsite(clientStream);
            }
            else
            {
                //Something we don't know how to handle, send error 404
                this.sendResponseHeader(clientStream, HTTP_STATUS_CODE.NOT_FOUND, 0, string.Empty);
            }

            return this.getKeepAliveStatus(request);

            #region OldChoice
            /*
            //AJAX Request for value
            if (request.Contains(this.GetValueString))
            {
                this.sendValue(clientStream);
            }
            //Request for Files / html pages
            else if (request.Contains(".html") || request.Contains(".png") || request.Contains(".jpg"))
            {
                Debug.WriteLine(request);
                //int pos = request.IndexOf("/");
                //int pos2 = request.IndexOf(" ");
                //string path = request.Substring(pos, pos2 - pos);
                //if (File.Exists(path))
                //{
                //    BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open));
                //    byte[] dataToSend = reader.ReadBytes((int)reader.BaseStream.Length);
                //    clientStream.Write(dataToSend, 0, dataToSend.Length);
                //    //ToDo write Header with  
                //    //    Content-Type: image/png
                //}
            }
            //Request for StartPage "/" --> index.html
            // Or 
            else
            {
                
            }
             */
            #endregion
        }

        private string extractRequestedAddress(string request)
        {
            string[] newLineSeparator = new string[] {"\r\n"};

            //Search for GET or POST 
            string trimmedRequest = request.Trim();
            if (trimmedRequest.ToUpper().StartsWith("GET"))
            {
                string[] lines = trimmedRequest.Split(newLineSeparator, StringSplitOptions.RemoveEmptyEntries);
                string firstL = lines[0];
                firstL = firstL.Remove(0, 3);
                firstL = firstL.TrimStart();
                int pos = firstL.IndexOf(" ");
                if (pos > 0)
                {
                    return firstL.Substring(0, pos);
                }
                else
                {
                    Debug.WriteLine("No address specified, returning: " + request);
                    return string.Empty;
                }
            }
            else
                if (trimmedRequest.ToUpper().StartsWith("POST"))
                {
                    return string.Empty;
                }
                //We don't know what it is, ->send dev/null
                else
                    return string.Empty;
        }

        /// <summary>
        /// Searches for any Connection-attributes like keep-alive or close
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        private bool getKeepAliveStatus(string request)
        {
            if (request.ToLower().Contains("keep-alive:"))
            {
                return true;
            }
            else
                return false;
        }

        private void sendResponseHeader(NetworkStream clientStream, HTTP_STATUS_CODE Code, int length, string ending)
        {
            StringBuilder response = new StringBuilder();

            //Write DataHeader
            response.Append(this.responseBegin + this.STATUS_DICT[(int)Code] + this.NewLine);
            response.Append("Connection: keep-alive" + this.NewLine);
            response.Append("Date: " + DateTime.Now.ToString("s", this.culture) + this.NewLine);
            response.Append("Server: " + this.ServerName + this.NewLine);

            if (!string.IsNullOrEmpty(ending))
            {
                if (this.MIME_DICT.ContainsKey(ending))
                {
                    response.Append("Content-Type: " + this.MIME_DICT[ending] + this.charSetDef + this.NewLine);
                }
                else
                    response.Append("Content-Type: " + this.MIME_DICT["html"] + this.charSetDef + this.NewLine);

                response.Append("Content-Length: " + length + this.NewLine);
            }
            response.Append(this.NewLine);

            byte[] responseData = Encoding.UTF8.GetBytes(response.ToString());
            clientStream.Write(responseData, 0, responseData.Length);
        }

        private void sendWebsite(NetworkStream clientStream)
        {
            //Send Website

            clientStream.Write(websiteheader1, 0, websiteheader1.Length);

            string websitePath = Path.Combine(this.WWWRoot, WiPoMServer.Properties.Settings.Default.website);

            TextReader reader = new StreamReader(websitePath);
            byte[] indexPage = Encoding.UTF8.GetBytes(reader.ReadToEnd());

            byte[] length = Encoding.UTF8.GetBytes(indexPage.Length.ToString());
            clientStream.Write(length, 0, length.Length);

            clientStream.Write(websiteheader2, 0, websiteheader2.Length);

            clientStream.Write(indexPage, 0, indexPage.Length);
        }
        private void sendFile(NetworkStream clientStream, string address)
        {
            //Send file
            string fileName = address.Contains("/") ? address.Substring(1, address.Length - 1) : address;
            fileName = Path.Combine(this.WWWRoot, fileName);
            if (File.Exists(fileName))
            {
                BinaryReader reader = new BinaryReader(File.Open(fileName, FileMode.Open));
                byte[] dataToSend = reader.ReadBytes((int)reader.BaseStream.Length);

                this.sendResponseHeader(clientStream, HTTP_STATUS_CODE.OK, dataToSend.Length, Path.GetExtension(address));
                clientStream.Write(dataToSend, 0, dataToSend.Length);
                reader.Close();
            }
            else
                //File not found answer (Error 404)
                this.sendResponseHeader(clientStream, HTTP_STATUS_CODE.NOT_FOUND, 0, string.Empty);
        }

        private void decodeValue(string request, NetworkStream clientStream)
        {
            Debug.WriteLine("Decode Value Request:\t" + request);
            string valueName = string.Empty;
            int pos = request.IndexOf("/");
            if (pos != -1)
                valueName = request.Substring(pos, request.Length - pos);
            if (valueName.Contains("."))
            {
                bool enable = true;

                string[] values = valueName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);

                if (values.Length >= 3)
                {
                    if (values[1] == "On")
                    {
                        enable = true;
                    }
                    else if (values[1] == "Off")
                    {
                        enable = false;
                    }
                    this.controller.setValue(values[2], enable);
                    Debug.WriteLine("Control value was set:\t" + values[1] + "\t" + enable);
                }
                else if (values.Length == 2)
                {
                    this.controller.setValue(values[1], enable);
                    Debug.WriteLine("Control value was set:\t" + values[1] + "\t" + enable);
                }
                else
                    Debug.WriteLine("Shouldn't have happend :(\r\n There was no Value in path to submit");
            }
        }

        private void sendValue(NetworkStream clientStream)
        {
            byte[] data = Encoding.UTF8.GetBytes(((int)this.controller.getValue(0)).ToString()); //+ "\r\n\r\n");

            clientStream.Write(apacheResponseData, 0, apacheResponseData.Length);
            byte[] dataLength = Encoding.UTF8.GetBytes(data.Length.ToString());
            clientStream.Write(dataLength, 0, dataLength.Length);
            clientStream.Write(ending, 0, ending.Length);

            clientStream.Write(data, 0, data.Length);

#if AJAX_DEBUG
            Debug.WriteLine("*******************************************");
            Debug.WriteLine("*                 AJAX Request            *");
            Debug.WriteLine("*******************************************");
            Debug.WriteLine("*******************************************");
            Debug.WriteLine("               Sending Value              *");
            Debug.WriteLine("                   "
                + this.controller.getValue(0) + "              *");
            Debug.WriteLine("*******************************************");
#endif
        }

        private string readRequest(NetworkStream clientStream)
        {
            StreamReader reader = new StreamReader(clientStream);
            StringBuilder sb = new StringBuilder();
            int countEmptyRows = 0;
            string row = string.Empty;
            do
            {
                try
                {
                    row = reader.ReadLine();
                }
                catch (Exception e)
                {
                    Debug.WriteLine("Exception at ReadRequest at Readline:\t" + e.Message + "\r\n" + e.StackTrace);
                    throw new IOException();
                }
                if (row != null)
                {
                    if (row != string.Empty)
                        sb.Append(row);
                    else
                        countEmptyRows++;
                }
                else
                    break;

            } while (countEmptyRows != 1);

            return sb.ToString();

            #region old
            /* Old Variant
            StringBuilder stringbuilder = new StringBuilder();

            byte[] receivedData = new byte[128];
            bool hasEnd = false;
            int pos = -1;
            while (!hasEnd)
            {
                int read = clientStream.Read(receivedData, 0, receivedData.Length);
                if (read == 0)
                    break;
                string receivedString = Encoding.UTF8.GetString(receivedData);
                if (receivedString.Contains("\r\n\r\n"))
                {
                    hasEnd = true;
                    pos = receivedString.IndexOf("\r\n\r\n");
                    stringbuilder.Append(receivedString, 0, pos);
                }
                else
                {
                    stringbuilder.Append(receivedString);
                }
            }

            //We got answer;
            if (pos != -1)
            {
                string answer = stringbuilder.ToString();
                return answer;
            }
            else
                return string.Empty;
             * */
            #endregion
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (tcpListener != null)
                tcpListener.Stop();
            foreach (var item in this.ThreadList)
            {
                if (item != null)
                {
                    try
                    {
                        item.Abort();
                    }
                    catch
                    { }
                }
            }
        }

        #endregion
    }
}
