#include <syncobj.hxx>

#define INITIAL_HEADERS_COUNT           16
#define HEADERS_INCREMENT               4

#define INVALID_HEADER_INDEX            0xff
#define INVALID_HEADER_SLOT             0xFFFFFFFF

//
// STRESS_BUG_DEBUG - used to catch a specific stress fault,
//   where we have a corrupted crit sec.
//


#define CLEAR_DEBUG_CRIT(x)
#define IS_DEBUG_CRIT_OK(x)

typedef enum {
    HTTP_METHOD_TYPE_UNKNOWN = -1,
    HTTP_METHOD_TYPE_FIRST = 0,
    HTTP_METHOD_TYPE_GET= HTTP_METHOD_TYPE_FIRST,
    HTTP_METHOD_TYPE_HEAD,
    HTTP_METHOD_TYPE_POST,
    HTTP_METHOD_TYPE_PUT,
    HTTP_METHOD_TYPE_PROPFIND,
    HTTP_METHOD_TYPE_PROPPATCH,
    HTTP_METHOD_TYPE_LOCK,
    HTTP_METHOD_TYPE_UNLOCK,
    HTTP_METHOD_TYPE_COPY,
    HTTP_METHOD_TYPE_MOVE,
    HTTP_METHOD_TYPE_MKCOL,
    HTTP_METHOD_TYPE_CONNECT,
    HTTP_METHOD_TYPE_DELETE,
    HTTP_METHOD_TYPE_LINK,
    HTTP_METHOD_TYPE_UNLINK,
    HTTP_METHOD_TYPE_BMOVE,
    HTTP_METHOD_TYPE_BCOPY,
    HTTP_METHOD_TYPE_BPROPFIND,
    HTTP_METHOD_TYPE_BPROPPATCH,
    HTTP_METHOD_TYPE_BDELETE,
    HTTP_METHOD_TYPE_SUBSCRIBE,
    HTTP_METHOD_TYPE_UNSUBSCRIBE,
    HTTP_METHOD_TYPE_NOTIFY,
    HTTP_METHOD_TYPE_POLL, 
    HTTP_METHOD_TYPE_CHECKIN,
    HTTP_METHOD_TYPE_CHECKOUT,
    HTTP_METHOD_TYPE_INVOKE,
    HTTP_METHOD_TYPE_SEARCH,
    HTTP_METHOD_TYPE_PIN,
    HTTP_METHOD_TYPE_MPOST,
    HTTP_METHOD_TYPE_LAST = HTTP_METHOD_TYPE_MPOST
} HTTP_METHOD_TYPE;


//
// HTTP_HEADERS - array of pointers to general HTTP header strings. Headers are
// stored without line termination. _HeadersLength maintains the cumulative
// amount of buffer space required to store all the headers. Accounts for the
// missing line termination sequence
//

#define HTTP_HEADER_SIGNATURE   0x64616548  // "Head"

class HTTP_HEADERS {

public:

    //
    // _bKnownHeaders - array of bytes which index into lpHeaders
    //   the 0 column is an error catcher, so all indexes need to be biased by 1 (++'ed)
    //

    BYTE _bKnownHeaders[HTTP_QUERY_MAX+2];

private:



#if INET_DEBUG

    DWORD _Signature;

#endif

    //
    // _lpHeaders - (growable) array of pointers to headers added by app
    //

    HEADER_STRING * _lpHeaders;

    //
    // _TotalSlots - number of pointers in (i.e. size of) _lpHeaders
    //

    DWORD _TotalSlots;

    //
    // _NextOpenSlot - offset where the next array is open for allocation
    //

    DWORD _NextOpenSlot;

    //
    // _FreeSlots - number of available headers in _lpHeaders
    //

    DWORD _FreeSlots;

    //
    // _HeadersLength - the amount of buffer space required to store the headers.
    // For each header, includes +2 for the line termination that must be added
    // when the header buffer is generated, or when the headers are queried
    //

    DWORD _HeadersLength;

    //
    // _IsRequestHeaders - TRUE if this HTTP_HEADERS object describes the
    // request headers
    //

    BOOL _IsRequestHeaders;

    //
    // _lpszVerb etc. - in the case of request headers, we maintain these
    // pointers and corresponding lengths to make modifying the request line
    // easier in the case of a redirect
    //
    // N.B. The pointers are just offsets into the request line AND MUST NOT BE
    // FREED
    //

    LPSTR _lpszVerb;
    DWORD _dwVerbLength;
    LPSTR _lpszObjectName;
    DWORD _dwObjectNameLength;
    LPSTR _lpszVersion;
    DWORD _dwVersionLength;

    DWORD _RequestVersionMajor;
    DWORD _RequestVersionMinor;

    //
    // _Error - status code if error
    //

    DWORD _Error;

    //
    // _CritSec - acquire this when accessing header structure - stops multiple
    // threads clashing while modifying headers
    //
    // Question: why do we allow multi-threaded access to headers on same request?
    //

    CCritSec _CritSec;

    //
    // private methods
    //

    DWORD
    AllocateHeaders(
        IN DWORD dwNumberOfHeaders
        );

public:

    HTTP_HEADERS() {

#if INET_DEBUG

        _Signature = HTTP_HEADER_SIGNATURE;

#endif

        CLEAR_DEBUG_CRIT(_szCritSecBefore);
        CLEAR_DEBUG_CRIT(_szCritSecAfter);
        _CritSec.Init();
        Initialize();
    }

    ~HTTP_HEADERS() {

        DEBUG_ENTER((DBG_OBJECTS,
                    None,
                    "~HTTP_HEADERS",
                    "%#x",
                    this
                    ));

#if INET_DEBUG

        INET_ASSERT(_Signature == HTTP_HEADER_SIGNATURE);

#endif

        FreeHeaders();

        DEBUG_LEAVE(0);

    }

    VOID Initialize(VOID) {
        _lpHeaders = NULL;
        _TotalSlots = 0;
        _FreeSlots = 0;
        _HeadersLength = 0;
        _lpszVerb = NULL;
        _dwVerbLength = 0;
        _lpszObjectName = NULL;
        _dwObjectNameLength = 0;
        _lpszVersion = NULL;
        _dwVersionLength = 0;
        _RequestVersionMajor = 0;
        _RequestVersionMinor = 0;
        _NextOpenSlot = 0;
        memset((void *) _bKnownHeaders, INVALID_HEADER_SLOT, ARRAY_ELEMENTS(_bKnownHeaders));
        _Error = AllocateHeaders(INITIAL_HEADERS_COUNT);
    }

    BOOL IsHeaderPresent(DWORD dwQueryIndex) const {
        return (_bKnownHeaders[dwQueryIndex] != INVALID_HEADER_INDEX) ? TRUE : FALSE ;
    }

    BOOL LockHeaders(VOID) {
        return _CritSec.Lock();
    }

    VOID UnlockHeaders(VOID) {
        _CritSec.Unlock();
    }

    VOID
    FreeHeaders(
        VOID
        );

    DWORD
    CopyHeaders(
        IN OUT LPSTR * lpBuffer,
        IN LPSTR lpszObjectName,
        IN DWORD dwObjectNameLength
        );

#ifdef COMPRESSED_HEADERS

    VOID
    CopyCompressedHeaders(
        IN OUT LPSTR * lpBuffer
        );

#endif //COMPRESSED_HEADERS

    HEADER_STRING *
    FASTCALL
    FindFreeSlot(
        DWORD* piSlot
        );

    HEADER_STRING *
    GetSlot(
        DWORD iSlot
        )
    {
        return &_lpHeaders[iSlot];
    }

    LPSTR GetHeaderPointer (LPBYTE pbBase, DWORD iSlot)
    {
        INET_ASSERT (iSlot < _TotalSlots);
        return _lpHeaders[iSlot].StringAddress((LPSTR) pbBase);
    }

    VOID
    ShrinkHeader(
        LPBYTE pbBase,
        DWORD  iSlot,
        DWORD  dwOldQueryIndex,
        DWORD  dwNewQueryIndex,
        DWORD  cbNewSize
        );

    DWORD
    inline
    FastFind(
        IN DWORD  dwQueryIndex,
        IN DWORD  dwIndex
        );

    DWORD
    inline
    FastNukeFind(
        IN DWORD  dwQueryIndex,
        IN DWORD  dwIndex,
        OUT BYTE **lplpbPrevIndex
        );

    DWORD
    inline
    SlowFind(
        IN LPSTR lpBase,
        IN LPCSTR lpszHeaderName,
        IN DWORD dwHeaderNameLength,
        IN DWORD dwIndex,
        IN DWORD dwHash,
        OUT DWORD *lpdwQueryIndex,
        OUT BYTE  **lplpbPrevIndex
        );

    BYTE
    inline
    FastAdd(
        IN DWORD  dwQueryIndex,
        IN DWORD  dwSlot
        );

    BOOL
    inline
    HeaderMatch(
        IN DWORD dwHash,
        IN LPSTR lpszHeaderName,
        IN DWORD dwHeaderNameLength,
        OUT DWORD *lpdwQueryIndex
        );

    VOID
    RemoveAllByIndex(
        IN DWORD dwQueryIndex
        );

    VOID RemoveHeader(IN DWORD dwIndex, IN DWORD dwQueryIndex, IN BYTE *pbPrevByte) {

        INET_ASSERT(dwIndex < _TotalSlots);
        INET_ASSERT(dwIndex != 0);
        // INET_ASSERT(_HeadersLength > 2);
        INET_ASSERT(_lpHeaders[dwIndex].StringLength() > 2);
        INET_ASSERT(_FreeSlots <= _TotalSlots);

        //
        // remove the length of the header + 2 for CR-LF from the total headers
        // length
        //

        if (_HeadersLength) {
            _HeadersLength -= _lpHeaders[dwIndex].StringLength()
                            + (sizeof("\r\n") - 1)
                            ;
        }

        //
        // Update the cached known headers, if this is one.
        //

        if ( dwQueryIndex < INVALID_HEADER_INDEX )
        {
            *pbPrevByte = (BYTE) _lpHeaders[dwIndex].GetNextKnownIndex();
        }

        //
        // set the header string to NULL. Frees the header buffer
        //

        _lpHeaders[dwIndex] = (LPSTR)NULL;

        //
        // we have freed a slot in the headers array
        //

        if ( _FreeSlots == 0 )
        {
            _NextOpenSlot = dwIndex;
        }

        ++_FreeSlots;

        INET_ASSERT(_FreeSlots <= _TotalSlots);

    }

    DWORD GetSlotsInUse (void) {
        return _TotalSlots - _FreeSlots;
    }

    DWORD HeadersLength(VOID) const {
        return _HeadersLength;
    }

    DWORD ObjectNameLength(VOID) const {
        return _dwObjectNameLength;
    }

    LPSTR ObjectName(VOID) const {
        return _lpszObjectName;
    }

    DWORD
    AddHeader(
        IN LPSTR lpszHeaderName,
        IN DWORD dwHeaderNameLength,
        IN LPSTR lpszHeaderValue,
        IN DWORD dwHeaderValueLength,
        IN DWORD dwIndex,
        IN DWORD dwFlags
        );

    DWORD
    AddHeader(
        IN DWORD dwQueryIndex,
        IN LPSTR lpszHeaderValue,
        IN DWORD dwHeaderValueLength,
        IN DWORD dwIndex,
        IN DWORD dwFlags
        );

    DWORD
    ReplaceHeader(
        IN LPSTR lpszHeaderName,
        IN DWORD dwHeaderNameLength,
        IN LPSTR lpszHeaderValue,
        IN DWORD dwHeaderValueLength,
        IN DWORD dwIndex,
        IN DWORD dwFlags
        );

    DWORD
    ReplaceHeader(
        IN DWORD dwQueryIndex,
        IN LPSTR lpszHeaderValue,
        IN DWORD dwHeaderValueLength,
        IN DWORD dwIndex,
        IN DWORD dwFlags
        );

    DWORD
    FindHeader(
        IN LPSTR lpBase,
        IN LPCSTR lpszHeaderName,
        IN DWORD dwHeaderNameLength,
        IN DWORD dwModifiers,
        OUT LPVOID lpBuffer,
        IN OUT LPDWORD lpdwBufferLength,
        IN OUT LPDWORD lpdwIndex
        );

    DWORD
    FindHeader(
        IN LPSTR lpBase,
        IN DWORD dwQueryIndex,
        IN DWORD dwModifiers,
        OUT LPVOID lpBuffer,
        IN OUT LPDWORD lpdwBufferLength,
        IN OUT LPDWORD lpdwIndex
        );

    DWORD
    FastFindHeader(
        IN LPSTR lpBase,
        IN DWORD dwQueryIndex,
        OUT LPVOID *lplpBuffer,
        IN OUT LPDWORD lpdwBufferLength,
        IN OUT DWORD dwIndex
        );


    DWORD
    QueryRawHeaders(
        IN LPSTR lpBase,
        IN BOOL bCrLfTerminated,
        IN LPVOID lpBuffer,
        IN OUT LPDWORD lpdwBufferLength
        );

    DWORD
    QueryFilteredRawHeaders(
        IN LPSTR lpBase,
        IN LPSTR *lplpFilterList,
        IN DWORD cListElements,
        IN BOOL  fExclude,
        IN BOOL  fSkipVerb,
        IN BOOL bCrLfTerminated,
        IN LPVOID lpBuffer,
        IN OUT LPDWORD lpdwBufferLength
        );

    DWORD
    AddRequest(
        IN LPSTR lpszVerb,
        IN LPSTR lpszObjectName,
        IN LPSTR lpszVersion
        );

    DWORD
    ModifyRequest(
        IN HTTP_METHOD_TYPE tMethod,
        IN LPSTR lpszObjectName,
        IN DWORD dwObjectNameLength,
        IN LPSTR lpszVersion OPTIONAL,
        IN DWORD dwVersionLength
        );

    HEADER_STRING * GetFirstHeader(VOID) const {

        INET_ASSERT(_lpHeaders != NULL);

        return _lpHeaders;
    }

    HEADER_STRING * GetEmptyHeader(VOID) const {
        for (DWORD i = 0; i < _TotalSlots; ++i) {
            if (_lpHeaders[i].HaveString() && _lpHeaders[i].StringLength() == 0) {
                return &_lpHeaders[i];
            }
        }
        return NULL;
    }

    VOID SetIsRequestHeaders(BOOL bRequestHeaders) {
        _IsRequestHeaders = bRequestHeaders;
    }

    DWORD GetError(VOID) const {
        return _Error;
    }

    LPSTR GetVerb(LPDWORD lpdwVerbLength) const {
        *lpdwVerbLength = _dwVerbLength;
        return _lpszVerb;
    }

    //VOID SetRequestVersion(DWORD dwMajor, DWORD dwMinor) {
    //    _RequestVersionMajor = dwMajor;
    //    _RequestVersionMinor = dwMinor;
    //}

    VOID
    SetRequestVersion(
        VOID
        );

    DWORD MajorVersion(VOID) const {
        return _RequestVersionMajor;
    }

    DWORD MinorVersion(VOID) const {
        return _RequestVersionMinor;
    }
};