#include "mqttfsim_client/simconnect_handler.h"

using namespace sc_helpers;

HANDLE simconnect_handler::simconnect_;
HANDLE *simconnect_handler::p_simconnect_ = NULL;
bool simconnect_handler::b_sc_connected_ = false;
int simconnect_handler::max_req_id_ = 0;

typedef void (*sc_custom_dispatch_proc_callback)(SIMCONNECT_RECV*, DWORD, void*);
sc_custom_dispatch_proc_callback simconnect_handler::s_sc_custom_dispatch_proc_cb_ = nullptr;

std::vector<simconnect_handler*> simconnect_handler::sch_instances_ = {};
std::vector<SIMCONNECT_DATA_DEFINITION_ID> simconnect_handler::data_definitions_ = {};

void CALLBACK simconnect_handler::dispatch_callback(SIMCONNECT_RECV* pData, DWORD cbData, void *pContext)
{
    switch(pData->dwID)
    {
        case SIMCONNECT_RECV_ID_EVENT:
        {
            SIMCONNECT_RECV_EVENT *evt = (SIMCONNECT_RECV_EVENT*)pData;

            if ((int)evt->uEventID > sch_instances_.size()) break;

            simconnect_handler *caller = sch_instances_[(int)evt->uEventID];

            caller->sch_process_sc_event(evt, pContext);
            break;
        }
        case SIMCONNECT_RECV_ID_SIMOBJECT_DATA:
        {
            SIMCONNECT_RECV_SIMOBJECT_DATA *p_obj_data = (SIMCONNECT_RECV_SIMOBJECT_DATA*)pData;

            if (p_obj_data->dwRequestID > sch_instances_.size()) break;

            simconnect_handler *caller = sch_instances_[p_obj_data->dwRequestID];

            caller->sch_process_sc_simobj_data(p_obj_data, caller);

            break;
        }
        case SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:
        {
            SIMCONNECT_RECV_ASSIGNED_OBJECT_ID *p_obj_data = (SIMCONNECT_RECV_ASSIGNED_OBJECT_ID*)pData;

            std::cout << "RequestID=" << p_obj_data->dwRequestID << ": Received assigned object ID=" << p_obj_data->dwObjectID << std::endl;

            if (p_obj_data->dwRequestID > sch_instances_.size()) break;

            simconnect_handler *caller = sch_instances_[p_obj_data->dwRequestID];

            caller->sch_process_sc_assigned_simobject_id(p_obj_data, caller);
            break;
        }
    }

    if (s_sc_custom_dispatch_proc_cb_ != nullptr)
    {
        s_sc_custom_dispatch_proc_cb_(pData, cbData, pContext);
    }
}

HRESULT simconnect_handler::connect(LPCSTR  client_program_name)
{
    return s_connect(client_program_name);
}

HRESULT simconnect_handler::s_connect(LPCSTR  client_program_name)
{
    if (p_simconnect_ == nullptr) p_simconnect_ = &simconnect_;
    HRESULT hr = SimConnect_Open(p_simconnect_, client_program_name, NULL, 0, 0, 0);
    b_sc_connected_ = SUCCEEDED(hr);
    return hr;
}

HRESULT simconnect_handler::s_connect(HANDLE *h_simconnect_ptr, LPCSTR  client_program_name)
{   
    p_simconnect_ = h_simconnect_ptr;
    return s_connect(client_program_name);
}

HRESULT simconnect_handler::disconnect(void)
{
    return s_disconnect();
}

HRESULT simconnect_handler::s_disconnect(void)
{
    HRESULT hr = SimConnect_Close(*p_simconnect_);
    b_sc_connected_ = !SUCCEEDED(hr);
    return hr;
}

void simconnect_handler::call_dispatch(void)
{
    SimConnect_CallDispatch(*p_simconnect_, dispatch_callback, this);
}

void simconnect_handler::s_call_dispatch(void)
{
    SimConnect_CallDispatch(*p_simconnect_, dispatch_callback, NULL);
}

void simconnect_handler::call_dispatch(void * pContext)
{
    SimConnect_CallDispatch(*p_simconnect_, dispatch_callback, pContext);
}

HRESULT simconnect_handler::s_add_to_data_definition(const int &propdef_count, const property_definition props[], SIMCONNECT_DATA_DEFINITION_ID definition_id)
{
    HRESULT hr;

    if (std::find(std::begin(data_definitions_), std::end(data_definitions_), definition_id) != std::end(data_definitions_)) 
    {

        return S_OK;
    }

    for (unsigned int i = 0; i < propdef_count; ++i)
    {
        const property_definition &prop = props[i];
        hr = SimConnect_AddToDataDefinition(*p_simconnect_, definition_id, prop.pVarName, prop.pUnits, prop.eDataType);
        if (!SUCCEEDED(hr))
        {
            throw new std::exception("Error adding data definition");
        }
    }

    data_definitions_.insert(std::begin(data_definitions_), definition_id);

    return hr;
}

HRESULT simconnect_handler::request_data_on_simobject(
    SIMCONNECT_DATA_DEFINITION_ID  define_id,
    SIMCONNECT_OBJECT_ID  object_id,
    SIMCONNECT_PERIOD  period)
{
    return SimConnect_RequestDataOnSimObject(*p_simconnect_, id_, define_id, object_id, period);
}

HRESULT simconnect_handler::request_data_on_simobject_type(
    SIMCONNECT_DATA_DEFINITION_ID  define_id,
    DWORD radius,
    SIMCONNECT_SIMOBJECT_TYPE simobject_type)
{
    return SimConnect_RequestDataOnSimObjectType(*p_simconnect_, id_, define_id, radius, simobject_type);
}

void simconnect_handler::sch_process_sc_simobj_data(SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData, void *pContext) 
{
    if (process_sc_simobj_data_cb_ != nullptr)
    {
        process_sc_simobj_data_cb_(pObjData, pContext);
    }
};

void simconnect_handler::sch_process_sc_event(SIMCONNECT_RECV_EVENT *pEvent, void *pContext) 
{
    if (process_sc_event_cb_ != nullptr)
    {
        process_sc_event_cb_(pEvent, pContext);
    }
};

void simconnect_handler::sch_process_sc_assigned_simobject_id(SIMCONNECT_RECV_ASSIGNED_OBJECT_ID *p_obj_data, void *p_context) 
{
    if (process_sc_assigned_simobj_id_cb_ != nullptr)
    {
        process_sc_assigned_simobj_id_cb_(p_obj_data, p_context);
    }
};

void simconnect_handler::assign_process_sc_simobj_data(process_sc_simobj_data_callback func)
{
    process_sc_simobj_data_cb_ = func;
}

void simconnect_handler::assign_process_sc_assigned_simobject_id(process_sc_assigned_simobject_id_callback func)
{
    process_sc_assigned_simobj_id_cb_ = func;
}

void simconnect_handler::assign_process_sc_event(process_sc_event_callback func)
{
    process_sc_event_cb_ = func;
}

HRESULT simconnect_handler::s_menu_add_item(const char * menu_item, SIMCONNECT_CLIENT_EVENT_ID event_id)
{
    return SimConnect_MenuAddItem(*p_simconnect_, menu_item, event_id, 0);
}

HRESULT simconnect_handler::s_menu_add_sub_item(const char * menu_item, SIMCONNECT_CLIENT_EVENT_ID parent_event_id, SIMCONNECT_CLIENT_EVENT_ID event_id)
{
    return SimConnect_MenuAddSubItem(*p_simconnect_, parent_event_id, menu_item, event_id, 0);
}

HRESULT simconnect_handler::s_menu_delete_item(SIMCONNECT_CLIENT_EVENT_ID event_id)
{
    return SimConnect_MenuDeleteItem(*p_simconnect_, event_id);
}

HRESULT simconnect_handler::s_menu_delete_sub_item(SIMCONNECT_CLIENT_EVENT_ID parent_event_id, SIMCONNECT_CLIENT_EVENT_ID event_id)
{
    return SimConnect_MenuDeleteSubItem(*p_simconnect_, parent_event_id, event_id);
}

HRESULT simconnect_handler::s_set_data_on_simobject(
    SIMCONNECT_DATA_DEFINITION_ID define_id, 
    const SIMCONNECT_OBJECT_ID &simobject_id, 
    const DWORD &array_count, 
    const DWORD &data_size, 
    void *p_data)
{
    return SimConnect_SetDataOnSimObject(*p_simconnect_, define_id, simobject_id, 0, array_count, data_size, p_data);
}

HRESULT simconnect_handler::s_transmit_client_event(
    const SIMCONNECT_OBJECT_ID &simobject_id, 
    SIMCONNECT_CLIENT_EVENT_ID event_id,
    const DWORD &data,
    SIMCONNECT_NOTIFICATION_GROUP_ID group_id,
    SIMCONNECT_EVENT_FLAG event_flag)
{
    return SimConnect_TransmitClientEvent(*p_simconnect_, simobject_id, event_id, data, group_id, event_flag);
}

HRESULT simconnect_handler::s_ai_create_parked_atc_aircraft(
    const char * container,
    const char * tail_number,
    const char * airport,
    SIMCONNECT_DATA_REQUEST_ID request_id)
{
    return SimConnect_AICreateParkedATCAircraft(*p_simconnect_, container, tail_number, airport, request_id);
}

HRESULT simconnect_handler::s_ai_create_simulated_object(
    const char *container,
    SIMCONNECT_DATA_INITPOSITION init,
    SIMCONNECT_DATA_REQUEST_ID request_id)
{
    return SimConnect_AICreateSimulatedObject(*p_simconnect_, container, init, request_id);
}

HRESULT simconnect_handler::s_ai_release_control(SIMCONNECT_OBJECT_ID object_id, SIMCONNECT_DATA_REQUEST_ID request_id)
{
    return SimConnect_AIReleaseControl(*p_simconnect_, object_id, request_id);
}

HRESULT simconnect_handler::s_ai_remove_object(const SIMCONNECT_OBJECT_ID &object_id)
{
    return SimConnect_AIRemoveObject(*p_simconnect_, object_id, REQUEST_ID_AI_REMOVE_OBJECT);
}

HRESULT simconnect_handler::s_map_client_event_to_sim_event(SIMCONNECT_CLIENT_EVENT_ID event_id, const char * event_name)
{
    return SimConnect_MapClientEventToSimEvent(*p_simconnect_, event_id, event_name);
}

HRESULT simconnect_handler::s_add_client_event_to_notification_group(SIMCONNECT_NOTIFICATION_GROUP_ID group_id, SIMCONNECT_CLIENT_EVENT_ID event_id, BOOL b_maskable)
{
    return SimConnect_AddClientEventToNotificationGroup(*p_simconnect_, group_id, event_id, b_maskable);
}

HRESULT simconnect_handler::s_set_notification_group_priority(SIMCONNECT_NOTIFICATION_GROUP_ID group_id, DWORD priority)
{
    return SimConnect_SetNotificationGroupPriority(*p_simconnect_, group_id, priority);
}
#include "mqttfsim_client/simconnect_handler.h"

using namespace sc_helpers;

HANDLE simconnect_handler::simconnect_;
HANDLE *simconnect_handler::p_simconnect_ = NULL;
bool simconnect_handler::b_sc_connected_ = false;
int simconnect_handler::max_req_id_ = 0;

typedef void (*sc_custom_dispatch_proc_callback)(SIMCONNECT_RECV*, DWORD, void*);
sc_custom_dispatch_proc_callback simconnect_handler::s_sc_custom_dispatch_proc_cb_ = nullptr;

std::vector<simconnect_handler*> simconnect_handler::sch_instances_ = {};
std::vector<SIMCONNECT_DATA_DEFINITION_ID> simconnect_handler::data_definitions_ = {};

void CALLBACK simconnect_handler::dispatch_callback(SIMCONNECT_RECV* pData, DWORD cbData, void *pContext)
{
    switch(pData->dwID)
    {
        case SIMCONNECT_RECV_ID_EVENT:
        {
            SIMCONNECT_RECV_EVENT *evt = (SIMCONNECT_RECV_EVENT*)pData;

            if ((int)evt->uEventID > sch_instances_.size()) break;

            simconnect_handler *caller = sch_instances_[(int)evt->uEventID];

            caller->sch_process_sc_event(evt, pContext);
            break;
        }
        case SIMCONNECT_RECV_ID_SIMOBJECT_DATA:
        {
            SIMCONNECT_RECV_SIMOBJECT_DATA *p_obj_data = (SIMCONNECT_RECV_SIMOBJECT_DATA*)pData;
            
            if (p_obj_data->dwRequestID > sch_instances_.size()) break;

            simconnect_handler *caller = sch_instances_[p_obj_data->dwRequestID];
            
            caller->sch_process_sc_simobj_data(p_obj_data, caller);
            
            break;
        }
        case SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:
        {
            SIMCONNECT_RECV_ASSIGNED_OBJECT_ID *p_obj_data = (SIMCONNECT_RECV_ASSIGNED_OBJECT_ID*)pData;

            std::cout << "RequestID=" << p_obj_data->dwRequestID << ": Received assigned object ID=" << p_obj_data->dwObjectID << std::endl;
            
            if (p_obj_data->dwRequestID > sch_instances_.size()) break;

            simconnect_handler *caller = sch_instances_[p_obj_data->dwRequestID];
            
            caller->sch_process_sc_assigned_simobject_id(p_obj_data, caller);
            break;
        }
    }

    if (s_sc_custom_dispatch_proc_cb_ != nullptr)
    {
        s_sc_custom_dispatch_proc_cb_(pData, cbData, pContext);
    }
}

HRESULT simconnect_handler::connect(LPCSTR  client_program_name)
{
    return s_connect(client_program_name);
}

HRESULT simconnect_handler::s_connect(LPCSTR  client_program_name)
{
    if (p_simconnect_ == nullptr) p_simconnect_ = &simconnect_;
    HRESULT hr = SimConnect_Open(p_simconnect_, client_program_name, NULL, 0, 0, 0);
    b_sc_connected_ = SUCCEEDED(hr);
    return hr;
}

HRESULT simconnect_handler::s_connect(HANDLE *h_simconnect_ptr, LPCSTR  client_program_name)
{   
    p_simconnect_ = h_simconnect_ptr;
    return s_connect(client_program_name);
}

HRESULT simconnect_handler::disconnect(void)
{
    return s_disconnect();
}

HRESULT simconnect_handler::s_disconnect(void)
{
    HRESULT hr = SimConnect_Close(*p_simconnect_);
    b_sc_connected_ = !SUCCEEDED(hr);
    return hr;
}

void simconnect_handler::call_dispatch(void)
{
    SimConnect_CallDispatch(*p_simconnect_, dispatch_callback, this);
}

void simconnect_handler::s_call_dispatch(void)
{
    SimConnect_CallDispatch(*p_simconnect_, dispatch_callback, NULL);
}

void simconnect_handler::call_dispatch(void * pContext)
{
    SimConnect_CallDispatch(*p_simconnect_, dispatch_callback, pContext);
}

HRESULT simconnect_handler::s_add_to_data_definition(const int &propdef_count, const property_definition props[], SIMCONNECT_DATA_DEFINITION_ID definition_id)
{
    HRESULT hr;

    if (std::find(std::begin(data_definitions_), std::end(data_definitions_), definition_id) != std::end(data_definitions_)) 
    {
        // Data definitions cannot be re-used as this will cause unexpected behavior in SimConnect.
        return S_OK;
    }

    for (unsigned int i = 0; i < propdef_count; ++i)
    {
        const property_definition &prop = props[i];
        hr = SimConnect_AddToDataDefinition(*p_simconnect_, definition_id, prop.pVarName, prop.pUnits, prop.eDataType);
        if (!SUCCEEDED(hr))
        {
            throw new std::exception("Error adding data definition");
        }
    }

    data_definitions_.insert(std::begin(data_definitions_), definition_id);

    return hr;
}

HRESULT simconnect_handler::request_data_on_simobject(
    SIMCONNECT_DATA_DEFINITION_ID  define_id,
    SIMCONNECT_OBJECT_ID  object_id,
    SIMCONNECT_PERIOD  period)
{
    return SimConnect_RequestDataOnSimObject(*p_simconnect_, id_, define_id, object_id, period);
}

HRESULT simconnect_handler::request_data_on_simobject_type(
    SIMCONNECT_DATA_DEFINITION_ID  define_id,
    DWORD radius,
    SIMCONNECT_SIMOBJECT_TYPE simobject_type)
{
    return SimConnect_RequestDataOnSimObjectType(*p_simconnect_, id_, define_id, radius, simobject_type);
}

void simconnect_handler::sch_process_sc_simobj_data(SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData, void *pContext) 
{
    if (process_sc_simobj_data_cb_ != nullptr)
    {
        process_sc_simobj_data_cb_(pObjData, pContext);
    }
};

void simconnect_handler::sch_process_sc_event(SIMCONNECT_RECV_EVENT *pEvent, void *pContext) 
{
    if (process_sc_event_cb_ != nullptr)
    {
        process_sc_event_cb_(pEvent, pContext);
    }
};

void simconnect_handler::sch_process_sc_assigned_simobject_id(SIMCONNECT_RECV_ASSIGNED_OBJECT_ID *p_obj_data, void *p_context) 
{
    if (process_sc_assigned_simobj_id_cb_ != nullptr)
    {
        process_sc_assigned_simobj_id_cb_(p_obj_data, p_context);
    }
};

void simconnect_handler::assign_process_sc_simobj_data(process_sc_simobj_data_callback func)
{
    process_sc_simobj_data_cb_ = func;
}

void simconnect_handler::assign_process_sc_assigned_simobject_id(process_sc_assigned_simobject_id_callback func)
{
    process_sc_assigned_simobj_id_cb_ = func;
}

void simconnect_handler::assign_process_sc_event(process_sc_event_callback func)
{
    process_sc_event_cb_ = func;
}

HRESULT simconnect_handler::s_menu_add_item(const char * menu_item, SIMCONNECT_CLIENT_EVENT_ID event_id)
{
    return SimConnect_MenuAddItem(*p_simconnect_, menu_item, event_id, 0);
}

HRESULT simconnect_handler::s_menu_add_sub_item(const char * menu_item, SIMCONNECT_CLIENT_EVENT_ID parent_event_id, SIMCONNECT_CLIENT_EVENT_ID event_id)
{
    return SimConnect_MenuAddSubItem(*p_simconnect_, parent_event_id, menu_item, event_id, 0);
}

HRESULT simconnect_handler::s_menu_delete_item(SIMCONNECT_CLIENT_EVENT_ID event_id)
{
    return SimConnect_MenuDeleteItem(*p_simconnect_, event_id);
}

HRESULT simconnect_handler::s_menu_delete_sub_item(SIMCONNECT_CLIENT_EVENT_ID parent_event_id, SIMCONNECT_CLIENT_EVENT_ID event_id)
{
    return SimConnect_MenuDeleteSubItem(*p_simconnect_, parent_event_id, event_id);
}

HRESULT simconnect_handler::s_set_data_on_simobject(
    SIMCONNECT_DATA_DEFINITION_ID define_id, 
    const SIMCONNECT_OBJECT_ID &simobject_id, 
    const DWORD &array_count, 
    const DWORD &data_size, 
    void *p_data)
{
    return SimConnect_SetDataOnSimObject(*p_simconnect_, define_id, simobject_id, 0, array_count, data_size, p_data);
}

HRESULT simconnect_handler::s_transmit_client_event(
    const SIMCONNECT_OBJECT_ID &simobject_id, 
    SIMCONNECT_CLIENT_EVENT_ID event_id,
    const DWORD &data,
    SIMCONNECT_NOTIFICATION_GROUP_ID group_id,
    SIMCONNECT_EVENT_FLAG event_flag)
{
    return SimConnect_TransmitClientEvent(*p_simconnect_, simobject_id, event_id, data, group_id, event_flag);
}

HRESULT simconnect_handler::s_ai_create_parked_atc_aircraft(
    const char * container,
    const char * tail_number,
    const char * airport,
    SIMCONNECT_DATA_REQUEST_ID request_id)
{
    return SimConnect_AICreateParkedATCAircraft(*p_simconnect_, container, tail_number, airport, request_id);
}

HRESULT simconnect_handler::s_ai_create_simulated_object(
    const char *container,
    SIMCONNECT_DATA_INITPOSITION init,
    SIMCONNECT_DATA_REQUEST_ID request_id)
{
    return SimConnect_AICreateSimulatedObject(*p_simconnect_, container, init, request_id);
}

HRESULT simconnect_handler::s_ai_release_control(SIMCONNECT_OBJECT_ID object_id, SIMCONNECT_DATA_REQUEST_ID request_id)
{
    return SimConnect_AIReleaseControl(*p_simconnect_, object_id, request_id);
}

HRESULT simconnect_handler::s_ai_remove_object(const SIMCONNECT_OBJECT_ID &object_id)
{
    return SimConnect_AIRemoveObject(*p_simconnect_, object_id, REQUEST_ID_AI_REMOVE_OBJECT);
}

HRESULT simconnect_handler::s_map_client_event_to_sim_event(SIMCONNECT_CLIENT_EVENT_ID event_id, const char * event_name)
{
    return SimConnect_MapClientEventToSimEvent(*p_simconnect_, event_id, event_name);
}

HRESULT simconnect_handler::s_add_client_event_to_notification_group(SIMCONNECT_NOTIFICATION_GROUP_ID group_id, SIMCONNECT_CLIENT_EVENT_ID event_id, BOOL b_maskable)
{
    return SimConnect_AddClientEventToNotificationGroup(*p_simconnect_, group_id, event_id, b_maskable);
}

HRESULT simconnect_handler::s_set_notification_group_priority(SIMCONNECT_NOTIFICATION_GROUP_ID group_id, DWORD priority)
{
    return SimConnect_SetNotificationGroupPriority(*p_simconnect_, group_id, priority);
}