/*
 *   ksame 0.4 - simple Game
 *   Copyright (C) 1997,1998  Marcus Kreutzberger
 *
 *   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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include "StoneField.h"
#include <kdebug.h>
#include <assert.h>

StoneFieldState::StoneFieldState(const StoneField &stonefield)
{
    field=new unsigned char[stonefield.maxstone];
    for (int i=0;i<stonefield.maxstone;i++)
        field[i]=stonefield.field[i].color;

    colors=stonefield.colors;
    board=stonefield.board;
    score=stonefield.score;
    gameover=stonefield.gameover;
}

StoneFieldState::~StoneFieldState() {
    delete[] field;
}

void
StoneFieldState::restore(StoneField &stonefield) const {
    for (int i=0;i<stonefield.maxstone;i++) {
        stonefield.field[i].color=field[i];
        stonefield.field[i].changed=true;
        stonefield.field[i].marked=false;
    }

    stonefield.colors=colors;
    stonefield.board=board;
    stonefield.score=score;
    stonefield.marked=0;
    stonefield.gameover=gameover;
}

StoneField::StoneField(int width, int height,
                       int colors, unsigned int board,
                       bool undoenabled)
{
    Q_ASSERT(width>0);
    Q_ASSERT(height>0);

    if (undoenabled) undolist=new TQPtrList<StoneFieldState>;
    else undolist=0;

    sizex=width;
    sizey=height;
    maxstone=sizex*sizey;
    field=new Stone[maxstone];
    newGame(board,colors);
    m_gotBonus= false;
}

StoneField::~StoneField() {
    delete[] field;
    delete undolist;
    kdDebug() << "~StoneField\n" << endl;
}

int StoneField::count(int color){
  int c = 0;
  Stone *stone=field;
  for (int i=0;i<maxstone;i++,stone++) {
    if(stone->color == color)
      c++;
  }
  return c;
}

int
StoneField::width() const {
    return sizex;
}

int
StoneField::height() const {
    return sizey;
}

void
StoneField::newGame(unsigned int board,int colors) {
    kdDebug() << "StoneField::newgame board "
              << board << " colors " << colors << endl;
    if (colors<1) colors=3;
    if (colors>7) colors=7;
    this->colors=colors;
    this->board=board;
    reset();
}

void
StoneField::reset() {
    random.setSeed(board);

    Stone *stone=field;
    for (int i=0;i<maxstone;i++,stone++) {
        stone->color=1+random.getLong(colors);
        stone->marked=false;
        stone->changed=true;
    }

    gameover=-1;
    score=0;
    marked=0;

    if (undolist) {
        undolist->setAutoDelete(true);
        undolist->clear();
    }

    int c[7];
    int j;
    for (j=0;j<7;j++) c[j]=0;

    for (j=0,stone=field;j<maxstone;j++,stone++) {
        c[stone->color]++;
    }
    kdDebug() << "red    " << c[1] << endl;
    kdDebug() << "blue   " << c[2] << endl;
    kdDebug() << "yellow " << c[3] << endl;
    kdDebug() << "green  " << c[4] << endl;
}

int
StoneField::map(int x,int y) {
	assert (!(x<0||y<0||x>=sizex||y>=sizey));
	return x+y*sizex;
}

int
StoneField::mark(int x,int y,bool force) {
	int index=map(x,y);

	if (index<0) {
		unmark();
		return 0;
	}

	if (field[index].marked) return -1;
	unmark();

	mark(index,field[index].color);

	if (marked==1&&!force) {
		field[index].marked=false;
		marked=0;
	}
	return marked;
}

void
StoneField::mark(int index,unsigned char color) {
	if ( index<0 || index>=maxstone ) return;

	Stone &stone=field[index];

	if (stone.marked) return;

	if (!stone.color || stone.color!=color) return;

	stone.changed=true;
	stone.marked=true;
	marked++;

	// mark left
	if ((index%sizex)!=0) mark(index-1,color);
	// mark right
	if (((index+1)%sizex)!=0) mark(index+1,color);
	// mark upward
	if (index>=sizex) mark(index-sizex,color);
	// mark downward
	if (index<(sizex-1)*sizey) mark(index+sizex,color);
}

void
StoneField::unmark() {
	if (!marked) return;

	Stone *stone=field;
	for (int i=0;i<maxstone;i++,stone++) {
		stone->marked=false;
		stone->changed=true;
	}
	marked=0;
}

int
StoneField::remove(int x,int y,bool force) {
	int index=map(x,y);

	if (index<0) return 0;

	if (!field[index].marked) {
		mark(x,y,force);
	}

	if (!marked) return 0;

	// remove a single stone??
	if (marked==1&&!force) return 0;

	// add current field to undolist
	if (undolist)
		undolist->append(new StoneFieldState(*this));

	// increase score
	if (marked>2)
		score+=(marked-2)*(marked-2);

	// remove marked stones
	Stone *stone=field;
	for (int i=0;i<maxstone;i++,stone++) {
		if (stone->marked) {
			stone->color=0;
			stone->changed=true;
			stone->marked=false;
		}
	}
	int removed=marked;
	marked=0;

	for (int col=0;col<sizex;col++) {
		int i1=col+maxstone-sizex;
		while ( i1>=0 && field[i1].color ) i1-=sizex;
		int i2=i1;
		while (i2>=0) {
			while ( i2>=0 && !field[i2].color ) i2-=sizex;
			while ( i2>=0 && field[i2].color ) {
				field[i1].color=field[i2].color;
				field[i1].changed=true;
				field[i2].color=0;
				field[i2].changed=true;
				i1-=sizex;
				i2-=sizex;
			}
		}
	}

	// find the last column that has something
	int lastcol = sizex;
	while (lastcol > 0 && !field[map(lastcol-1, sizey-1)].color) {
		lastcol--;
	}

	for (int col=0;col<lastcol-1;) {
		bool empty = true;
		for (int row = 0; row < sizey; row++)
			if (field[map(col, row)].color) {
				empty = false;
				break;
			}
		if (!empty) {
			col++;
			continue;
		}
		int nextfullcol = col + 1;
		while (nextfullcol < sizex &&
			   !field[map(nextfullcol, sizey - 1)].color)
			nextfullcol++;

		if (nextfullcol > sizex - 1)
			break; // we're ready

		for (int row=0; row < sizey; row++) {
			int source = map(nextfullcol, row);
			int dest = map(col, row);
			field[dest].color=field[source].color;
			field[dest].changed=true;
			field[source].color=0;
			field[source].changed=true;
		}
	}

	// add a bonus, if field is empty
	if (!field[map(0, sizey-1)].color) {
		score+=1000;
		m_gotBonus= true;
	}

	// gameover is undefined
	gameover=-1;
	return removed;
}

bool StoneField::undoPossible() const {
	return !(!undolist||undolist->isEmpty());
}

int
StoneField::undo(int count) {
	if (!undoPossible())
		return 0;
	if (count <= 0)
		return 0;
     int undocount=1;
     StoneFieldState *state=0;
     undolist->setAutoDelete(true);
     while (--count>0) {
	  if (undolist->count()==1) break;
	  undolist->removeLast();
	  undocount++;
     }
     state=undolist->getLast();
     Q_ASSERT(state);
     state->restore(*this);
     undolist->removeLast();
     return undocount;
}

bool
StoneField::isGameover() const {
     register int i=maxstone-1;;
     register unsigned char color;

     if (gameover>=0) return (bool)gameover;
     // kdDebug() << "-->gameover" << endl;

     while (i>=0) {
	  // kdDebug() << i << " " << field[i].color << endl;
	  // ignore empty fields
	  while ( i>=0 && field[i].color==0 ) i--;
	  // Wenn Stein gefunden,
	  // dann die Nachbarn auf gleiche Farbe pruefen.
	  while (  i>=0 && (color=field[i].color) ) {
	       // check left
	       if ( (i%sizex)!=0 && field[i-1].color==color)
		    goto check_gameover;
	       // check upward
	       if ( i>=sizex && field[i-sizex].color==color)
		    goto check_gameover;
	       i--;
	  }
     }
 check_gameover:
     gameover=(i<0);
     //  kdDebug() << "<--gameover" << endl;
     return (bool)gameover;
}

bool StoneField::gotBonus() const {
	return m_gotBonus;
}

int
StoneField::getBoard() const {
     return board;
}

int
StoneField::getScore() const {
     return score;
}

int
StoneField::getColors() const {
     return colors;
}

int
StoneField::getMarked() const {
     return marked;
}

int
StoneField::getFieldSize() const {
     return maxstone;
}

struct Stone *
StoneField::getField() const {
     return field;
}