/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef ECXX_PLATFORM_NODEMCU_WEB_SERVER_H
#define ECXX_PLATFORM_NODEMCU_WEB_SERVER_H


#include <eCxx/CTL/Types.h>

#include <detail/mimetable.h>
#include <uri/UriBraces.h>


//
// Forward declaration of WiFi server class
//
class WiFiServer;


//
// Forward declaration of web server template class
//
namespace esp8266webserver {

    template<typename ServerType>
    class ESP8266WebServerTemplate;

} // namespace esp8266webserver


namespace eCxx {


//
// HTTP request method
// NOTE : Synchronize the values' positions with
//        "src/eCxx/Platform/NodeMCU/libNodeMCU/libraries/ESP8266WebServer/src/ESP8266WebServer.h"
//
enum class HTTPRequestMethod : uint8_t {
    Any,
    Get,
    Head,
    Post,
    Put,
    Patch,
    Delete,
    Options
};


//
// HTTP authentication method
// NOTE : Synchronize the values' positions with
//        "src/eCxx/Platform/NodeMCU/libNodeMCU/libraries/ESP8266WebServer/src/ESP8266WebServer.h"
//
enum class HTTPAuthMethod : uint8_t {
    Basic,
    Digest
};


//
// HTTP upload status
// NOTE : Synchronize the values' positions with
//        "src/eCxx/Platform/NodeMCU/libNodeMCU/libraries/ESP8266WebServer/src/ESP8266WebServer.h"
//
enum HTTPUploadStatus {
    FileStart,
    FileWrite,
    FileEnd,
    FileAborted
};


//
// HTTP upload structure
// NOTE : Synchronize the contained member variables with
//        "src/eCxx/Platform/NodeMCU/libNodeMCU/libraries/ESP8266WebServer/src/ESP8266WebServer.h"
//
struct HTTPUpload {
    const HTTPUploadStatus status;
    const NodeMCU::WString name;
    const NodeMCU::WString type;
    const NodeMCU::WString fileName;
    const size_t           totalSize;     // Total size of uploaded file so far
    const size_t           currentSize;   // Size of data currently in the buffer
    const size_t           contentLength; // Size of entire post request (file size + headers and other request data)
    const uint8_t* const   buffer;

    inline HTTPUpload(
        HTTPUploadStatus        status_,
        const NodeMCU::WString& name_,
        const NodeMCU::WString& type_,
        const NodeMCU::WString& fileName_,
        size_t                  totalSize_,
        size_t                  currentSize_,
        size_t                  contentLength_,
        const uint8_t*          buffer_
    );
};


//
// A simple and thin wrapper class for NodeMCU's ESP8266WebServer class so that the public API
// will appear more consistent with other eCxx library APIs
//
template <typename WebServerImplT>
class BasicWebServer : public Uncopyable {
    public:
        using HandlerFunction = eCxx::FunctionalVoidCallback;

        using URI             = ::Uri;
        using URIBraces       = ::UriBraces;

        using MIME            = mime::type;
        using MIMETable       = const mime::Entry* const;

        static constexpr MIMETable MimeTable = &::mime::mimeTable[0];

    public:
        BasicWebServer() = delete;

    public:
        BasicWebServer(uint16_t port);
        ~BasicWebServer();

        void begin();
        void end();

        void enableCrossOriginRequest(bool enabled);

        void handleClient();

        void onNotFound(HandlerFunction hfn);

        void on(const URI& uri, HandlerFunction hfn);
        void on(const URI& uri, HTTPRequestMethod method, HandlerFunction hfn);
        void on(const URI& uri, HTTPRequestMethod method, HandlerFunction hfn, HandlerFunction hfnUpload);

        void serveStaticFileFromInternalFlash(const char* uri, const char* path, const char* cacheHeader = nullptr);

        const WString& uri() const;
        HTTPRequestMethod method() const;

        const HTTPUpload httpUpload();

        uint8_t headerCount() const;
        bool hasHeaderName(const WString& name) const;
        const WString& headerName(uint8_t i) const;
        const WString& headerValue(uint8_t i) const;
        const WString& headerValue(const WString& name) const;

        uint8_t argCount() const;
        bool hasArgName(const WString& name) const;
        const WString& argName(uint8_t i) const;
        const WString& argValue(uint8_t i) const;
        const WString& argValue(const WString& name) const;
        const WString& pathArgValue(uint8_t i) const;

        bool authenticate(const char* username, const char* password);
        bool authenticateDigest(const char* username, const char* hash);
        void requestAuthentication(HTTPAuthMethod mode, const char* realm, const char* onFailMessage);

        void send(int16_t code);
        void send(int16_t code, const char* contentType, const char* content);
        void send(int16_t code, const WString& contentType, WString& content);
        void send(int16_t code, const WString& contentType, const WString& content);
        void send(int16_t code, PGMCharPointer contentType, PGMCharPointer content);
        void send(int16_t code, PGMCharPointer contentType, PGMCharPointer content, size_t contentLength);

        void setContentLength(const size_t contentLength);
        void sendHeader(const WString& name, const WString& value, bool first = false);
        void sendContent(const WString& content);
        void sendContent(const char* content);
        void sendContent(const char* content, size_t size);
        void sendContent(PGMCharPointer content);
        void sendContent(PGMCharPointer content, size_t size);

        bool beginChunkedResponseMode(int16_t code, PGMCharPointer contentType);
        void endChunkedResponseMode();

        size_t streamFile(NodeMCU::FileSystem::File& file, const WString& contentType, HTTPRequestMethod requestMethod = HTTPRequestMethod::Get);
        size_t streamFileFromInternalFlash(const char* path, const WString& contentType, HTTPRequestMethod requestMethod = HTTPRequestMethod::Get);

        static const WString urlDecode(const WString& text);
        static const WString responseCodeToString(int16_t code);

    protected:
        WebServerImplT* _webServer;
};


//
// Non-SLL web server class
//
using WebServer = BasicWebServer< esp8266webserver::ESP8266WebServerTemplate<WiFiServer> >;


} // namespace eCxx


//
// Remove these symbols
//
#pragma GCC poison Uri
#pragma GCC poison UriBrace
#pragma GCC poison mimeTable


#endif

