#include "Controller.h"

#include "Utils/Platform.hpp"

#include <boost/regex.hpp>
#include <utility>
#include <vector>

#include "Assistant.h"
#include "MaaUtils/NoWarningCV.hpp"

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4068)
#endif
#include <zlib/decompress.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif

#include "AdbController.h"
#include "ControllerAPI.h"
#include "MaaFwAdbController.h"
#include "MaatouchController.h"
#include "MinitouchController.h"
#include "PlayToolsController.h"
#ifdef _WIN32
#include "Win32Controller.h"
#endif

#include "Common/AsstTypes.h"
#include "Utils/Logger.hpp"

asst::Controller::Controller(const AsstCallback& callback, Assistant* inst) :
    InstHelper(inst),
    m_callback(callback)
{
    LogTraceFunction;
}

asst::Controller::~Controller()
{
    LogTraceFunction;
}

std::shared_ptr<asst::ControllerAPI>
    asst::Controller::create_controller(ControllerType type, PlatformType platform_type) const
{
    try {
        switch (type) {
        case ControllerType::Adb:
            return std::make_shared<AdbController>(m_callback, m_inst, platform_type);
        case ControllerType::Minitouch:
            return std::make_shared<MinitouchController>(m_callback, m_inst, platform_type);
        case ControllerType::Maatouch:
            return std::make_shared<MaatouchController>(m_callback, m_inst, platform_type);
        case ControllerType::MacPlayTools:
            return std::make_shared<PlayToolsController>(m_callback, m_inst, platform_type);
        case ControllerType::MaaFwAdb:
            return std::make_shared<MaaFwAdbController>(m_callback, m_inst, platform_type);
        default:
            return nullptr;
        }
    }
    catch (const std::exception& e) {
        Log.error("Unable to create controller: {}", e.what());
        return nullptr;
    }
}

size_t asst::Controller::get_pipe_data_size() const noexcept
{
    return m_controller->get_pipe_data_size();
}

size_t asst::Controller::get_version() const noexcept
{
    return m_controller->get_version();
}

asst::ControllerType asst::Controller::get_controller_type() const noexcept
{
    return m_controller_type;
}

std::pair<int, int> asst::Controller::get_scale_size() const noexcept
{
    return m_scale_size;
}

void asst::Controller::clear_info() noexcept
{
    m_uuid.clear();
    m_scale_size = { WindowWidthDefault, WindowHeightDefault };
    m_controller = nullptr;
    m_scale_proxy = nullptr;
}

void asst::Controller::callback(AsstMsg msg, const json::value& details)
{
    if (m_callback) {
        m_callback(msg, details, m_inst);
    }
}

#define CHECK_EXIST(object, return_value)                       \
    if (!object) {                                              \
        Log.error(__FUNCTION__, "|", #object, "is not inited"); \
        return return_value;                                    \
    }

void asst::Controller::sync_params()
{
    if (!m_controller) {
        // 参数没有实时同步，但是在连接时会被同步
        Log.info("skip sync_params, retry when connect");
        return;
    }
    m_controller->set_swipe_with_pause(m_swipe_with_pause);
    m_controller->set_kill_adb_on_exit(m_kill_adb_on_exit);
}

bool asst::Controller::back_to_home()
{
    CHECK_EXIST(m_controller, false);
    m_controller->back_to_home();
    return true;
}

cv::Mat asst::Controller::get_resized_image_cache() const
{
    const cv::Size d_size(m_scale_size.first, m_scale_size.second);

    std::shared_lock<std::shared_mutex> image_lock(m_image_mutex);
    if (m_cache_image.empty()) {
        Log.error("image is empty");
        return { d_size, CV_8UC3 };
    }
    cv::Mat resized_mat;
    cv::resize(m_cache_image, resized_mat, d_size, 0.0, 0.0, cv::INTER_AREA);
    return resized_mat;
}

bool asst::Controller::start_game(const std::string& client_type)
{
    CHECK_EXIST(m_controller, false);
    return m_controller->start_game(client_type);
}

bool asst::Controller::stop_game(const std::string& client_type)
{
    CHECK_EXIST(m_controller, false);
    return m_controller->stop_game(client_type);
}

bool asst::Controller::click(const Point& p)
{
    CHECK_EXIST(m_controller, false);
    return m_scale_proxy->click(p);
}

bool asst::Controller::click(const Rect& rect)
{
    CHECK_EXIST(m_controller, false);
    return m_scale_proxy->click(rect);
}

bool asst::Controller::input(const std::string& text)
{
    CHECK_EXIST(m_controller, false);
    return m_controller->input(text);
}

bool asst::Controller::swipe(
    const Point& p1,
    const Point& p2,
    int duration,
    bool extra_swipe,
    double slope_in,
    double slope_out,
    bool with_pause)
{
    CHECK_EXIST(m_controller, false);
    return m_scale_proxy->swipe(p1, p2, duration, extra_swipe, slope_in, slope_out, with_pause);
}

bool asst::Controller::swipe(
    const Rect& r1,
    const Rect& r2,
    int duration,
    bool extra_swipe,
    double slope_in,
    double slope_out,
    bool with_pause,
    bool high_resolution_swipe_fix)
{
    CHECK_EXIST(m_controller, false);
    return m_scale_proxy
        ->swipe(r1, r2, duration, extra_swipe, slope_in, slope_out, with_pause, high_resolution_swipe_fix);
}

bool asst::Controller::inject_input_event(InputEvent& event)
{
    CHECK_EXIST(m_controller, false);
    return m_controller->inject_input_event(event);
}

bool asst::Controller::press_esc()
{
    LogTraceFunction;

    CHECK_EXIST(m_controller, false);
    return m_controller->press_esc();
}

asst::ControlFeat::Feat asst::Controller::support_features()
{
    CHECK_EXIST(m_controller, ControlFeat::NONE);
    return m_controller->support_features();
}

bool asst::Controller::connect(const std::string& adb_path, const std::string& address, const std::string& config)
{
    LogTraceFunction;

    clear_info();

    m_controller = create_controller(m_controller_type, m_platform_type);
    if (!m_controller) {
        Log.error("connect failed");
        return false;
    }

    sync_params();

    if (!m_controller->connect(adb_path, address, config)) {
        Log.error("connect failed");
        m_controller = nullptr;
        return false;
    }

    m_uuid = m_controller->get_uuid();

#ifdef ASST_DEBUG
    if (config == "DEBUG") {
        return true;
    }
#endif

    // try to find the fastest way
    if (!screencap()) {
        Log.error("Cannot find a proper way to screencap!");
        return false;
    }

    auto proxy_callback = [&](const json::object& details) {
        json::value connection_info = json::object {
            { "uuid", m_uuid },
            { "details",
              json::object {
                  { "adb", adb_path },
                  { "address", address },
                  { "config", config },
              } },
        } | details;
        callback(AsstMsg::ConnectionInfo, connection_info);
    };

    try {
        m_scale_proxy = std::make_shared<ControlScaleProxy>(m_controller, m_controller_type, proxy_callback);
    }
    catch (const std::exception& e) {
        Log.error("Cannot create controller proxy: {}", e.what());
        return false;
    }

    if (!m_scale_proxy) {
        Log.error("Cannot create controller proxy!");
        return false;
    }

    m_scale_size = m_scale_proxy->get_scale_size();

    return true;
}

#ifdef _WIN32
bool asst::Controller::attach_window(
    void* hwnd,
    Win32ScreencapMethod screencap_method,
    Win32InputMethod mouse_method,
    Win32InputMethod keyboard_method)
{
    LogTraceFunction;

    clear_info();

    auto win32_controller = std::make_shared<Win32Controller>(m_callback, m_inst);
    if (!win32_controller->attach(hwnd, screencap_method, mouse_method, keyboard_method)) {
        Log.error("attach_window failed");
        return false;
    }

    m_controller = win32_controller;
    m_controller_type = ControllerType::Win32;
    m_uuid = m_controller->get_uuid();

    // 尝试截图
    if (!screencap()) {
        Log.error("Cannot screencap!");
        return false;
    }

    auto proxy_callback = [&](const json::object& details) {
        json::value connection_info = json::object {
            { "uuid", m_uuid },
            { "details",
              json::object {
                  { "hwnd", reinterpret_cast<uintptr_t>(hwnd) },
                  { "screencap_method", screencap_method },
                  { "mouse_method", mouse_method },
                  { "keyboard_method", keyboard_method },
              } },
        } | details;
        callback(AsstMsg::ConnectionInfo, connection_info);
    };

    try {
        m_scale_proxy = std::make_shared<ControlScaleProxy>(m_controller, m_controller_type, proxy_callback);
    }
    catch (const std::exception& e) {
        Log.error("Cannot create controller proxy: {}", e.what());
        return false;
    }

    if (!m_scale_proxy) {
        Log.error("Cannot create controller proxy!");
        return false;
    }

    m_scale_size = m_scale_proxy->get_scale_size();

    return true;
}
#endif

bool asst::Controller::inited() noexcept
{
    CHECK_EXIST(m_controller, false);
    return m_controller->inited();
}

void asst::Controller::set_touch_mode(const TouchMode& mode) noexcept
{
    switch (mode) {
    case TouchMode::Adb:
        m_controller_type = ControllerType::Adb;
        break;
    case TouchMode::Minitouch:
        m_controller_type = ControllerType::Minitouch;
        break;
    case TouchMode::Maatouch:
        m_controller_type = ControllerType::Maatouch;
        break;
    case TouchMode::MacPlayTools:
        m_controller_type = ControllerType::MacPlayTools;
        break;
    case TouchMode::MaaFwAdb:
        m_controller_type = ControllerType::MaaFwAdb;
        break;
    default:
        m_controller_type = ControllerType::Minitouch;
    }
}

void asst::Controller::set_swipe_with_pause(bool enable) noexcept
{
    m_swipe_with_pause = enable;
    sync_params();
}

void asst::Controller::set_adb_lite_enabled(bool enable) noexcept
{
    m_platform_type = enable ? PlatformType::AdbLite : PlatformType::Native;
}

void asst::Controller::set_kill_adb_on_exit(bool enable) noexcept
{
    m_kill_adb_on_exit = enable;
    sync_params();
}

void asst::Controller::set_client_type(const std::string& client_type) noexcept
{
    m_client_type = client_type;
}

const std::string& asst::Controller::get_client_type() const noexcept
{
    return m_client_type;
}

const std::string& asst::Controller::get_uuid() const
{
    return m_uuid;
}

cv::Mat asst::Controller::get_image(bool raw)
{
    if (get_scale_size() == std::pair(0, 0)) {
        Log.error("Unknown image size");
        return {};
    }

    // 有些模拟器adb偶尔会莫名其妙截图失败，多试几次
    static constexpr int MaxTryCount = 20;
    bool success = false;
    for (int i = 0; i < MaxTryCount && inited(); ++i) {
        if (need_exit()) {
            break;
        }
        if (screencap()) {
            success = true;
            break;
        }
    }
    while (!success && !need_exit()) {
        if (screencap(true)) {
            break;
        }
        Log.error(__FUNCTION__, "screencap failed!");
        json::value info = json::object {
            { "uuid", m_uuid },
            { "what", "ScreencapFailed" },
            { "why", "ScreencapFailed" },
            { "details", json::object {} },
        };
        callback(AsstMsg::ConnectionInfo, info);

        const cv::Size d_size(m_scale_size.first, m_scale_size.second);
        m_cache_image = cv::Mat(d_size, CV_8UC3);

        break;
    }

    if (raw) {
        std::shared_lock<std::shared_mutex> image_lock(m_image_mutex);
        cv::Mat copy = m_cache_image.clone();
        return copy;
    }

    return get_resized_image_cache();
}

cv::Mat asst::Controller::get_image_cache() const
{
    return get_resized_image_cache();
}

bool asst::Controller::screencap(bool allow_reconnect)
{
    CHECK_EXIST(m_controller, false);
    std::unique_lock<std::shared_mutex> image_lock(m_image_mutex);
    return m_controller->screencap(m_cache_image, allow_reconnect);
}
