// Copyright (C) 2013-2023 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <hooks/callout_handle.h>
#include <hooks/callout_manager.h>
#include <hooks/library_handle.h>
#include <hooks/library_manager_collection.h>
#include <hooks/hooks_manager.h>
#include <hooks/server_hooks.h>

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

#include <string>
#include <vector>

using namespace std;

namespace isc {
namespace hooks {

// Constructor

HooksManager::HooksManager() : test_mode_(false) {
    // Nothing present, so create the collection with any empty set of
    // libraries, and get the CalloutManager.
    HookLibsCollection libraries;
    lm_collection_.reset(new LibraryManagerCollection(libraries));
    lm_collection_->loadLibraries(false);
    callout_manager_ = lm_collection_->getCalloutManager();
}

// Return reference to singleton hooks manager.

HooksManager&
HooksManager::getHooksManager() {
    static HooksManager manager;
    return (manager);
}

// Are callouts present?

bool
HooksManager::calloutsPresentInternal(int index) {
    return (callout_manager_->calloutsPresent(index));
}

bool
HooksManager::calloutsPresent(int index) {
    return (getHooksManager().calloutsPresentInternal(index));
}

bool
HooksManager::commandHandlersPresentInternal(const std::string& command_name) {
    return (callout_manager_->commandHandlersPresent(command_name));
}

bool
HooksManager::commandHandlersPresent(const std::string& command_name) {
    return (getHooksManager().commandHandlersPresentInternal(command_name));
}

// Call the callouts

void
HooksManager::callCalloutsInternal(int index, CalloutHandle& handle) {
    callout_manager_->callCallouts(index, handle);
}

void
HooksManager::callCallouts(int index, CalloutHandle& handle) {
    getHooksManager().callCalloutsInternal(index, handle);
}

void
HooksManager::callCommandHandlersInternal(const std::string& command_name,
                                          CalloutHandle& handle) {
    callout_manager_->callCommandHandlers(command_name, handle);
}

void
HooksManager::callCommandHandlers(const std::string& command_name,
                                  CalloutHandle& handle) {
    getHooksManager().callCommandHandlersInternal(command_name, handle);
}

// Load the libraries.  This will delete the previously-loaded libraries
// (if present) and load new ones. If loading libraries fails, initialize with
// empty list.

bool
HooksManager::loadLibrariesInternal(const HookLibsCollection& libraries,
                                    bool multi_threading_enabled) {
    if (test_mode_) {
        return (true);
    }

    ServerHooks::getServerHooks().getParkingLotsPtr()->clear();

    // Keep a weak pointer on the existing library manager collection.
    boost::weak_ptr<LibraryManagerCollection> weak_lmc(lm_collection_);

    // Create the library manager collection.
    lm_collection_.reset(new LibraryManagerCollection(libraries));

    // If there was another owner the previous library manager collection
    // was not destroyed and libraries not closed.
    if (!weak_lmc.expired()) {
        isc_throw(LibrariesStillOpened, "some libraries are still opened");
    }

    // Load the libraries.
    bool status = lm_collection_->loadLibraries(multi_threading_enabled);

    if (status) {
        // ... and obtain the callout manager for them if successful.
        callout_manager_ = lm_collection_->getCalloutManager();
    } else {
        // Unable to load libraries, reset to state before this function was
        // called.
        static_cast<void>(unloadLibrariesInternal());
    }

    return (status);
}

bool
HooksManager::loadLibraries(const HookLibsCollection& libraries,
                            bool multi_threading_enabled) {
    return (getHooksManager().loadLibrariesInternal(libraries,
                                                    multi_threading_enabled));
}

// Unload the libraries.  This just deletes all internal objects (which will
// cause the libraries to be unloaded) and initializes them with empty list if
// requested.

bool
HooksManager::unloadLibrariesInternal() {
    if (test_mode_) {
        return (true);
    }

    ServerHooks::getServerHooks().getParkingLotsPtr()->clear();

    // Keep a weak pointer on the existing library manager collection.
    boost::weak_ptr<LibraryManagerCollection> weak_lmc(lm_collection_);

    // Create the collection with any empty set of libraries.
    HookLibsCollection libraries;
    lm_collection_.reset(new LibraryManagerCollection(libraries));

    // If there was another owner the previous library manager collection
    // was not destroyed and libraries not closed.
    boost::shared_ptr<LibraryManagerCollection> still_here = weak_lmc.lock();
    if (still_here) {
        // Restore the library manager collection.
        lm_collection_ = still_here;
        return (false);
    }

    // Load the empty set of libraries.
    lm_collection_->loadLibraries(false);

    // Get the CalloutManager.
    callout_manager_ = lm_collection_->getCalloutManager();

    return (true);
}

bool
HooksManager::unloadLibraries() {
    return (getHooksManager().unloadLibrariesInternal());
}

void
HooksManager::prepareUnloadLibrariesInternal() {
    if (test_mode_) {
        return;
    }

    static_cast<void>(lm_collection_->prepareUnloadLibraries());
}

void
HooksManager::prepareUnloadLibraries() {
    getHooksManager().prepareUnloadLibrariesInternal();
}

// Create a callout handle

boost::shared_ptr<CalloutHandle>
HooksManager::createCalloutHandleInternal() {
    return (boost::make_shared<CalloutHandle>(callout_manager_, lm_collection_));
}

boost::shared_ptr<CalloutHandle>
HooksManager::createCalloutHandle() {
    return (getHooksManager().createCalloutHandleInternal());
}

// Get the list of the names of loaded libraries.

std::vector<std::string>
HooksManager::getLibraryNamesInternal() const {
    return (lm_collection_->getLibraryNames());
}

HookLibsCollection
HooksManager::getLibraryInfoInternal() const {
    return (lm_collection_->getLibraryInfo());
}

std::vector<std::string>
HooksManager::getLibraryNames() {
    return (getHooksManager().getLibraryNamesInternal());
}

HookLibsCollection
HooksManager::getLibraryInfo() {
    return (getHooksManager().getLibraryInfoInternal());
}

// Shell around ServerHooks::registerHook()

int
HooksManager::registerHook(const std::string& name) {
    return (ServerHooks::getServerHooks().registerHook(name));
}

// Return pre- and post- library handles.

isc::hooks::LibraryHandle&
HooksManager::preCalloutsLibraryHandleInternal() {
    return (callout_manager_->getPreLibraryHandle());
}

isc::hooks::LibraryHandle&
HooksManager::preCalloutsLibraryHandle() {
    return (getHooksManager().preCalloutsLibraryHandleInternal());
}

isc::hooks::LibraryHandle&
HooksManager::postCalloutsLibraryHandleInternal() {
    return (callout_manager_->getPostLibraryHandle());
}

isc::hooks::LibraryHandle&
HooksManager::postCalloutsLibraryHandle() {
    return (getHooksManager().postCalloutsLibraryHandleInternal());
}

// Validate libraries

std::vector<std::string>
HooksManager::validateLibraries(const std::vector<std::string>& libraries,
                                bool multi_threading_enabled) {
    return (LibraryManagerCollection::validateLibraries(libraries,
                                                        multi_threading_enabled));
}

// Test mode

void
HooksManager::setTestMode(bool mode) {
    getHooksManager().test_mode_ = mode;
}

bool
HooksManager::getTestMode() {
    return (getHooksManager().test_mode_);
}

} // namespace util
} // namespace isc
