#pragma once

#include "framework/meta_tools.hpp"
#include "platform/ipc.hpp"
#include "os.hpp"

#include "bit_field.h"

#include <cstdint>

namespace HLE {

namespace OS {
class Thread;
struct Handle;
using Result = uint32_t;
}

namespace IPC {

using namespace Platform::IPC;

/// Dummy structure used for IPC command tags that don't need any actual data
struct EmptyValue {
};

struct MappedBuffer {
    /*VAddr*/uint32_t addr;
    uint32_t size;

    uint32_t EndAddr() const {
        return addr + size;
    }
};

struct StaticBuffer {
    uint32_t addr;
    uint32_t size;
    uint32_t id;
};

template<typename T>
struct NormalParamToCommandTagHelper { using type = CommandTags::serialized_tag<T>; };
template<>
struct NormalParamToCommandTagHelper<uint32_t> { using type = CommandTags::uint32_tag; };
template<>
struct NormalParamToCommandTagHelper<uint64_t> { using type = CommandTags::uint64_tag; };

template<typename T>
using NormalParamToCommandTag = typename NormalParamToCommandTagHelper<T>::type;


template<typename F, typename T, typename ArgsTuple, size_t... Idxs>
auto DoDeserialize(F& f, std::index_sequence<Idxs...>) {
    return Meta::CallWithSequentialEvaluation<decltype(detail::GetDeserializer<T>()(f(NormalParamToCommandTag<std::tuple_element_t<Idxs, ArgsTuple>>{})...))> {
        detail::GetDeserializer<T>(),
        f(NormalParamToCommandTag<std::tuple_element_t<Idxs, ArgsTuple>>{})...
    }.GetResult();
}

/// Utility class to parse IPC messages from TLS
class TLSReader {
    OS::Thread& thread;

    // Skip header
    uint32_t offset = 0x84;

    OS::Handle ParseHandle();

    template<std::size_t Num>
    using HandleReturnType = std::conditional_t<Num == 1, OS::Handle, std::array<OS::Handle, Num>>;

public:
    TLSReader(OS::Thread& thread);
    TLSReader(const TLSReader&) = delete;
    TLSReader(TLSReader&&) = default;

    OS::Result operator()(const IPC::CommandTags::result_tag& /* unused */);
    uint32_t operator()(const IPC::CommandTags::uint32_tag& /* unused */);
    uint64_t operator()(const IPC::CommandTags::uint64_tag& /* unused */);
    StaticBuffer operator()(const IPC::CommandTags::static_buffer_tag& /* unused */);
    StaticBuffer operator()(const IPC::CommandTags::pxi_buffer_tag<false>& /* unused */);
    StaticBuffer operator()(const IPC::CommandTags::pxi_buffer_tag<true>& /* unused */);
    /*ProcessId*/uint32_t operator()(const IPC::CommandTags::process_id_tag& /* unused */);
    MappedBuffer operator()(const IPC::CommandTags::map_buffer_r_tag& /* unused */);
    MappedBuffer operator()(const IPC::CommandTags::map_buffer_w_tag& /* unused */);

    template<typename T>
    T operator()(const IPC::CommandTags::serialized_tag<T>& /* unused */) {
        using arg_list = typename Meta::function_traits<decltype(detail::GetDeserializer<T>())>::args;
        return DoDeserialize<TLSReader, T, arg_list>(*this, std::make_index_sequence<std::tuple_size<arg_list>::value>{});
    }

    template<HandleType Type, std::size_t Num>
    HandleReturnType<Num> operator()(const IPC::CommandTags::handle_close_tag<Type, Num>& /* unused */) {
        return (*this)(IPC::CommandTags::handle_tag<Type, Num> { });
    }
    template<HandleType Type, std::size_t Num>
    HandleReturnType<Num> operator()(const IPC::CommandTags::handle_tag<Type, Num>& /* unused */) {
        offset += 4; // Move past descriptor
        if constexpr (Num == 1) {
            return ParseHandle();
        } else {
            HandleReturnType<Num> ret;
            for (auto& handle : ret) {
                handle = ParseHandle();
            }
            return ret;
        }
    }
};

template<typename F, typename DataTuple, typename Data, size_t... Idxs>
inline void DoWrite(F& f, const Data& data, std::index_sequence<Idxs...>) {
    (f(NormalParamToCommandTag<std::tuple_element_t<Idxs, DataTuple>>{}, std::get<Idxs>(data)), ...);
}

/// Utility class to construct IPC messages
class TLSWriter {
    OS::Thread& thread;

    // Initial offset (response header omitted)
    uint32_t offset = 0x84;

    void WriteHandleDescriptor(const OS::Handle* data, size_t num, bool close);

public:
    TLSWriter(OS::Thread& thread);
    TLSWriter(const TLSWriter& tls) = delete;
    TLSWriter(TLSWriter&& tls) = default;

    void operator()(IPC::CommandTags::result_tag, OS::Result data);
    void operator()(IPC::CommandTags::uint32_tag, uint32_t data);
    void operator()(IPC::CommandTags::uint64_tag, uint64_t data);
    void operator()(IPC::CommandTags::map_buffer_r_tag, const MappedBuffer& buffer);
    void operator()(IPC::CommandTags::map_buffer_w_tag, const MappedBuffer& buffer);
    void operator()(IPC::CommandTags::static_buffer_tag, const StaticBuffer& buffer);
    void operator()(IPC::CommandTags::process_id_tag, const EmptyValue&);

    void operator()(IPC::CommandTags::pxi_buffer_tag<false>, const StaticBuffer& buffer);
    void operator()(IPC::CommandTags::pxi_buffer_tag<true>, const StaticBuffer& buffer);

    template<HandleType Type>
    void operator()(IPC::CommandTags::handle_close_tag<Type>, OS::Handle data) {
        WriteHandleDescriptor(&data, 1, true);
    }

    template<HandleType Type>
    void operator()(IPC::CommandTags::handle_tag<Type>, OS::Handle data) {
        WriteHandleDescriptor(&data, 1, false);
    }

    template<HandleType Type, size_t num>
    void operator()(IPC::CommandTags::handle_close_tag<Type,num>, const std::array<OS::Handle,num>& data) {
        WriteHandleDescriptor(data.data(), num, true);
    }

    template<HandleType Type, size_t num>
    void operator()(IPC::CommandTags::handle_tag<Type,num>, const std::array<OS::Handle,num>& data) {
        WriteHandleDescriptor(data.data(), num, false);
    }

    template<typename T>
    void operator()(IPC::CommandTags::serialized_tag<T>, T& data) {
        auto parameters = detail::Serialize<T>(data);
        constexpr auto tuple_size = std::tuple_size<decltype(parameters)>::value;
        DoWrite<TLSWriter, decltype(parameters)>(*this, parameters, std::make_index_sequence<tuple_size>{});
    }
};

namespace detail {
template<bool NoError>
struct CHECK_EQUALITY_VERBOSELY_DEFAULT_ERROR { static_assert(NoError, "Given integers are not equal!"); };
}

/**
 * Utility structure for asserting that the compile-time constants A and B
 * are equal. If they aren't, the type Error<true> will be instantiated, which
 * is supposed to abort compilation due to a static_assert failure.
 *
 * The error message can be customized by passing an own Error template, which
 * is expected to have a static_assert fail if its instantiated with a "true"
 * argument. This is useful to give helpful error messages instead of cryptic
 * "5 != 3" messages.
 *
 * @todo Move this into a utility header
 */
template<int A, int B,
         template<bool Err> class Error = detail::CHECK_EQUALITY_VERBOSELY_DEFAULT_ERROR>
struct CHECK_EQUALITY_VERBOSELY
{
    // Force Error<A==B> to be instantiated and error out if A!=B
    // If A==B, use sizeof to form the value "true".
    static constexpr bool value = sizeof(Error<A==B>);
};

/// Exception class thrown by SendIPCRequest on error
struct IPCError {
    IPCError(uint32_t response_header, OS::Result result) noexcept : header(response_header), result(result) {
    }
    ~IPCError() noexcept = default;

    uint32_t header;
    OS::Result result;
};

template<typename F, typename Tuple, size_t... Idxs>
auto TransformTupleSequentiallyHelper(F&& f, Tuple&& tuple, std::index_sequence<Idxs...>)
{
    return std::tuple<std::invoke_result_t<F, std::tuple_element_t<Idxs, Tuple>>...> { f(std::get<Idxs>(tuple))... };
}

template<typename F, typename Tuple>
auto TransformTupleSequentially(F&& f, Tuple&& tuple)
{
    return TransformTupleSequentiallyHelper(std::forward<F>(f), std::forward<Tuple>(tuple), std::make_index_sequence<std::tuple_size<Tuple>::value>{});
}

namespace detail {
template<bool NoError>
struct IPCArgumentMatchError { static_assert(NoError, "Given number of function arguments doesn't match the expected number per the IPC message descriptor"); };

template<typename ResponseList>
struct IPCResponseChecker;

template<typename... Parameters>
struct IPCResponseChecker<boost::mp11::mp_list<Parameters...>> {
    void Check(Parameters...) {
        // Do nothing, just syntax-check this is a valid function call
    }
};

template<typename T>
struct TagToTypeHelper;
template<>
struct TagToTypeHelper<CommandTags::uint32_tag> : boost::mp11::mp_identity<uint32_t> {};
template<>
struct TagToTypeHelper<CommandTags::uint64_tag> : boost::mp11::mp_identity<uint64_t> {};
template<>
struct TagToTypeHelper<CommandTags::result_tag> : boost::mp11::mp_identity<OS::Result> {};
template<HandleType T>
struct TagToTypeHelper<CommandTags::handle_tag<T, 1>> : boost::mp11::mp_identity<OS::Handle> {};
template<HandleType T>
struct TagToTypeHelper<CommandTags::handle_close_tag<T, 1>> : boost::mp11::mp_identity<OS::Handle> {};
template<HandleType T, std::size_t num>
struct TagToTypeHelper<CommandTags::handle_tag<T, num>> : boost::mp11::mp_identity<std::array<OS::Handle, num>> {};
template<HandleType T, std::size_t num>
struct TagToTypeHelper<CommandTags::handle_close_tag<T, num>> : boost::mp11::mp_identity<std::array<OS::Handle, num>> {};
template<>
struct TagToTypeHelper<CommandTags::map_buffer_r_tag> : boost::mp11::mp_identity<MappedBuffer> {};
template<>
struct TagToTypeHelper<CommandTags::map_buffer_w_tag> : boost::mp11::mp_identity<MappedBuffer> {};
template<>
struct TagToTypeHelper<CommandTags::map_buffer_rw_tag> : boost::mp11::mp_identity<MappedBuffer> {};
template<>
struct TagToTypeHelper<CommandTags::static_buffer_tag> : boost::mp11::mp_identity<StaticBuffer> {};
template<typename T>
struct TagToTypeHelper<CommandTags::serialized_tag<T>> : boost::mp11::mp_identity<T> {};
template<bool read_only>
struct TagToTypeHelper<CommandTags::pxi_buffer_tag<read_only>> : boost::mp11::mp_identity<StaticBuffer> {};
template<>
struct TagToTypeHelper<CommandTags::process_id_tag> : boost::mp11::mp_identity<uint32_t> {};
template<typename T>
using TagToType = typename TagToTypeHelper<T>::type;

template<typename Command, bool IsResponse, typename Thread, typename ArgTagsTuple>
struct IPCMessageWriter;

template<typename Command, bool IsResponse, typename Thread, typename... ArgTags>
struct IPCMessageWriter< Command, IsResponse, Thread, boost::mp11::mp_list<ArgTags...>> {
    Thread& thread;

    using TypeList = std::conditional_t<IsResponse, typename Command::response_list, typename Command::request_list>;

    template<typename... Data>
    auto operator()(Data... data) {
        if constexpr (IsResponse) {
            // Check if the returned value matches the command definition
            // TODO: Enable this check for requests, too
            using paramlist = boost::mp11::mp_transform<TagToType, TypeList>;
            IPCResponseChecker<paramlist>{}.Check(data...);
        }

        thread.WriteTLS(0x80, (IsResponse ? Command::response_header : Command::request_header));

        IPC::TLSWriter writer(thread);
        (writer(ArgTags { }, data), ...);
    }
};

} // namespace detail

template<typename Command, typename Thread, typename... Data>
void WriteIPCReplyFromTuple(Thread& thread, std::tuple<Data...> data) {
    std::apply(detail::IPCMessageWriter<Command, true, Thread, typename Command::response_list> { thread }, data);
}

template<typename Func, typename MainArgs, typename... ExtraArgs>
struct IPCMessageResultsHelper;

template<typename Func, typename... MainArgs, typename... ExtraArgs>
struct IPCMessageResultsHelper<Func, boost::mp11::mp_list<MainArgs...>, ExtraArgs...>
    : std::invoke_result<   Func,
                            std::invoke_result_t<IPC::TLSReader, MainArgs>...,
                            ExtraArgs...> {
};

template<typename Func, typename Command, typename... ExtraArgs>
using IPCMessageResults = IPCMessageResultsHelper<Func, typename Command::request_list, ExtraArgs...>;

template<   typename Func, typename... ExtraArgs, typename... ArgTags>
auto DispatchIPCMessageHelper(IPC::TLSReader reader, Func&& handler, ExtraArgs&&... args, boost::mp11::mp_list<ArgTags...>) {
    // Read request data from TLS (via ArgTags) and invoke request handler
    // TODO: The following may perform implicit conversion of uint64_t
    //       parameters to uint32_t (or vice versa), hence it actually provides
    //       less compile-time safety that we would like it to, currently.
    if constexpr (std::is_invocable_v<  Func, ExtraArgs...,
                                        std::invoke_result_t<IPC::TLSReader, ArgTags>...>) {
        // Use CallWithSequentialEvaluation to get well-defined execution order
        // TODO: We should static assert for handlers returning void, but this somehow doesn't always compile currently
//        static_assert(  !std::is_invocable_r_v<void, Func, ExtraArgs..., std::invoke_result_t<IPC::TLSReader, ArgTags>...>,
//                        "Handler must have non-void return type");
        // std::move is needed to work around a clang bug with class template argument deduction
        return std::move(Meta::CallWithSequentialEvaluation {
                    std::forward<Func>(handler),
                    std::forward<ExtraArgs>(args)...,
                    reader(ArgTags{})... }).GetResult();
    } else {
        // handler is not invocable with the given arguments:
        // Use a regular function call to get a good compiler diagnostic
        return std::forward<Func>(handler)(
                    std::forward<ExtraArgs>(args)...,
                    reader(ArgTags{})...);
    }
}

/**
 * Read IPC request data from the thread's TLS and forward the decoded
 * arguments to the given handler.
 */
template<typename Command, typename Func, typename Thread, typename... ExtraArgs>
auto DispatchIPCMessage(Func&& handler, Thread& thread, ExtraArgs&&... args) {
    if (thread.ReadTLS(0x80) != Command::request_header)
        throw std::runtime_error(fmt::format("Expected command header {:#x}, but got {:#x}", Command::request_header, thread.ReadTLS(0x80)));

    return DispatchIPCMessageHelper<Func, ExtraArgs...>(IPC::TLSReader(thread), std::forward<Func>(handler), std::forward<ExtraArgs>(args)..., typename Command::request_list { });
}

/**
 * Read IPC request data from the thread's TLS, forward the decoded
 * arguments to the given handler, and write results back to TLS
 */
template<typename Command, typename Func, typename Thread, typename... ExtraArgs>
void HandleIPCCommand(Func&& handler, Thread& thread, ExtraArgs&&... args) {
    // Decode request data from TLS and invoke handler
    auto output_data = DispatchIPCMessage<Command>(std::forward<Func>(handler), thread, std::forward<ExtraArgs>(args)...);

    // Write response to TLS
    // TODO: Do not pass output_data per copy!
    WriteIPCReplyFromTuple<Command>(thread, output_data);
}

namespace detail {
/**
 * Unwrap small tuples:
 * For single-element tuples, this returns the contained element.
 * For zero-element tuples, this returns void.
 * Otherwise, this just returns the unmodified tuple.
 */
template<typename... T>
inline auto UnwrapTuple(std::tuple<T...>&& tuple) {
    return tuple;
}

template<typename... T>
inline decltype(auto) UnwrapTuple(std::tuple<T...>& tuple) {
    return tuple;
}

template<typename T>
inline decltype(auto) UnwrapTuple(std::tuple<T>&& tuple) {
    return std::get<0>(tuple);
}

template<typename T>
inline decltype(auto) UnwrapTuple(std::tuple<T>& tuple) {
    return std::get<0>(tuple);
}

inline void UnwrapTuple(std::tuple<>&&) {
}

inline void UnwrapTuple(std::tuple<>&) {
}

}

/**
 * Submit an IPC request from the given thread through the given session
 * handle. The request parameters are taken from the parameter pack, which is
 * expected to be compatible with the parameter list inferred from the Command
 * type.
 * @return A tuple of return values inferred from the given Command. This does
 *         not include the return code. If only a single value is returned, the
 *         tuple is unwrapped to the value itself. If no value is returned,
 *         this function returns void.
 * @throws IPCError if sending the IPC request was unsuccessful, if the
 *                  response indicates a failure, and if the response header
 *                  is different from the one indicated by Command.
 * @todo Consider changing "t..." to use (universal) reference semantics
 * @todo Currently, things like the process_id tag require an EmptyValue
 *       dummy argument, despite not actually needing any input data
 */
template<typename Command, typename Thread, typename... T>
auto SendIPCRequest(Thread& thread, OS::Handle session_handle, T... t) {
    // Write request data to TLS
    detail::IPCMessageWriter<Command, false, Thread, typename Command::request_list> { thread } (t...);

    // Send IPC request and wait for response
    // TODO: The command header returned here shouldn't be read from TLS but
    //       rather constructed on-the-fly!
    OS::Result result = std::get<0>(thread.CallSVC(&OS::OS::SVCSendSyncRequest, session_handle));
    if (result != 0 /* RESULT_OK */)
        throw IPCError(thread.ReadTLS(0x080), result);

    // Read result code of the response
    auto reader = TLSReader(thread);
    result = reader(CommandTags::result_tag{});
    if (result != 0)
        throw IPCError{thread.ReadTLS(0x80), result};

    // TODO: Currently, we build the response header incorrectly and hence
    //       cannot actually perform this important check
    //assert(thread.ReadTLS(0x80) == Command::response_header);

    // Read results, but before that drop the result code (if it doesn't
    // indicate success, we aborted before anyway)
    using ResponseTypeList = boost::mp11::mp_rename<typename Command::response_list, std::tuple>;
    // TODO: Filter out EmptyValue values!
    using FixedResponseTypeList = boost::mp11::mp_pop_front<ResponseTypeList>;
    auto result_data = TransformTupleSequentially(reader, FixedResponseTypeList{});

    // Return data: Decay to "void" or an unwrapped type if the data tuple is
    // empty or a singleton, respectively
    return detail::UnwrapTuple(result_data);
}

}  // namespace IPC

}  // namespace HLE