#pragma once

#include <library/cpp/http/server/http.h>
#include <library/cpp/uri/uri.h>
#include <util/datetime/cputimer.h>
#include <util/generic/ptr.h>
#include <util/string/vector.h>
#include <library/cpp/cgiparam/cgiparam.h>

namespace NWebmaster {

struct THttpCookie {
    THttpCookie() = default;
    THttpCookie(const TString &cookieHeader);
    bool Get(const TString &name, TString &dest) const;

private:
    TMap<TString, TString> Cookies;
};

class THttpServer: public ::THttpServer, public ::THttpServer::ICallBack {
public:
    struct TRequest;

    struct IServerAction {
        typedef TAtomicSharedPtr<IServerAction> Ptr;

    public:
        virtual ~IServerAction() { }
        virtual bool Invoke(TRequest &context) = 0;
    };

    template<typename TObjectPtr, typename TMemberPtr>
    struct TServerAction : IServerAction {
        TServerAction(TObjectPtr object, TMemberPtr memberPtr)
            : Object(object)
            , MemberPtr(memberPtr)
        {
        }

        bool Invoke(TRequest &context) override {
            return (Object->*MemberPtr)(context);
        }

    private:
        TObjectPtr Object;
        TMemberPtr MemberPtr;
    };

    struct TUserService {
        virtual ~TUserService() { }

        template<typename TObjectPtr, typename TMemberPtr>
        void AddAction(const TString &name, TObjectPtr object, TMemberPtr member) {
            if (Actions.find(name) != Actions.end()) {
                ythrow yexception() << "there is already action with name " << name;
            }

            Actions[name].Reset(new TServerAction<TObjectPtr, TMemberPtr>(object, member));
        }

        virtual bool Reply(TRequest &context) {
            auto it = Actions.find(context.Method);

            if (it != Actions.end()) {
                return it->second->Invoke(context);
            }

            context.Die(404);

            return true;
        }

        virtual void *CreateThreadSpecificResource() {
            return nullptr;
        }

        virtual void DestroyThreadSpecificResource(void */*tsr*/) { }

    private:
        TMap<TString, IServerAction::Ptr> Actions;
    };

    struct TReplier: public TRequestReplier {
        TReplier(TUserService *userService);
        bool DoReply(const TReplyParams &params) override;

    public:
        TUserService *UserService;
    };

    struct TRequest {
    public:
        TRequest(TReplier &replier, const TRequestReplier::TReplyParams &params);
        void Die(int code, const TString& what = "");
        void SendResponse(const TString& content, int code, const TString& contentType);
        TString GetTimerString() const;
        TString GetRemoteAddr() const;
        bool GetParameter(const TString &name, TString &value) const;
        bool GetFilledParameter(const TString &name, TString &value) const;
        THttpInput& Input() {
            return ReplierParams.Input;
        }
        THttpOutput& Output() {
            return ReplierParams.Output;
        }
    public:
        TCgiParameters Params;
        TString Method;
        TString Query;
        TSimpleTimer Timer;
        THttpCookie Cookies;
        TMap<TString, TString> HeadersMap;
        const TRequestReplier::TReplyParams &ReplierParams;
        void *ThreadSpecificResource;
        TReplier& ReplierInstance;
    };

    typedef TSimpleSharedPtr<THttpServer> Ptr;

    THttpServer(size_t port, size_t threads, TUserService *userService);
    THttpServer(const THttpServerOptions &options, TUserService *userService);
    ~THttpServer() override;
    TRequestReplier* CreateClient() override;
    void *CreateThreadSpecificResource() override;
    void DestroyThreadSpecificResource(void *tsr) override;

private:
    TUserService *UserService;
};

} //namespace NWebmaster
