#include <stdlib.h>
#include <stdio.h>
#include <fstream.h>
#include <iostream.h>
#include <string.h>

// for pipes
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>

#include <assert.h>

// ansi c interface to i/o modules
#include "x.h"
#include "keyboard.h"

// c++ modules
#include "colour.h"
#include "imageIO.h"
#include "grafwin.h"
#include "world2.h"

static uint8* _help_screen = NULL;


//
// Constructor inits 2-d spatial array

World2::World2(char *sFileName, int& nDefaultWidth, int& nDefaultHeight)
{
  // use a command pipe to get commands from a named pipe
  unlink( "pipe" );
  mknod( "pipe", S_IFIFO, 0 );
  chmod( "pipe", 0660 );
  m_fdPipe = open( "pipe", O_RDONLY | O_NDELAY);	// command pipe open



  InitFunPtrs();

  m_nBlobSize = 100;
  m_nCurAr = m_nOldAr   = NULL;
  m_nWidth    = nDefaultWidth;
  m_nHeight   = nDefaultHeight;
  m_nLength   = m_nWidth * m_nHeight;
 
  m_nFrameSeq    = 0;
  m_nDisplayFreq = 1;

  if( sFileName == NULL || !Load( sFileName ))
  {
	m_nCurAr = new uint8[m_nLength];
	m_nOldAr = new uint8[m_nLength];
  	KillAll();
	SeedBlob();
  }
  else
  {
	nDefaultWidth  = m_nWidth;
	nDefaultHeight = m_nHeight;
  }

  _help_screen = m_nOldAr;

  m_time = 0;
  m_nCycle = 4;

}


World2::~World2(void)
{
  delete [] m_nCurAr;
  delete [] m_nOldAr;
  delete [] m_pRuleAr;

  close( m_fdPipe );		// command pipe close
}

int World2::PipedCommand()
{
	char c[2];
	if( read( m_fdPipe, &c[0], 1 ) )
		return c[0];
	else
		return -1;
}



// global distribution of random seeds
void World2::Seed(int percent)
{
  register int i, j, offset;
  for(i = 1, offset = X_MAX+1; i < LAST_Y; ++i, offset += X_MAX)
    for(j = 1; j < LAST_X; ++j)
    {
        if ((rand() % 100) < percent)  Birth(offset, j, (char)(rand() % COLOURS));
    }
}

// specific seeding
void World2::SeedBlob()
{
  uint8 n  = (uint8) (rand() % COLOURS);
  int blob = m_nBlobSize/2;

  int nCenterWidth = m_nWidth/2;
  int nCenterHeight = m_nHeight/2;

  int i = (nCenterHeight - blob);
  int offset = (i*m_nWidth)+1;

  for(; i < (nCenterHeight + blob); ++i, offset += m_nWidth)
    for(int j = (nCenterWidth - blob); j < (nCenterWidth + blob); ++j)
    {
        Birth(offset, j, n);
    }
}



// set array to all DEAD

void World2::KillAll()
{
      memset(m_nCurAr, 0, m_nLength);
      memset(m_nOldAr, 0, m_nLength);
}


//////////////////////////////////////////////////////////////////////////////
// return a count of what your neighbours are (zero or not)
//////////////////////////////////////////////////////////////////////////////
static int count_diamond(uint8* m_nOldAr, int i, int j)
{
        int k = 0;
	int tmp2 = i;
	int tmp1 = tmp2 - X_MAX;	// set rows
	int tmp3 = tmp2 + X_MAX;

	if (m_nOldAr[tmp1 + j]) k++;
	if (m_nOldAr[tmp2 + j - 1]) k++;
	if (m_nOldAr[tmp2 + j + 1]) k++;
	if (m_nOldAr[tmp3 + j]) k++;

        return k;
}

static int count_box(uint8* m_nOldAr, int i, int j)
{
        int k = 0;
	int tmp2 = i;
	int tmp1 = tmp2 - X_MAX;	// set rows
	int tmp3 = tmp2 + X_MAX;

	if (m_nOldAr[tmp1 + j - 1]) k++;
	if (m_nOldAr[tmp1 + j]) k++;
	if (m_nOldAr[tmp1 + j + 1]) k++;

	if (m_nOldAr[tmp2 + j - 1]) k++;
	if (m_nOldAr[tmp2 + j + 1]) k++;

	if (m_nOldAr[tmp3 + j - 1]) k++;
	if (m_nOldAr[tmp3 + j]) k++;
	if (m_nOldAr[tmp3 + j + 1]) k++;

        return k;
}

/*
static int count_box2(uint8* m_nOldAr, int i, int j)
{
	static int half = COLOURS/2;
        int k = 0;
	int tmp2 = i;
	int tmp1 = tmp2 - X_MAX;	// set rows
	int tmp3 = tmp2 + X_MAX;

	if (m_nOldAr[tmp1 + j - 1] > half) k++;
	if (m_nOldAr[tmp1 + j] > half) k++;
	if (m_nOldAr[tmp1 + j + 1] > half) k++;

	if (m_nOldAr[tmp2 + j - 1] > half) k++;
	if (m_nOldAr[tmp2 + j + 1] > half) k++;

	if (m_nOldAr[tmp3 + j - 1] > half) k++;
	if (m_nOldAr[tmp3 + j] > half) k++;
	if (m_nOldAr[tmp3 + j + 1] > half) k++;

        return k;
}
*/


//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//                      RULES
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void World2::Threshhold_Growth(int i, int j)
{
        if      ( count_diamond(m_nOldAr, i, j) >= 2 )
                Birth(i, j, m_nOldAr[i+j] + m_nCycle);
        else    Death(i, j);

}

//////////////////////////////////////////////////////////////////////////////
void World2::Cyclic_Particle_Sys(int i, int j)
{
    int tmp2 = i;
    int tmp1 = tmp2 - X_MAX;	// set rows
    int tmp3 = tmp2 + X_MAX;
    int location = i + j;


  if ( m_nOldAr[location] )
        Birth(i, j, m_nOldAr[location] + m_nCycle);
  else if (     ( m_nOldAr[tmp2 + j-1] == 1 )
        ||      ( m_nOldAr[tmp2 + j+1] == 1 )

        ||      ( m_nOldAr[tmp3 + j+1] == 1 )
        ||      ( m_nOldAr[tmp3 + j  ] == 1 )
        ||      ( m_nOldAr[tmp3 + j-1] == 1 )

        ||      ( m_nOldAr[tmp1 + j+1] == 1 )
        ||      ( m_nOldAr[tmp1 + j  ] == 1 )
        ||      ( m_nOldAr[tmp1 + j-1] == 1 ))
        Birth(i, j);
}

//////////////////////////////////////////////////////////////////////////////
void World2::Stepping_Stone(int i, int j)
{
  char nbr = 0;
  char c, k;
  int tmp2 = i;
  int tmp1 = tmp2 - X_MAX;	// set rows
  int tmp3 = tmp2 + X_MAX;

  c = m_nOldAr[tmp2 + j];

  k = (char)(rand() % 4);             // pick random neighbour

  if (k == 0)
        nbr = m_nOldAr[tmp2 + j+1];
  else if (k == 1)
        nbr = m_nOldAr[tmp2 + j-1];
  else if (k == 2)
        nbr = m_nOldAr[tmp3 + j];
  else if (k == 3)
        nbr = m_nOldAr[tmp1 + j];

  if ((nbr % 12)+1 >= (c % 12)) // allow only a successor to replace me
  {
    Birth(i, j, nbr);
  }else
    Birth(i, j, c);
}




//////////////////////////////////////////////////////////////////////////////
void World2::Voter(int i, int j)
{
        // look at range 1 box neighbourhood around u
	int tmp = i;
	uint8 value;

        int k = count_box(m_nOldAr, i, j);
	value = m_nOldAr[tmp + j];
	if (value > (COLOURS /2) ) k++;

        if(k > 4)
        {
                Birth(i, j, value + m_nCycle);
        }else if (k < 4){
                Death(i, j);
        }else{
                if (rand() % 2)
			Birth(i, j, value + m_nCycle);
                else
                        Death(i, j);
    	}
}

//////////////////////////////////////////////////////////////////////////////
void World2::Voter2(int i, int j)
{
        // look at range 1 box neighbourhood around u

        uint8 k = count_diamond(m_nOldAr, i, j);
	uint8 value = m_nOldAr[i + j];
	if (value) k++;

        if((k > 3))
        {
                Birth(i, j, value + m_nCycle);
        }else{
                Death(i, j);
    	}
}




//////////////////////////////////////////////////////////////////////////////
void World2::Parity(int i, int j)
{
        int k = count_diamond(m_nOldAr, i, j);

        if (k % 2)
	{
                Birth(i, j, m_nOldAr[i + j] + m_nCycle);
        }else{
                Death(i, j);
    	}
}

//////////////////////////////////////////////////////////////////////////////
void World2::Conway(int i, int j)
{
        int k = count_box(m_nOldAr, i, j);
	char value = m_nOldAr[i + j];

        if (value)                                  // survive?
                //if (m_nOldAr[tmp + j] > 250)                  // die?
                        //Death(i, j);
                //else 
                if ((k == 2) || (k == 3)) 
                        Birth(i, j, value + m_nCycle);
                else
                        Death(i, j);
        else                                            // birth?
                if ((k == 3) || (k == 6))
                        Birth(i, j);    
                else
                        Death(i, j);
           
        
}

/*
// attempt at cca
void World2::Test(int i, int j)
{
        int k;

        if (m_nOldAr[i][j] % 4)                                      // survive?
                Birth(i, j, m_nOldAr[i][j] + m_nCycle);
        else                                            // birth?
                k = count_box(m_nOldAr, i, j);
                if (k == 2)     // threshm_nOldAr
                        Birth(i, j);
                else
                        Death(i, j);
}
*/
void World2::Test(int i, int j)
{
	int tmp2 = i;
	int tmp1 = tmp2 - X_MAX;	// set rows
	int tmp3 = tmp2 + X_MAX;
	int r;

        if (m_nOldAr[tmp2 + j])
                Birth(i, j, m_nOldAr[tmp2 + j] + m_nCycle);
        else if((r = rand() % 8) < 4)             // survive?
                if(r == 0)	// left
                        Birth(i, j, m_nOldAr[tmp2 + j-1]);
                else if (r == 1)//right
                        Birth(i, j, m_nOldAr[tmp2 + j+1]);
                else if (r == 2)//above
                        Birth(i, j, m_nOldAr[tmp1 + j]);
                else if (r == 3)//below
                        Birth(i, j, m_nOldAr[tmp3 + j]);
        else
                Death(i, j);
}


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






//
// Update operators by row or global

void World2::Update()
{
   register int i, j, offset;
   register pFun fun = m_pRuleAr[m_nRule];

   for(i = 1, offset = X_MAX; i < LAST_Y; i++, offset += X_MAX)
      for(j = 1; j < LAST_X; j++)
	 fun(offset, j);
}


void World2::Display()
{
    if( !(Time() % GetDisplayFreq()) )
	    _display->Update( m_nCurAr );
}



unsigned long World2::IncTime()
{
  // switch pointers
  m_nOldAr = (uint8*) ((long)m_nOldAr ^ (long)m_nCurAr);
  m_nCurAr = (uint8*) ((long)m_nCurAr ^ (long)m_nOldAr);
  m_nOldAr = (uint8*) ((long)m_nOldAr ^ (long)m_nCurAr);

  return ++m_time;
}

COLOR*  World2::GenPPM()
{
  COLOR* colArray = new COLOR [ SCREEN_SIZE ];
  register int index;

  if(colArray == NULL)
  {
    cout << "GenPPM() array was not allocated!!!" << endl;
    cout << "Image not Generated" << endl;
    return NULL;
  }

        // write data 
  for ( index = 0; index < SCREEN_SIZE ; index++ )
  {
      colArray[index] = _palette->GetColor( m_nCurAr[index] );
  }
  return (colArray);
}

BOOL World2::Load(char *name)   /* load PPM image */
{
  register int x, y, i;
  register COLOR* the_array;

  the_array = LoadPPM(name, &y, &x);

  if( the_array == NULL )
  {
	cout << "unable to load image! [" << name << "]" << endl;
 	m_nCurAr = new uint8[m_nLength];
 	m_nOldAr = new uint8[m_nLength];
	return FALSE;
  }

  m_nWidth  = x;
  m_nHeight = y;
  m_nLength = x * y;
  m_nCurAr = new uint8[m_nLength];
  m_nOldAr = new uint8[m_nLength];

  // create a palette from the image data
  _palette->SortPalette( the_array, m_nLength );

  // m_nCurAr[i] is an 8-bit index into the colour table
  for(i = 0; (i < m_nLength); ++i)
  {
    m_nCurAr[i] = _palette->MatchColor( &the_array[i] );
  }

  delete [] the_array;
  return TRUE;
}

BOOL World2::Save(char *sName, int n)    /* save PPM image */
{
  char sFileName[256];
  COLOR* array;

  sprintf(sFileName, "%s.%d", sName, n);            /* name extension */

  array = GenPPM();        // class function
  assert( array != NULL );

  int bRet = ::SavePPM(sFileName, array, m_nHeight, m_nWidth); // external API
  delete [] array;

  // delete temp storage
  return bRet;
}

void World2::Help()
{
    if(_help_screen != NULL) _display->Update(_help_screen);
    // get next event without busy waiting
    while(_display->Events() == NO_EVENT)	
       sleep(1); 
}
    //
    // coordination

void World2::EventLoop(BOOL bSaveFrames, int nLastFrame)
{
    int status, i = 0;
    int seed_percent = 50;
    
    RemoveBorders();
    Display();

    for(; (m_nFrameSeq <= nLastFrame) && (status != 'q');)
    {
	if( bSaveFrames && !(Time() % GetDisplayFreq()) )
	{
		Save("ca", m_nFrameSeq++);
	}

    //do{
	status = PipedCommand();	// input from outside(tcl) via pipe
	if( status == -1 )
	{
		status = _display->Events();
	}

        switch( status )
        {
                case NO_EVENT:			// default
                        break;
		case MOUSE_1:
                        NextRule();
                        break;
                case KEY_1:
                        SetRule( 1 );
                        break;
                case KEY_2:
                        SetRule( 2 );
                        break;
                case KEY_3:
                        SetRule( 3 );
                        break;
                case KEY_4:
                        SetRule( 4 );
                        break;
                case KEY_5:
                        SetRule( 5 );
                        break;
                case KEY_6:
                        SetRule( 6 );
                        break;
                case KEY_7:
                        SetRule( 7 );
                        break;
                case KEY_0:
                        SetRule( 0 );
                        break;
		case KEY_MINUS:
			if (m_nBlobSize > 10) m_nBlobSize -= 10;
			break;
		case KEY_EQUALS:
			if (m_nBlobSize < 200) m_nBlobSize += 10;
       			break;
		case KEY_BACKSLASH:
                        cout << "blob is "  << m_nBlobSize << endl;
			SeedBlob();
       			break;
                case KEY_S:
                case MOUSE_2:
                        Save("world2", i++);
                        break;
                case KEY_Q:
                case KEY_ESC:
                case MOUSE_3:
                        status = 'q';
                        break;
                case KEY_UP:
                        seed_percent += 10;
                        cout << "reseeding at: " << seed_percent << " percent";
                        KillAll();
                        Seed(seed_percent);
                        break;
                case KEY_DOWN:
                        seed_percent -= 10;
                        cout << "reseeding at: " << seed_percent 
                             << " percent" << endl;
                        KillAll();
                        Seed(seed_percent);
                        break;
                case KEY_LEFT:
		case KEY_L:
                        cout << "reseeding w/ blob" << m_nBlobSize << endl;
                        KillAll();
                        SeedBlob();
                        break;

		case KEY_Z:
			DecCycle();
			cout << "cycle at: " << GetCycle() << endl;
			break;
		case KEY_X:
			IncCycle();
			cout << "cycle at: " << GetCycle() << endl;
			break;
		case KEY_C:
			SetCycle(0);
			cout << "cycle halted" << endl;
			break;
                case KEY_H:
			Help();
			break;

		case KEY_U:
			cout << "Displaying every " << GetDisplayFreq() << " updates" << endl;
			IncDisplayFreq();
			break;
		case KEY_D:
			cout << "Displaying every " << GetDisplayFreq() << " updates" << endl;
			DecDisplayFreq();
			break;
                default:
                        cout << "Undefined keypress: " << status << endl;
                        break;
        }
   //} while ( status != NO_EVENT );

	IncTime();

	if( !(Time() % GetDisplayFreq()) )
	{
		cout << "time(" << Time()
		     << ") rule(" << GetRule()
		     << ")\tupdating... " << flush;
	}

	Update();

	if( !(Time() % GetDisplayFreq()) )
	{
		cout << "done, displaying... " << endl;
	}

	Display();
    }
}

