/*
 * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
#include "VshlCapabilitiesApi.h"

#include <list>
#include <json.hpp>

#include "afb/AFBApiImpl.h"
#include "afb/AFBRequestImpl.h"
#include "capabilities/CapabilitiesFactory.h"
#include "capabilities/CapabilityMessagingService.h"
#include "utilities/events/EventRouter.h"
#include "utilities/logging/Logger.h"

using namespace std;

CTLP_CAPI_REGISTER("vshlsupport-api");

static std::string TAG = "vshlcapabilities::plugins::VshlCapabilitiesApi";

static std::string EVENTS_JSON_ATTR_VA_ID = "va_id";
static std::string EVENTS_JSON_ATTR_EVENTS = "events";

static std::string CAPABILITIES_JSON_ATTR_ACTION = "action";
static std::string CAPABILITIES_JSON_ATTR_ACTIONS = "actions";
static std::string CAPABILITIES_JSON_ATTR_PAYLOAD = "payload";

static std::shared_ptr<vshlcapabilities::utilities::logging::Logger> sLogger;
static std::shared_ptr<vshlcapabilities::common::interfaces::IAFBApi> sAfbApi;
static std::unique_ptr<vshlcapabilities::capabilities::CapabilitiesFactory> sCapabilitiesFactory;
static std::unique_ptr<vshlcapabilities::capabilities::CapabilityMessagingService> sCapabilityMessagingService;
static std::unique_ptr<vshlcapabilities::utilities::events::EventRouter> sEventRouter;

using json = nlohmann::json;
using Level = vshlcapabilities::utilities::logging::Logger::Level;

CTLP_ONLOAD(plugin, ret) {
    if (plugin->api == nullptr) {
        return -1;
    }

    // Logger
    sLogger = vshlcapabilities::utilities::logging::Logger::create(plugin->api);

    // AFB Wrapper
    sAfbApi = vshlcapabilities::afb::AFBApiImpl::create(plugin->api);

    // EventRouter
    sEventRouter = vshlcapabilities::utilities::events::EventRouter::create(sLogger);
    if (!sEventRouter) {
        sLogger->log(Level::ERROR, TAG, "Failed to create EventRouter");
        return -1;
    }

    sCapabilitiesFactory = vshlcapabilities::capabilities::CapabilitiesFactory::create(sLogger);
    if (!sCapabilitiesFactory) {
        sLogger->log(Level::ERROR, TAG, "Failed to create CapabilitiesFactory");
        return -1;
    }

    sCapabilityMessagingService = vshlcapabilities::capabilities::CapabilityMessagingService::create(sLogger, sAfbApi);
    if (!sCapabilityMessagingService) {
        sLogger->log(Level::ERROR, TAG, "Failed to create CapabilityMessagingService");
        return -1;
    }

    return 0;
}


CTLP_CAPI(guiMetadataSubscribe, source, argsJ, eventJ) {
    if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
        return -1;
    }

    shared_ptr<vshlcapabilities::common::interfaces::ICapability> guMetadataCapability = sCapabilitiesFactory->getGuiMetadata();
    if (!guMetadataCapability) {
        sLogger->log(
            Level::WARNING,
            TAG,
            "guimetadataSubscribe: Failed to "
            "fetch guimetadata capability "
            "object.");
        return -1;
    }

    if (eventJ == nullptr) {
        sLogger->log(Level::WARNING, TAG, "guimetadataSubscribe: No arguments supplied.");
        return -1;
    }

    json subscribeJson = json::parse(json_object_to_json_string(eventJ));
    if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) {
        sLogger->log(Level::ERROR, TAG, "guimetadataSubscribe: No events array found in subscribe json");
        return -1;
    }
    list<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>());

    // SUbscribe this client for the guimetadata events.
    auto request = vshlcapabilities::afb::AFBRequestImpl::create(source->request);
    for (auto event : events) {
        if (!sCapabilityMessagingService->subscribe(*request, guMetadataCapability, event)) {
            sLogger->log(Level::ERROR, TAG, "guimetadataSubscribe: Failed to subscribe to event: " + event);
            return -1;
        }
    }

    afb_req_success(
        source->request, json_object_new_string("Subscription to guimetadata events successfully completed."), NULL);
    return 0;
}

CTLP_CAPI(guiMetadataPublish, source, argsJ, eventJ) {
    if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
        return -1;
    }

    shared_ptr<vshlcapabilities::common::interfaces::ICapability> guMetadataCapability = sCapabilitiesFactory->getGuiMetadata();
    if (!guMetadataCapability) {
        sLogger->log(
            Level::WARNING,
            TAG,
            "guimetadataPublish: Failed to fetch "
            "guimetadata capability object.");
        return -1;
    }

    if (eventJ == nullptr) {
        sLogger->log(Level::WARNING, TAG, "guimetadataPublish: No arguments supplied.");
        return -1;
    }

    json_object* actionJ = json_object_object_get(eventJ , CAPABILITIES_JSON_ATTR_ACTION.c_str());
    if (actionJ == nullptr) {
        sLogger->log(Level::ERROR, TAG, "guimetadataPublish: No action found in publish json");
        return -1;        
    }

    std::string action = std::string(json_object_get_string(actionJ));
    if (action.empty()) {
        sLogger->log(Level::ERROR, TAG, "guimetadataPublish: Invalid action input found in publish json");
        return -1;        
    }

    json_object* payloadJ = json_object_object_get(eventJ , CAPABILITIES_JSON_ATTR_PAYLOAD.c_str());
    if (payloadJ == nullptr) {
        sLogger->log(Level::ERROR, TAG, "guimetadataPublish: No playload found in publish json");
        return -1;        
    }

    // Bump reference count on payload object since we're reusing it for downstream
    json_object_get(payloadJ);
    if (!sCapabilityMessagingService->publish(guMetadataCapability, action, payloadJ)) {
        sLogger->log(Level::ERROR, TAG, "guimetadataPublish: Failed to publish message: " + action);
        // The publish/forwarder code path returns false when there are no
        // subscribers, without calling afb_event_push, so need to free
        // payload to avoid leaking.
        json_object_put(payloadJ);
        return -1;
    }

    afb_req_success(source->request, json_object_new_string("Successfully published guimetadata messages."), NULL);
    return 0;
}

CTLP_CAPI(phonecontrolSubscribe, source, argsJ, eventJ) {
    if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
        return -1;
    }

    shared_ptr<vshlcapabilities::common::interfaces::ICapability> phoneControlCapability = sCapabilitiesFactory->getPhoneControl();
    if (!phoneControlCapability) {
        sLogger->log(Level::WARNING, TAG, "phoneControlSubscribe: Failed to fetch phone control capability object.");
        return -1;
    }

    if (eventJ == nullptr) {
        sLogger->log(Level::WARNING, TAG, "phoneControlSubscribe: No arguments supplied.");
        return -1;
    }

    json subscribeJson = json::parse(json_object_to_json_string(eventJ));
    if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) {
        sLogger->log(Level::ERROR, TAG, "phoneControlSubscribe: No events array found in subscribe json");
        return -1;
    }
    list<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>());

    // SUbscribe this client for the phone call control events.
    auto request = vshlcapabilities::afb::AFBRequestImpl::create(source->request);
    for (auto event : events) {
        if (!sCapabilityMessagingService->subscribe(*request, phoneControlCapability, event)) {
            sLogger->log(Level::ERROR, TAG, "phoneControlSubscribe: Failed to subscribe to event: " + event);
            return -1;
        }
    }

    afb_req_success(
        source->request, json_object_new_string("Subscription to phone control events successfully completed."), NULL);
    return 0;
}

CTLP_CAPI(phonecontrolPublish, source, argsJ, eventJ) {
    if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
        return -1;
    }

    shared_ptr<vshlcapabilities::common::interfaces::ICapability> phoneControlCapability = sCapabilitiesFactory->getPhoneControl();
    if (!phoneControlCapability) {
        sLogger->log(Level::WARNING, TAG, "phoneControlPublish: Failed to fetch navigation capability object.");
        return -1;
    }

    if (eventJ == nullptr) {
        sLogger->log(Level::WARNING, TAG, "phoneControlPublish: No arguments supplied.");
        return -1;
    }

    json_object* actionJ = json_object_object_get(eventJ , CAPABILITIES_JSON_ATTR_ACTION.c_str());
    if (actionJ == nullptr) {
        sLogger->log(Level::ERROR, TAG, "phoneControlPublish: No action found in publish json");
        return -1;        
    }

    std::string action = std::string(json_object_get_string(actionJ));
    if (action.empty()) {
        sLogger->log(Level::ERROR, TAG, "phoneControlPublish: Invalid action input found in publish json");
        return -1;        
    }

    json_object* payloadJ = json_object_object_get(eventJ , CAPABILITIES_JSON_ATTR_PAYLOAD.c_str());
    if (payloadJ == nullptr) {
        sLogger->log(Level::ERROR, TAG, "phoneControlPublish: No playload found in publish json");
        return -1;        
    }

    // Bump reference count on payload object since we're reusing it for downstream
    json_object_get(payloadJ);
    if (!sCapabilityMessagingService->publish(phoneControlCapability, action, payloadJ)) {
        sLogger->log(Level::ERROR, TAG, "phoneControlPublish: Failed to publish message: " + action);
        // The publish/forwarder code path returns false when there are no
        // subscribers, without calling afb_event_push, so need to free
        // payload to avoid leaking.
        json_object_put(payloadJ);
        return -1;
    }

    afb_req_success(source->request, json_object_new_string("Successfully published phone control messages."), NULL);
    return 0;
}

CTLP_CAPI(navigationSubscribe, source, argsJ, eventJ) {
    if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
        return -1;
    }

    shared_ptr<vshlcapabilities::common::interfaces::ICapability> navigationCapability = sCapabilitiesFactory->getNavigation();
    if (!navigationCapability) {
        sLogger->log(Level::WARNING, TAG, "navigationSubscribe: Failed to fetch navigation capability object.");
        return -1;
    }

    if (eventJ == nullptr) {
        sLogger->log(Level::WARNING, TAG, "navigationSubscribe: No arguments supplied.");
        return -1;
    }

    json subscribeJson = json::parse(json_object_to_json_string(eventJ));
    if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) {
        sLogger->log(Level::ERROR, TAG, "navigationSubscribe: No events array found in subscribe json");
        return -1;
    }
    list<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>());

    // SUbscribe this client for the navigation events.
    auto request = vshlcapabilities::afb::AFBRequestImpl::create(source->request);
    for (auto event : events) {
        if (!sCapabilityMessagingService->subscribe(*request, navigationCapability, event)) {
            sLogger->log(Level::ERROR, TAG, "navigationSubscribe: Failed to subscribe to event: " + event);
            return -1;
        }
    }

    afb_req_success(
        source->request, json_object_new_string("Subscription to navigation events successfully completed."), NULL);
    return 0;
}

CTLP_CAPI(navigationPublish, source, argsJ, eventJ) {
    if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
        return -1;
    }

    shared_ptr<vshlcapabilities::common::interfaces::ICapability> navigationCapability = sCapabilitiesFactory->getNavigation();
    if (!navigationCapability) {
        sLogger->log(Level::WARNING, TAG, "navigationPublish: Failed to fetch navigation capability object.");
        return -1;
    }

    if (eventJ == nullptr) {
        sLogger->log(Level::WARNING, TAG, "navigationPublish: No arguments supplied.");
        return -1;
    }

    json_object* actionJ = json_object_object_get(eventJ , CAPABILITIES_JSON_ATTR_ACTION.c_str());
    if (actionJ == nullptr) {
        sLogger->log(Level::ERROR, TAG, "navigationPublish: No action found in publish json");
        return -1;        
    }

    std::string action = std::string(json_object_get_string(actionJ));
    if (action.empty()) {
        sLogger->log(Level::ERROR, TAG, "navigationPublish: Invalid action input found in publish json");
        return -1;        
    }

    json_object* payloadJ = json_object_object_get(eventJ , CAPABILITIES_JSON_ATTR_PAYLOAD.c_str());
    if (payloadJ == nullptr) {
        sLogger->log(Level::ERROR, TAG, "navigationPublish: No playload found in publish json");
        return -1;        
    }

    // Bump reference count on payload object since we're reusing it for downstream
    json_object_get(payloadJ);
    if (!sCapabilityMessagingService->publish(navigationCapability, action, payloadJ)) {
        sLogger->log(Level::ERROR, TAG, "navigationPublish: Failed to publish message: " + action);
        // The publish/forwarder code path returns false when there are no
        // subscribers, without calling afb_event_push, so need to free
        // payload to avoid leaking.
        json_object_put(payloadJ);
        return -1;
    }

    afb_req_success(source->request, json_object_new_string("Successfully published navigation messages."), NULL);
    return 0;
}

CTLP_CAPI(playbackControllerSubscribe, source, argsJ, eventJ) {
    if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
        return -1;
    }

    shared_ptr<vshlcapabilities::common::interfaces::ICapability> playbackcontrollerCapability = sCapabilitiesFactory->getPlaybackController();
    if (!playbackcontrollerCapability) {
        sLogger->log(Level::WARNING, TAG, "playbackControllerSubscribe: Failed to fetch playbackcontroller capability object.");
        return -1;
    }

    if (eventJ == nullptr) {
        sLogger->log(Level::WARNING, TAG, "playbackControllerSubscribe: No arguments supplied.");
        return -1;
    }

    json subscribeJson = json::parse(json_object_to_json_string(eventJ));
    if (subscribeJson.find(CAPABILITIES_JSON_ATTR_ACTIONS) == subscribeJson.end()) {
        sLogger->log(Level::ERROR, TAG, "playbackControllerSubscribe: No events array found in subscribe json");
        return -1;
    }
    list<string> events(subscribeJson[CAPABILITIES_JSON_ATTR_ACTIONS].get<list<string>>());

    // SUbscribe this client for the navigation events.
    auto request = vshlcapabilities::afb::AFBRequestImpl::create(source->request);
    for (auto event : events) {
        if (!sCapabilityMessagingService->subscribe(*request, playbackcontrollerCapability, event)) {
            sLogger->log(Level::ERROR, TAG, "playbackControllerSubscribe: Failed to subscribe to event: " + event);
            return -1;
        }
    }

    afb_req_success(
        source->request, json_object_new_string("Subscription to playbackcontroller events successfully completed."), NULL);
    return 0;
}

CTLP_CAPI(playbackControllerPublish, source, argsJ, eventJ) {
    if (sCapabilitiesFactory == nullptr || sCapabilityMessagingService == nullptr) {
        return -1;
    }

    shared_ptr<vshlcapabilities::common::interfaces::ICapability> playbackcontrollerCapability = sCapabilitiesFactory->getPlaybackController();
    if (!playbackcontrollerCapability) {
        sLogger->log(Level::WARNING, TAG, "playbackControllerPublish: Failed to fetch playbackcontroller capability object.");
        return -1;
    }

    if (eventJ == nullptr) {
        sLogger->log(Level::WARNING, TAG, "playbackControllerPublish: No arguments supplied.");
        return -1;
    }

    json_object* actionJ = json_object_object_get(eventJ , CAPABILITIES_JSON_ATTR_ACTION.c_str());
    if (actionJ == nullptr) {
        sLogger->log(Level::ERROR, TAG, "playbackControllerPublish: No action found in publish json");
        return -1;        
    }

    std::string action = std::string(json_object_get_string(actionJ));
    if (action.empty()) {
        sLogger->log(Level::ERROR, TAG, "playbackControllerPublish: Invalid action input found in publish json");
        return -1;        
    }

    json_object* payloadJ = json_object_object_get(eventJ , CAPABILITIES_JSON_ATTR_PAYLOAD.c_str());
    if (payloadJ == nullptr) {
        sLogger->log(Level::ERROR, TAG, "playbackControllerPublish: No playload found in publish json");
        return -1;        
    }

    // Bump reference count on payload object since we're reusing it for downstream
    json_object_get(payloadJ);
    if (!sCapabilityMessagingService->publish(playbackcontrollerCapability, action, payloadJ)) {
        sLogger->log(Level::ERROR, TAG, "playbackControllerPublish: Failed to publish message: " + action);
        // The publish/forwarder code path returns false when there are no
        // subscribers, without calling afb_event_push, so need to free
        // payload to avoid leaking.
        json_object_put(payloadJ);
        return -1;
    }
     
    afb_req_success(source->request, json_object_new_string("Successfully published playbackcontroller messages."), NULL);
    return 0;
}