/* 1d BTW sandpile animation (version 2.02 - Windows)
 * 
 * Copyright (C) 2005 N.R. Moloney 
 *
 * Distributed under the Qt Non-Commercial license version 1.0
 */

#include <qapplication.h>
#include <qcombobox.h>
#include <qdatetime.h>
#include <qgroupbox.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qpushbutton.h>
#include <qtimer.h>
#include <qvbuttongroup.h>

#include <string.h>

#include "btw1d.h"

Sandpile::Sandpile( QWidget * parent, const char * name ) : 
  QWidget( parent, name )
{  
  QGroupBox * added = new QGroupBox( "No. grains added", this, "added" );
  added->move( leftAlign, 65 );
  added->resize( LCDWidth,LCDHeight );

  hitsLCD = new QLCDNumber( 7, added, "hitsLCD" );
  hitsLCD->setGeometry( 30,30, 100,50 );
  hitsLCD->setSegmentStyle( QLCDNumber::Filled );
  hitsLCD->display( 0 );

  QGroupBox * grains = new QGroupBox( "No. grains in pile", this, "grains" );
  grains->move( leftAlign, 215 );
  grains->resize( LCDWidth,LCDHeight );

  grainsLCD = new QLCDNumber( 5, grains, "grainsLCD" );
  grainsLCD->setGeometry( 30,30, 100,50 );
  grainsLCD->setSegmentStyle( QLCDNumber::Filled );
  
  QGroupBox * avalanche = new QGroupBox( "Avalanche size s", this, "avalanche" );
  avalanche->move( leftAlign, 365 );
  avalanche->resize( LCDWidth, LCDHeight );

  sLCD = new QLCDNumber( 6, avalanche, "sLCD" );
  sLCD->setGeometry( 30,30, 100,50 );
  sLCD->setSegmentStyle( QLCDNumber::Filled );
  sLCD->display( 0 );

  QGroupBox * largest = new QGroupBox( "Largest avalanche", this, "largest" );
  largest->move( leftAlign, 515 );
  largest->resize( LCDWidth, LCDHeight );
  
  largestLCD = new QLCDNumber( 6, largest, "largestLCD" );
  largestLCD->setGeometry( 30,30, 100,50 );
  largestLCD->setSegmentStyle( QLCDNumber::Filled );
  largestLCD->display( 0 );

  QVButtonGroup * startAgain = new QVButtonGroup( this, "startAgain" );
  startAgain->move( 820,offsety );
  startAgain->resize( buttonWidth,buttonHeight );

  startAgainButton = 
    new QPushButton( "Re-initialise", startAgain, "startAgainButton" );
  QObject::connect( startAgainButton, SIGNAL( clicked() ), 
		    this, SLOT( slotStartAgain() ) );

  QGroupBox * speedBox = new QGroupBox( "Speed", this, "speedBox" );
  speedBox->move( rightAlign, 350 );
  speedBox->resize( buttonWidth,comboBoxHeight );

  QComboBox * runningSpeed = new QComboBox( speedBox, "read-only combobox" );
  runningSpeed->move( 10,25 );
  runningSpeed->resize( 140,30 );
  runningSpeed->insertItem( "fast" );
  runningSpeed->insertItem( "slow" );
  runningSpeed->insertItem( "pause" );

  QObject::connect( runningSpeed, SIGNAL( activated( int ) ),
		    this, SLOT( slotSpeed( int ) ) );

  QPainter paint( this );
  paint.drawText( rightAlign,460, "System size L = 40" );
  paint.drawText( rightAlign+30,500, "Stable site" );
  paint.drawText( rightAlign+30,520, "Toppling site" );

  paint.setPen( NoPen );
  paint.setBrush( Qt::white );
  paint.drawRect( rightAlign,490, 10,10 );
  paint.setBrush( Qt::red );
  paint.drawRect( rightAlign,510, 10,10 );

  QVButtonGroup * exit = new QVButtonGroup( this, "exit" );
  exit->move( rightAlign,595 );
  exit->resize( buttonWidth,buttonHeight );

  exitButton = new QPushButton( "Quit", exit, "exitButton" );
  QObject::connect( exitButton, SIGNAL( clicked() ), qApp, SLOT( quit() ) );

  speed = FAST;

  h = new int [L+1];
  toppling = new int [L+1];
  newToppling = new int [L+1];

  initialiseLattice();

  startTimer( 0 );
  QTimer * counter = new QTimer( this );
  connect( counter, SIGNAL( timeout() ), this, SLOT( drive() ) );
  counter->start( 0 );
}

///////////////////////////////////////////////////

void Sandpile::paintEvent( QPaintEvent * )
{
  QPainter paint( this );
  paint.drawText( rightAlign,460, "System size L = 40" );
  paint.drawText( rightAlign+30,500, "Stable site" );
  paint.drawText( rightAlign+30,520, "Toppling site" );

  paint.setPen( NoPen );
  paint.setBrush( Qt::white );
  paint.drawRect( rightAlign,490, 10,10 );
  paint.setBrush( Qt::red );
  paint.drawRect( rightAlign,510, 10,10 );

  QPixmap buffer( L*pixel+wall,H*pixel );
  buffer.fill( Qt::black );

  QPainter bufferpainter( &buffer );
  bufferpainter.setPen( NoPen );
            
  for( int i = 0; i < L; i++ )
  {
    if( h[i] - h[i+1] <= zc )
    {
      bufferpainter.setBrush( Qt::white );
      bufferpainter.drawRect( i*pixel+wall,(H-h[i])*pixel-wall, pixel,h[i]*pixel );
    }
    else
    {
      bufferpainter.setBrush( Qt::white );
      bufferpainter.drawRect( i*pixel+wall,(H-h[i]+1)*pixel-wall, pixel,(h[i]-1)*pixel );
      bufferpainter.setBrush( Qt::red );
      bufferpainter.drawRect( i*pixel+wall,(H-h[i])*pixel-wall, pixel,pixel );
    }
  }

  bufferpainter.setBrush( Qt::gray );
  bufferpainter.drawRect( 0,0, wall,H*pixel );
  bufferpainter.drawRect( wall,H*pixel-wall, l*pixel,wall );

  bufferpainter.end();

  bitBlt( this, offsetx,offsety, &buffer );

  return;
}

///////////////////////////////////////////////////

void Sandpile::initialiseLattice()
{
  memset( h, 0, (L+1)*sizeof( int ) );

  hits = hTotal = numToppling = s = sMax = 0;

  hitsLCD->display( hits );
  grainsLCD->display( hTotal );
  sLCD->display( s );
  largestLCD->display( sMax );

  return;
}

///////////////////////////////////////////////////

void Sandpile::drive()
{
  if( speed == PAUSE )
  {
    return;
  }
  else if( numToppling )
  {
    relax();
    return;
  }
  else
  {
    QPainter paint( this );
    paint.setPen( NoPen );

    do
    {
      h[0]++;
      
      hitsLCD->display( ++hits );
      grainsLCD->display( ++hTotal );

      if( h[0] - h[1] <= zc )
      {
	    paint.setBrush( Qt::white );
      }
      else
      {
	    paint.setBrush( Qt::red );
      }
      paint.drawRect( offsetx+wall,offsety+(H-h[0])*pixel-wall, pixel,pixel );

      if( speed == SLOW )
      {
	    QTime t;
	    t.start();
	    while( t.elapsed() < SLOWTIME );
	    paint.flush();
      }
    }
    while( h[0] - h[1] <= zc );

    s = 0;
    numToppling = 1;
    toppling[0] = 0;
    memset( newToppling, 0, (L+1)*sizeof( int ) );

    relax();
    return;
  }
}

///////////////////////////////////////////////////

void Sandpile::relax()
{
  int i, n;
  int numNewToppling;

  QPainter paint( this );
  paint.setPen( NoPen );

  numNewToppling = 0;
  
  for( n = 0; n < numToppling; n++ )
  {
    i = toppling[n];

    if( h[i] - h[i+1] <= zc ) continue;

    h[i]--;
    paint.setBrush( Qt::black );
    paint.drawRect( offsetx+i*pixel+wall,offsety+(H-h[i]-1)*pixel-wall, pixel,pixel );
	
    if( i < l-1 )
    {
      h[i+1]++;
      if( h[i+1] - h[i+2] > zc )
      {
	    newToppling[numNewToppling++] = i+1;
	    paint.setBrush( Qt::red );
      }
      else
      {
	    paint.setBrush( Qt::white );
      }
      paint.drawRect( offsetx+(i+1)*pixel+wall,offsety+(H-h[i+1])*pixel-wall, 
		      pixel,pixel );
	  update();
    }
    else if( i == l-1 )
    {
      grainsLCD->display( --hTotal );
    }

    if( speed == SLOW )
    {
      QTime t;
      t.start();
      while( t.elapsed() < SLOWTIME );
      paint.flush();
    }
  }
  s += numToppling;
  memcpy( toppling, newToppling, numNewToppling*sizeof(int) ); 
  numToppling = numNewToppling;

  sLCD->display( s );
  paint.flush();

  if(s > sMax )
  {
    sMax = s;
    largestLCD->display( sMax );
  }

  return;
}

///////////////////////////////////////////////////

void Sandpile::update()
{
  QPixmap buffer( L*pixel+wall,H*pixel );
  buffer.fill( Qt::black );

  QPainter bufferpainter( &buffer );
  bufferpainter.setPen( NoPen );
            
  for( int i = 0; i < L; i++ )
  {
    if( h[i] - h[i+1] <= zc )
    {
      bufferpainter.setBrush( Qt::white );
      bufferpainter.drawRect( i*pixel+wall,(H-h[i])*pixel-wall, pixel,h[i]*pixel );
    }
    else
    {
      bufferpainter.setBrush( Qt::white );
      bufferpainter.drawRect( i*pixel+wall,(H-h[i]+1)*pixel-wall, pixel,(h[i]-1)*pixel );
      bufferpainter.setBrush( Qt::red );
      bufferpainter.drawRect( i*pixel+wall,(H-h[i])*pixel-wall, pixel,pixel );
    }
  }

  bufferpainter.setBrush( Qt::gray );
  bufferpainter.drawRect( 0,0, wall,H*pixel );
  bufferpainter.drawRect( wall,H*pixel-wall, l*pixel,wall );

  bufferpainter.end();

  bitBlt( this, offsetx,offsety, &buffer );

  return;
}

///////////////////////////////////////////////////

void Sandpile::slotStartAgain()
{
  initialiseLattice();
  update();

  return;
}

///////////////////////////////////////////////////

void Sandpile::slotSpeed( int S )
{
  speed = S;
  
  return;
}


