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

#include <qdatetime.h>
#include <qgroupbox.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qpoint.h>
#include <qstring.h> 
#include <qtimer.h>
#include <qwidget.h>

#include <math.h>
#include <string.h>

#include "lcg.h"
#include "btw2d.h"

extern long seed;

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

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

  QGroupBox * average = new QGroupBox( "Average slope <z>", this, "average" );
  average->move( leftAlign, 165 );
  average->resize( boxWidth,boxHeight );

  slopeLCD = new QLCDNumber( 4, average, "slopeLCD" );
  slopeLCD->setGeometry( 30,30, 100,50 );
  slopeLCD->setSegmentStyle( QLCDNumber::Filled );
  
  QGroupBox * avalanche = new QGroupBox( "Avalanche size s", this, "avalanche" );
  avalanche->move( leftAlign, 315 );
  avalanche->resize( boxWidth, boxHeight );

  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, 465 );
  largest->resize( boxWidth, boxHeight );
  
  largestLCD = new QLCDNumber( 6, largest, "largestLCD" );
  largestLCD->setGeometry( 30,30, 100,50 );
  largestLCD->setSegmentStyle( QLCDNumber::Filled );
  largestLCD->display( 0 );

  mode = RUN_FREE;
  speed = FAST;

  burn = FALSE;
  display = TRUE;

  h = new int [L*L];
  toppling = new int [L*L];
  newToppling = new int [L*L];
  redraw = new int [L*L];

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

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

void Sandpile::paintEvent( QPaintEvent * )
{
  QPixmap buffer( L*pixel,L*pixel );
  buffer.fill( Qt::black );

  QPainter bufferpainter( &buffer );
  bufferpainter.setPen( NoPen );

  for( int i = 0; i < L*L; i++ )
  { 
    if( h[i] == 1 ) 
    {
      bufferpainter.setBrush( Qt::darkGray );
      bufferpainter.drawRect( (i%L)*pixel,(i/L)*pixel, pixel,pixel );
    }
    if( h[i] == 2 ) 
    {
      bufferpainter.setBrush( Qt::lightGray );
      bufferpainter.drawRect( (i%L)*pixel,(i/L)*pixel, pixel,pixel );
    }
    if( h[i] == 3 ) 
    {
      bufferpainter.setBrush( Qt::white );
      bufferpainter.drawRect( (i%L)*pixel,(i/L)*pixel, pixel,pixel );
    }
    if( h[i] >= 4 ) 
    {
      bufferpainter.setBrush( Qt::red );
      bufferpainter.drawRect( (i%L)*pixel,(i/L)*pixel, pixel,pixel );
    }
  }

  bufferpainter.end();

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

  return;
}

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

void Sandpile::mousePressEvent( QMouseEvent * e )
{
  QPoint pnt = e->pos();

  int x = (pnt.x() - offsetx)/pixel;
  int y = pnt.y()/pixel;

  if( mode == MANUAL_SPRINKLE )
  {
    targetSite = x + y*L;
  }
  else
  { 
    targetSite = -1;
  }

  return;
}

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

void Sandpile::initialiseLattice()
{
  hits = numToppling = s = sMax = 0;
  targetSite = -1;

  int ave = 0;

  int i;
  int fill = 0;

  if( (mode == RUN_FREE) || (mode == MANUAL_SPRINKLE) )
  {
    for( i = 0; i < L*L; i++ )
    {   
      h[i] = int( lcg()*4 );      
      ave += h[i];
    }

    slopeLCD->display( double(ave)/(L*L) );
    grainsLCD->display( hits );
    sLCD->display( 0 );
    largestLCD->display( sMax );
  }
  else
  {
    if( mode == FRACTALSAND0 ) fill = 0;
    if( mode == FRACTALSAND1 ) fill = 1;
    if( mode == FRACTALSAND2 ) fill = 2;

    for( i = 0; i < L*L; i++ )
    {
      h[i] = fill; 
    }
  }

  update();

  return;
}

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

void Sandpile::sprinkle()
{
  if( speed == PAUSE ) return;

  if( numToppling )
  {
    relax();
    return;
  }

  static bool burntAlready = FALSE;

  int site;

  if( (mode == FRACTALSAND0) || 
      (mode == FRACTALSAND1) || 
      (mode == FRACTALSAND2) )
  {
    bool topple = FALSE;

    do
    {
      QPainter paint( this );
      paint.setPen( NoPen );
	
      site = L*L/2;
      h[site]++;

      if( (h[site]) == 4 ) topple = TRUE;

      if( display )
      {
	if( (h[site]) == 4 ) paint.setBrush( Qt::red ); 
	if( (h[site]) == 3 ) paint.setBrush( Qt::white ); 
	if( (h[site]) == 2 ) paint.setBrush( Qt::lightGray ); 
	if( (h[site]) == 1 ) paint.setBrush( Qt::darkGray ); 

	paint.drawRect( offsetx+(site%L)*pixel,(site/L)*pixel, pixel,pixel );
      }

      grainsLCD->display( "" );
    }
    while( !topple );
    
    s = 0;
    numToppling = 1;
    toppling[0] = site;
    memset( newToppling, 0, L*L*sizeof( newToppling ) );

    sLCD->display( "" );
    slopeLCD->display( "" );
    largestLCD->display( "" );    

    relax();

    return;
  }

  if( burn )
  {
    if( !burntAlready ) burnEdges();

    burntAlready = TRUE;

    return;
  }
  else
  {
    burntAlready = FALSE;
  }

  if( mode == MANUAL_SPRINKLE )
  {
    if( targetSite < 0 )
    {
      return;
    }
    else if( (++h[targetSite]) == 4 )
    {
      if( display )
      {
	QPainter paint( this );
	paint.setPen( NoPen );
	paint.setBrush( Qt::red );
	paint.drawRect( offsetx+(targetSite%L)*pixel,(targetSite/L)*pixel, 
			pixel,pixel );
      }
      
      grainsLCD->display( ++hits );

      s = 0;
      numToppling = 1;
      toppling[0] = targetSite;
      memset( newToppling, 0, L*L*sizeof( newToppling ) );
      relax();
      targetSite = -1;

      return;
    }
    else
    {
      QPainter paint( this );
      paint.setPen( NoPen );
	
      if( display )
      {
	if( h[targetSite] == 1 ) paint.setBrush( Qt::darkGray );
        if( h[targetSite] == 2 ) paint.setBrush( Qt::lightGray );
        if( h[targetSite] == 3 ) paint.setBrush( Qt::white );

	paint.drawRect( offsetx+(targetSite%L)*pixel,(targetSite/L)*pixel, 
			pixel,pixel );
      }

      targetSite = -1;

      grainsLCD->display( ++hits );

      return;
    }
  }
  else
  {
    bool topple = FALSE;

    do
    {
      QPainter paint( this );
      paint.setPen( NoPen );
	
      site = int( lcg()*L*L );          
      h[site]++;

      if( (h[site]) == 4 ) topple = TRUE;

      if( display )
      {
	if( (h[site]) == 4 ) paint.setBrush( Qt::red );
	if( (h[site]) == 3 ) paint.setBrush( Qt::white );
        if( (h[site]) == 2 ) paint.setBrush( Qt::lightGray ); 	
	if( (h[site]) == 1 ) paint.setBrush( Qt::darkGray );

	paint.drawRect( offsetx+(site%L)*pixel,(site/L)*pixel, pixel,pixel ); 
      }
      
      grainsLCD->display( ++hits );
    }
    while( !topple );

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

    return;
  }
  
  return;
}

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

void Sandpile::relax()
{
  int i, n;
  int numRedraw, numNewToppling;
  
  QPainter paint( this );
  paint.setPen( NoPen );

  numNewToppling = numRedraw = 0;

  if( speed == SLOW )
  {
    QTime t;
    t.start();
    while( t.elapsed() < 100 );
  }

  for( n = 0; n < numToppling; n++ )
  {
    if( h[toppling[n]] < 4 ) continue; 
 
    i = toppling[n];
    h[i] -= 4;
    redraw[numRedraw++] = i;

    if( i >= L )
    {
      h[i-L]++; redraw[numRedraw++] = i - L; 
      if(h[i-L] == 4) newToppling[numNewToppling++] = i - L; 
    }
    if( (i+1)%L != 0 )  
    {
      h[i+1]++; redraw[numRedraw++] = i + 1;
      if(h[i+1] == 4) newToppling[numNewToppling++] = i + 1; 
    }
    if( i < L*(L-1) )
    {
      h[i+L]++; redraw[numRedraw++] = i + L;
      if(h[i+L] == 4) newToppling[numNewToppling++] = i + L; 
    }      
    if( i%L != 0 )
    {
      h[i-1]++; redraw[numRedraw++] = i - 1;
      if(h[i-1] == 4) newToppling[numNewToppling++] = i - 1;
    }
  }
  s += numToppling;

  memcpy( toppling, newToppling, numNewToppling*sizeof( toppling ) );
  numToppling = numNewToppling;

  if( display )
  {
    for( i = 0; i < numRedraw; i++ )
    {
      if( h[redraw[i]] == 0 ) paint.setBrush( Qt::black );
      if( h[redraw[i]] == 1 ) paint.setBrush( Qt::darkGray );
      if( h[redraw[i]] == 2 ) paint.setBrush( Qt::lightGray );
      if( h[redraw[i]] == 3 ) paint.setBrush( Qt::white );
      if( h[redraw[i]] == 4 ) paint.setBrush( Qt::red );
	
      paint.drawRect( offsetx+(redraw[i]%L)*pixel,(redraw[i]/L)*pixel, 
		      pixel,pixel );
    }

    if( (mode == RUN_FREE) || (mode == MANUAL_SPRINKLE) )
    {
      sLCD->display( int(s) );
    }
  }

  if( (mode == RUN_FREE) || (mode == MANUAL_SPRINKLE) )
  {
    if( s > sMax )
    {
      sMax = s;
      largestLCD->display( sMax );
    }

    if( hits%100 == 0 )
    {
      int ave = 0;
      QString average;

      for( i = 0; i < L*L; i++ )
      {
	ave += h[i];
      }

      slopeLCD->display( double(ave)/(L*L) );
    }
  }

  return;
}

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

void Sandpile::slotRunningStyle( int index )
{
  if( ((mode == RUN_FREE) && (index == MANUAL_SPRINKLE)) ||
      ((mode == MANUAL_SPRINKLE) && (index == RUN_FREE)) ||
      (mode == index) )
  {
    mode = index;

    return;
  }
  else
  {
    mode = index;
    initialiseLattice();
    
    return;
  }
}

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

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

  return;
}

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

void Sandpile::slotDisplay( bool toggle )
{
  if( (display = toggle) )
  {
    update();
  }

  return;
}

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

void Sandpile::slotBurn( bool toggle )
{
  if( !(burn = toggle) )
  {
    update();
  }

  return;
}

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

void Sandpile::burnEdges()
{
  int i, n;
  int numRedraw;

  bool * toppled = new bool [L*L];
  int * cp_h = new int [L*L];
  int * Burning = new int [L*L];
  int * newBurning = new int [L*L];
  int numNewBurning;
  int numBurning;

  memset( toppled, FALSE, L*L*sizeof( bool ) );
  memcpy( cp_h, h, L*L*sizeof( int ) );
  memset( Burning, 0, L*L*sizeof( int ) );
  memset( newBurning, 0, L*L*sizeof( int ) );

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

  numBurning = 0;

  // top-left corner

  cp_h[0] += 2;
  if( cp_h[0] >= 4 )  Burning[numBurning++] = 0; 

  // top-right corner

  cp_h[L-1] += 2;
  if( cp_h[L-1] >= 4 )  Burning[numBurning++] = L-1; 

  // bottom-left corner

  cp_h[L*(L-1)] += 2;
  if( cp_h[L*(L-1)] >= 4 )  Burning[numBurning++] = L*(L-1); 

  // bottom-right corner

  cp_h[L*L-1] += 2;
  if( cp_h[L*L-1] >= 4 )  Burning[numBurning++] = L*L-1; 

  // top row

  for( i = 1; i < L-1; i++ )
  {
    if( ++cp_h[i] >= 4 )  Burning[numBurning++] = i; 
  }

  // left row

  for( i = 1; i < L-1; i++ )
  {
    if( ++cp_h[i*L] >= 4 )  Burning[numBurning++] = i*L; 
  }

  // right row 

  for( i = 1; i < L-1; i++ )
  {
    if( ++cp_h[(i+1)*L-1] >= 4 )  Burning[numBurning++] = (i+1)*L-1; 
  }

  // bottom row

  for( i = L*(L-1)+1; i < L*L-1; i++ )
  {
    if( ++cp_h[i] >= 4 )  Burning[numBurning++] = i; 
  }

  do
  {
    numNewBurning = numRedraw = 0;

    for( n = 0; n < numBurning; n++ )
    {
      if( cp_h[Burning[n]] < 4 ) { continue; }

      i = Burning[n];

      cp_h[i] -= 4;
      
      paint.setBrush( Qt::yellow );
      paint.drawRect( offsetx+(i%L)*pixel,(i/L)*pixel, pixel,pixel );

      toppled[i] = !toppled[i];

      redraw[numRedraw++] = i;

      if( i >= L )
      {
	cp_h[i-L]++; redraw[numRedraw++] = i - L; 
	if(cp_h[i-L] == 4) newBurning[numNewBurning++] = i - L; 
      }

      if( (i+1)%L != 0 )  
      {
	cp_h[i+1]++; redraw[numRedraw++] = i + 1;
	if(cp_h[i+1] == 4) newBurning[numNewBurning++] = i + 1; 
      }

      if( i < L*(L-1) )
      {
	cp_h[i+L]++; redraw[numRedraw++] = i + L;
	if(cp_h[i+L] == 4) newBurning[numNewBurning++] = i + L; 
      }

      if( i%L != 0 )
      {
	cp_h[i-1]++; redraw[numRedraw++] = i - 1;
	if(cp_h[i-1] == 4) newBurning[numNewBurning++] = i - 1; 
      }
    }

    memcpy( Burning, newBurning, numNewBurning*sizeof( int ) );
    numBurning = numNewBurning;

    for( i = 0; i < numRedraw; i++ )
    {
      if( cp_h[redraw[i]] == 0 && !toppled[redraw[i]] )
      {
	paint.setBrush( Qt::black );
	paint.drawRect( offsetx+(redraw[i]%L)*pixel,(redraw[i]/L)*pixel, 
			pixel,pixel );
      }
      if( cp_h[redraw[i]] == 1 && !toppled[redraw[i]] )
      { 
	paint.setBrush( Qt::darkGray );
	paint.drawRect( offsetx+(redraw[i]%L)*pixel,(redraw[i]/L)*pixel, 
			pixel,pixel );
      }
      if( cp_h[redraw[i]] == 2 && !toppled[redraw[i]] )
      { 
	paint.setBrush( Qt::lightGray );
	paint.drawRect( offsetx+(redraw[i]%L)*pixel,(redraw[i]/L)*pixel, 
			pixel,pixel );
      }
      if( cp_h[redraw[i]] == 3 && !toppled[redraw[i]] )
      { 
	paint.setBrush( Qt::white );
	paint.drawRect( offsetx+(redraw[i]%L)*pixel,(redraw[i]/L)*pixel, 
			pixel,pixel );
      }
      if( cp_h[redraw[i]] == 4 && !toppled[redraw[i]] )
      { 
	paint.setBrush( Qt::red );
	paint.drawRect( offsetx+(redraw[i]%L)*pixel,(redraw[i]/L)*pixel, 
			pixel,pixel );
      }
    }
  }
  while( numBurning );

  delete toppled;
  delete cp_h;
  delete Burning;
  delete newBurning;

  return;
}

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

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

  return;
}

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

void Sandpile::update()
{
  QPixmap buffer( L*pixel,L*pixel );

  buffer.fill( Qt::black );

  QPainter bufferpainter( &buffer );
  bufferpainter.setPen( NoPen );

  for( int i = 0; i < L*L; i++ )
  { 
    if( h[i] == 1 ) 
    {
      bufferpainter.setBrush( Qt::darkGray );
      bufferpainter.drawRect( (i%L)*pixel,(i/L)*pixel, pixel,pixel );
    }
    if( h[i] == 2 ) 
    {
      bufferpainter.setBrush( Qt::lightGray );
      bufferpainter.drawRect( (i%L)*pixel,(i/L)*pixel, pixel,pixel );
    }
    if( h[i] == 3 ) 
    {
      bufferpainter.setBrush( Qt::white );
      bufferpainter.drawRect( (i%L)*pixel,(i/L)*pixel, pixel,pixel );
    }
    if( h[i] >= 4 ) 
    {
      bufferpainter.setBrush( Qt::red );
      bufferpainter.drawRect( (i%L)*pixel,(i/L)*pixel, pixel,pixel );
    }
  }

  bufferpainter.end();

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

  return;
}
