#ifndef ANGELSCRIPTDEBUGGER_REQUESTS_HPP
#define ANGELSCRIPTDEBUGGER_REQUESTS_HPP
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "../../extern/json.hpp"
#include "BaseProtocol.hpp"
#include "Types.hpp"

namespace DebugAdapterProtocol {

#define RequestDefinition(name, jsonName, ...)                                                                         \
    static const char __c##name[] = jsonName;                                                                          \
    struct name##Arguments : public RequestArguments __VA_ARGS__;                                                      \
    using name##Request = DefinedRequest<name##Arguments, __c##name>

#define RequestResponseDefinition(name, jsonName, ...)                                                                 \
    static const char __cResponse##name[] = jsonName;                                                                  \
    struct name##ResponseBody : public ResponseBody __VA_ARGS__;                                                       \
    using name##Response = DefinedResponse<name##ResponseBody, __cResponse##name>

#define EmptyRequest(name, jsonName)                                                                                   \
    RequestDefinition(name, jsonName, {explicit name##Arguments(nlohmann::json & j) : RequestArguments(j){}})

    RequestDefinition(Attach, "attach", {
        size_t port;
        std::string scriptPath;

        explicit AttachArguments(nlohmann::json & j)
            : RequestArguments(j), port(j["port"]), scriptPath(j["scriptPath"]) {}
    });

    RequestDefinition(BreakpointLocations, "breakpointLocations", {
        Source source;
        size_t line;
        std::optional<size_t> column;
        std::optional<size_t> endLine;
        std::optional<size_t> endColumn;
        BreakpointLocationsArguments() {}
        explicit BreakpointLocationsArguments(nlohmann::json & j)
            : RequestArguments(j), source(j["source"]), line(j["line"]) {
            JsonDeserializeOptional(j, column);
            JsonDeserializeOptional(j, endLine);
            JsonDeserializeOptional(j, endColumn);
        }

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = RequestArguments::ToJson();
            o["source"] = source.ToJson();
            o["line"] = line;
            JsonSerializeOptional(o, column);
            JsonSerializeOptional(o, endLine);
            JsonSerializeOptional(o, endColumn);
            return o;
        }
    });

    EmptyRequest(Completions, "completions");
    EmptyRequest(ConfigurationDone, "configurationDone");
    EmptyRequest(Continue, "continue");
    EmptyRequest(DataBreakpointInfo, "dataBreakpointInfo");
    EmptyRequest(Disassemble, "disassemble");

    RequestDefinition(Disconnect, "disconnect", {
        std::optional<bool> restart;
        std::optional<bool> terminateDebuggee;
        std::optional<bool> suspendDebuggee;

        explicit DisconnectArguments(nlohmann::json & j) : RequestArguments(j) {
            JsonDeserializeOptional(j, restart);
            JsonDeserializeOptional(j, terminateDebuggee);
            JsonDeserializeOptional(j, suspendDebuggee);
        }
    });

    EmptyRequest(Evaluate, "evaluate");
    EmptyRequest(ExceptionInfo, "exceptionInfo");
    EmptyRequest(Goto, "goto");
    EmptyRequest(GotoTargets, "gotoTargets");
    EmptyRequest(Initialize, "initialize");
    RequestResponseDefinition(Initialize, "initialize", {
        std::optional<bool> supportsConfigurationDoneRequest = true;
        std::optional<bool> supportsFunctionBreakpoints;
        std::optional<bool> supportsConditionalBreakpoints;
        std::optional<bool> supportsHitConditionalBreakpoints;
        std::optional<bool> supportsEvaluateForHovers;
        std::optional<bool> supportsStepBack;
        std::optional<bool> supportsSetVariable;
        std::optional<bool> supportsRestartFrame;
        std::optional<bool> supportsGotoTargetsRequest;
        std::optional<bool> supportsStepInTargetsRequest;
        std::optional<bool> supportsCompletionsRequest;
        std::optional<bool> supportsModulesRequest;
        std::optional<bool> supportsRestartRequest;
        std::optional<bool> supportsExceptionOptions;
        std::optional<bool> supportsValueFormattingOptions;
        std::optional<bool> supportsExceptionInfoRequest;
        std::optional<bool> supportTerminateDebuggee;
        std::optional<bool> supportSuspendDebuggee;
        std::optional<bool> supportsDelayedStackTraceLoading;
        std::optional<bool> supportsLoadedSourcesRequest;
        std::optional<bool> supportsLogPoints;
        std::optional<bool> supportsTerminateThreadsRequest;
        std::optional<bool> supportsSetExpression;
        std::optional<bool> supportsTerminateRequest;
        std::optional<bool> supportsDataBreakpoints;
        std::optional<bool> supportsReadMemoryRequest;
        std::optional<bool> supportsWriteMemoryRequest;
        std::optional<bool> supportsDisassembleRequest;
        std::optional<bool> supportsCancelRequest;
        std::optional<bool> supportsBreakpointLocationsRequest;
        std::optional<bool> supportsClipboardContext;
        std::optional<bool> supportsSteppingGranularity;
        std::optional<bool> supportsInstructionBreakpoints;
        std::optional<bool> supportsExceptionFilterOptions;

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            JsonSerializeOptional(o, supportsConfigurationDoneRequest);
            JsonSerializeOptional(o, supportsFunctionBreakpoints);
            JsonSerializeOptional(o, supportsConditionalBreakpoints);
            JsonSerializeOptional(o, supportsHitConditionalBreakpoints);
            JsonSerializeOptional(o, supportsEvaluateForHovers);
            JsonSerializeOptional(o, supportsStepBack);
            JsonSerializeOptional(o, supportsSetVariable);
            JsonSerializeOptional(o, supportsRestartFrame);
            JsonSerializeOptional(o, supportsGotoTargetsRequest);
            JsonSerializeOptional(o, supportsStepInTargetsRequest);
            JsonSerializeOptional(o, supportsCompletionsRequest);
            JsonSerializeOptional(o, supportsModulesRequest);
            JsonSerializeOptional(o, supportsRestartRequest);
            JsonSerializeOptional(o, supportsExceptionOptions);
            JsonSerializeOptional(o, supportsValueFormattingOptions);
            JsonSerializeOptional(o, supportsExceptionInfoRequest);
            JsonSerializeOptional(o, supportTerminateDebuggee);
            JsonSerializeOptional(o, supportSuspendDebuggee);
            JsonSerializeOptional(o, supportsDelayedStackTraceLoading);
            JsonSerializeOptional(o, supportsLoadedSourcesRequest);
            JsonSerializeOptional(o, supportsLogPoints);
            JsonSerializeOptional(o, supportsTerminateThreadsRequest);
            JsonSerializeOptional(o, supportsSetExpression);
            JsonSerializeOptional(o, supportsTerminateRequest);
            JsonSerializeOptional(o, supportsDataBreakpoints);
            JsonSerializeOptional(o, supportsReadMemoryRequest);
            JsonSerializeOptional(o, supportsWriteMemoryRequest);
            JsonSerializeOptional(o, supportsDisassembleRequest);
            JsonSerializeOptional(o, supportsCancelRequest);
            JsonSerializeOptional(o, supportsBreakpointLocationsRequest);
            JsonSerializeOptional(o, supportsClipboardContext);
            JsonSerializeOptional(o, supportsSteppingGranularity);
            JsonSerializeOptional(o, supportsInstructionBreakpoints);
            JsonSerializeOptional(o, supportsExceptionFilterOptions);
            return o;
        }
    });

    EmptyRequest(Launch, "launch");
    EmptyRequest(LoadedSources, "loadedSources");
    EmptyRequest(Modules, "modules");
    EmptyRequest(Next, "next");
    EmptyRequest(Pause, "pause");
    EmptyRequest(ReadMemory, "readMemory");
    EmptyRequest(Restart, "restart");
    EmptyRequest(RestartFrame, "restartFrame");
    EmptyRequest(ReverseContinue, "reverseContinue");

    RequestDefinition(Scopes, "scopes", {
        size_t frameId;
        explicit ScopesArguments(nlohmann::json & j) : RequestArguments(j), frameId(j["frameId"]) {}
    });
    RequestResponseDefinition(Scopes, "scopes", {
        std::vector<Scope> scopes;

        ScopesResponseBody(const std::vector<Scope>& scopes) : scopes(scopes) {}

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["scopes"] = nlohmann::json::array();
            for (auto& t : scopes) {
                o["scopes"].push_back(t.ToJson());
            }
            return o;
        }
    });

    RequestDefinition(SetBreakpoints, "setBreakpoints", {
        Source source;
        std::optional<std::vector<SourceBreakpoint>> breakpoints;
        std::optional<bool> sourceModified;

        explicit SetBreakpointsArguments(nlohmann::json & j) : RequestArguments(j), source(j["source"]) {
            if (!j["breakpoints"].empty()) {
                std::vector<SourceBreakpoint> v;
                for (auto& i : j["breakpoints"]) {
                    v.emplace_back(i);
                }
                breakpoints = v;
            }
            JsonDeserializeOptional(j, sourceModified);
        }
    });
    RequestResponseDefinition(SetBreakpoints, "setBreakpoints", {
        std::vector<Breakpoint> breakpoints;
        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["breakpoints"] = nlohmann::json::array();
            for (auto& bp : breakpoints) {
                o["breakpoints"].push_back(bp.ToJson());
            }
            return o;
        }
    });

    EmptyRequest(SetDataBreakpoints, "setDataBreakpoints");
    EmptyRequest(SetExceptionBreakpoints, "setExceptionBreakpoints");
    EmptyRequest(SetExpression, "setExpression");
    EmptyRequest(SetFunctionBreakpoints, "setFunctionBreakpoints");
    EmptyRequest(SetInstructionBreakpoints, "setInstructionBreakpoints");
    EmptyRequest(SetVariable, "setVariable");
    EmptyRequest(Source, "source");
    EmptyRequest(StackTrace, "stackTrace");

    RequestResponseDefinition(StackTrace, "stackTrace", {
        std::vector<StackFrame> stackFrames;
        std::optional<size_t> totalFrames;

        StackTraceResponseBody(std::vector<StackFrame> stackFrames)
            : stackFrames(stackFrames), totalFrames(stackFrames.size()) {}

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["stackFrames"] = nlohmann::json::array();
            for (auto& t : stackFrames) {
                o["stackFrames"].push_back(t.ToJson());
            }
            JsonSerializeOptional(o, totalFrames);
            return o;
        }
    });

    EmptyRequest(StepBack, "stepBack");
    EmptyRequest(StepIn, "stepIn");
    EmptyRequest(StepInTargets, "stepInTargets");
    EmptyRequest(StepOut, "stepOut");

    RequestDefinition(Terminate, "terminate", {
        std::optional<bool> restart;
        explicit TerminateArguments(nlohmann::json & j) : RequestArguments(j), restart(j["restart"]) {}
    });

    EmptyRequest(TerminateThreads, "terminateThreads");
    RequestResponseDefinition(Threads, "threads", {
        std::vector<Thread> threads;

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["threads"] = nlohmann::json::array();
            for (auto& t : threads) {
                o["threads"].push_back(t.ToJson());
            }
            return o;
        }
    });

    EmptyRequest(Threads, "threads");

    RequestDefinition(Variables, "variables", {
        size_t variablesReference;

        explicit VariablesArguments(nlohmann::json & j)
            : RequestArguments(j), variablesReference(j["variablesReference"]) {}
    });
    RequestResponseDefinition(Variables, "variables", {
        std::vector<Variable> variables;

        VariablesResponseBody(const std::vector<Variable>& v) : variables(v) {}

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["variables"] = nlohmann::json::array();
            for (auto& t : variables) {
                o["variables"].push_back(t.ToJson());
            }
            return o;
        }
    });

    EmptyRequest(WriteMemory, "writeMemory");

#undef EmptyRequest
#undef RequestDefinition
}

#endif // ANGELSCRIPTDEBUGGER_REQUESTS_HPP
