/*
 * 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 "VshlCoreApi.h"

#include <list>

#include "afb/AFBApiImpl.h"
#include "afb/AFBRequestImpl.h"
#include "core/VRRequestProcessor.h"
#include "utilities/events/EventRouter.h"
#include "utilities/logging/Logger.h"
#include "voiceagents/VoiceAgentEventNames.h"
#include "voiceagents/VoiceAgentsDataManager.h"

#include "json.hpp"

using namespace std;

CTLP_CAPI_REGISTER("vshl-core-api");

static std::string TAG = "vshlcore::plugins::VshlCoreAPI";

static std::string VA_JSON_ATTR_DEFAULT = "default";
static std::string VA_JSON_ATTR_AGENTS = "agents";
static std::string VA_JSON_ATTR_ID = "id";
static std::string VA_JSON_ATTR_NAME = "name";
static std::string VA_JSON_ATTR_API = "api";
static std::string VA_JSON_ATTR_ACTIVE = "active";
static std::string VA_JSON_ATTR_WWS = "wakewords";
static std::string VA_JSON_ATTR_ACTIVE_WW = "activewakeword";
static std::string VA_JSON_ATTR_DESCRIPTION = "description";
static std::string VA_JSON_ATTR_VENDOR = "vendor";

static std::string STARTLISTENING_JSON_ATTR_REQUEST = "request_id";

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

static std::shared_ptr<vshlcore::utilities::logging::Logger> sLogger;
static std::shared_ptr<vshlcore::common::interfaces::IAFBApi> sAfbApi;
static std::unique_ptr<vshlcore::core::VRRequestProcessor> sVRRequestProcessor;
static std::unique_ptr<vshlcore::voiceagents::VoiceAgentsDataManager> sVoiceAgentsDataManager;
static std::unique_ptr<vshlcore::utilities::events::EventRouter> sEventRouter;

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

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

    // Logger
    sLogger = vshlcore::utilities::logging::Logger::create(plugin->api);
    // sLogger->log(Level::INFO, TAG, "Vshl plugin loaded & initialized.");

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

    // VRRequestProcessor
    auto vrRequestProcessorDelegate = vshlcore::core::VRRequestProcessorDelegate::create(sLogger, sAfbApi);
    sVRRequestProcessor = vshlcore::core::VRRequestProcessor::create(sLogger, vrRequestProcessorDelegate);
    if (!sVRRequestProcessor) {
        sLogger->log(Level::ERROR, TAG, "Failed to create VRRequestProcessor");
        return -1;
    }

    // VoiceAgentDataManager
    sVoiceAgentsDataManager = vshlcore::voiceagents::VoiceAgentsDataManager::create(sLogger, sAfbApi);
    if (!sVoiceAgentsDataManager) {
        sLogger->log(Level::ERROR, TAG, "Failed to create VoiceAgentsDataManager");
        return -1;
    }
    sVoiceAgentsDataManager->addVoiceAgentsChangeObserver(sVRRequestProcessor->getVoiceAgentsChangeObserver());

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

    return 0;
}

CTLP_CAPI(onAuthStateEvent, source, argsJ, eventJ) {
    if (sEventRouter == nullptr) {
        return -1;
    }

    string eventName = vshlcore::voiceagents::VSHL_EVENT_AUTH_STATE_EVENT;
    json eventJson = json::parse(json_object_to_json_string(eventJ));
    if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) {
        sLogger->log(Level::ERROR, TAG, "onAuthStateEvent: No voiceagent id found.");
        return -1;
    }
    std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get<string>());

    sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ));

    return 0;
}

CTLP_CAPI(onConnectionStateEvent, source, argsJ, eventJ) {
    if (sEventRouter == nullptr) {
        return -1;
    }

    string eventName = vshlcore::voiceagents::VSHL_EVENT_CONNECTION_STATE_EVENT;
    json eventJson = json::parse(json_object_to_json_string(eventJ));
    if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) {
        sLogger->log(Level::ERROR, TAG, "onConnectionStateEvent: No voiceagent id found.");
        return -1;
    }
    std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get<string>());

    sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ));

    return 0;
}

CTLP_CAPI(onDialogStateEvent, source, argsJ, eventJ) {
    if (sEventRouter == nullptr) {
        return -1;
    }

    string eventName = vshlcore::voiceagents::VSHL_EVENT_DIALOG_STATE_EVENT;
    json eventJson = json::parse(json_object_to_json_string(eventJ));
    if (eventJson.find(EVENTS_JSON_ATTR_VA_ID) == eventJson.end()) {
        sLogger->log(Level::ERROR, TAG, "onDialogStateEvent: No voiceagent id found.");
        return -1;
    }
    std::string voiceAgentId(eventJson[EVENTS_JSON_ATTR_VA_ID].get<string>());

    sEventRouter->handleIncomingEvent(eventName, voiceAgentId, json_object_to_json_string(eventJ));

    return 0;
}

CTLP_CAPI(loadVoiceAgentsConfig, source, argsJ, eventJ) {
    if (sVoiceAgentsDataManager == nullptr) {
        sLogger->log(Level::WARNING, TAG, "loadVoiceAgentsConfig: Voice service not initialized.");
        return -1;
    }

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

    json agentsConfigJson = json::parse(json_object_to_json_string(argsJ));
    if (agentsConfigJson.find(VA_JSON_ATTR_AGENTS) == agentsConfigJson.end()) {
        sLogger->log(Level::ERROR, TAG, "loadVoiceAgentsConfig: No agents object found in agents json");
        return -1;
    }

    json agentsJson = agentsConfigJson[VA_JSON_ATTR_AGENTS];
    for (auto agentIt = agentsJson.begin(); agentIt != agentsJson.end(); ++agentIt) {
        json agentJson = *agentIt;

        if (agentJson.find(VA_JSON_ATTR_ID) == agentJson.end() ||
            agentJson.find(VA_JSON_ATTR_ACTIVE) == agentJson.end() ||
            agentJson.find(VA_JSON_ATTR_NAME) == agentJson.end() ||
            agentJson.find(VA_JSON_ATTR_API) == agentJson.end() ||
            agentJson.find(VA_JSON_ATTR_WWS) == agentJson.end() ||
            agentJson.find(VA_JSON_ATTR_ACTIVE_WW) == agentJson.end() ||
            agentJson.find(VA_JSON_ATTR_DESCRIPTION) == agentJson.end() ||
            agentJson.find(VA_JSON_ATTR_VENDOR) == agentJson.end()) {
            std::stringstream error;
            error << "loadVoiceAgentsConfig: One or more missing params in agent "
                     "config "
                  << agentJson.dump();
            sLogger->log(Level::WARNING, TAG, error.str().c_str());
            continue;
        }

        std::string id(agentJson[VA_JSON_ATTR_ID].get<string>());
        std::string name(agentJson[VA_JSON_ATTR_NAME].get<string>());
        std::string api(agentJson[VA_JSON_ATTR_API].get<string>());
        std::string description(agentJson[VA_JSON_ATTR_DESCRIPTION].get<string>());
        std::string vendor(agentJson[VA_JSON_ATTR_VENDOR].get<string>());
        std::string activeWakeword(agentJson[VA_JSON_ATTR_ACTIVE_WW].get<string>());
        bool isActive(agentJson[VA_JSON_ATTR_ACTIVE].get<bool>());

        shared_ptr<unordered_set<string>> wakewords = std::make_shared<unordered_set<string>>();
        json wakewordsJson = agentJson[VA_JSON_ATTR_WWS];
        for (auto wwIt = wakewordsJson.begin(); wwIt != wakewordsJson.end(); ++wwIt) {
            wakewords->insert(wwIt->get<string>());
        }

        sVoiceAgentsDataManager->addNewVoiceAgent(
            id, name, description, api, vendor, activeWakeword, isActive, wakewords);
    }

    // Set the default agent.
    if (agentsConfigJson.find(VA_JSON_ATTR_DEFAULT) == agentsConfigJson.end()) {
        sLogger->log(Level::ERROR, TAG, "loadVoiceAgentsConfig: No default agent found in agents json");
        return -1;
    }
    std::string defaultAgentId(agentsConfigJson[VA_JSON_ATTR_DEFAULT].get<string>());
    sVoiceAgentsDataManager->setDefaultVoiceAgent(defaultAgentId);

    return 0;
}

CTLP_CAPI(startListening, source, argsJ, eventJ) {
    if (sVoiceAgentsDataManager == nullptr) {
        return -1;
    }

    if (sVRRequestProcessor == nullptr) {
        return -1;
    }

    int result = 0;
    string requestId = sVRRequestProcessor->startListening();

    if (!requestId.empty()) {
        json responseJson;
        responseJson[STARTLISTENING_JSON_ATTR_REQUEST] = requestId;
        AFB_ReqSuccess(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL);
    } else {
        AFB_ReqFail(source->request, NULL, "Failed to startListening...");
    }

    return 0;
}

CTLP_CAPI(cancelListening, source, argsJ, eventJ) {
    return 0;
}

CTLP_CAPI(enumerateVoiceAgents, source, argsJ, eventJ) {
    if (sVoiceAgentsDataManager == nullptr) {
        return -1;
    }
    
    auto agents = sVoiceAgentsDataManager->getAllVoiceAgents();
    std::string defaultAgentId(sVoiceAgentsDataManager->getDefaultVoiceAgent());

    json responseJson;
    json agentsJson = json::array();

    for (auto agent : agents) {
        json agentJson;
        agentJson[VA_JSON_ATTR_ID] = agent->getId();
        agentJson[VA_JSON_ATTR_NAME] = agent->getName();
        agentJson[VA_JSON_ATTR_DESCRIPTION] = agent->getDescription();
        agentJson[VA_JSON_ATTR_API] = agent->getApi();
        agentJson[VA_JSON_ATTR_VENDOR] = agent->getVendor();
        agentJson[VA_JSON_ATTR_ACTIVE] = agent->isActive();
        agentJson[VA_JSON_ATTR_ACTIVE_WW] = agent->getActiveWakeword();

        auto wakewords = agent->getWakeWords();
        if (wakewords != nullptr) {
            json wakewordsJson;
            for (auto wakeword : *wakewords) {
                wakewordsJson.push_back(wakeword);
            }
            agentJson[VA_JSON_ATTR_WWS] = wakewordsJson;
        }

        agentsJson.push_back(agentJson);
    }

    responseJson[VA_JSON_ATTR_AGENTS] = agentsJson;
    responseJson[VA_JSON_ATTR_DEFAULT] = defaultAgentId;

    AFB_ReqSuccess(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL);

    return 0;
}

CTLP_CAPI(subscribe, source, argsJ, eventJ) {
    if (sVoiceAgentsDataManager == nullptr) {
        return -1;
    }

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

    json subscribeJson = json::parse(json_object_to_json_string(eventJ));
    if (subscribeJson.find(EVENTS_JSON_ATTR_VA_ID) == subscribeJson.end()) {
        sLogger->log(Level::ERROR, TAG, "subscribe: No voiceagent id found in subscribe json");
        return -1;
    }
    std::string voiceAgentId(subscribeJson[EVENTS_JSON_ATTR_VA_ID].get<string>());

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

    // Subscribe this client for the listed events.
    auto request = vshlcore::afb::AFBRequestImpl::create(source->request);
    for (auto event : events) {
        if (!sVoiceAgentsDataManager->subscribeToVshlEventFromVoiceAgent(*request, event, voiceAgentId)) {
            sLogger->log(Level::ERROR, TAG, "subscribe: Failed to subscribe to event: " + event);
            return -1;
        }
    }

    AFB_ReqSuccess(source->request, json_object_new_string("Subscription to events successfully completed."), NULL);

    return 0;
}

CTLP_CAPI(setDefaultVoiceAgent, source, argsJ, eventJ) {
    if (sVoiceAgentsDataManager == nullptr) {
        return -1;
    }

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

    json jsonRequest = json::parse(json_object_to_json_string(eventJ));
    if (jsonRequest.find(VA_JSON_ATTR_ID) == jsonRequest.end()) {
        sLogger->log(Level::ERROR, TAG, "setDefaultVoiceAgent: voice agent id not found in request json");
        return -1;
    }

    std::string voiceAgentId(jsonRequest[VA_JSON_ATTR_ID].get<string>());
    if (!sVoiceAgentsDataManager->setDefaultVoiceAgent(voiceAgentId)) {
        sLogger->log(Level::ERROR, TAG, "setDefaultVoiceAgent: Failed to set default agent");
        return -1;
    }

    AFB_ReqSuccess(source->request, NULL, NULL);
    return 0;
}