/***************************************************************************
 *
 * knetworkmanager-wireless_device_tray.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
 *
 **************************************************************************/

// QT includes
#include <qguardedptr.h>

// KDE includes
#include <kdebug.h>
#include <kiconloader.h>
#include <klocale.h>
#include <knotifyclient.h>

// QT DBus
#include <dbus/qdbuserror.h>
#include <dbus/qdbusobjectpath.h>

// KNM includes
#include "knetworkmanager-wireless_device_tray.h"
#include "knetworkmanager-wireless_device.h"
#include "knetworkmanager-wireless_menuitem.h"
#include "knetworkmanager-wireless_network.h"
#include "knetworkmanager-accesspoint.h"
#include "knetworkmanager-menu_subhead.h"
#include "knetworkmanager-connection_store.h"
#include "knetworkmanager-wireless_connection.h"
#include "knetworkmanager-connection_setting_info.h"
#include "knetworkmanager-connection_setting_wireless.h"
#include "knetworkmanager-connection_setting_wireless_security.h"
#include "knetworkmanager-nm_proxy.h"
#include "knetworkmanager-wireless_manager.h"
#include "knetworkmanager-connection_settings_dialog.h"

using namespace ConnectionSettings;

class WirelessDeviceTrayPrivate
{
	public:
		WirelessDeviceTrayPrivate() :dev(0), activeAccessPoint(0) { }
		~WirelessDeviceTrayPrivate() {}

		WirelessDevice* dev;
		QGuardedPtr<AccessPoint> activeAccessPoint;
};

QStringList WirelessDeviceTray::getToolTipText()
{
	QStringList tooltip = DeviceTrayComponent::getToolTipText();

	AccessPoint * ap = d->dev->getActiveAccessPoint();
	if (ap)
		tooltip.append(i18n("Network: %1").arg(ap->getDisplaySsid()));

	return tooltip;
}

void WirelessDeviceTray::newConnection()
{
	// create a new wireless connection
	Connection* conn = new WirelessConnection();

	// open a dialog for editing the connection
	ConnectionSettingsDialogImpl* dlg = new ConnectionSettingsDialogImpl(conn, true, NULL, tray(), "connect_something", false, Qt::WDestructiveClose);
	dlg->show();
}

bool WirelessDeviceTray::findMatchingNetwork(const WirelessConnection* conn, const QValueList<WirelessNetwork>& nets, WirelessNetwork& net)
{
	Wireless* wireless = conn->getWirelessSetting();
	WirelessSecurity* security = conn->getWirelessSecuritySetting();

	if (!wireless && !security)
		return false;

	for (QValueList<WirelessNetwork>::ConstIterator it = nets.begin(); it != nets.end(); ++it)
	{
		if (wireless->getEssid() == (*it).getSsid())
		{
			net = *it;
			return true;
		}	
	}
	return false;
}

WirelessConnection* WirelessDeviceTray::findMatchingConnection(const WirelessNetwork& net, const QValueList<WirelessConnection*>& connections)
{
	// try to find a connection matching this network
	for (QValueList<WirelessConnection*>::ConstIterator it = connections.begin(); it != connections.end(); ++it)
	{
		Wireless* wireless = (*it)->getWirelessSetting();
		WirelessSecurity* security = (*it)->getWirelessSecuritySetting();

		// should not happen but its ever better to check
		if (!wireless || !security)
			continue;

		if (wireless->getEssid() == net.getSsid())
		{
			return *it;
		}
	}

	return NULL;
}

void WirelessDeviceTray::addWirelessNetworks(KPopupMenu* menu)
{
	// get all wireless networks
	QValueList<WirelessNetwork> nets = WirelessManager::getWirelessNetworks(d->dev);

	// get all wireless connections
	QValueList<WirelessConnection*> conns = WirelessManager::getWirelessConnections();

	// get the currently active connection
	NMProxy* nm = NMProxy::getInstance();
	Connection* active_conn = nm->getActiveConnection(d->dev);
	if (active_conn)
		kdDebug() << active_conn->getObjectPath().data() << endl;

	// add all wireless connections in range
	// (we may have more then one connection per network)
	for (QValueList<WirelessConnection*>::iterator it = conns.begin(); it != conns.end(); ++it)
	{
		WirelessNetworkItem* wirelessNetworkItem;
		WirelessNetwork net;

		// only show connections which are in range
		if ( !findMatchingNetwork(*it, nets, net) )
			continue;

		wirelessNetworkItem = new WirelessNetworkItem (menu,
		                            d->dev,
		                            net,
		                            *it,
		                            false);
		int id = menu->insertItem (wirelessNetworkItem, -1, -1);	
		menu->setItemChecked(id, ((Connection*)(*it) == active_conn));
		menu->connectItem(id, wirelessNetworkItem, SLOT(slotActivate()));
	}

	// now add all connections which are not in range to a submenu
	QPopupMenu* popup = new QPopupMenu(menu);

	uint networkItemsAdded = 0;
	for (QValueList<WirelessConnection*>::iterator it = conns.begin(); it != conns.end(); ++it)
	{
		WirelessNetworkItem* wirelessNetworkItem;
		WirelessNetwork net;

		// only show connections which are out of range
		if ( findMatchingNetwork(*it, nets, net) )
			continue;

		Info* info = (*it)->getInfoSetting();
		Wireless* wireless = (*it)->getWirelessSetting();

		if (!info || !wireless)
			continue;

		wirelessNetworkItem = new WirelessNetworkItem (menu,
		                            d->dev,
		                            net,
		                            *it,
		                            false);

		int id = popup->insertItem (wirelessNetworkItem, -1, -1);	
		popup->connectItem(id, wirelessNetworkItem, SLOT(slotActivate()));
		networkItemsAdded += 1;
	}
	
	if (networkItemsAdded) {
		menu->insertSeparator();
		menu->insertItem(i18n("Connect to other network"), popup);
	}
}

void WirelessDeviceTray::addMenuItems(KPopupMenu* menu)
{
	NMProxy* nm = NMProxy::getInstance();
	QDBusError err;

	// device title
	Subhead* subhead = new Subhead (menu, "subhead", d->dev->getInterface(), SmallIcon("wireless", QIconSet::Automatic));
	menu->insertItem (subhead, -1, -1);

    // bolding subhead instead
	//menu->insertSeparator();

	if (!nm->getWirelessEnabled(err))
	{
		// wireless disabled -> do not show any connections
		subhead = new Subhead(menu, "subhead2", i18n("Wireless disabled"), SmallIcon("no", QIconSet::Automatic));
		menu->insertItem(subhead, -1, -1);
	}
	else if (!nm->getWirelessHardwareEnabled(err))
	{
		// wireless disabled -> do not show any connections
		subhead = new Subhead(menu, "subhead2", i18n("Wireless disabled by Killswitch"), SmallIcon("no", QIconSet::Automatic));
		menu->insertItem(subhead, -1, -1);
	}
	else
	{
		// networks
		addWirelessNetworks(menu);

		// bring the device down
		KAction* deactivate = tray()->actionCollection()->action("deactivate_device");
		if (deactivate)
			deactivate->plug(menu);
	}
	menu->insertSeparator();
}

void WirelessDeviceTray::slotUpdateDeviceState(NMDeviceState state)
{
    slotCheckActiveAccessPoint();
	if (state == NM_DEVICE_STATE_ACTIVATED)
	{
		// trigger an update of the connections seen bssids property

		AccessPoint * ap = d->dev->getActiveAccessPoint();
		if (ap) {
			int strength = ap->getStrength();

			if (strength > 80)
				setPixmapForState((NMDeviceState)state, KSystemTray::loadIcon("nm_signal_100"));
			else if (strength > 55)
				setPixmapForState((NMDeviceState)state, KSystemTray::loadIcon("nm_signal_75"));
			else if (strength > 30)
				setPixmapForState((NMDeviceState)state, KSystemTray::loadIcon("nm_signal_50"));
			else if (strength > 5)
				setPixmapForState((NMDeviceState)state, KSystemTray::loadIcon("nm_signal_25"));
			else
				setPixmapForState((NMDeviceState)state, KSystemTray::loadIcon("nm_signal_00"));
		}
	}
}

void WirelessDeviceTray::slotCheckActiveAccessPoint()
{
	// the active AP changed, if a connection is already active we have roamed
	// thus add the bssid to the list of seen bssids
	NMProxy* nm = NMProxy::getInstance();
	if (!nm)
		return;

	WirelessConnection* active_conn = dynamic_cast<WirelessConnection*>(nm->getActiveConnection(d->dev));
	if (active_conn && d->dev->getState() == NM_DEVICE_STATE_ACTIVATED)
	{
		if ( d->dev->getActiveAccessPoint() != d->activeAccessPoint) {
			if (!d->activeAccessPoint.isNull())
				disconnect( d->activeAccessPoint, SIGNAL(strengthChanged(Q_UINT8)), this, SLOT(apStrengthChanged(Q_UINT8)));

			d->activeAccessPoint = d->dev->getActiveAccessPoint();
			if ( d->activeAccessPoint ) {
				connect( d->activeAccessPoint, SIGNAL(strengthChanged(Q_UINT8)), this, SLOT(apStrengthChanged(Q_UINT8)));
				ConnectionSettings::Wireless* wireless = active_conn->getWirelessSetting();
				wireless->addSeenBssid(d->activeAccessPoint->getHwAddress());
			}
		}
	}
}

void WirelessDeviceTray::apStrengthChanged(Q_UINT8 strength)
{
    kdDebug() << k_funcinfo << (uint)strength << endl;
    NMDeviceState state = device()->getState();
    if (strength > 80)
        setPixmapForState(state, KSystemTray::loadIcon("nm_signal_100"));
    else if (strength > 55)
        setPixmapForState(state, KSystemTray::loadIcon("nm_signal_75"));
    else if (strength > 30)
        setPixmapForState(state, KSystemTray::loadIcon("nm_signal_50"));
    else if (strength > 5)
        setPixmapForState(state, KSystemTray::loadIcon("nm_signal_25"));
    else
        setPixmapForState(state, KSystemTray::loadIcon("nm_signal_00"));
    emit uiUpdated();
}

void WirelessDeviceTray::slotAccessPointAdded(AccessPoint* ap)
{
	KNotifyClient::event( tray()->winId(), "knm-nm-network-found", i18n("KNetworkManager New Wireless Network Found") );
}

void WirelessDeviceTray::slotAccessPointRemoved(const QString&)
{
	KNotifyClient::event( tray()->winId(), "knm-nm-network-gone", i18n("KNetworkManager Wireless Network Disappeared") );
}

WirelessDeviceTray::WirelessDeviceTray (WirelessDevice* dev, KSystemTray * parent, const char * name)
	: DeviceTrayComponent (dev, parent, name)
{
	d = new WirelessDeviceTrayPrivate();
	d->dev = dev;

	// we want other icons for wireless devices
	setPixmapForState(NM_DEVICE_STATE_UNKNOWN, KSystemTray::loadIcon("wireless_off"));
	setPixmapForState(NM_DEVICE_STATE_UNAVAILABLE, KSystemTray::loadIcon("wireless_off"));
	setPixmapForState(NM_DEVICE_STATE_UNMANAGED, KSystemTray::loadIcon("wireless_off"));
	setPixmapForState(NM_DEVICE_STATE_DISCONNECTED, KSystemTray::loadIcon("wireless"));
	setPixmapForState(NM_DEVICE_STATE_ACTIVATED, KSystemTray::loadIcon("nm_signal_50"));

	// get notified when the device state changes
	connect(dev, SIGNAL(StateChanged(NMDeviceState)), this, SLOT(slotUpdateDeviceState(NMDeviceState)));

	// if the active access point changed but not the connection we roamed to a new AP
	connect(dev, SIGNAL(propertiesChanged()), this, SLOT(slotCheckActiveAccessPoint()));

	// get notified of all AP changes
	connect(dev, SIGNAL(accessPointAdded(AccessPoint*)), this, SLOT(slotAccessPointAdded(AccessPoint*)));
	connect(dev, SIGNAL(accessPointRemoved(const QString&)), this, SLOT(slotAccessPointRemoved(const QString&)));
}

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


#include "knetworkmanager-wireless_device_tray.moc"
