/***************************************************************************
 *
 * tdenetman-wireless_device_tray.cpp - A NetworkManager frontend for TDE
 *
 * Copyright (C) 2012 Timothy Pearson <kb9vqf@pearsoncomputing.net>
 * 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
 *
 **************************************************************************/

// TQt includes
#include <tqevent.h>
#include <tqvbox.h>
#include <tqlayout.h>
#include <tqpushbutton.h>
#include <tqbitmap.h>
#include <tqimage.h>
#include <tqpixmap.h>
#include <tqpixmapcache.h>
#include <tqpainter.h>
#include <tqstyle.h>
#include <tqstring.h>
#include <tqguardedptr.h>

// TDE includes
#include <kdebug.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <knotifyclient.h>

// TDENM includes
#include "tdenetman-wireless_device_tray.h"
#include "tdenetman-wireless_menuitem.h"
#include "tdenetman-wireless_network.h"
#include "tdenetman-menu_subhead.h"
#include "tdenetman-wireless_manager.h"
#include "tdenetman-connection_settings_dialog.h"

using namespace ConnectionSettings;

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

		TQString dev;
		TDENetworkWiFiAPInfo* activeAccessPoint;
};

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

	TDENetworkDevice* dev = dynamic_cast<TDENetworkDevice*>(hwdevices->findByUniqueID(d->dev));
	if (dev)
	{
		TDENetworkConnectionManager* deviceConnMan = dev->connectionManager();
		if (deviceConnMan)
		{
			TDENetworkWiFiAPInfo * ap = deviceConnMan->findAccessPointByBSSID(deviceConnMan->deviceInformation().wiFiInfo.activeAccessPointBSSID);
			if (ap)
			{
				tooltip.append(i18n("Network: %1").arg(ap->friendlySSID()));
				int strength = (ap->signalQuality*100.0);
				tooltip.append(i18n("Signal Strength: %1%").arg(strength));
			}
		}
	}

	return tooltip;
}

void WirelessDeviceTray::newConnection()
{
	newConnection(0);
}

void WirelessDeviceTray::newConnection(int id)
{
	TDEGlobalNetworkManager* nm = TDEGlobal::networkManager();
	if (!nm)
	{
		return;
	}

	// create a new wireless connection
	TDENetworkConnection* conn = new TDEWiFiConnection();
	nm->loadConnectionAllowedValues(conn);

	// open a dialog for editing the connection
	char use_new_wireless_essid = 0;
	if ((id < 0) && newWirelessPopupSSIDMap.contains(id)) {
		use_new_wireless_essid = 1;
	}
	ConnectionSettingsDialogImpl* dlg = new ConnectionSettingsDialogImpl(conn, true, (use_new_wireless_essid)?newWirelessPopupSSIDMap[id]:TQByteArray(), tray(), "connect_something", false, TQt::WDestructiveClose);
	dlg->show();
}

bool WirelessDeviceTray::findMatchingNetwork(const TDEWiFiConnection* conn, const TQValueList<WirelessNetwork>& nets, WirelessNetwork& net)
{
	const TDEWiFiConnection* wireless = dynamic_cast<const TDEWiFiConnection*>(conn);

	if (!wireless) {
		return false;
	}

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

TDEWiFiConnection* WirelessDeviceTray::findMatchingConnection(const WirelessNetwork& net, const TQValueList<TDEWiFiConnection*>& connections)
{
	// try to find a connection matching this network
	for (TQValueList<TDEWiFiConnection*>::ConstIterator it = connections.begin(); it != connections.end(); ++it) {
		const TDEWiFiConnection* wireless = dynamic_cast<const TDEWiFiConnection*>(*it);

		if (!wireless) {
			continue;
		}

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

	return NULL;
}

void WirelessDeviceTray::addWirelessNetworks(TDEPopupMenu* menu)
{
	TDEGlobalNetworkManager* nm = TDEGlobal::networkManager();
	TDENetworkDevice* dev = dynamic_cast<TDENetworkDevice*>(hwdevices->findByUniqueID(d->dev));

#ifdef DEBUG_STATE
	printf("Updating wireless network list\n");
#endif // DEBUG_STATE

	// get all wireless networks
	TQValueList<WirelessNetwork> nets = WirelessManager::getWirelessNetworks(dev);

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

	// get the currently active connection
	TDENetworkConnectionManager* deviceConnMan = (dev ? dev->connectionManager() : NULL);
	TDENetworkConnection* active_conn = NULL;
	if (nm && deviceConnMan)
	{
		TDENetworkDeviceInformation devInfo = deviceConnMan->deviceStatus();
		if ((!(devInfo.statusFlags & TDENetworkConnectionStatus::Disconnected))
			&& (!(devInfo.statusFlags & TDENetworkConnectionStatus::Invalid)))
		{
			active_conn = nm->findConnectionByUUID(devInfo.activeConnectionUUID);
		}
	}

	// add all wireless connections in range
	// (we may have more then one connection per network)
	for (TQValueList<TDEWiFiConnection*>::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)->UUID,
						false);
		int id = menu->insertItem (wirelessNetworkItem, -1, -1);	
		menu->setItemChecked(id, ((TDENetworkConnection*)(*it) == active_conn));
		menu->connectItem(id, wirelessNetworkItem, TQ_SLOT(slotActivate()));
	}

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

	uint networkItemsAdded = 0;
	for (TQValueList<TDEWiFiConnection*>::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;
		}

		const TDEWiFiConnection* wireless = dynamic_cast<const TDEWiFiConnection*>(*it);

		if (!wireless) {
			continue;
		}

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

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

	// List available unsaved networks
	TQPopupMenu* newpopup = new TQPopupMenu(menu);
	uint newnetworkItemsAdded = 0;
	TQValueList<WirelessNetwork> newnets = WirelessManager::getWirelessNetworks(0, WirelessNetwork::MATCH_SSID);

	newWirelessPopupSSIDMap.clear();
	for (TQValueList<WirelessNetwork>::Iterator it = newnets.begin(); it != newnets.end(); ++it)
	{
		// Only display networks with no existing connnection
		if ( findMatchingConnection(*it, conns) != NULL) {
			continue;
		}

		WirelessNetworkItem* wirelessNetworkItem;
		wirelessNetworkItem = new WirelessNetworkItem (newpopup,
						d->dev,
						*it,
						NULL,
						false);

 		int id = newpopup->insertItem (wirelessNetworkItem, -1, -1);
 		newWirelessPopupSSIDMap[id] = (*it).getSsid();
		newpopup->connectItem(id, this, TQ_SLOT(newConnection(int)));
		
 		newnetworkItemsAdded += 1;
 	}
 
	if (newnetworkItemsAdded) {
 		menu->insertSeparator();
 		menu->insertItem(i18n("Connect to new network"), newpopup);
 	}
	// Signal done with wireless menu
	//menu->insertSeparator();
}

void WirelessDeviceTray::addMenuItems(TDEPopupMenu* menu)
{
	TDENetworkDevice* dev = dynamic_cast<TDENetworkDevice*>(hwdevices->findByUniqueID(d->dev));
	if (!dev)
	{
		return;
	}

	// get the currently active connection
	TDEGlobalNetworkManager* nm = TDEGlobal::networkManager();

	// device title
	Subhead* subhead = new Subhead (menu, "subhead", TQString("Wireless Connection (%1)").arg(dev->deviceNode()), SmallIcon("wireless", TQIconSet::Automatic));
	menu->insertItem (subhead, -1, -1);

	TDENetworkConnectionManager* deviceConnMan = dev->connectionManager();
	if (!nm || !deviceConnMan || !deviceConnMan->deviceInformation().managed)
	{
		// device is not managed by NM -> do not show any connections
		subhead = new Subhead(menu, "subhead2", i18n("Not managed"), SmallIcon("no", TQIconSet::Automatic));
		menu->insertItem(subhead, -1, -1);
	}
	else if (!nm->wiFiEnabled())
	{
		// wireless disabled -> do not show any connections
		subhead = new Subhead(menu, "subhead2", i18n("Wireless disabled"), SmallIcon("no", TQIconSet::Automatic));
		menu->insertItem(subhead, -1, -1);
	}
	else if (!nm->wiFiHardwareEnabled())
	{
		// wireless disabled -> do not show any connections
		subhead = new Subhead(menu, "subhead2", i18n("Wireless disabled by Killswitch"), SmallIcon("no", TQIconSet::Automatic));
		menu->insertItem(subhead, -1, -1);
	}
	else
	{
		// networks
		addWirelessNetworks(menu);

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

void WirelessDeviceTray::setPixmapForStates(TDENetworkConnectionStatus::TDENetworkConnectionStatus states, TQString pixmap) {
	TDENetworkConnectionStatus::TDENetworkConnectionStatus flag = (TDENetworkConnectionStatus::TDENetworkConnectionStatus)0x80000000;
	while ((TQ_UINT32)flag > 0) {
		if (states & flag) {
			setPixmapForState(flag, pixmap);
		}
		flag = (TDENetworkConnectionStatus::TDENetworkConnectionStatus)((TQ_UINT32)flag >> 1);
	}
}

void WirelessDeviceTray::slotUpdateDeviceState(TDENetworkConnectionStatus::TDENetworkConnectionStatus newState, TDENetworkConnectionStatus::TDENetworkConnectionStatus prevState, TQString deviceNode)
{
	TDENetworkDevice* dev = dynamic_cast<TDENetworkDevice*>(hwdevices->findByUniqueID(d->dev));

	if (!dev)
	{
		return;
	}
	if (dev->deviceNode() != deviceNode)
	{
		kdDebug() << k_funcinfo << "WARNING: Got networkDeviceStateChanged signal for interface '" << deviceNode << "', but my interface is '" << dev->deviceNode() << "'!  Ignoring...";
		return;
	}

#ifdef DEBUG_STATE
	printf("Wireless state: 0x%08x\n", newState);
#endif // DEBUG_STATE

	slotCheckActiveAccessPoint();

	if (newState == TDENetworkConnectionStatus::Connected) {
		// trigger an update of the connections seen bssids property
		TDENetworkConnectionManager* deviceConnMan = dev->connectionManager();
		if (deviceConnMan)
		{
			TDENetworkWiFiAPInfo * ap = deviceConnMan->findAccessPointByBSSID(deviceConnMan->deviceInformation().wiFiInfo.activeAccessPointBSSID);
			if (ap)
			{
				int strength = (ap->signalQuality*100.0);

				if (strength > 80)
				{
					setPixmapForStates(newState, "nm_signal_100");
				}
				else if (strength > 55)
				{
					setPixmapForStates(newState, "nm_signal_75");
				}
				else if (strength > 30)
				{
					setPixmapForStates(newState, "nm_signal_50");
				}
				else if (strength > 5)
				{
					setPixmapForStates(newState, "nm_signal_25");
				}
				else
				{
					setPixmapForStates(newState, "nm_signal_00");
				}
			}
		}
	}

	// Update tray icon
	TQTimer::singleShot(0, this, TQ_SLOT(sendUpdateUI()));
}

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

	TDENetworkDevice* dev = dynamic_cast<TDENetworkDevice*>(hwdevices->findByUniqueID(d->dev));

	// get the currently active connection
	TDEGlobalNetworkManager* nm = TDEGlobal::networkManager();
	TDENetworkConnectionManager* deviceConnMan = (dev ? dev->connectionManager() : NULL);
	TDENetworkConnection* active_conn = NULL;

	if (nm && deviceConnMan)
	{
		TDENetworkDeviceInformation devInfo = deviceConnMan->deviceStatus();
		if ((!(devInfo.statusFlags & TDENetworkConnectionStatus::Disconnected))
			&& (!(devInfo.statusFlags & TDENetworkConnectionStatus::Invalid)))
		{
			active_conn = nm->findConnectionByUUID(devInfo.activeConnectionUUID);
		}

		if (active_conn && devInfo.statusFlags == TDENetworkConnectionStatus::Connected)
		{
			TDENetworkDeviceInformation devInfo = deviceConnMan->deviceInformation();
			TDENetworkWiFiAPInfo * activeap = deviceConnMan->findAccessPointByBSSID(devInfo.wiFiInfo.activeAccessPointBSSID);
			if ( activeap != d->activeAccessPoint)
			{
				d->activeAccessPoint = activeap;
				if ( d->activeAccessPoint )
				{
					TDEWiFiConnection* wireless = dynamic_cast<TDEWiFiConnection*>(active_conn);
					if (wireless)
					{
						if (!(wireless->heardBSSIDs.contains(d->activeAccessPoint->BSSID)))
						{
							wireless->heardBSSIDs.append(d->activeAccessPoint->BSSID);
						}
					}
				}
			}
		}
	}
}

void WirelessDeviceTray::apPropertyChanged(TDEMACAddress BSSID, TDENetworkAPEventType::TDENetworkAPEventType event)
{
	TDENetworkDevice* dev = dynamic_cast<TDENetworkDevice*>(hwdevices->findByUniqueID(d->dev));
	if (!dev)
	{
		return;
	}

	if (event == TDENetworkAPEventType::SignalStrengthChanged) {
		TDENetworkConnectionManager* deviceConnMan = dev->connectionManager();
		if (deviceConnMan)
		{
			TDENetworkWiFiAPInfo * ap = deviceConnMan->findAccessPointByBSSID(BSSID);
			if (ap)
			{
				TQ_UINT32 strength = (ap->signalQuality*100.0);
				kdDebug() << k_funcinfo << strength << endl;
				TDENetworkConnectionStatus::TDENetworkConnectionStatus state = deviceConnMan->deviceInformation().statusFlags;
				if (strength > 80)
				{
					setPixmapForStates(state, "nm_signal_100");
				}
				else if (strength > 55)
				{
					setPixmapForStates(state, "nm_signal_75");
				}
				else if (strength > 30)
				{
					setPixmapForStates(state, "nm_signal_50");
				}
				else if (strength > 5)
				{
					setPixmapForStates(state, "nm_signal_25");
				}
				else
				{
					setPixmapForStates(state, "nm_signal_00");
				}
				TQTimer::singleShot(0, this, TQ_SLOT(sendUpdateUI()));
			}
		}
	}
}

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

void WirelessDeviceTray::slotAccessPointRemoved(TDEMACAddress)
{
	KNotifyClient::event( tray()->winId(), "tdenm-nm-network-gone", i18n("TDENetworkManager Wireless Network Disappeared") );
}

void WirelessDeviceTray::tdeAccessPointStatusChangedHandler(TDEMACAddress BSSID, TDENetworkAPEventType::TDENetworkAPEventType event) {
	TDENetworkDevice* dev = dynamic_cast<TDENetworkDevice*>(hwdevices->findByUniqueID(d->dev));
	if (!dev)
	{
		return;
	}

	TDENetworkConnectionManager* deviceConnMan = dev->connectionManager();

	if (event == TDENetworkAPEventType::Discovered) {
		if (deviceConnMan)
		{
			TDENetworkWiFiAPInfo* apinfo = deviceConnMan->findAccessPointByBSSID(BSSID);
			slotAccessPointAdded(apinfo);
		}
	}
	else if (event == TDENetworkAPEventType::Lost) {
		slotAccessPointRemoved(BSSID);
	}
	else if (event == TDENetworkAPEventType::SignalStrengthChanged) {
		if (deviceConnMan &&
		    deviceConnMan->deviceInformation().wiFiInfo.activeAccessPointBSSID == BSSID)
		{
			apPropertyChanged(BSSID, event);
		}
	}
	else if (event == TDENetworkAPEventType::AccessPointChanged) {
		slotCheckActiveAccessPoint();
	}
}

void WirelessDeviceTray::sendUpdateUI()
{
	emit uiUpdated();
}

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

	// we want other icons for wireless devices
	setPixmapForState(TDENetworkConnectionStatus::Invalid, "wireless_off");
	setPixmapForState(TDENetworkConnectionStatus::LinkUnavailable, "wireless_off");
	setPixmapForState(TDENetworkConnectionStatus::UnManaged, "wireless_off");
	setPixmapForState(TDENetworkConnectionStatus::Disconnected, "wireless");
	setPixmapForState(TDENetworkConnectionStatus::Connected, "nm_signal_50");

	// initial hardware information update
	TDENetworkDevice* netdev = dynamic_cast<TDENetworkDevice*>(hwdevices->findByUniqueID(d->dev));
	TDENetworkConnectionManager* deviceConnMan = (netdev)?netdev->connectionManager():NULL;

	// get notified when the device state changes
	connect(deviceConnMan, TQ_SIGNAL(networkDeviceStateChanged(TDENetworkConnectionStatus::TDENetworkConnectionStatus, TDENetworkConnectionStatus::TDENetworkConnectionStatus, TQString)), this, TQ_SLOT(slotUpdateDeviceState(TDENetworkConnectionStatus::TDENetworkConnectionStatus, TDENetworkConnectionStatus::TDENetworkConnectionStatus, TQString)));

	// get notified of all AP changes
	connect(deviceConnMan, TQ_SIGNAL(accessPointStatusChanged(TDEMACAddress, TDENetworkAPEventType::TDENetworkAPEventType)), this, TQ_SLOT(tdeAccessPointStatusChangedHandler(TDEMACAddress, TDENetworkAPEventType::TDENetworkAPEventType)));

	// force status update to ensure correct icon is shown on startup
	if ((netdev) && (deviceConnMan)) {
		slotUpdateDeviceState(deviceConnMan->deviceInformation().statusFlags, TDENetworkConnectionStatus::Invalid, netdev->deviceNode());
	}
}

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


#include "tdenetman-wireless_device_tray.moc"
