//
// Copyright (c) ZeroC, Inc. All rights reserved.
//

namespace IceInternal
{
    using System.Diagnostics;
    using System.Collections.Generic;
    using System.Text;

    internal sealed class WebSocketException : System.Exception
    {
        internal WebSocketException() :
            base("", null)
        {
        }

        internal WebSocketException(string message) :
            base(message, null)
        {
        }

        internal WebSocketException(string message, System.Exception cause) :
            base(message, cause)
        {
        }

        internal WebSocketException(System.Exception cause) :
            base("", cause)
        {
        }
    }

    internal sealed class HttpParser
    {
        internal HttpParser()
        {
            _type = Type.Unknown;
            _versionMajor = 0;
            _versionMinor = 0;
            _status = 0;
            _state = State.Init;
        }

        internal enum Type
        {
            Unknown,
            Request,
            Response
        };

        internal int isCompleteMessage(ByteBuffer buf, int begin, int end)
        {
            byte[] raw = buf.rawBytes();
            int p = begin;

            //
            // Skip any leading CR-LF characters.
            //
            while(p < end)
            {
                byte ch = raw[p];
                if(ch != (byte)'\r' && ch != (byte)'\n')
                {
                    break;
                }
                ++p;
            }

            //
            // Look for adjacent CR-LF/CR-LF or LF/LF.
            //
            bool seenFirst = false;
            while(p < end)
            {
                byte ch = raw[p++];
                if(ch == (byte)'\n')
                {
                    if(seenFirst)
                    {
                        return p;
                    }
                    else
                    {
                        seenFirst = true;
                    }
                }
                else if(ch != (byte)'\r')
                {
                    seenFirst = false;
                }
            }

            return -1;
        }

        internal bool parse(ByteBuffer buf, int begin, int end)
        {
            byte[] raw = buf.rawBytes();
            int p = begin;
            int start = 0;
            const char CR = '\r';
            const char LF = '\n';

            if(_state == State.Complete)
            {
                _state = State.Init;
            }

            while(p != end && _state != State.Complete)
            {
                char c = (char)raw[p];

                switch(_state)
                {
                case State.Init:
                {
                    _method = new StringBuilder();
                    _uri = new StringBuilder();
                    _versionMajor = -1;
                    _versionMinor = -1;
                    _status = -1;
                    _reason = "";
                    _headers.Clear();
                    _state = State.Type;
                    continue;
                }
                case State.Type:
                {
                    if(c == CR || c == LF)
                    {
                        break;
                    }
                    else if(c == 'H')
                    {
                        //
                        // Could be the start of "HTTP/1.1" or "HEAD".
                        //
                        _state = State.TypeCheck;
                        break;
                    }
                    else
                    {
                        _state = State.Request;
                        continue;
                    }
                }
                case State.TypeCheck:
                {
                    if(c == 'T') // Continuing "H_T_TP/1.1"
                    {
                        _state = State.Response;
                    }
                    else if(c == 'E') // Expecting "HEAD"
                    {
                        _state = State.Request;
                        _method.Append('H');
                        _method.Append('E');
                    }
                    else
                    {
                        throw new WebSocketException("malformed request or response");
                    }
                    break;
                }
                case State.Request:
                {
                    _type = Type.Request;
                    _state = State.RequestMethod;
                    continue;
                }
                case State.RequestMethod:
                {
                    if(c == ' ' || c == CR || c == LF)
                    {
                        _state = State.RequestMethodSP;
                        continue;
                    }
                    _method.Append(c);
                    break;
                }
                case State.RequestMethodSP:
                {
                    if(c == ' ')
                    {
                        break;
                    }
                    else if(c == CR || c == LF)
                    {
                        throw new WebSocketException("malformed request");
                    }
                    _state = State.RequestURI;
                    continue;
                }
                case State.RequestURI:
                {
                    if(c == ' ' || c == CR || c == LF)
                    {
                        _state = State.RequestURISP;
                        continue;
                    }
                    _uri.Append(c);
                    break;
                }
                case State.RequestURISP:
                {
                    if(c == ' ')
                    {
                        break;
                    }
                    else if(c == CR || c == LF)
                    {
                        throw new WebSocketException("malformed request");
                    }
                    _state = State.Version;
                    continue;
                }
                case State.RequestLF:
                {
                    if(c != LF)
                    {
                        throw new WebSocketException("malformed request");
                    }
                    _state = State.HeaderFieldStart;
                    break;
                }
                case State.HeaderFieldStart:
                {
                    //
                    // We've already seen a LF to reach this state.
                    //
                    // Another CR or LF indicates the end of the header fields.
                    //
                    if(c == CR)
                    {
                        _state = State.HeaderFieldEndLF;
                        break;
                    }
                    else if(c == LF)
                    {
                        _state = State.Complete;
                        break;
                    }
                    else if(c == ' ')
                    {
                        //
                        // Could be a continuation line.
                        //
                        _state = State.HeaderFieldContStart;
                        break;
                    }

                    _state = State.HeaderFieldNameStart;
                    continue;
                }
                case State.HeaderFieldContStart:
                {
                    if(c == ' ')
                    {
                        break;
                    }

                    _state = State.HeaderFieldCont;
                    start = p;
                    continue;
                }
                case State.HeaderFieldCont:
                {
                    if(c == CR || c == LF)
                    {
                        if(p > start)
                        {
                            if(_headerName.Length == 0)
                            {
                                throw new WebSocketException("malformed header");
                            }
                            Debug.Assert(_headers.ContainsKey(_headerName));
                            string s = _headers[_headerName];
                            StringBuilder newValue = new StringBuilder(s);
                            newValue.Append(' ');
                            for(int i = start; i < p; ++i)
                            {
                                newValue.Append((char)raw[i]);
                            }
                            _headers[_headerName] = newValue.ToString();
                            _state = c == CR ? State.HeaderFieldLF : State.HeaderFieldStart;
                        }
                        else
                        {
                            //
                            // Could mark the end of the header fields.
                            //
                            _state = c == CR ? State.HeaderFieldEndLF : State.Complete;
                        }
                    }

                    break;
                }
                case State.HeaderFieldNameStart:
                {
                    Debug.Assert(c != ' ');
                    start = p;
                    _headerName = "";
                    _state = State.HeaderFieldName;
                    continue;
                }
                case State.HeaderFieldName:
                {
                    if(c == ' ' || c == ':')
                    {
                        _state = State.HeaderFieldNameEnd;
                        continue;
                    }
                    else if(c == CR || c == LF)
                    {
                        throw new WebSocketException("malformed header");
                    }
                    break;
                }
                case State.HeaderFieldNameEnd:
                {
                    if(_headerName.Length == 0)
                    {
                        StringBuilder str = new StringBuilder();
                        for(int i = start; i < p; ++i)
                        {
                            str.Append((char)raw[i]);
                        }
                        _headerName = str.ToString().ToLower();
                        //
                        // Add a placeholder entry if necessary.
                        //
                        if(!_headers.ContainsKey(_headerName))
                        {
                            _headers[_headerName] = "";
                            _headerNames[_headerName] = str.ToString();
                        }
                    }

                    if(c == ' ')
                    {
                        break;
                    }
                    else if(c != ':' || p == start)
                    {
                        throw new WebSocketException("malformed header");
                    }

                    _state = State.HeaderFieldValueStart;
                    break;
                }
                case State.HeaderFieldValueStart:
                {
                    if(c == ' ')
                    {
                        break;
                    }

                    //
                    // Check for "Name:\r\n"
                    //
                    if(c == CR)
                    {
                        _state = State.HeaderFieldLF;
                        break;
                    }
                    else if(c == LF)
                    {
                        _state = State.HeaderFieldStart;
                        break;
                    }

                    start = p;
                    _state = State.HeaderFieldValue;
                    continue;
                }
                case State.HeaderFieldValue:
                {
                    if(c == CR || c == LF)
                    {
                        _state = State.HeaderFieldValueEnd;
                        continue;
                    }
                    break;
                }
                case State.HeaderFieldValueEnd:
                {
                    Debug.Assert(c == CR || c == LF);
                    if(p > start)
                    {
                        StringBuilder str = new StringBuilder();
                        for(int i = start; i < p; ++i)
                        {
                            str.Append((char)raw[i]);
                        }
                        string s = null;
                        if(!_headers.TryGetValue(_headerName, out s) || s.Length == 0)
                        {
                            _headers[_headerName] = str.ToString();
                        }
                        else
                        {
                            _headers[_headerName] = s + ", " + str.ToString();
                        }
                    }

                    if(c == CR)
                    {
                        _state = State.HeaderFieldLF;
                    }
                    else
                    {
                        _state = State.HeaderFieldStart;
                    }
                    break;
                }
                case State.HeaderFieldLF:
                {
                    if(c != LF)
                    {
                        throw new WebSocketException("malformed header");
                    }
                    _state = State.HeaderFieldStart;
                    break;
                }
                case State.HeaderFieldEndLF:
                {
                    if(c != LF)
                    {
                        throw new WebSocketException("malformed header");
                    }
                    _state = State.Complete;
                    break;
                }
                case State.Version:
                {
                    if(c != 'H')
                    {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionH;
                    break;
                }
                case State.VersionH:
                {
                    if(c != 'T')
                    {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionHT;
                    break;
                }
                case State.VersionHT:
                {
                    if(c != 'T')
                    {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionHTT;
                    break;
                }
                case State.VersionHTT:
                {
                    if(c != 'P')
                    {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionHTTP;
                    break;
                }
                case State.VersionHTTP:
                {
                    if(c != '/')
                    {
                        throw new WebSocketException("malformed version");
                    }
                    _state = State.VersionMajor;
                    break;
                }
                case State.VersionMajor:
                {
                    if(c == '.')
                    {
                        if(_versionMajor == -1)
                        {
                            throw new WebSocketException("malformed version");
                        }
                        _state = State.VersionMinor;
                        break;
                    }
                    else if(c < '0' || c > '9')
                    {
                        throw new WebSocketException("malformed version");
                    }
                    if(_versionMajor == -1)
                    {
                        _versionMajor = 0;
                    }
                    _versionMajor *= 10;
                    _versionMajor += (int)(c - '0');
                    break;
                }
                case State.VersionMinor:
                {
                    if(c == CR)
                    {
                        if(_versionMinor == -1 || _type != Type.Request)
                        {
                            throw new WebSocketException("malformed version");
                        }
                        _state = State.RequestLF;
                        break;
                    }
                    else if(c == LF)
                    {
                        if(_versionMinor == -1 || _type != Type.Request)
                        {
                            throw new WebSocketException("malformed version");
                        }
                        _state = State.HeaderFieldStart;
                        break;
                    }
                    else if(c == ' ')
                    {
                        if(_versionMinor == -1 || _type != Type.Response)
                        {
                            throw new WebSocketException("malformed version");
                        }
                        _state = State.ResponseVersionSP;
                        break;
                    }
                    else if(c < '0' || c > '9')
                    {
                        throw new WebSocketException("malformed version");
                    }
                    if(_versionMinor == -1)
                    {
                        _versionMinor = 0;
                    }
                    _versionMinor *= 10;
                    _versionMinor += (c - '0');
                    break;
                }
                case State.Response:
                {
                    _type = Type.Response;
                    _state = State.VersionHT;
                    continue;
                }
                case State.ResponseVersionSP:
                {
                    if(c == ' ')
                    {
                        break;
                    }

                    _state = State.ResponseStatus;
                    continue;
                }
                case State.ResponseStatus:
                {
                    // TODO: Is reason string optional?
                    if(c == CR)
                    {
                        if(_status == -1)
                        {
                            throw new WebSocketException("malformed response status");
                        }
                        _state = State.ResponseLF;
                        break;
                    }
                    else if(c == LF)
                    {
                        if(_status == -1)
                        {
                            throw new WebSocketException("malformed response status");
                        }
                        _state = State.HeaderFieldStart;
                        break;
                    }
                    else if(c == ' ')
                    {
                        if(_status == -1)
                        {
                            throw new WebSocketException("malformed response status");
                        }
                        _state = State.ResponseReasonStart;
                        break;
                    }
                    else if(c < '0' || c > '9')
                    {
                        throw new WebSocketException("malformed response status");
                    }
                    if(_status == -1)
                    {
                        _status = 0;
                    }
                    _status *= 10;
                    _status += (c - '0');
                    break;
                }
                case State.ResponseReasonStart:
                {
                    //
                    // Skip leading spaces.
                    //
                    if(c == ' ')
                    {
                        break;
                    }

                    _state = State.ResponseReason;
                    start = p;
                    continue;
                }
                case State.ResponseReason:
                {
                    if(c == CR || c == LF)
                    {
                        if(p > start)
                        {
                            StringBuilder str = new StringBuilder();
                            for(int i = start; i < p; ++i)
                            {
                                str.Append((char)raw[i]);
                            }
                            _reason = str.ToString();
                        }
                        _state = c == CR ? State.ResponseLF : State.HeaderFieldStart;
                    }

                    break;
                }
                case State.ResponseLF:
                {
                    if(c != LF)
                    {
                        throw new WebSocketException("malformed status line");
                    }
                    _state = State.HeaderFieldStart;
                    break;
                }
                case State.Complete:
                {
                    Debug.Assert(false); // Shouldn't reach
                    break;
                }
                }

                ++p;
            }

            return _state == State.Complete;
        }

        internal Type type()
        {
            return _type;
        }

        internal string method()
        {
            Debug.Assert(_type == Type.Request);
            return _method.ToString();
        }

        internal string uri()
        {
            Debug.Assert(_type == Type.Request);
            return _uri.ToString();
        }

        internal int versionMajor()
        {
            return _versionMajor;
        }

        internal int versionMinor()
        {
            return _versionMinor;
        }

        internal int status()
        {
            return _status;
        }

        internal string reason()
        {
            return _reason;
        }

        internal string getHeader(string name, bool toLower)
        {
            string s = null;
            if(_headers.TryGetValue(name.ToLower(), out s))
            {
                return toLower ? s.Trim().ToLower() : s.Trim();
            }

            return null;
        }

        internal Dictionary<string, string> getHeaders()
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();
            foreach(KeyValuePair<string, string> e in _headers)
            {
                dict[_headerNames[e.Key]] = e.Value.Trim();
            }
            return dict;
        }

        private Type _type;

        private StringBuilder _method = new StringBuilder();
        private StringBuilder _uri = new StringBuilder();

        private Dictionary<string, string> _headers = new Dictionary<string, string>();
        private Dictionary<string, string> _headerNames = new Dictionary<string, string>();
        private string _headerName = "";

        private int _versionMajor;
        private int _versionMinor;

        private int _status;
        private string _reason;

        private enum State
        {
            Init,
            Type,
            TypeCheck,
            Request,
            RequestMethod,
            RequestMethodSP,
            RequestURI,
            RequestURISP,
            RequestLF,
            HeaderFieldStart,
            HeaderFieldContStart,
            HeaderFieldCont,
            HeaderFieldNameStart,
            HeaderFieldName,
            HeaderFieldNameEnd,
            HeaderFieldValueStart,
            HeaderFieldValue,
            HeaderFieldValueEnd,
            HeaderFieldLF,
            HeaderFieldEndLF,
            Version,
            VersionH,
            VersionHT,
            VersionHTT,
            VersionHTTP,
            VersionMajor,
            VersionMinor,
            Response,
            ResponseVersionSP,
            ResponseStatus,
            ResponseReasonStart,
            ResponseReason,
            ResponseLF,
            Complete
        };
        private State _state;
    }
}
