/*---------------------------------------------------------------------------*/
/* http_client.h                                                             */
/* copyright (c) innovaphone 2015                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/


typedef enum {
    HTTP_CL_STATE_OFFLINE = 0,
    HTTP_CL_STATE_RESOLVE_HOST_NAME,
    HTTP_CL_STATE_RESOLVED_HOST_NAME,
    HTTP_CL_STATE_CONNECT_TUNNEL,
    HTTP_CL_STATE_READY,
    HTTP_CL_STATE_HEADER_SENT,
    HTTP_CL_STATE_READ_HEADER,
    HTTP_CL_STATE_READ_AUTH_DATA,
    HTTP_CL_STATE_REAUTHENTICATE,
    HTTP_CL_STATE_START_READ_CONTENT,
    HTTP_CL_STATE_READ_CONTENT,
    HTTP_CL_STATE_READ_CHUNKED,
    HTTP_CL_STATE_READ_CHUNK_SIZE,
    HTTP_CL_STATE_READ_CHUNK_END,
    HTTP_CL_STATE_AUTH_NTLM_T1M,
    HTTP_CL_STATE_RECONNECT,
    HTTP_CL_STATE_SHUTDOWN
} http_cl_state_t;


class CustomField {
public:
    CustomField(const char * field, const char * value, CustomField * next)
        : next(next)
    {
        fieldWithValue = (char *)malloc(strlen(field) + strlen(value) + 3);
        _sprintf(fieldWithValue, "%s: %s", field, value);

    }
    
    ~CustomField()
    {
        free(fieldWithValue);
    }

    char * fieldWithValue;
    CustomField * next;
};



/*---------------------------------------------------------------------------*/
/* class HTTPRedirectData                                                    */
/*---------------------------------------------------------------------------*/

class HTTPRedirectData {
public:
    enum class redirect_type_t {
        RELATIVE,
        RELATIVE_ABSOLUTE,
        ABSOLUTE
    };

private:
    bool permanentRedirect;
    bool redirectCanceled = false;
    redirect_type_t redirectType = redirect_type_t::RELATIVE_ABSOLUTE;

    http_request_type_t requestMethod = http_request_type_t::HTTP_GET;
    std::string server;
    std::string resource;
    std::string contentType;
    size_t contentLength = 0;
    CustomField * customHeaderFields = nullptr;

    std::string authUser;
    std::string authPwd;
    int authMethod = 0;

    byte * userBuffer = nullptr;
    size_t userBufferSize = 0;
    bool recvPartitial = false;

public:
    HTTPRedirectData(bool permanent);
    ~HTTPRedirectData();

    void Cancel() { this->redirectCanceled = true; }
    void Type(redirect_type_t rt);
    void Location(const std::string & server, const std::string & resource);

    void RequestData(http_request_type_t reqMethod, const char * contentType, size_t contLen);
    void AuthData(const char * userName, const char * pwd, int authMethod);
    void CustomHeaderFields(CustomField * field);
    void UserBuffer(byte * buf, size_t len, bool partitial);

    bool IsPermanent() { return this->permanentRedirect; }
    bool RedirectCanceled() { return this->redirectCanceled; }
    redirect_type_t Type() { return this->redirectType; }

    http_request_type_t RequestMethod() { return this->requestMethod; }
    const char * Server() { return this->server.c_str(); }
    const char * Resource() { return this->resource.c_str(); }
    const char * ContentType() { return this->contentType.empty() ? nullptr : this->contentType.c_str(); }
    size_t ContentLength() { return this->contentLength; }

    bool HasCustomFields() { return this->customHeaderFields != nullptr; }
    CustomField * CustomHeaderFields() { return this->customHeaderFields; }

    const char * AuthUser() { return this->authUser.empty() ? nullptr : this->authUser.c_str(); }
    const char * AuthPwd() { return this->authPwd.empty() ? nullptr : this->authPwd.c_str(); }
    int AuthMethod() { return this->authMethod; }

    byte * UserBuffer() { return this->userBuffer; }
    size_t UserBufferSize() { return this->userBufferSize; }
    bool RecvPartitial() { return this->recvPartitial;  }
};


class HTTPClient : public IHTTPClient, public USocket, public UDns, public UIoExec, public UTimer {
protected:
    IIoMux * const iomux;
    IInstanceLog * const log;
    UHTTPClient * user;
    ISocketProvider * tcpSocketProvider;
    ISocketProvider * tlsSocketProvider;
    ISocket * socket;
    ISocketContext * socketContext;
    IDns * dns;
    bool dnsSpecified;
	bool requestHasDataToSend = false;
    CustomField * customFieldList;

    bool connected;
    bool recvPartial;
    bool useTLS;
    bool chunkedTransfer;
    bool chunkedTransferCalled;
    bool retryAuthenticated;
    bool fakingAuth;
    bool consumingData; // Will be used to read rest-data from the socket (e. g. for authentication).
    bool dnsRetried;
    int port;
    char * hostName;
    char hostIPAddress[46]; // The maximum length of address is 45 chars + closing zero (45 bytes is for IPv4 mapped IPv6 address)
    class HTTPProxies * proxies;
    class HTTPProxy * proxy;
    char proxyIPAddress[46]; // The maximum length of address is 45 chars + closing zero (45 bytes is for IPv4 mapped IPv6 address)

    HTTPRedirectData * httpRedirectData;

    char * authUserName;
    char * authUserPassword;
    int authMethods;
    size_t authToRead;
    http_request_type_t requestType;
    char * resource;
    char * contentType;

    bool sendContentForAuth;

    struct {
        dword nc;
        char * nonce;
        char * opaque;
        char * qop;
        char * realm;
        char * algorithm;
    } digest;

    bool ntlmAuthenticated;
    http_result_t lastResult;
    http_shutdown_reason_t shutdownReason;
    HTTPParser * httpParser;
    HTTPHeader * httpHeader;

    byte headerBuffer[1024];
    byte * headerBufferDataStart;
    size_t headerBufferDataSize;
    byte * userBuffer;
    size_t userBufferSize;
    byte * savedUserBuffer;
    size_t savedUserBufferSize;
    http_cl_state_t savedState;
    http_result_t savedLastResult;

    http_cl_state_t state;
    int execCnt;
    int sendCnt;
    
    size_t answerContentLength;
    int responseCode;
    char * responseCodeDescription;
    
    size_t contentLength;
    size_t bytesToSend;
    size_t bytesToRead;
    size_t userBufferAlreadyRead;
    char chunkSizeNum[21]; // Max = \r\nFFFFFFFFFFFFFFFF\r\n + ending 0
    bool readUntilConnectionClose;

    byte * dataToSend;
    size_t dataToSendSize;
    bool dataLast;
    dword passErrorToUserFlags;

    class ITimer timer;
    dword timeoutMs;
    bool timeoutOnConnect;

    void MakeMD5String(const byte * in, size_t len, char * out33byte);
    void SetDigest(const char * nonce, const char * opaque, const char * qop, const char * realm, const char * algorithm, bool stale);
    void SetDigestHeader();
    void SetRequest();
    void ConnectProxy();
    void ResolveDns();
    void ConnectSocket();
    void CopyDataAlreadyLoaded();
    void StartConsumeData(size_t len, size_t dataRead, http_result_t res = HTTP_RESULT_OK);
    void PrepareRedirect(size_t len, size_t dataRead, bool isPermanentRedirect);
    void FollowRedirect(bool reconnectComplete = false);
    void Authenticate();
    void AuthenticateNTLM();
    void AuthenticateDigest();
    void AuthenticateBasic();
    void RecvAuthData(byte * buffer, size_t len);
    int GetSupportedAuthMethodsFromHeader();
    void StartReadRequestData();
    void StartReadChunk();
    void ParseRequestAnswer(char * buf, size_t len);
    void SocketSend(const void * buf, size_t len);

    char * GetNTLMType1Message();
    char * GetNTLMType3Message();
    size_t GetDigestAuthBufferSize();

    inline void UserHTTPClientConnectComplete();
    inline void UserHTTPClientShutdown(http_shutdown_reason_t reason);
    inline void UserHTTPClientSendResult();
    inline void UserHTTPClientRecvResult(bool transferComplete);
    inline void UserHTTPClientRecvCanceled(byte * buf);
    inline void UserHTTPClientRequestComplete();

    void DoShutdown();
    void DoRecv();
    void DoSocketRecv(void * buf, size_t len, bool recvPartial = false);
    void DoSend(const byte * data = NULL, size_t size = 0);
    void HandleRequestAsOK(size_t len, size_t dataRead, http_result_t res = HTTP_RESULT_OK);
    void StopTimer();
    void StartTimer();
    
    bool ConnectionIsKeelAlive();
    void AdjustBufferCounter(size_t diff);
    void DebugPrintHTTPHeader(const char * caption, bool showReceivedHeader = false);

  public:
    HTTPClient(class IIoMux * const iomux,
               class ISocketProvider * const tcpSocketProvider,
               class ISocketProvider * const tlsSocketProvider,
               class UHTTPClient * const user,
               class IInstanceLog * const log,
               class IDns * const dns,
               class ISocketContext * const socketContext);
    virtual ~HTTPClient();

    void Connect(const char * address,
                 const char * user = NULL,
                 const char * pwd = NULL,
                 int authMethods = HTTP_AUTH_NTLM | HTTP_AUTH_DIGEST) override;
    void Recv(byte * buffer, size_t size, bool recvPartial = false) override;
    void Send(const byte * data = NULL, size_t size = 0, bool last = false) override;
    void Shutdown() override;
    void Reconnect() override;
    void PassErrorToUser(http_error_t err) override;
    void SendContentForAuthentication(bool doSend) override { this->sendContentForAuth = doSend; }

    void SetRequestType(http_request_type_t reqType, const char * resourceName, size_t contentLength = 0, const char * contentType = "text/xml; charset=utf-8") override;
    void SetCustomHeaderField(const char * field, const char * value) override;
    http_result_t GetHTTPResult() override;
    int GetResponseCode() override;
    const char * GetResponseCodeDescription() override;
    size_t GetContentLength(bool & chunked) override;
    size_t GetHeaderFieldValueCount(const char * headerField) override;
    const char * GetHeaderFieldValue(const char * headerField, size_t index = 0) override;

    bool Connected() override { return this->connected; }
    void SetUser(UHTTPClient * const user) { this->user = user; }

    void SocketConnectComplete(ISocket * const socket) override;
    void SocketShutdown(ISocket * const socket, shutdownreason_t reason) override;
    void SocketSendResult(ISocket * const socket) override;
    void SocketRecvResult(ISocket * const socket, void * buf, size_t len) override;
    void SocketRecvCanceled(ISocket * const socket, void * buf) override;
    void SocketUpgradeToTLSComplete(ISocket* const socket) override;

    void DnsGetHostByNameResult(const char * addr, bool isIPv6) override;
    void IoExec(void * execContext) override;
    void TimerOnTimeout(ITimer * timer) override;
    void SetTimeout(dword timeoutMs) override;
};
