/*
 *
 * $Id: sourceheader,v 1.3 2005/01/19 13:03:46 trueg Exp $
 * Copyright (C) 2005-2007 Sebastian Trueg <trueg@k3b.org>
 * Copyright (C) 2013 Timothy Pearson <kb9vqf@pearsoncomputing.net>
 *
 * This file is part of the K3b project.
 * Copyright (C) 1998-2007 Sebastian Trueg <trueg@k3b.org>
 * Copyright (C) 2013 Timothy Pearson <kb9vqf@pearsoncomputing.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * See the file "COPYING" for the exact licensing terms.
 */

#include "k3bconnection.h"
#include "k3bdevice.h"

#include <k3bdebug.h>
#include <tdelocale.h>

#include <tqtimer.h>
#include <tqvariant.h>

#ifdef HAVE_TDEHWLIB
#include <tdehardwaredevices.h>
#else
#define TDEHardwareDevices void
#endif

K3bDevice::Connection* K3bDevice::Connection::s_instance = 0;


class K3bDevice::Connection::Private
{
	public:
		Private()
		: bOpen(false),
		  m_hwdevices(NULL) {
			//
		}
		
		bool bOpen;
		TDEHardwareDevices *m_hwdevices;

		TQMap<TQString, TQString> udiDeviceMap;
		TQMap<TQString, TQString> deviceUdiMap;

		TQMap<TQString, bool> deviceMediumUdiMap;
};


K3bDevice::Connection* K3bDevice::Connection::instance()
{
	if (s_instance == 0) {
		s_instance = new Connection(0);
	}

	if ((!s_instance->isConnected()) && (!s_instance->open())) {
		k3bDebug() << "(K3bDevice::Connection) failed to initialize the TDE hardware backend." << endl;
	}

	return s_instance;
}


K3bDevice::Connection::Connection( TQObject* parent, const char* name )
  : TQObject( parent, name )
{
	d = new Private();
}


K3bDevice::Connection::~Connection()
{
	s_instance = 0;
	close();
	delete d;
}


bool K3bDevice::Connection::isConnected() const
{
	return d->bOpen;
}


bool K3bDevice::Connection::open()
{
#ifdef HAVE_TDEHWLIB
	// Initialize the TDE device manager
	d->m_hwdevices = TDEGlobal::hardwareDevices();

	// Connect device monitoring signals/slots
	connect(d->m_hwdevices, TQ_SIGNAL(hardwareAdded(TDEGenericDevice*)), this, TQ_SLOT(AddDeviceHandler(TDEGenericDevice*)));
	connect(d->m_hwdevices, TQ_SIGNAL(hardwareRemoved(TDEGenericDevice*)), this, TQ_SLOT(RemoveDeviceHandler(TDEGenericDevice*)));
	connect(d->m_hwdevices, TQ_SIGNAL(hardwareUpdated(TDEGenericDevice*)), this, TQ_SLOT(ModifyDeviceHandler(TDEGenericDevice*)));

	d->bOpen = true;
	
	//
	// Report all devices
	//
	TDEGenericHardwareList hwlist = d->m_hwdevices->listAllPhysicalDevices();
	TDEGenericDevice *hwdevice;
	for (hwdevice = hwlist.first(); hwdevice; hwdevice = hwlist.next()) {
		AddDeviceHandler(hwdevice);
	}
	
	return true;
#else
	return false;
#endif
}


void K3bDevice::Connection::close()
{
	d->bOpen = false;
}


TQStringList K3bDevice::Connection::devices() const
{
	return TQStringList(d->udiDeviceMap.values());
}

void K3bDevice::Connection::AddDeviceHandler(TDEGenericDevice* hwdevice)
{
#ifdef HAVE_TDEHWLIB
	if (hwdevice->type() != TDEGenericDeviceType::Disk) {
		return;
	}
	TQString udi = hwdevice->uniqueID();
	TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice);

	if (sdevice->diskType() & TDEDiskDeviceType::Optical) {
		TQString blockDevice = sdevice->deviceNode();
		if (!blockDevice.isEmpty()) {
			k3bDebug() << "Mapping udi " << udi << " to device " << blockDevice << endl;
			d->udiDeviceMap[udi] = blockDevice;
			d->deviceUdiMap[blockDevice] = udi;
			emit deviceAdded(blockDevice);
			// Check for medium
			if (sdevice->mediaInserted()) {
				d->deviceMediumUdiMap[blockDevice] = true;
				emit mediumChanged(blockDevice);
			}
		}
	}
#endif
}

void K3bDevice::Connection::RemoveDeviceHandler(TDEGenericDevice* hwdevice)
{
#ifdef HAVE_TDEHWLIB
	if (hwdevice->type() != TDEGenericDeviceType::Disk) {
		return;
	}
	TQString udi = hwdevice->uniqueID();
	TQString blockDevice = hwdevice->deviceNode();

	TQMapIterator<TQString, TQString> it = d->udiDeviceMap.find(udi);
	if (it != d->udiDeviceMap.end()) {
		k3bDebug() << "Unmapping udi " << udi << " from device " << it.data() << endl;
		emit deviceRemoved(it.data());
		d->udiDeviceMap.erase(it);
		d->deviceUdiMap.erase(it.data());

		if (d->deviceMediumUdiMap[blockDevice]) {
			d->deviceMediumUdiMap[blockDevice] = false;
			emit mediumChanged(blockDevice);
		}
	}
#endif
}

void K3bDevice::Connection::ModifyDeviceHandler(TDEGenericDevice* hwdevice)
{
#ifdef HAVE_TDEHWLIB
	if (hwdevice->type() != TDEGenericDeviceType::Disk) {
		return;
	}
	TQString udi = hwdevice->uniqueID();
	TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice);
	TQString blockDevice = hwdevice->deviceNode();

	if (d->deviceMediumUdiMap[blockDevice] != sdevice->mediaInserted()) {
		d->deviceMediumUdiMap[blockDevice] = sdevice->mediaInserted();
		emit mediumChanged(blockDevice);
	}
#endif
}

int K3bDevice::Connection::lock(Device* dev)
{
#ifdef HAVE_TDEHWLIB
	if (!d->deviceUdiMap.contains(dev->blockDeviceName())) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TQString udi = d->deviceUdiMap[dev->blockDeviceName()];
	TDEGenericDevice *hwdevice = d->m_hwdevices->findByUniqueID(udi);
	if (!hwdevice) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}

	if (hwdevice->type() != TDEGenericDeviceType::Disk) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice);

	if (sdevice->lockDriveMedia(true)) {
		return ErrorCodes::Success;
	}
	else {
		return ErrorCodes::Device_Volume_InvalidEjectOption;
	}
#else
		return ErrorCodes::Device_Volume_NoSuchDevice;
#endif
}


int K3bDevice::Connection::unlock(Device* dev)
{
#ifdef HAVE_TDEHWLIB
	if (!d->deviceUdiMap.contains(dev->blockDeviceName())) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TQString udi = d->deviceUdiMap[dev->blockDeviceName()];
	TDEGenericDevice *hwdevice = d->m_hwdevices->findByUniqueID(udi);
	if (!hwdevice) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}

	if (hwdevice->type() != TDEGenericDeviceType::Disk) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice);

	if (sdevice->lockDriveMedia(false)) {
		return ErrorCodes::Success;
	}
	else {
		return ErrorCodes::Device_Volume_InvalidEjectOption;
	}
#else
		return ErrorCodes::Device_Volume_NoSuchDevice;
#endif
}


int K3bDevice::Connection::mount( K3bDevice::Device* dev,
				     const TQString& mountPoint,
				     const TQString& fstype,
				     const TQStringList& options )
{
#ifdef HAVE_TDEHWLIB
	if (!d->deviceUdiMap.contains(dev->blockDeviceName())) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TQString udi = d->deviceUdiMap[dev->blockDeviceName()];
	TDEGenericDevice *hwdevice = d->m_hwdevices->findByUniqueID(udi);
	if (!hwdevice) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}

	if (hwdevice->type() != TDEGenericDeviceType::Disk) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice);

	// FIXME
	// Options from 'options' are not currently loaded into 'mountOptions'
	TDEStorageMountOptions mountOptions;
	TQStringVariantMap mountResult = sdevice->mountDevice(mountPoint, mountOptions);
	TQString mountedPath = mountResult.contains("mountPath") ? mountResult["mountPath"].toString() : TQString::null;
	if (mountedPath.isEmpty()) {
		return ErrorCodes::CommunicationError;
	}
	else {
		return ErrorCodes::Success;
	}
#else
		return ErrorCodes::Device_Volume_NoSuchDevice;
#endif
}


int K3bDevice::Connection::unmount(K3bDevice::Device* dev, const TQStringList& options)
{
#ifdef HAVE_TDEHWLIB
	if (!d->deviceUdiMap.contains(dev->blockDeviceName())) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TQString udi = d->deviceUdiMap[dev->blockDeviceName()];
	TDEGenericDevice *hwdevice = d->m_hwdevices->findByUniqueID(udi);
	if (!hwdevice) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}

	if (hwdevice->type() != TDEGenericDeviceType::Disk) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice);

	// FIXME
	// Options from 'options' are not currently loaded into 'mountOptions'
	TQString mountOptions;

	TQStringVariantMap unmountResult = sdevice->unmountDevice();
	if (unmountResult["result"].toBool() == false) {
		// Unmount failed!
		return ErrorCodes::CommunicationError;
	}
	else {
		return ErrorCodes::Success;
	}
#else
		return ErrorCodes::Device_Volume_NoSuchDevice;
#endif
}


int K3bDevice::Connection::eject(K3bDevice::Device* dev, const TQStringList& options)
{
#ifdef HAVE_TDEHWLIB
	if (!d->deviceUdiMap.contains(dev->blockDeviceName())) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TQString udi = d->deviceUdiMap[dev->blockDeviceName()];
	TDEGenericDevice *hwdevice = d->m_hwdevices->findByUniqueID(udi);
	if (!hwdevice) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}

	if (hwdevice->type() != TDEGenericDeviceType::Disk) {
		return ErrorCodes::Device_Volume_NoSuchDevice;
	}
	TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice);

	if (sdevice->ejectDriveMedia()) {
		return ErrorCodes::Success;
	}
	else {
		return ErrorCodes::Device_Volume_InvalidEjectOption;
	}
#else
		return ErrorCodes::Device_Volume_NoSuchDevice;
#endif
}

#include "k3bconnection.moc"
