/*
 * Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * Copyright 2019 Konsulko Group
 *
 * 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 <sstream>
#include <fstream>
#include <filesystem>
#include <json.hpp>
#include <json-c/json.h>

#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"

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::string CONFIG_JSON_FILE = "/etc/xdg/AGL/voice-high.json";
static std::string JSON_EXT = ".json";
static std::string VA_CONFIG_JSON_DIR = "/etc/xdg/AGL/voiceagents";

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;
}

// Helper function to add events for voice agent to controller
// configuration.  It relies on the controller configuration being
// available via the userdata of the API pointer.
static int AddVoiceAgentEvents(std::string &agentApi) {
    // Retrieve section config from api handle
    CtlConfigT *ctrlConfig = (CtlConfigT*) afb_api_get_userdata(sAfbApi->getApi());
    if (ctrlConfig == nullptr) {
        sLogger->log(Level::ERROR, TAG, "AddVoiceAgentEvents: ctrlConfig == nullptr");
        return -1;
    }

    CtlSectionT* section = nullptr;
    for (int idx = 0; ctrlConfig->sections[idx].key != NULL; ++idx) {
        if (!strcasecmp(ctrlConfig->sections[idx].key, "events")) {
            section = &(ctrlConfig->sections[idx]);
            break;
        }
    }
    if (section == nullptr) {
        sLogger->log(Level::ERROR, TAG, "AddVoiceAgentEvents: events section not found");
        return -1;
    }

    // Fill out events JSON array to pass to the controller
    json_object* eventsJ = json_object_new_array();

    json_object *eventJ = json_object_new_object();
    std::string uid = agentApi + "/voice_authstate_event";
    json_object *uidJ = json_object_new_string(uid.c_str());
    json_object_object_add(eventJ, "uid", uidJ);
    json_object *actionJ = json_object_new_string("plugin://vshl-core#onAuthStateEvent");
    json_object_object_add(eventJ, "action", actionJ);
    json_object_array_add(eventsJ, eventJ);

    eventJ = json_object_new_object();
    uid = agentApi + "/voice_connectionstate_event";
    uidJ = json_object_new_string(uid.c_str());
    json_object_object_add(eventJ, "uid", uidJ);
    actionJ = json_object_new_string("plugin://vshl-core#onConnectionStateEvent");
    json_object_object_add(eventJ, "action", actionJ);
    json_object_array_add(eventsJ, eventJ);

    eventJ = json_object_new_object();
    uid = agentApi + "/voice_dialogstate_event";
    uidJ = json_object_new_string(uid.c_str());
    json_object_object_add(eventJ, "uid", uidJ);
    actionJ = json_object_new_string("plugin://vshl-core#onDialogStateEvent");
    json_object_object_add(eventJ, "action", actionJ);
    json_object_array_add(eventsJ, eventJ);

    // Call into controller to add event actions
    // NOTE: AFAICT the JSON objects end up reused by the controller data
    //       structures, so eventsJ should not be freed with a put after
    //       this call.
    int rc = AddActionsToSection(sAfbApi->getApi(), section, eventsJ, 0);
    if (rc != 0) {
        stringstream message;
        message << "AddVoiceAgentEvents: AddActionsToSection rc = " << rc;
        sLogger->log(Level::WARNING, TAG, message.str().c_str());
    }
    return rc;
}

// Helper function to parse a voiceagent's configuration
static bool parseVoiceAgentConfig(const json &agentJson) {
    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()) {
	stringstream error;
	error << "loadVoiceAgentConfig: One or more missing params in agent "
	    "config "
	      << agentJson.dump();
	sLogger->log(Level::WARNING, TAG, error.str().c_str());
	return false;
    }

    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>());
    }

    if (sAfbApi->requireApi(api, true) != 0) {
	stringstream error;
	error << "loadVoiceAgentConfig: Failed to require API \""
	      << api
	      << "\" for voiceagent \""
	      << name
	      << "\".";
	sLogger->log(Level::ERROR, TAG, error.str().c_str());
	return false;
    }

    if (AddVoiceAgentEvents(api) != 0) {
	stringstream error;
	error << "loadVoiceAgentConfig: Failed to add events for voiceagent \""
	      << name
	      << "\".";
	sLogger->log(Level::ERROR, TAG, error.str().c_str());
	return false;
    }

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

// Helper function to parse voiceagents configuration
static void parseVoiceAgentsConfig(const json &agentsConfigJson, std::string &defaultVoiceAgent) {
    if (agentsConfigJson.find(VA_JSON_ATTR_AGENTS) != agentsConfigJson.end()) {
	json agentsJson = agentsConfigJson[VA_JSON_ATTR_AGENTS];
	for (auto agentIt = agentsJson.begin(); agentIt != agentsJson.end(); ++agentIt) {
	    json agentJson = *agentIt;
	    parseVoiceAgentConfig(agentJson);
	}
    }

    // Save the default agent if specified
    if (agentsConfigJson.find(VA_JSON_ATTR_DEFAULT) != agentsConfigJson.end()) {
	defaultVoiceAgent = agentsConfigJson[VA_JSON_ATTR_DEFAULT].get<string>();
    }
}

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

    string defaultVoiceAgent;

    if (argsJ != nullptr) {
	// Parse any agent configs from controller arguments
	json agentsConfigJson = json::parse(json_object_to_json_string(argsJ));
	parseVoiceAgentsConfig(agentsConfigJson, defaultVoiceAgent);
    }

    // Load external configuration if present
    filesystem::path agentsConfigFile = CONFIG_JSON_FILE;
    if(filesystem::exists(agentsConfigFile)) {
	stringstream message;
	message << "loadVoiceAgentsConfig: reading " << agentsConfigFile.string();;
	sLogger->log(Level::INFO, TAG, message.str().c_str());

	ifstream agentsConfigJsonFile(agentsConfigFile.string());
	json agentsConfigJson;
	agentsConfigJsonFile >> agentsConfigJson;
	parseVoiceAgentsConfig(agentsConfigJson, defaultVoiceAgent);
    }

    // Load any external individual voiceagent configurations
    error_code ec;
    for (const auto& entry : filesystem::directory_iterator(VA_CONFIG_JSON_DIR, ec)) {
	if (entry.is_regular_file(ec) &&
	    entry.path().extension().string() == JSON_EXT) {
	    const auto filename = entry.path().string();

	    stringstream message;
	    message << "loadVoiceAgentsConfig: reading " << filename;
	    sLogger->log(Level::INFO, TAG, message.str().c_str());

	    ifstream jsonFile(filename);
	    json agentJson;
	    jsonFile >> agentJson;
	    parseVoiceAgentConfig(agentJson);
	}
    }

    // Set the default agent if specified
    if (!defaultVoiceAgent.empty()) {
	stringstream message;
	message << "loadVoiceAgentsConfig: setting default agent to " << defaultVoiceAgent;
	sLogger->log(Level::INFO, TAG, message.str().c_str());

	sVoiceAgentsDataManager->setDefaultVoiceAgent(defaultVoiceAgent);
    } else {
        sLogger->log(Level::WARNING, TAG, "loadVoiceAgentsConfig: No default agent found");
    }

    // Always return zero so service will start
    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_req_success(source->request, json_tokener_parse(responseJson.dump().c_str()), NULL);
    } else {
        afb_req_fail(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_req_success(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_req_success(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_req_success(source->request, NULL, NULL);
    return 0;
}