//-----------------------------------------------------------------------------
//
// kblob - Basic screen saver for KDE
//
// Copyright (c)  Tiaan Wessels, 1997
//
// To add new alg :
//		- add blob_alg enum in blob.h before ALG_LAST
//		- choose 2 letter prefix for alg and add vars needed to private vars
//		  in KBlobSaver in blob.h
//		- add xxSetup and xxNextFrame method definitions in blob.h
//		- implement methods in this file. xxSetup to init vars mentioned
//		  in step 2. xxNextFrame to advance blob painter ( calc tx,ty and
//		  use box() method to position painter
//		- add descriptive string in alg_str array in this file before "Random"
//		- add to Algs array in KBlobSaver constructor in this file
//		- test by setup saver and choosing alg from list

#include <stdlib.h>
#include <time.h>
#include <limits.h>
#include <math.h>

#include <qcolor.h>
#include <qcolormap.h>
#include <qlabel.h>
#include <QListWidget>
#include <qlayout.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qimage.h>
#include <QVBoxLayout>
#include <QHBoxLayout>

#include <kapplication.h>
#include <kconfig.h>
#include <kmessagebox.h>
#include <knuminput.h>
#include <klocale.h>
#include <kglobal.h>
#include <krandomsequence.h>

#include "blob.moc"
#include "blob.h"

#define SMALLRAND(a)	(int)(rnd->getLong(a)+1)


// libkscreensaver interface
class KBlobSaverInterface : public KScreenSaverInterface
{

public:
    virtual KAboutData* aboutData() {
        return new KAboutData( "kblob.kss", "klock", ki18n( "KBlob" ), "2.2.0", ki18n( "KBlob" ) );
    }


    virtual KScreenSaver* create( WId id )
    {
        return new KBlobSaver( id );
    }

    virtual QDialog* setup()
    {
        return new KBlobSetup();
    }
};

int main( int argc, char *argv[] )
{
    KBlobSaverInterface kss;
    return kScreenSaverMain( argc, argv, kss );
}

static KRandomSequence *rnd = 0;

QString alg_str[5];
void initAlg()
{
    alg_str[0] = i18n("Random Linear");
    alg_str[1] = i18n("Horizontal Sine");
    alg_str[2] = i18n("Circular Bounce");
    alg_str[3] = i18n("Polar Coordinates");
    alg_str[4] = i18n("Random");
}

//-----------------------------------------------------------------------------
// the blob screensaver's code

KBlobSaver::KBlobSaver ( WId id)
    : KScreenSaver( id )
{
    setAttribute( Qt::WA_OpaquePaintEvent, true );

    {
        QPalette palette;
        palette.setColor( backgroundRole(), Qt::black );
        setPalette( palette );
	setAutoFillBackground(true);
    }

    rnd = new KRandomSequence();
    initAlg();
    QColor color;
    float ramp = (256.0-64.0)/(float)RAMP;

    // if 8-bit, create lookup table for color ramping further down
    if (QPixmap::defaultDepth() == 8)
    {
        QColormap cmap = QColormap::instance();
        memset(lookup, 0, 256*sizeof(uint));
        int i;
        for (i = 0; i < RAMP; i++)
        {
            color.setRgb(64+(int)(ramp*(float)i), 0, 0);
            colors[i] = cmap.pixel(color);
        }
        memset(lookup, cmap.pixel(Qt::black), sizeof(uint)*256);
        for (i = 0; i < RAMP-1; i++)
            lookup[colors[i]] = colors[i+1];
        lookup[cmap.pixel(Qt::black)] = lookup[colors[RAMP-1]] = colors[0];
    }
    else
    {
        // make special provision for preview mode
        if (height() < 400)
        {
            if (QPixmap::defaultDepth() > 8 )
                setColorInc(7);
            else
                setColorInc(4);
        }
        else
        {
            if (QPixmap::defaultDepth() > 8 )
                setColorInc(3);
            else
                setColorInc(2);
        }
    }

    // the dimensions of the blob painter
    dim = height()/70+1;

    // record starting time to know when to change frames
    start = time(NULL);

    // init some parameters used by all algorithms
    xhalf = width()/2;
    yhalf = height()/2;

    // means a new algorithm should be set at entrance of timer
    newalg = newalgp = 1;

    // init algorithm space
    Algs[0].Name = alg_str[0];
    Algs[0].Init = &KBlobSaver::lnSetup;
    Algs[0].NextFrame = &KBlobSaver::lnNextFrame;

    Algs[1].Name = alg_str[1];
    Algs[1].Init = &KBlobSaver::hsSetup;
    Algs[1].NextFrame = &KBlobSaver::hsNextFrame;

    Algs[2].Name = alg_str[2];
    Algs[2].Init = &KBlobSaver::cbSetup;
    Algs[2].NextFrame = &KBlobSaver::cbNextFrame;

    Algs[3].Name = alg_str[3];
    Algs[3].Init = &KBlobSaver::pcSetup;
    Algs[3].NextFrame = &KBlobSaver::pcNextFrame;

    // get setup from kde registry
    readSettings();

    // start timer which will update blob painter
    timer.start(SPEED);
    connect(&timer, SIGNAL(timeout()), SLOT(update()));
    show();
}

KBlobSaver::~KBlobSaver()
{
    timer.stop();

    delete rnd; rnd = 0;
}

void KBlobSaver::setAlgorithm(int a )
{
    newalg = newalgp = ((a == ALG_RANDOM) ? 1 : 2);
    alg = a;
}

void KBlobSaver::lnSetup()
{
    // initialize the blob movement dictators with random vals
    // incrementals on axis
    ln_xinc = SMALLRAND(3);
    ln_yinc = SMALLRAND(2);

    // start position
    tx = SMALLRAND(width()-dim-ln_xinc*2);
    ty = SMALLRAND(height()-dim-ln_yinc*2);
}

void KBlobSaver::hsSetup()
{
    hs_per = SMALLRAND(7);
    hs_radians = 0.0;
    hs_rinc = (hs_per*M_PI)/(hs_per*90*4);
    hs_flip = 1.0;
}

void KBlobSaver::cbSetup()
{
    cb_radians = 0.0;
    cb_rinc = (2.0*M_PI)/360.0;
    cb_sradians = 0.0;
    cb_deviate = SMALLRAND(height()/20)+(height()/15);
    cb_radius = height()/2-cb_deviate*2-2*dim;
    cb_devradinc = (rnd->getDouble()*10.0*2.0*M_PI)/360.0;
}

void KBlobSaver::pcSetup()
{
    pc_angle = 0.0;
    pc_radius = 0.0;
    pc_inc = (2.0*M_PI)/720.0;
    pc_crot = 0.0;
    pc_div = SMALLRAND(4)-1;
}

// render next frame ( or change algorithms )
void KBlobSaver::paintEvent(QPaintEvent *)
{
    time_t now = time(NULL);

    // should algorithm be changed
    if (now-start > showlen)
        newalg = newalgp;

    // set new algorithm
    if (newalg)
    {
        QPainter p(this);
        p.fillRect(rect(), p.background());
        if (newalg == 1)
            alg = SMALLRAND(ALG_LAST)-1;
        (this->*Algs[alg].Init)();
        newalg = 0;
        start = time(NULL);
    }

    // gen next fram for current algorithm
    (this->*Algs[alg].NextFrame)();
}

void KBlobSaver::lnNextFrame()
{
    int dir;

    // depending on the algorithm to use, move the blob painter to
    // a new location
    // check for wall hit to change direction
    if (tx+dim+ln_xinc > (int)width()-1 || tx+ln_xinc < 0)
    {
        if (ln_xinc > 0)
            dir = -1;
        else
            dir = 1;
        ln_xinc = SMALLRAND(3)*dir;
    }
    if (ty+dim+ln_yinc > (int)height()-1 || ty+ln_yinc < 0)
    {
        if (ln_yinc > 0)
            dir = -1;
        else
            dir = 1;
        ln_yinc = SMALLRAND(2)*dir;
    }

    // move box to new position
    tx += ln_xinc;
    ty += ln_yinc;

    // draw new box
    box(tx, ty);
}

void KBlobSaver::hsNextFrame()
{
    static int xlen = width()-(4*dim);
    static int ylen = height()-(4*dim);

    // calc x as offset on angle line and y as vertical offset
    // on interval -1..1 sine of angle
    tx = (int)((hs_radians/(hs_per*M_PI))*(float)xlen);
    ty = (int)((float)(ylen/4)*(hs_flip*sin(hs_radians)))+yhalf;

    // draw new box
    box(tx, ty);

    // set new radians
    hs_radians += hs_rinc;
    if (hs_radians > hs_per*M_PI)
    {
        hs_rinc *= -1.0;
        hs_radians += hs_rinc;
        hs_flip *= -1.0;
    }
    else if (hs_radians < 0.0)
        hsSetup();
}

void KBlobSaver::cbNextFrame()
{
    int deviate;

    // calculate deviation of circle main radius
    deviate = (int)(sin(cb_sradians)*cb_deviate);

    // calculate topleft of box as a circle with a sine perturbed radius
    tx = (int)(cos(cb_radians)*(cb_radius+deviate))+xhalf;
    ty = (int)(sin(cb_radians)*(cb_radius+deviate))+yhalf;

    // draw the box
    box(tx, ty);

    // increase greater circle render angle
    cb_radians += cb_rinc;
    if (cb_radians > 2.0*M_PI)
        cb_radians -= 2.0*M_PI;

    // increase radius deviation offset on sine wave
    cb_sradians += cb_devradinc;
}

void KBlobSaver::pcNextFrame()
{
    static float scale = (float)height()/3.0 - 4.0*dim;

    // simple polar coordinate equation
    if (pc_div < 1.0)
        pc_radius = cos(2.0*pc_angle);
    else
        pc_radius = 1.0/pc_div + cos(2.0*pc_angle);

    tx = (int)(scale*pc_radius*cos(pc_angle+pc_crot))+xhalf;
    ty = (int)(scale*pc_radius*sin(pc_angle+pc_crot))+yhalf;

    // advance blob painter
    box(tx, ty);

    // new movement parameters
    pc_angle += pc_inc;
    if (pc_angle > 2.0*M_PI)
    {
        pc_angle -= 2.0*M_PI;
        pc_crot += M_PI/45.0;
    }
}

void KBlobSaver::box ( int x, int y )
{
    // for bad behaving algorithms that wants to cause an X trap
    // confine to the valid region before using potentially fatal XGetImage
    if ((x+dim) >= width())
        x = width()-dim-1;
    else if (x < 0)
        x = 0;
    if ((y+dim) > height())
        y = height()-dim-1;
    else if (y < 0)
        y = 0;

    // get the box region from the display to upgrade
    QImage img = QPixmap::grabWindow(winId(), x, y, dim, dim).toImage();

    // depending on the depth of the display, use either lookup table for
    // next rgb val ( 8-bit ) or ramp the color directly for other displays
    if ( img.depth() == 8)
    {
        // manipulate image by upgrading each pixel with 1 using a lookup
        // table as the color allocation could have resulted in a spread out
        // configuration of the color ramp
        for (int j = 0; j < img.height(); j++)
        {
            for (int i = 0; i < img.width(); i++)
            {
                img.scanLine(j)[i] = lookup[img.scanLine(j)[i]];
            }
        }
    }
    else
    {
        for (int j = 0; j < img.height(); j++)
        {
            for (int i = 0; i < img.width(); i++)
            {
                QRgb p = img.pixel( i, j );
                p += (colorInc<<18);
                img.setPixel( i, j, p );
            }
        }
    }

    // put the image back onto the screen
    QPainter p(this);
    p.drawImage( x, y, img );
}

void KBlobSaver::readSettings()
{
    KConfigGroup config(KGlobal::config(), "Settings");

    // number of seconds to spend on a frame
    showlen = config.readEntry("Showtime", 3*60);

    // algorithm to use. if not set then use random
    alg = config.readEntry("Algorithm", int(ALG_RANDOM));
    if (alg == ALG_RANDOM)
	newalg = 1;
    else
	newalg = 2;
    newalgp = newalg;
}

//-----------------------------------------------------------------------------
// dialog to setup screen saver parameters
//
KBlobSetup::KBlobSetup(QWidget *parent)
    : KDialog( parent)
{
    setCaption(i18n( "Setup Blob Screen Saver" ));
    setButtons(Ok|Cancel|Help);
    setDefaultButton(Ok);
    setModal(true);
    initAlg();

    // get saver configuration from kde registry
    readSettings();

    setButtonText( Help, i18n( "A&bout" ) );
    QWidget *main = new QWidget(this);
    setMainWidget(main);

    QHBoxLayout *tl = new QHBoxLayout( main );
    //tl->setContentsMargins(0, 0, 0, 0);
    tl->setSpacing(spacingHint());

    QVBoxLayout *vbox = new QVBoxLayout;
    tl->addLayout(vbox);

    // seconds to generate on a frame
    QLabel *label = new QLabel(i18n("Frame duration:"), main);
    stime = new KIntNumInput( showtime, main );
    stime->setSuffix( i18n( " sec" ) );
    vbox->addWidget(label);
    vbox->addWidget(stime);

    // available algorithms
    label = new QLabel(i18n("Algorithm:"), main);
    algs = new QListWidget(main);
    algs->setMinimumSize(150, 105);
    for (int i = 0; i <= ALG_RANDOM; i++)
        algs->addItem(alg_str[i]);
    algs->setCurrentRow(alg);
    vbox->addWidget(label);
    vbox->addWidget(algs);

    // preview window
    QWidget *preview = new QWidget( main );
    preview->setFixedSize(220, 170);
    {
        QPalette palette;
        palette.setColor( preview->backgroundRole(), Qt::black );
        preview->setPalette( palette );
    }
    preview->show();
    tl->addWidget(preview);
    saver = new KBlobSaver(preview->winId());
    saver->setDimension(3);
    if (QPixmap::defaultDepth() > 8)
        saver->setColorInc(7);
    else
        saver->setColorInc(4);

    tl->addStretch();

    // so selecting an algorithm will start previewing that alg
    connect(algs, SIGNAL(itemChanged(QListWidgetItem*)), this,
            SLOT(setAlgorithm(QListWidgetItem*)));
    connect(this,SIGNAL(okClicked()),this,SLOT(slotOk()));
    connect(this,SIGNAL(helpClicked()),this, SLOT(slotHelp()));
}

void KBlobSetup::setAlgorithm(QListWidgetItem* item)
{
   if(!item)
     return;
   int pos = algs->currentRow();
   saver->setAlgorithm(pos);
}

void KBlobSetup::readSettings()
{
    KConfigGroup config(KGlobal::config(), "Settings");

    // number of seconds to spend on a frame
    showtime = config.readEntry("Showtime", 3*60);

    // algorithm to use. if not set then use random
    alg = config.readEntry("Algorithm", (int)ALG_LAST);
}

// Ok pressed - save settings and exit
void KBlobSetup::slotOk()
{
    KConfigGroup config( KGlobal::config(), "Settings");
    config.writeEntry("Showtime", stime->value());
    config.writeEntry("Algorithm", algs->currentRow());

    config.sync();

    accept();
}

void KBlobSetup::slotHelp()
{
    KMessageBox::about(this,
                       i18n("Blobsaver Version 0.1\n\nWritten by Tiaan Wessels 1997\ntiaan@netsys.co.za"));
    if (saver)
        setAlgorithm(algs->currentItem());
}


