/***************************************************************************
                          notificationmanager.cpp - manage the notifications
                            stack and updates them as needed
                             -------------------
    begin                : Thursday April 19 2007
    copyright            : (C) 2007 by Valerio Pilo
    email                : valerio@kmess.org
 ***************************************************************************/

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

#include "notificationmanager.h"

#include "../contact/contactbase.h"
#include "../utils/xautolock.h"
#include "../currentaccount.h"
#include "../kmessdebug.h"

#include <QSystemTrayIcon>
#include <QWidget>

#include <KLocale>
#ifndef Q_WS_MAC
#include <KNotification>
#else
#include "macnotification.h"
#endif
#include <KWindowSystem>

#ifdef Q_WS_WIN
#include <windows.h>
#endif


// Initialize the instance to zero
NotificationManager* NotificationManager::instance_(0);



// Class constructor
NotificationManager::NotificationManager()
{
  // Initialize the interfaces to screen saver and fullscreen apps checks
  autoLock_ = new XAutoLock();
}



// Class destructor
NotificationManager::~NotificationManager()
{
#ifndef Q_WS_MAC
  foreach( KNotification *notification, events_.keys() )
  {
    notification->close();
  }

  events_.clear();
  eventSettings_.clear();
#endif

  delete autoLock_;
}



// Associate a KNotification action with an item of the Buttons bitfield
NotificationManager::Buttons NotificationManager::getButtonFromAction( int buttons, int action )
{
  int number = 1;

  // It's important to keep the synchronization and the same order for the buttons,
  // both here and in getButtonsLabels(): KNotification just uses the index for each action
  if( ( buttons & BUTTON_OPEN_CHAT ) && number++ == action )
  {
    return BUTTON_OPEN_CHAT;
  }
  if( ( buttons & BUTTON_OPEN_OFFLINE_CHAT ) && number++ == action )
  {
    return BUTTON_OPEN_OFFLINE_CHAT;
  }
  if( ( buttons & BUTTON_VIEW_MESSAGE ) && number++ == action )
  {
    return BUTTON_VIEW_MESSAGE;
  }
  if( ( buttons & BUTTON_MORE_DETAILS ) && number++ == action )
  {
    return BUTTON_MORE_DETAILS;
  }
  if( ( buttons & BUTTON_OPEN_MAILBOX ) && number++ == action )
  {
    return BUTTON_OPEN_MAILBOX;
  }
  if( ( buttons & BUTTON_HIDE ) && number++ == action )
  {
    return BUTTON_HIDE;
  }

  return BUTTON_INVALID;
}



// Associate a Buttons bitfield with button actions
QStringList NotificationManager::getButtonsLabels( int buttons )
{
  QStringList labels;

  // It's important to keep the synchronization and the same order for the buttons,
  // both here and in getButtonFromAction(): KNotification just uses the index for each action
  if( buttons & BUTTON_OPEN_CHAT )
  {
    labels << i18nc( "Button text for KDE notification boxes", "Start Chatting" );
  }
  if( buttons & BUTTON_OPEN_OFFLINE_CHAT )
  {
    labels << i18nc( "Button text for KDE notification boxes", "Leave a Message" );
  }
  if( buttons & BUTTON_VIEW_MESSAGE )
  {
    labels << i18nc( "Button text for KDE notification boxes", "View Message" );
  }
  if( buttons & BUTTON_MORE_DETAILS )
  {
    labels << i18nc( "Button text for KDE notification boxes", "Details" );
  }
  if( buttons & BUTTON_OPEN_MAILBOX )
  {
    labels << i18nc( "Button text for KDE notification boxes", "Read Email" );
  }
  if( buttons & BUTTON_HIDE )
  {
    labels << i18nc( "Button text for KDE notification boxes", "Hide" );
  }

  return labels;
}



// Return the current tray object
QSystemTrayIcon *NotificationManager::getTrayObject()
{
  return trayObject_;
}



// Insert a new notification in the stack if needed, or update an existing one
void NotificationManager::notify( const QString &event, const QString &text, EventSettings settings )
{
  // Check if the screen saver is running
  if( autoLock_->isScreenSaverActive() )
  {
#ifdef KMESSDEBUG_NOTIFICATIONMANAGER
    kmDebug() << "Screen saver/locker is active, not showing notifications.";
#endif
    return;
  }

  CurrentAccount *currentAccount = CurrentAccount::instance();

  // Do nothing if, when busy, the user doesn't want to be disturbed
  if( currentAccount->getStatus() == STATUS_BUSY
  &&  currentAccount->getHideNotificationsWhenBusy() )
  {
#ifdef KMESSDEBUG_NOTIFICATIONMANAGER
    kmDebug() << "User is busy, not showing notifications.";
#endif
    return;
  }

  // On Mac, notification emitting goes via the MacNotifications class
#ifdef Q_WS_MAC
  MacNotification::notify( event, text, settings );
#else
  bool isFullscreen = false;

 #ifdef Q_WS_X11
  // Check if a full screen application is running by querying the window manager and asking
  // the state of the currently active window
  KWindowInfo info = KWindowSystem::windowInfo( KWindowSystem::activeWindow(), NET::WMState | NET::FullScreen );
  isFullscreen = ( info.valid() && info.hasState( NET::FullScreen ) );
 #else
  #ifdef Q_WS_WIN
  // Windows version
  HWND hwnd = GetForegroundWindow();
  RECT rcWindow;
  GetWindowRect( hwnd, &rcWindow );
  int screenWidth  = GetSystemMetrics( SM_CXSCREEN );
  int screenHeight = GetSystemMetrics( SM_CYSCREEN );

  int windowWidth  = ( rcWindow.right  - rcWindow.left );
  int windowHeight = ( rcWindow.bottom - rcWindow.top );

  isFullscreen = ( screenWidth == windowWidth && screenHeight == windowHeight );
  #else
    #warning Full screen application detection is not implemented for this platform yet.
  #endif
 #endif

  if( isFullscreen )
  {
    kmDebug() << "Active window is full screen, not showing notifications.";
    return;
  }

 #ifdef KMESSDEBUG_NOTIFICATIONMANAGER
  #ifdef Q_WS_X11
  kmDebug() << "Active window was valid?" << info.valid() <<". If so, it is not fullscreen, showing notifications.";
  #endif
 #endif

  KNotification *notification;

  // An event already exists for this contact, update it
  notification = events_.key( settings.contact, (KNotification*)0 );
  if( notification )
  {
 #ifdef KMESSDEBUG_NOTIFICATIONMANAGER
    kmDebug() << "Updating existing notification" << notification;
 #endif
    notification->setText   ( text );
    notification->setActions( getButtonsLabels( settings.buttons ) );
    // We can't update the event ID, crap.

    if( settings.contact != 0 )
    {
      notification->setPixmap( QPixmap( settings.contact->getContactPicturePath() ).scaled( 96, 96 ) );
    }

    eventSettings_[ notification ] = settings;

    notification->update();
    return;
  }

  // We have to send a new event

  // Create the notification
  notification = new KNotification( event );

 #ifdef KMESSDEBUG_NOTIFICATIONMANAGER
  kmDebug() << "Showing notification" << notification << "for event" << event;
 #endif

  // Add it to our lists to be able to find it later
  events_       .insert( notification, settings.contact );
  eventSettings_.insert( notification, settings );

  // Set it up
  notification->setWidget( settings.widget );
  notification->setText   ( text );
  notification->setActions( getButtonsLabels( settings.buttons ) );
  notification->setFlags  ( KNotification::CloseOnTimeout
                          | KNotification::CloseWhenWidgetActivated );

  if( settings.contact != 0 )
  {
    notification->setPixmap( QPixmap( settings.contact->getContactPicturePath() ).scaled( 96, 96 ) );
  }

  // The slots will call the appropriate *Notification class
  connect( notification, SIGNAL(       activated(unsigned int) ),
           this,         SLOT  ( relayActivation(unsigned int) ) );
  connect( notification, SIGNAL(       destroyed(QObject*)     ),
           this,         SLOT  (          remove(QObject*)     ) );

  notification->sendEvent();
#endif /* !Q_WS_MAC */
}



// Return a singleton instance of the current account
NotificationManager* NotificationManager::instance()
{
  // If the instance is null, create a new manager and return that.
  if ( instance_ == 0 )
  {
    instance_ = new NotificationManager();
  }
  return instance_;
}



// Forward an activation action to the class which will manage it
void NotificationManager::relayActivation( unsigned int action )
{
#ifdef Q_WS_MAC
  #warning TODO!
#else
  KNotification *notification = static_cast<KNotification*>( sender() );
  if( ! notification || ! eventSettings_.contains( notification ) )
  {
    kmWarning() << "Cannot relay notification activation, pointer" << notification << "not found!";
    return;
  }

 #ifdef KMESSDEBUG_NOTIFICATIONMANAGER
  kmDebug() << "Relaying activation for notification" << notification;
 #endif

  EventSettings settings = eventSettings_[ notification ];
  Buttons clickedButton = getButtonFromAction( settings.buttons, action );

  emit eventActivated( settings, clickedButton );
#endif /* !Q_WS_MAC */
}



// Delete an expired notification
void NotificationManager::remove( QObject *object )
{
#ifdef Q_WS_MAC
  // This doesn't need to happen on Mac
#else
  KNotification *notification = static_cast<KNotification*>( object );

  if( notification == 0 )
  {
 #ifdef KMESSDEBUG_NOTIFICATIONMANAGER
    kmDebug() << "Cannot remove notification, pointer" << notification << "not found!";
 #endif
    return;
  }

  // Don't delete the notification, it will do it automatically
  events_.remove( notification );
  eventSettings_.remove( notification );

 #ifdef KMESSDEBUG_NOTIFICATIONMANAGER
  kmDebug() << "Deleted notification" << notification << "from the stack";
 #endif
#endif /* !Q_WS_MAC */
}



// Set the tray object that will be used
void NotificationManager::setTrayObject( QSystemTrayIcon *trayObject )
{
  trayObject_ = trayObject;
}



#include "notificationmanager.moc"
