/*
 * laptop_daemon.cpp
 * Copyright (C) 1999 Paul Campbell <paul@taniwha.com>
 *
 * this replaces kcmlaptop - there are 2 parts - one is the panels that
 *	put the setup configuration in the "kcmlaptop" configm, the other
 *	is the laptop_daemon (this guy) who watches the battery state
 *	and does stuff as a result
 *
 * This file contains the implementation of the main laptop battery monitoring daemon
 *
 * $Id: laptop_daemon.cpp,v 1.17 2001/07/24 08:55:55 campbell Exp $
 *
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h>

#include <kconfig.h>
#include <klocale.h>
#include <kcmdlineargs.h>
#include "laptop_daemon.h"
#include "portable.h"
#include "notify.h"
#include <kaboutdata.h>
#include <knotifyclient.h>
#include <kaudioplayer.h>

#include "kpcmcia.h"

#include <unistd.h>
#include <sys/time.h>

static const char *description =
	I18N_NOOP("KDE Laptop Daemon");

static const char *version = "v0.0.1";

laptop_daemon::laptop_daemon(): KUniqueApplication()
{
	exists = laptop_portable::has_power_management();
        backoff = 0;
        triggered[0] = 0;
        triggered[1] = 0;
	timer = 0;
	dock_widget = 0;
	oldTimer = 0;
        connect(this, SIGNAL(signal_checkBattery()), SLOT(checkBatteryNow()));

	//hasAudio = (audioServer.serverStatus() == 0) ? true : false;

        // FIXME: make these configurable.  Some system definitely don't
        //        use /var/run/stab
        _pcmcia = new KPCMCIA(8, "/var/run/stab");
        connect(_pcmcia, SIGNAL(cardUpdated(int)), this, SLOT(updatePCMCIA(int)));
}

laptop_daemon::~laptop_daemon()
{
	delete _pcmcia;
}

int laptop_daemon::newInstance()
{
  restart();
  return 0;
}

void laptop_daemon::restart()
{
	exists = laptop_portable::has_power_management();
        if (oldTimer > 0) {
                killTimer(oldTimer);
		oldTimer=0;
	}
	if (timer) {
		timer->stop();
		delete timer;
		timer = 0;
	}

	KConfig *config = new KConfig("kcmlaptoprc");

	if (config == 0) {
		::fprintf(stderr, "laptop_daemon: can't open kcmlaptop config files\n");
		::exit(2);
	}

	config->setGroup("LaptopPower");
	power_action[0] = config->readNumEntry("PowerSuspend", 0);
	power_action[1] = config->readNumEntry("NoPowerSuspend", 1);
  	power_wait[0] = config->readNumEntry("PowerWait", 20);
  	power_wait[1] = config->readNumEntry("NoPowerWait", 5);

	// General settings
	config->setGroup("BatteryDefault");
       	poll = config->readNumEntry("Poll", 20);
        enabled = config->readBoolEntry("Enable", false);
        noBatteryIcon = config->readEntry("NoBatteryPixmap", "laptop_nobattery");
        noChargeIcon = config->readEntry("NoChargePixmap", "laptop_nocharge");
        chargeIcon = config->readEntry("ChargePixmap", "laptop_charge");

	int can_suspend = laptop_portable::has_suspend();

	config->setGroup("BatteryLow");
        low[0] = config->readNumEntry("LowVal", 15);
        runCommand[0] = config->readBoolEntry("RunCommand", false);
        runCommandPath[0] = config->readEntry("RunCommandPath");
        playSound[0] = config->readBoolEntry("PlaySound", false);
        playSoundPath[0] = config->readEntry("PlaySoundPath");
        systemBeep[0] = config->readBoolEntry("SystemBeep", true);
        notify[0] = config->readBoolEntry("Notify", true);
        do_suspend[0] = config->readBoolEntry("Suspend", false);
        do_standby[0] = config->readBoolEntry("Standby", false);
        have_time = config->readNumEntry("HaveTime", 2);

        if (!have_time && laptop_portable::has_battery_time())
        	have_time = 1;

	config->setGroup("BatteryCritical");
        low[1] = config->readNumEntry("LowVal", 5);
        runCommand[1] = config->readBoolEntry("RunCommand", false);
        runCommandPath[1] = config->readEntry("RunCommandPath");
        playSound[1] = config->readBoolEntry("PlaySound", false);
        playSoundPath[1] = config->readEntry("PlaySoundPath");
        systemBeep[1] = config->readBoolEntry("SystemBeep", true);
        notify[1] = config->readBoolEntry("Notify", (can_suspend?false:true));
        do_suspend[1] = config->readBoolEntry("Suspend", (can_suspend?true:false));
        do_standby[1] = config->readBoolEntry("Standby", false);

	delete config;
	config = 0;

	//
	//	look for reasons NOT to run the daemon
	//
	if (!exists)
		shutDown();
 	if (!enabled &&
		!runCommand[0] && !playSound[0] && !systemBeep[0] && !notify[0] && !do_suspend[0] && !do_standby[0] &&
		!runCommand[1] && !playSound[1] && !systemBeep[1] && !notify[1] && !do_suspend[1] && !do_standby[1] &&
		power_action[0] == 0 && power_action[1] == 0) // if no reason to be running quit
		shutDown();

	// change the dock state if necessary

	if (enabled) {
		if (!dock_widget) {
			dock_widget = new laptop_dock(this);
			dock_widget->setPCMCIA(_pcmcia);
  			dock_widget->show();
		}
	} else {
		if (dock_widget) {
			delete dock_widget;
			dock_widget = 0;
		}
	}
	

	last_time = time(0);
	if (power_action[0] || power_action[1]) {
		if (powered) {
			power_time = time(0)+60*power_wait[0];
		} else {
			power_time = time(0)+60*power_wait[1];
		}	
		timer =  new QTimer( this );
		connect( timer, SIGNAL(timeout()), this, SLOT(timerDone()) );
		timer->start( 2*1000, TRUE );                 // 1 seconds single-shot
	} else {
		timer = 0;
	}
	start_monitor();
}

void laptop_daemon::timerDone()
{
	unsigned long t = time(0);

	if (t >= (last_time+120)) {	// time suddenly jumped - we just powered up?
		backoff = 0;
		if (powered) {
			power_time = t+60*power_wait[0];
		} else {
			power_time = t+60*power_wait[1];
		}	
	} else
	if (backoff) {
		if (t >= power_time) {
			backoff = 0;
			if (powered) {
				power_time = t+60*power_wait[0];
			} else {
				power_time = t+60*power_wait[1];
			}	
		}
	} else
	if (laptop_portable::poll_activity()) {		// still active?
		if (powered) {
			power_time = t+60*power_wait[0];
		} else {
			power_time = t+60*power_wait[1];
		}	
	} else
	if (t >= power_time) {
		int val;

		if (powered) {
			val = power_action[0];
		} else {
			val = power_action[1];
		}	
		switch (val) {
		case 1:
			invokeStandby();
			break;
		case 2:
			invokeSuspend();
			break;
		}
		backoff = 1;
		power_time = t+60;		// wait to give us time to get in and out of suspend prior to
						// suspend
	}
	last_time = t;
	timer->start( 2*1000, TRUE );           // 1 seconds single-shot
}

void laptop_daemon::dock_quit()
{
	if (dock_widget)
		delete dock_widget;
	dock_widget = 0;
}


void laptop_daemon::updatePCMCIA(int num)
{
  //kdDebug() << "PCMCIA card " << num << " was updated." << endl; 

  // Two things we do here.  We provide notifications for cards
  // being inserted / cards going to sleep / cards waking up
  // and cards being safe to eject.
  // The second thing we do is provide the desktop icon actions
  // via dcop.
}


void laptop_daemon::haveBatteryLow(int t, const int num, const int type)
{
	displayPixmap();

	// beep if we are allowed to
	if (systemBeep[t]) {
		//KNotifyClient::beep();
		kapp->beep();
	}

	// run a command if we have to
	if (runCommand[t]) {
		// make sure the command exists
		if (!runCommandPath[t].isEmpty()) {
			KProcess command;
			command << runCommandPath[t];
			command.start(KProcess::DontCare);
		}
	}

	if (do_suspend[t])
		invokeSuspend();
	if (do_standby[t])
		invokeStandby();

	// play a sound if we have to
	if (playSound[t]) {
		 KAudioPlayer::play(playSoundPath[t]);  
	}

	// notify if we must
	if (notify[t]) {
		KBatteryNotify notify_dlg(num, type);
		notify_dlg.exec();
	}
}

int laptop_daemon::calcBatteryTime(int percent, long time, bool restart)
{
#define MAX_SAMPLES_USED 3
  static int percents[MAX_SAMPLES_USED];
  static long times[MAX_SAMPLES_USED];
  static int lastused=-1;
  int r=-1;

  if ( (lastused==-1) || restart )
  {
     percents[0]=percent;
     times[0]=time;
     lastused=0;
  }
  else
  {
     // Add the % and time to the arrays
     // (or just update the time if the % hasn't changed)
     if (percents[lastused]!=percent)
	if (lastused!=MAX_SAMPLES_USED-1)
	{
	   lastused++;
	   percents[lastused]=percent;
	   times[lastused]=time;
	}
	else
	{
	  for (int i=1;i<MAX_SAMPLES_USED;i++)
	  {  percents[i-1]=percents[i]; times[i-1]=times[i]; };
	}
     percents[lastused]=percent;
     times[lastused]=time;

     //Now let's do the real calculations

     if (lastused==0) return -1;

     // Copy the data to temporary variables
     double tp[MAX_SAMPLES_USED];
     double tt[MAX_SAMPLES_USED];
     
     for (int i=0;i<=lastused;i++)
     { tp[i]=percents[i]; tt[i]=times[i]; };
     
     for (int c=lastused; c>1; c--)
     {
       for (int i=0; i<c-1; i++)
       {
          tp[i]=(tp[i]+tp[i+1])/2;
          tt[i]=(tt[i]+tt[i+1])/2;
       }
     }

     // Now we've reduced all the samples to an approximation with just a line

     if (tp[1]-tp[0]!=0)
        r=static_cast<int>(tt[0]-(tp[0]/(tp[1]-tp[0]))*(tt[1]-tt[0])-time);


  }
  
  return r;
}

void laptop_daemon::checkBatteryNow()
{
	struct power_result p;

	p = laptop_portable::poll_battery_state();

	powered = p.powered;
	left = p.time;
	val = p.percentage;

        if (left==-1)  // Let's try to calculate the expected battery time left
        {
	   timeval tv;
	   gettimeofday(&tv, 0);
	   left=calcBatteryTime(((powered)?100-val:val), tv.tv_sec, oldpowered!=powered );
        }

	if (timer && oldpowered != powered) {
		if (powered) {
			power_time = time(0)+60*power_wait[0];
		} else {
			power_time = time(0)+60*power_wait[1];
		}	
	}
	changed =  oldpowered != powered||oldexists != exists||oldval != val || oldleft!=left;
	oldpowered = powered;
	oldexists = exists;
	oldval = val;
	oldleft = left;
	if (changed)
		displayPixmap();
}

void laptop_daemon::start_monitor()
{
	checkBatteryNow();
	displayPixmap();
	oldTimer = startTimer(poll * 1000);
}

void laptop_daemon::setPollInterval(const int interval)
{
        poll = interval;

        // Kill any old timers that may be running
        if (oldTimer > 0) {
                killTimer(oldTimer);

                // Start a new timer will the specified time
                oldTimer = startTimer(interval * 1000);

                emit(signal_checkBattery());
        }
}

void laptop_daemon::timerEvent(QTimerEvent *)
{
        emit(signal_checkBattery());
}

int main(int argc, char **argv)
{
    if (!laptop_portable::has_power_management())
	exit(0);
    KAboutData aboutData( "klaptopdaemon", I18N_NOOP("KLaptop"),
        version, description, KAboutData::License_GPL,
        "(c) 1999, Paul Campbell");
    aboutData.addAuthor("Paul Campbell",0, "paul@taniwha.com");
    KCmdLineArgs::init( argc, argv, &aboutData );

    if (!laptop_daemon::start())
    {
       // Already running
       exit(0);
    }

    laptop_daemon a;
    return(a.exec());
}

void laptop_daemon::displayPixmap()
{
	if (have_time == 2 && exists && !powered) {		// in some circumstances
		KConfig *config = new KConfig("kcmlaptoprc");
		config->setGroup("BatteryLow");			// we can;t figure this out 'till
		have_time = (val < 0 ? 0 : 1);			// the battery is not charging
		config->writeEntry("HaveTime", have_time);
		config->sync();
		delete config;
	}
	
	if (dock_widget)
		dock_widget->displayPixmap();

	if (left >= 0) {
		if (!triggered[0]) {
			if (exists && !powered && left <= low[0]) {
				triggered[0] = 1;
				haveBatteryLow(0, left, 0);
			}
		} else {	
			if (!triggered[1]) {
				if (exists && !powered && left <= low[1]) {
					triggered[1] = 1;
					haveBatteryLow(1, left, 0);
				}
			}
			if (left > (low[1]+1))
				triggered[1] = 0;
			if (left > low[0])
				triggered[0] = 0;
		}
	} else
	if (have_time != 1) {
		if (!triggered[0]) {
			if (exists && !powered && val <= low[0]) {
				triggered[0] = 1;
				haveBatteryLow(0, val, 1);
			}
		} else {	
			if (!triggered[1]) {
				if (exists && !powered && val <= low[1]) {
					triggered[1] = 1;
					haveBatteryLow(1, val, 1);
				}
			}
			if (val > (low[1]+1))
				triggered[1] = 0;
			if (val > low[0])
				triggered[0] = 0;
		}
	}
}

void laptop_daemon::invokeStandby()
{
	laptop_portable::invoke_standby();
}

void laptop_daemon::invokeSuspend()
{
	laptop_portable::invoke_suspend();
}

#include "laptop_daemon.moc"
