#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <sstream>
#include <memory>
#include <opts.h>
#include <asynchronousContinuation.h>
#include <serializedAsynchronousContinuation.h>
#include <callback.h>
#include <componentThread.h>
#include <deviceManager/deviceManager.h>
#include <deviceManager/deviceReattacher.h>
#include <stimBuffApis/stimBuffApiManager.h>
#include <marionette/marionette.h>
#include <mind.h>

namespace smo {
namespace device {

std::vector<std::shared_ptr<DeviceAttachmentSpec>>
    DeviceManager::deviceAttachmentSpecs;
std::vector<std::shared_ptr<Device>>
    DeviceManager::devices;
std::vector<std::shared_ptr<DeviceRole>>
    DeviceManager::attachedDeviceRoles;
std::vector<DeviceAttachmentSpec>
    DeviceManager::commandLineDASpecs;

DeviceManager::~DeviceManager()
{
}

const std::string DeviceManager::stringifyDeviceSpecs(void)
{
	std::ostringstream oss;

	for (const auto& spec : DeviceManager::deviceAttachmentSpecs) {
		oss << "Device Attachment Spec: " << spec->stringify();
	}

	return oss.str();
}

class DeviceManager::NewDeviceAttachmentSpecInd
:	public SerializedAsynchronousContinuation<newDeviceAttachmentSpecIndCbFn>
{
public:
	NewDeviceAttachmentSpecInd(
		const DeviceAttachmentSpec &spec,
		const std::shared_ptr<ComponentThread> &caller,
		Callback<newDeviceAttachmentSpecIndCbFn> cb,
		std::vector<std::reference_wrapper<Qutex>> requiredLocks)
	:	SerializedAsynchronousContinuation<newDeviceAttachmentSpecIndCbFn>(
			caller, cb, requiredLocks),
		spec(spec)
	{}

public:
	DeviceAttachmentSpec spec;
	std::shared_ptr<DeviceAttachmentSpec> specPtr;
	std::shared_ptr<Device> device;

public:
	void newDeviceAttachmentSpecInd1_posted(
		[[maybe_unused]] std::shared_ptr<NewDeviceAttachmentSpecInd> context
		)
	{
		// First, add the spec to deviceAttachmentSpecs if it's not already there
		bool specExists = false;
		for (const auto& existingSpec : DeviceManager::deviceAttachmentSpecs)
		{
			if (*existingSpec == spec)
			{
				specExists = true;
				specPtr = existingSpec;
				break;
			}
		}

		if (!specExists)
		{
			specPtr = std::make_shared<DeviceAttachmentSpec>(spec);
			DeviceManager::deviceAttachmentSpecs.push_back(specPtr);
		}

		bool deviceExists = false;
		for (const auto& existingDevice : DeviceManager::devices)
		{
			if (existingDevice->deviceIdentifier != spec.deviceIdentifier)
				{ continue; }

			device = existingDevice;
			deviceExists = true;
			break;
		}

		// If device doesn't exist, create a new one and add it
		if (!device)
		{
			device = std::make_shared<Device>(spec.deviceIdentifier);
			DeviceManager::devices.push_back(device);
		}

		// Check if a DeviceRole w/ this spec already exists in attachedDeviceRoles
		bool deviceRoleExists = false;
		std::shared_ptr<DeviceRole> existingDeviceRole = nullptr;
		for (const auto& role : DeviceManager::attachedDeviceRoles)
		{
			if (*role->deviceAttachmentSpec == spec)
			{
				deviceRoleExists = true;
				existingDeviceRole = role;
				break;
			}
		}

		// If DeviceRole exists, both spec and device must also exist
		if (deviceRoleExists)
		{
			if (!specExists || !deviceExists)
			{
				throw std::runtime_error(
					"Program error: DeviceRole exists but spec or device doesn't "
					"pre-exist. specExists=" + std::to_string(specExists) +
					", deviceExists=" + std::to_string(deviceExists));
			}

			// Already attached, callback with success and return
			callOriginalCb(true, existingDeviceRole, specPtr);
			return;
		}

		DeviceManager::getInstance().attachStimBuffDeviceReq(
			specPtr,
			{context, std::bind(
				&NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd2,
				context.get(), context,
				std::placeholders::_1, std::placeholders::_2)});
	}

	void newDeviceAttachmentSpecInd2(
		[[maybe_unused]] std::shared_ptr<NewDeviceAttachmentSpecInd> context,
		bool success,
		std::shared_ptr<DeviceAttachmentSpec> deviceSpec
		)
	{
		if (!success)
		{
			std::cerr << __func__ << ": Attach failed for device spec "
				<< deviceSpec->stringify() << std::endl;
			callOriginalCb(false, nullptr, deviceSpec);
			return;
		}

		try {
			// Create DeviceRole and add it to both DeviceManager's and Device's collections
			auto deviceRole = std::make_shared<DeviceRole>(*device, specPtr);
			device->deviceRoles.push_back(deviceRole);
			DeviceManager::attachedDeviceRoles.push_back(deviceRole);

			// Callback with success
			callOriginalCb(true, deviceRole, specPtr);
		} catch (const std::exception& e) {
			// Attach failed, callback with error
			callOriginalCb(false, nullptr, specPtr);
		}
	}
};

class DeviceManager::RemoveDeviceAttachmentSpecReq
:	public SerializedAsynchronousContinuation<removeDeviceAttachmentSpecReqCbFn>
{
public:
	RemoveDeviceAttachmentSpecReq(
		const DeviceAttachmentSpec &spec,
		const std::shared_ptr<ComponentThread> &caller,
		Callback<removeDeviceAttachmentSpecReqCbFn> cb,
		std::vector<std::reference_wrapper<Qutex>> requiredLocks)
	:	SerializedAsynchronousContinuation<removeDeviceAttachmentSpecReqCbFn>(
			caller, cb, requiredLocks),
		spec(spec)
	{}

public:
	DeviceAttachmentSpec spec;
	std::shared_ptr<DeviceAttachmentSpec> specPtr;

public:
	void removeDeviceAttachmentSpecReq1_posted(
		[[maybe_unused]] std::shared_ptr<RemoveDeviceAttachmentSpecReq> context
		)
	{
		// Find the shared_ptr to the spec in the collection
		for (const auto& existingSpec : DeviceManager::deviceAttachmentSpecs)
		{
			if (*existingSpec == spec)
			{
				specPtr = existingSpec;
				break;
			}
		}

		if (!specPtr)
		{
			// Spec not found, callback with failure and return
			callOriginalCb(false, nullptr);
			return;
		}

		// Call detachStimBuffDeviceReq first - only clean up metadata if this succeeds
		DeviceManager::getInstance().detachStimBuffDeviceReq(
			specPtr,
			{context, std::bind(
				&RemoveDeviceAttachmentSpecReq::removeDeviceAttachmentSpecReq2,
				context.get(), context,
				std::placeholders::_1, std::placeholders::_2)});
	}

	void removeDeviceAttachmentSpecReq2(
		[[maybe_unused]] std::shared_ptr<RemoveDeviceAttachmentSpecReq> context,
		bool success,
		std::shared_ptr<DeviceAttachmentSpec> deviceSpec
		)
	{
		if (!success)
		{
			// Detach failed, callback with failure (metadata remains intact)
			callOriginalCb(false, deviceSpec);
			return;
		}

		// Detach succeeded, now find and clean up metadata
		try {
			// Find the DeviceRole in attachedDeviceRoles
			auto deviceRoleIt = std::find_if(
				DeviceManager::attachedDeviceRoles.begin(),
				DeviceManager::attachedDeviceRoles.end(),
				[&specPtr = specPtr](const std::shared_ptr<DeviceRole> &role) {
					return *role->deviceAttachmentSpec == *specPtr;
				}
			);

			if (deviceRoleIt == DeviceManager::attachedDeviceRoles.end())
			{
				// DeviceRole not found, callback with failure
				callOriginalCb(false, deviceSpec);
				return;
			}

			auto deviceRole = *deviceRoleIt;
			auto& device = deviceRole->parentDevice;

			// Remove DeviceRole from DeviceManager's collection
			DeviceManager::attachedDeviceRoles.erase(deviceRoleIt);

			// Remove DeviceRole from Device's collection
			auto deviceRoleIt2 = std::find(
				device.deviceRoles.begin(),
				device.deviceRoles.end(),
				deviceRole);
			if (deviceRoleIt2 != device.deviceRoles.end())
			{
				device.deviceRoles.erase(deviceRoleIt2);
			}

			// Remove DeviceAttachmentSpec from deviceAttachmentSpecs collection
			auto specIt = std::find_if(
				DeviceManager::deviceAttachmentSpecs.begin(),
				DeviceManager::deviceAttachmentSpecs.end(),
				[&specPtr = specPtr](
					const std::shared_ptr<DeviceAttachmentSpec> &existingSpec)
				{
					return *existingSpec == *specPtr;
				}
			);

			if (specIt != DeviceManager::deviceAttachmentSpecs.end())
			{
				DeviceManager::deviceAttachmentSpecs.erase(specIt);
			}

			// Callback with success
			callOriginalCb(true, deviceSpec);
		} catch (const std::exception& e) {
			// Cleanup failed, callback with error
			callOriginalCb(false, deviceSpec);
		}
	}
};

void DeviceManager::newDeviceAttachmentSpecInd(
	const DeviceAttachmentSpec &spec,
	Callback<newDeviceAttachmentSpecIndCbFn> callback)
{
	const auto& caller = ComponentThread::getSelf();

	auto request = std::make_shared<NewDeviceAttachmentSpecInd>(
		spec, caller, callback,
		LockSet<newDeviceAttachmentSpecIndCbFn>::Set{
			std::ref(DeviceManager::getInstance().qutex)
		});

	NewDeviceAttachmentSpecInd::LockerAndInvoker lockvoker(
		*request, mrntt::mrntt.thread,
		std::bind(
			&NewDeviceAttachmentSpecInd::newDeviceAttachmentSpecInd1_posted,
			request.get(), request));
}

void DeviceManager::removeDeviceAttachmentSpecReq(
	const DeviceAttachmentSpec &spec,
	Callback<removeDeviceAttachmentSpecReqCbFn> callback)
{
	const auto& caller = ComponentThread::getSelf();

	auto request = std::make_shared<RemoveDeviceAttachmentSpecReq>(
		spec, caller, callback,
		LockSet<removeDeviceAttachmentSpecReqCbFn>::Set{
			std::ref(DeviceManager::getInstance().qutex)
		});

	RemoveDeviceAttachmentSpecReq::LockerAndInvoker lockvoker(
		*request, mrntt::mrntt.thread,
		std::bind(
			&RemoveDeviceAttachmentSpecReq
				::removeDeviceAttachmentSpecReq1_posted,
			request.get(), request));
}

class DeviceManager::AttachStimBuffDeviceReq
:	public SerializedAsynchronousContinuation<
		DeviceManager::attachStimBuffDeviceReqCbFn>
{
public:
	AttachStimBuffDeviceReq(
		const std::shared_ptr<DeviceAttachmentSpec>& spec,
		const std::shared_ptr<ComponentThread> &caller,
		Callback<DeviceManager::attachStimBuffDeviceReqCbFn> cb,
		std::shared_ptr<stim_buff::StimBuffApiLib> &stimBuffApiLib,
		std::vector<std::reference_wrapper<Qutex>> requiredLocks)
	:	SerializedAsynchronousContinuation<attachStimBuffDeviceReqCbFn>(
			caller, cb, requiredLocks),
		spec(spec), stimBuffApiLib(stimBuffApiLib)
	{}

public:
	void attachStimBuffDeviceReq1_posted(
		[[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context
		)
	{
		if (caller->id != ComponentThread::MRNTT)
		{
			std::cerr << std::string(__func__)
				<< ": executed on non-mrntt thread: "
				<< caller->name << std::endl;
			callOriginalCb(false, spec);
			return;
		}

		if (stimBuffApiLib->isBeingDestroyed.load())
		{
			std::cerr << std::string(__func__) + ": Library is being destroyed"
				<< " for API '" << spec->stimBuffApi << "'. Bailing out.\n";
			callOriginalCb(false, spec);
			return;
		}

		if (!stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.attachDeviceReq)
		{
			std::cerr << std::string(__func__) + ": attachDeviceReq() is NULL "
				"for library '" << stimBuffApiLib->libraryPath << "'"
				<< std::endl;
			callOriginalCb(false, spec);
			return;
		}

		releaseQutexEarly(stim_buff::StimBuffApiManager::getInstance().qutex);

		/**		EXPLANATION:
		 * We pass in either the body or world thread here, depending on whether
		 * the device is an introspector (idev) or extrospector (edev).
		 *
		 * Introspectors are attached to the body thread; extrospectors are
		 * attached to the world thread.
		 */
		std::shared_ptr<ComponentThread> threadForAttachment;
		if (spec->sensorType == 'e')
		{
			threadForAttachment = mind::globalMind->world.thread;
			std::cout << __func__ << ": Attaching edev "
				<< spec->deviceIdentifier << " to world thread" << "\n";
		}
		else
		{
			threadForAttachment = mind::globalMind->body.thread;
			std::cout << __func__ << ": Attaching non-edev "
				<< spec->deviceIdentifier << " to body thread" << "\n";
		}

		stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.attachDeviceReq(
			spec, threadForAttachment,
			{context, std::bind(
				&AttachStimBuffDeviceReq::attachStimBuffDeviceReq2,
				context.get(), context,
				std::placeholders::_1, std::placeholders::_2)});
	 }

	 void attachStimBuffDeviceReq2(
		[[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context,
		bool success,
		std::shared_ptr<DeviceAttachmentSpec> deviceSpec
		)
	{
		callOriginalCb(success, deviceSpec);
	}

	void detachStimBuffDeviceReq1_posted(
		[[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context
		)
	{
		if (caller->id != ComponentThread::MRNTT)
		{
			std::cerr << std::string(__func__)
				<< ": executed on non-mrntt thread: "
				<< caller->name << std::endl;
			callOriginalCb(false, spec);
			return;
		}

		if (stimBuffApiLib->isBeingDestroyed.load())
		{
			std::cerr << std::string(__func__) + ": Library is being destroyed"
				<< " for API '" << spec->stimBuffApi << "'. Bailing out.\n";
			callOriginalCb(false, spec);
			return;
		}

		if (!stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.detachDeviceReq)
		{
			std::cerr << std::string(__func__) + ": detachDeviceReq() is NULL "
				"for library '" << stimBuffApiLib->libraryPath << "'"
				<< std::endl;
			callOriginalCb(false, spec);
			return;
		}

		releaseQutexEarly(stim_buff::StimBuffApiManager::getInstance().qutex);

		stimBuffApiLib->stimBuffApiDesc.sal_mgmt_libOps.detachDeviceReq(
			spec,
			{context, std::bind(
				&AttachStimBuffDeviceReq::detachStimBuffDeviceReq2,
				context.get(), context,
				std::placeholders::_1, std::placeholders::_2)});
	}

	void detachStimBuffDeviceReq2(
		[[maybe_unused]] std::shared_ptr<AttachStimBuffDeviceReq> context,
		bool success,
		std::shared_ptr<DeviceAttachmentSpec> deviceSpec
		)
	{
		callOriginalCb(success, deviceSpec);
	}

public:
	std::shared_ptr<DeviceAttachmentSpec> spec;
	std::shared_ptr<stim_buff::StimBuffApiLib> stimBuffApiLib;
};

void DeviceManager::attachStimBuffDeviceReq(
    const std::shared_ptr<DeviceAttachmentSpec>& spec,
    Callback<attachStimBuffDeviceReqCbFn> cb
    )
{
	const auto& caller = ComponentThread::getSelf();

	// Get the stim buff API lib's qutex
	auto libOpt = stim_buff::StimBuffApiManager::getInstance()
		.getStimBuffApiLibByApiName(spec->stimBuffApi);

		if (!libOpt)
	{
		std::cerr << "attachStimBuffDeviceReq: No library found for API '"
			<< spec->stimBuffApi << "'" << std::endl;
		cb.callbackFn(false, spec);
		return;
	}

	auto& lib = *libOpt.value();

	auto request = std::make_shared<AttachStimBuffDeviceReq>(
		spec, caller, cb, libOpt.value(),
		LockSet<attachStimBuffDeviceReqCbFn>::Set{
			std::ref(stim_buff::StimBuffApiManager::getInstance().qutex),
			std::ref(lib.qutex)
		});

	AttachStimBuffDeviceReq::LockerAndInvoker lockvoker(
		*request, mrntt::mrntt.thread,
		std::bind(
			&AttachStimBuffDeviceReq::attachStimBuffDeviceReq1_posted,
			request.get(), request));
}

void DeviceManager::detachStimBuffDeviceReq(
    const std::shared_ptr<DeviceAttachmentSpec>& spec,
    Callback<detachStimBuffDeviceReqCbFn> cb
    )
{
	const auto& caller = ComponentThread::getSelf();

	// Get the stim buff API lib's qutex
	auto libOpt = stim_buff::StimBuffApiManager::getInstance()
		.getStimBuffApiLibByApiName(spec->stimBuffApi);

		if (!libOpt)
	{
		std::cerr << "detachStimBuffDeviceReq: No library found for API '"
			<< spec->stimBuffApi << "'" << std::endl;
		cb.callbackFn(false, spec);
		return;
	}

	auto& lib = *libOpt.value();

	auto request = std::make_shared<DetachStimBuffDeviceReq>(
		spec, caller, cb, libOpt.value(),
		LockSet<detachStimBuffDeviceReqCbFn>::Set{
			std::ref(stim_buff::StimBuffApiManager::getInstance().qutex),
			std::ref(lib.qutex)
		});

	DetachStimBuffDeviceReq::LockerAndInvoker lockvoker(
		*request, mrntt::mrntt.thread,
		std::bind(
			&DetachStimBuffDeviceReq::detachStimBuffDeviceReq1_posted,
			request.get(), request));
}

class DeviceManager::AttachAllUnattachedDevicesFromReq
:	public PostedAsynchronousContinuation<
		attachAllUnattachedDevicesFromReqCbFn>
{
public:
	AttachAllUnattachedDevicesFromReq(
		const unsigned int totalNSpecs,
		const std::shared_ptr<std::vector<DeviceAttachmentSpec>>& specs,
		const std::shared_ptr<ComponentThread>& caller,
		Callback<attachAllUnattachedDevicesFromReqCbFn> cb)
	:	PostedAsynchronousContinuation<attachAllUnattachedDevicesFromReqCbFn>(
			caller, cb),
	loop(totalNSpecs), specs(specs)
	{}

public:
	void attachAllUnattachedDevicesFromReq1_posted(
		[[maybe_unused]] std::shared_ptr<AttachAllUnattachedDevicesFromReq>
			context
		)
	{
		for (const auto& spec : *specs)
		{
			DeviceManager::getInstance().newDeviceAttachmentSpecInd(
				spec,
				{context, std::bind(
					&AttachAllUnattachedDevicesFromReq
						::attachAllUnattachedDevicesFromReq2,
					context.get(), context,
					std::placeholders::_1, std::placeholders::_2,
					std::placeholders::_3)});
		}
	}

	// Callback methods for the attachment sequence
	void attachAllUnattachedDevicesFromReq2(
		std::shared_ptr<AttachAllUnattachedDevicesFromReq> context,
		bool success, [[maybe_unused]] std::shared_ptr<DeviceRole> deviceRole,
		std::shared_ptr<DeviceAttachmentSpec> spec
		)
	{
		if (!success)
		{
			std::cerr << __func__ << ": Failed to attach device: "
				<< spec->deviceIdentifier << "\n";
			// Fallthrough.
		}

		if (!context->loop.incrementSuccessOrFailureAndTestForCompletionDueTo(
			success))
		{
			return;
		}

		if (OptionParser::getOptions().verbose)
		{
			std::cout << __func__ << ": " << context->loop.nSucceeded.load()
					<< " devices attached, "
					<< context->loop.nFailed.load() << " devices failed\n";
		}

		context->callOriginalCb(loop);
	}

public:
	AsynchronousLoop loop;
	std::shared_ptr<std::vector<DeviceAttachmentSpec>> specs;
};

void DeviceManager::attachAllUnattachedDevicesFromReq(
	const std::shared_ptr<std::vector<DeviceAttachmentSpec>> &specs,
	Callback<attachAllUnattachedDevicesFromReqCbFn> cb
	)
{
	if (specs->size() == 0)
	{
		AsynchronousLoop tmp(0);
		cb.callbackFn(tmp);
		return;
	}

	const auto& caller = ComponentThread::getSelf();
	auto request = std::make_shared<AttachAllUnattachedDevicesFromReq>(
		specs->size(), specs, caller, std::move(cb));

	mrntt::mrntt.thread->getIoService().post(
		std::bind(
			&AttachAllUnattachedDevicesFromReq
				::attachAllUnattachedDevicesFromReq1_posted,
			request.get(), request));
}

void DeviceManager::attachAllUnattachedDevicesFromCmdlineReq(
	Callback<attachAllUnattachedDevicesFromReqCbFn> cb
	)
{
	auto specs = std::make_shared<std::vector<DeviceAttachmentSpec>>(
		commandLineDASpecs);
	attachAllUnattachedDevicesFromReq(specs, std::move(cb));
}

class DeviceManager::AttachAllUnattachedDevicesFromKnownListReq
:	public SerializedAsynchronousContinuation<
		attachAllUnattachedDevicesFromReqCbFn>
{
public:
	AttachAllUnattachedDevicesFromKnownListReq(
		const std::shared_ptr<ComponentThread> &caller,
		Callback<attachAllUnattachedDevicesFromReqCbFn> cb,
		std::vector<std::reference_wrapper<Qutex>> requiredLocks)
	:	SerializedAsynchronousContinuation<
			attachAllUnattachedDevicesFromReqCbFn>(
			caller, cb, requiredLocks)
	{}

public:
	void attachAllUnattachedDevicesFromKnownListReq1_posted(
		[[maybe_unused]]
		std::shared_ptr<AttachAllUnattachedDevicesFromKnownListReq> context
		)
	{
		// Create a vector to hold unattached device specs
		auto unattachedSpecs = std::make_shared<
			std::vector<DeviceAttachmentSpec>>();

		// Cycle through all DA specs in deviceAttachmentSpecs
		for (const auto& spec : DeviceManager::deviceAttachmentSpecs)
		{
			bool isAttached = false;

			// Cross reference with attachedDeviceRoles
			for (const auto& role : DeviceManager::attachedDeviceRoles)
			{
				if (*role->deviceAttachmentSpec == *spec)
				{
					isAttached = true;
					break;
				}
			}

			// If spec doesn't appear in attachedDeviceRoles, add it to vector
			if (!isAttached) {
				unattachedSpecs->push_back(*spec);
			}
		}

		// Release the DeviceManager qutex early before calling the inner method
		releaseQutexEarly(DeviceManager::getInstance().qutex);

		// Pass the vector to the existing function
		DeviceManager::getInstance().attachAllUnattachedDevicesFromReq(
			unattachedSpecs,
			{context, std::bind(
				&AttachAllUnattachedDevicesFromKnownListReq
					::attachAllUnattachedDevicesFromKnownListReq2,
				context.get(), context,
				std::placeholders::_1)});
	}

	void attachAllUnattachedDevicesFromKnownListReq2(
		[[maybe_unused]]
		std::shared_ptr<AttachAllUnattachedDevicesFromKnownListReq> context,
		AsynchronousLoop loop
		)
	{
		callOriginalCb(loop);
	}
};

void DeviceManager::attachAllUnattachedDevicesFromKnownListReq(
	Callback<attachAllUnattachedDevicesFromReqCbFn> cb
	)
{
	const auto& caller = ComponentThread::getSelf();

	auto request = std::make_shared<AttachAllUnattachedDevicesFromKnownListReq>(
		caller, cb,
		LockSet<attachAllUnattachedDevicesFromReqCbFn>::Set{
			std::ref(DeviceManager::getInstance().qutex)
		});

	AttachAllUnattachedDevicesFromKnownListReq::LockerAndInvoker lockvoker(
		*request, mrntt::mrntt.thread,
		std::bind(
			&AttachAllUnattachedDevicesFromKnownListReq
				::attachAllUnattachedDevicesFromKnownListReq1_posted,
			request.get(), request));
}

class DeviceManager::DetachAllAttachedDeviceRoles
:	public PostedAsynchronousContinuation<
		detachAllAttachedDeviceRolesCbFn>
{
public:
	DetachAllAttachedDeviceRoles(
		const unsigned int totalNSpecs,
		const std::shared_ptr<ComponentThread>& caller,
		Callback<detachAllAttachedDeviceRolesCbFn> cb)
	:	PostedAsynchronousContinuation<detachAllAttachedDeviceRolesCbFn>(
			caller, cb),
	loop(totalNSpecs)
	{}

	void detachAllAttachedDeviceRoles1_posted(
		[[maybe_unused]] std::shared_ptr<DetachAllAttachedDeviceRoles> context
		)
	{
		for (const auto& deviceRole : DeviceManager::attachedDeviceRoles)
		{
			DeviceManager::getInstance().detachStimBuffDeviceReq(
				deviceRole->deviceAttachmentSpec,
				{context, std::bind(
					&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles2,
					context.get(), context,
					std::placeholders::_1, std::placeholders::_2)});
		}
	}

	void detachAllAttachedDeviceRoles2(
		std::shared_ptr<DetachAllAttachedDeviceRoles> context,
		bool success, std::shared_ptr<DeviceAttachmentSpec> spec
		)
	{
		if (!success)
		{
			std::cerr << __func__ << ": Failed to detach device: "
				<< spec->deviceIdentifier << "\n";
			// Fallthrough.
		}

		if (!context->loop.incrementSuccessOrFailureAndTestForCompletionDueTo(
			success))
		{
			return;
		}

		if (OptionParser::getOptions().verbose)
		{
			std::cout << __func__ << ": " << context->loop.nSucceeded.load()
				<< " devices detached, "
				<< context->loop.nFailed.load() << " devices failed\n";
		}

		context->callOriginalCb(loop);
	}

public:
	AsynchronousLoop loop;
};

void DeviceManager::detachAllAttachedDeviceRoles(
	Callback<detachAllAttachedDeviceRolesCbFn> cb
	)
{
	if (DeviceManager::getInstance().attachedDeviceRoles.size() == 0)
	{
		AsynchronousLoop tmp(0);
		cb.callbackFn(tmp);
		return;
	}

	const auto& caller = ComponentThread::getSelf();
	auto request = std::make_shared<DetachAllAttachedDeviceRoles>(
		DeviceManager::getInstance().attachedDeviceRoles.size(),
		caller, std::move(cb));

	mrntt::mrntt.thread->getIoService().post(
		std::bind(
			&DetachAllAttachedDeviceRoles::detachAllAttachedDeviceRoles1_posted,
			request.get(), request));
}

void DeviceManager::initializeDeviceReattacher()
{
	deviceReattacher = std::make_unique<DeviceReattacher>(
		*this, mrntt::mrntt.thread);

	deviceReattacher->start();
}

void DeviceManager::finalizeDeviceReattacher()
{
	if (!deviceReattacher) { return; }

	deviceReattacher->stop();
	deviceReattacher.reset();
}

} // namespace device
} // namespace smo
