/***************************************************************************
 *
 * knetworkmanager-storage.cpp - A NetworkManager frontend for KDE
 *
 * Copyright (C) 2005, 2006 Novell, Inc.
 *
 * Author: Helmut Schaa <hschaa@suse.de>, <Helmut.Schaa@gmx.de>
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 **************************************************************************/

#include <qtimer.h>

#include <kglobal.h>
#include <kconfig.h>
#include <kstaticdeleter.h>
#include <kdebug.h>

#include "knetworkmanager-storage.h"
#include "knetworkmanager-connection_store.h"
#include "knetworkmanager-wireless_connection.h"
#include "knetworkmanager-wired_connection.h"
#include "knetworkmanager-cdma_connection.h"
#include "knetworkmanager-gsm_connection.h"
#include "knetworkmanager-vpn_connection.h"
#include "knetworkmanager-connection.h"
#include "knetworkmanager-connection_setting.h"
#include "xmlmarshaller.h"
#include "knetworkmanager-connection_setting_info.h"
#include "knetworkmanager-connection_setting_wired.h"
#include "knetworkmanager-connection_setting_wireless.h"
#include "knetworkmanager-connection_setting_wireless_security.h"
#include "knetworkmanager-connection_setting_ipv4.h"

#include <nm-setting-connection.h>
#include <nm-setting-cdma.h>
#include <nm-setting-gsm.h>
#include <nm-setting-wired.h>
#include <nm-setting-wireless.h>
#include <nm-setting-wireless-security.h>
#include <nm-setting-ip4-config.h>

using namespace ConnectionSettings;

static KStaticDeleter<Storage> sd;

// private stuff
class StoragePrivate
{
	public:
		StoragePrivate() {};
		~StoragePrivate() {};

		static Storage* _instance;
};

Storage* StoragePrivate::_instance = NULL;

Storage*
Storage::getInstance()
{
	if (StoragePrivate::_instance)
		return StoragePrivate::_instance;
	return sd.setObject(StoragePrivate::_instance, new Storage());
}

Storage::Storage()
{
	d = new StoragePrivate();

	// defer the connection init a bit
	QTimer::singleShot(0, this, SLOT(slotInit()));
}

Storage::~Storage()
{
	delete d;
}

void
Storage::slotInit()
{
	ConnectionStore* cstore = ConnectionStore::getInstance();

	// we want to get notified whenever a new connection is created, edited or deleted
	connect(cstore, SIGNAL(signalConnectionAdded(ConnectionSettings::Connection*)), this, SLOT(slotConnectionAdded(ConnectionSettings::Connection*)));
	connect(cstore, SIGNAL(signalConnectionRemoved(ConnectionSettings::Connection*)), this, SLOT(slotConnectionRemoved(ConnectionSettings::Connection*)));
}

void
Storage::slotConnectionAdded(Connection* con)
{
	// connection added, save it
	saveConnection(con);
	KGlobal::config()->sync();
}

void
Storage::slotConnectionRemoved(Connection* con)
{
	// find the appropriate connection and delete it from the storage
	deleteConnection(con);
	KGlobal::config()->sync();
}

void
Storage::slotSave()
{
	// let's save everything in the config file

} 

void
Storage::slotRestore()
{
	// read everything from the config file

}

Connection*
Storage::createConnectionByType(const QString& cType)
{
	// TODO: use a factory class here
	if (cType == NM_SETTING_WIRELESS_SETTING_NAME)
		return new WirelessConnection();
	else if (cType == NM_SETTING_WIRED_SETTING_NAME)
		return new WiredConnection();
	else if(cType == NM_SETTING_CDMA_SETTING_NAME)
		return new CDMAConnection();
	else if(cType == NM_SETTING_GSM_SETTING_NAME)
		return new GSMConnection();
	else if (cType == NM_SETTING_VPN_SETTING_NAME)
		return new VPNConnection();
	else
		return NULL;
}

void
Storage::restoreConnections()
{
	kdDebug() << k_funcinfo << endl;
	// let's read all connections from the config-file and add them to the connection-store
	ConnectionStore* store = ConnectionStore::getInstance();
	QStringList groups = KGlobal::config()->groupList();
	const QStringList::Iterator end = groups.end();
	for ( QStringList::Iterator it = groups.begin(); it != end; ++it )
	{
		if ( !(*it).startsWith( "Connection_" ) )
			continue;

		// restore that connection
		Connection* conn = NULL;
		if ( (conn = restoreConnection(*it)) != NULL)
		{
			// add the connection to the store
			store->addConnection(conn);
		}
	}

}

Connection*
Storage::restoreConnection(const QString& grpname)
{
	Connection* conn = NULL;
	kdDebug() << k_funcinfo << " " << grpname << endl;

	// we have a connection to restore
	KConfigGroup grp( KGlobal::config(), grpname);
	QString id = grp.readEntry("Id");
	QString cType = grp.readEntry("Type");

	// ID is needed!
	if (id.isEmpty() || cType.isEmpty())
		return NULL;

	// create a new connection object by its type
	conn = createConnectionByType(cType);

	// check if the connection was successfully created
	if (!conn)
		return NULL;

	// set the connection ID
	conn->setID(id);

	// restore all appropriate settings
	QStringList settings = grp.readListEntry("Settings");

	for (QStringList::ConstIterator it = settings.begin(); it != settings.end(); ++it)
	{
		if ( !restoreSetting(conn, *it) )
		{
			// setting could not be restored -> Error
			kdDebug() << "  Connection " << id.ascii() << " could not be restored." << endl;
			kdError() << k_funcinfo << " Connection " << id << " could not be restored." << endl;
			delete conn;
			conn = NULL;
			return NULL;
		}
	}

	// restore all appropriate secrets
	QStringList secrets = grp.readListEntry("Secrets");

	for (QStringList::ConstIterator it = secrets.begin(); it != secrets.end(); ++it)
	{
		if ( !restoreSecrets(conn, *it) )
		{
			// setting could not be restored -> Error
			kdDebug() << "  Connection " << id.ascii() << " could not be restored." << endl;
			kdError() << k_funcinfo << " Connection " << id << " could not be restored." << endl;
			delete conn;
			conn = NULL;
			return NULL;
		}
	}

	return conn;
}

bool
Storage::restoreSetting(Connection* conn, const QString& setting_grp_name)
{
	kdDebug() << k_funcinfo << " " << setting_grp_name << endl;
	kdDebug() << "restore setting: " << setting_grp_name.ascii() << endl;

	KConfigGroup setting_grp(KGlobal::config(), setting_grp_name);
	QMap<QString, QString> config_map = KGlobal::config()->entryMap(setting_grp_name);
	QString type = setting_grp.readEntry("Type");
		
	// get the appropriate setting from the connection
	ConnectionSetting* setting = conn->getSetting(type);
	if (!setting)
	{
		kdWarning() << k_funcinfo << "Connection " << conn->getID() << ": Setting " << type << " could not be restored" << endl;
		return false;
	}

	// read the SettingsMap from kconfig
	SettingsMap map;
	for(QMap<QString, QString>::ConstIterator it = config_map.begin(); it != config_map.end(); ++it)
	{
		if (!it.key().startsWith("Value_"))
			continue;
			
		QString key = it.key();
		// get the original name
		key.replace("Value_", "");

		QString xmldata = it.data();
		QDBusData dbusdata = XMLMarshaller::toQDBusData(xmldata);

		map.insert(key, dbusdata);
	}

	// restore the setting from the generated map
	setting->fromMap(map);
	return true;
}

bool
Storage::restoreSecrets(Connection* conn, const QString& secrets_grp_name)
{
	kdDebug() << k_funcinfo << " " << secrets_grp_name << endl;
	kdDebug() << "restore secret: " << secrets_grp_name.ascii() << endl;

	KConfigGroup secrets_grp(KGlobal::config(), secrets_grp_name);
	QMap<QString, QString> config_map = KGlobal::config()->entryMap(secrets_grp_name);
	QString type = secrets_grp.readEntry("Type");
		
	// get the appropriate setting from the connection
	ConnectionSetting* setting = conn->getSetting(type);
	if (!setting)
	{
		kdWarning() << k_funcinfo << "Connection " << conn->getID() << ": Secrets for setting " << type << " could not be restored" << endl;
		return false;
	}

	// read the SettingsMap from kconfig
	SettingsMap map;
	for(QMap<QString, QString>::ConstIterator it = config_map.begin(); it != config_map.end(); ++it)
	{
		if (!it.key().startsWith("Value_"))
			continue;
			
		QString key = it.key();
		// get the original name
		key.replace("Value_", "");

		QString xmldata = it.data();
		QDBusData dbusdata = XMLMarshaller::toQDBusData(xmldata);

		map.insert(key, dbusdata);
	}

	// restore the setting from the generated map
	setting->fromSecretsMap(map);
	return true;
}


void
Storage::saveConnections()
{
	kdDebug() << k_funcinfo << endl;
	kdDebug() << "Storage::saveConnections" << endl;
	// write all connections we get from the connection-store to disk
	ConnectionStore* store = ConnectionStore::getInstance();
	QValueList<ConnectionSettings::Connection*> connections = store->getConnections();

	for (QValueList<ConnectionSettings::Connection*>::ConstIterator it = connections.begin(); it != connections.end(); ++it)
	{
		// save this connection
		saveConnection(*it);
	}
	KGlobal::config()->sync();
}

bool
Storage::saveConnection(Connection* conn)
{
	KConfig* config = KGlobal::config();
	QString id = conn->getID();
	QString cType = conn->getType();

	kdDebug() << k_funcinfo << " <" << id << ">" << endl;
	kdDebug() << "Storage::saveConnection " << id.ascii() << endl;

	// connections without id are evil
	if (id.isEmpty() || cType.isEmpty())
		return false;

	// let's get the config group for this connection
	KConfigGroup grp(config, QString("Connection_%1").arg(id));
	QStringList settings_grps;
	QStringList secrets_grps;

	// save the connections settings to the configfile
	if (saveConnectionSettings(conn, settings_grps, secrets_grps))
	{
		grp.writeEntry("Type", cType);
		grp.writeEntry("Id", id);
		// save the list of settings groups
		grp.writeEntry("Settings", settings_grps);
		grp.writeEntry("Secrets", secrets_grps);
	}
	return false;
}

bool
Storage::saveConnectionSettings(Connection* conn, QStringList& settings_grps, QStringList& secrets_grps)
{
	QString id = conn->getID();

	// connections without id are evil
	if (id.isEmpty())
		return false;

	// iterate over all settings
	QValueList<ConnectionSetting*> settings = conn->getSettings();
	QString setting_grp;
	QString secrets_grp;

	// save all settings
	for (QValueList<ConnectionSetting*>::ConstIterator it = settings.begin(); it != settings.end(); ++it)
	{
		if (!saveConnectionSetting(conn, *it, setting_grp))
			return false;

		if ((*it)->hasSecrets())
		{
			if (!saveConnectionSecrets(conn, *it, secrets_grp))
				return false;
			secrets_grps.append(secrets_grp);
		}

		settings_grps.append(setting_grp);
	}

	return true;
}

bool
Storage::saveConnectionSetting(Connection* conn, ConnectionSetting* setting, QString& setting_grp)
{
	KConfig* config = KGlobal::config();
	QString id = conn->getID();
	QString type = setting->getType();

	kdDebug() << k_funcinfo << " <" << id << "> <" << type << ">" << endl;

	// ID is necessary
	if (id.isEmpty())
		return false;

	// get a group for this setting
	setting_grp = QString("ConnectionSetting_%1_%2").arg(id).arg(type);
	KConfigGroup grp(config, setting_grp);

	// write the type
	grp.writeEntry("Type", type);

	// write the values
	SettingsMap map = setting->toMap();
	for (SettingsMap::ConstIterator it = map.begin(); it != map.end(); ++it)
	{
		kdDebug() << k_funcinfo << "  " << QString("Value_%1").arg(it.key()) << " = " << XMLMarshaller::fromQDBusData( it.data() )<< endl;
		grp.writeEntry(QString("Value_%1").arg(it.key()), XMLMarshaller::fromQDBusData( it.data() ));
	}
	return true;
}

bool
Storage::saveConnectionSecrets(Connection* conn, ConnectionSetting* setting, QString& setting_grp)
{
	KConfig* config = KGlobal::config();
	QString id = conn->getID();
	QString type = setting->getType();

	kdDebug() << k_funcinfo << " <" << id << "> <" << type << ">" << endl;

	// ID is necessary
	if (id.isEmpty())
		return false;

	// get a group for this setting
	setting_grp = QString("ConnectionSecrets_%1_%2").arg(id).arg(type);
	KConfigGroup grp(config, setting_grp);

	// write the type
	grp.writeEntry("Type", type);

	// write the values
	SettingsMap map = setting->toSecretsMap(false);
	for (SettingsMap::ConstIterator it = map.begin(); it != map.end(); ++it)
	{
		kdDebug() << k_funcinfo << "  " << QString("Value_%1").arg(it.key()) << " = " << XMLMarshaller::fromQDBusData( it.data() )<< endl;
		grp.writeEntry(QString("Value_%1").arg(it.key()), XMLMarshaller::fromQDBusData( it.data() ));
	}
	return true;
}

bool
Storage::hasSecretsStored(Connection* connection)
{
	QString id = connection->getID();

	// ID is necessary
	if (id.isEmpty())
		return false;

	QValueList<ConnectionSetting*> settings = connection->getSettings();
	for (QValueList<ConnectionSetting*>::Iterator it = settings.begin(); it != settings.end(); ++it)
	{
		if (hasSecretsStored(connection, *it))
			return true;
	}
	return false;
}


bool
Storage::hasSecretsStored(Connection* connection, ConnectionSetting* setting)
{
	QString id = connection->getID();
	QString type = setting->getType();

	kdDebug() << "Storage::hasSecretsStored" << endl;

	// ID is necessary
	if (id.isEmpty())
		return false;

	// get a group for this setting
	QString setting_grp_name = QString("ConnectionSecrets_%1_%2").arg(id).arg(type);

	QMap<QString, QString> config_map = KGlobal::config()->entryMap(setting_grp_name);

	return !(config_map.isEmpty());
}

bool
Storage::restoreAllSecrets(Connection* connection)
{
	QString id = connection->getID();
	bool retval = true;

	if (id.isEmpty())
		return false;

	QValueList<ConnectionSetting*> settings = connection->getSettings();
	for (QValueList<ConnectionSetting*>::Iterator it = settings.begin(); it != settings.end(); ++it)
	{
		if (hasSecretsStored(connection, *it))
			if (!restoreSecrets(connection, *it))
				retval = false;
	}
	return retval;
}

bool
Storage::restoreSecrets(Connection* connection, ConnectionSetting* setting)
{
	QString id = connection->getID();
	QString type = setting->getType();

	kdDebug() << "Storage::restoreSecrets" << endl;
	// ID is necessary
	if (id.isEmpty())
		return false;

	// get a group for this setting
	QString setting_grp = QString("ConnectionSecrets_%1_%2").arg(id).arg(type);

	// restore the setting
	return restoreSecrets(connection, setting_grp);
}

bool
Storage::deleteConnection(Connection* conn)
{
	KConfig* config = KGlobal::config();
	QString id = conn->getID();
	QString cType = conn->getType();

	kdDebug() << k_funcinfo << " <" << id << ">" << endl;
	kdDebug() << "Storage::deleteConnection " << id.ascii() << endl;

	// connections without id are evil
	if (id.isEmpty() || cType.isEmpty())
		return false;

	// let's get the config group for this connection
	KConfigGroup grp(config, QString("Connection_%1").arg(id));


	// delete all associated settings
	QStringList settings = grp.readListEntry("Settings");

	for (QStringList::ConstIterator it = settings.begin(); it != settings.end(); ++it)
	{
		KConfigGroup setting(config, *it);
		setting.deleteGroup();
	}

	// delete all associated secrets
	QStringList secrets = grp.readListEntry("Secrets");

	for (QStringList::ConstIterator it = secrets.begin(); it != secrets.end(); ++it)
	{
		KConfigGroup setting(config, *it);
		setting.deleteGroup();
	}

	grp.deleteGroup();

	return true;
}

#include "knetworkmanager-storage.moc"
