/***************************************************************************
                          board.cpp  -  description
                             -------------------
    begin                : Thu Aug 17 2000
    copyright            : (C) 2000 by Waldemar Baraldi
    email                : baraldi@lacasilla.com.ar
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "board.h"
#include "player.h"
#include "mosaic.h"
#include "wallpaper.h"

#define MAX_SPEED   80

Board::Board():AnimatedCanvas(){
  int i,j;

  for (i=0;i<BoardRows;i++)
    for (j=0;j<BoardColumns;j++){
      board[i][j]=NULL;
      changes[i][j]=false;
    }
  paint_all=true;
  lps=new List();
  flow_row=flow_column=-1;
  entry=exit=NULL;
  actual_output=Void;
  actual_input=Void;
  state=Playing;
  tick_amount=1;
  tank=NULL;
  background=NULL;
  required=0;
  still_required=0;
  required_changed=false;
  entry_row=-1;
  entry_column=-1;
}

Board::~Board(){
  int i,j;
  for (i=0;i<BoardRows;i++)
    for (j=0;j<BoardColumns;j++)
      if (board[i][j]) delete board[i][j];
  delete lps;
  if (background) delete background;
  if (tank) delete tank;
}

void Board::setStartDelay(int delay){
  if (tank) delete tank;
  tank=new Tank(delay);
  tank->setPos(TankX, TankY);
}

void Board::setEntry(Entry * entry, int row, int column){
  flow_row=row;
  flow_column=column;
  actual_input=Void;
  actual_output=entry->getOutput(Void);
  setPipe(entry, row, column);
  entry_row=row;
  entry_column=column;
  entry->setLast(true);
  last=entry;
  last_row=row;
  last_column=column;
}

void Board::setExit(Exit * exit, int row, int column){
  setPipe(exit, row, column);
}

bool Board::isRemovable(int row, int column){
  if (board[row][column])
    return board[row][column]->isRemovable();
  return true;
}

void Board::setPipe(Pipe * pipe, int row, int column){
  required_changed=true;
  changes[row][column]=true;
  Pipe * aux=board[row][column];
  if (aux==last) {
    last=pipe;
    pipe->setLast(true);
  }
  if (aux){
    if (aux->isRemovable()){
      delete aux;
      board[row][column]=pipe;
      pipe->setPos(column*pipe->width()+x, row*pipe->height()+y);
    }
  }else{
    board[row][column]=pipe;
    pipe->setPos(column*pipe->width()+x, row*pipe->height()+y);
  }
}

Pipe * Board::getPipe(int row, int column){
  return board[row][column];
}

BoardState Board::getState(){
  return state;
}

unsigned int Board::getRequired(){
  return required;
}

unsigned int Board::getStillRequired(){
	if (!required_changed) return still_required;
	else return (still_required=countStillRequired());
}

unsigned int Board::countStillRequired(){
  int row=entry_row;
  int column=entry_column;
  bool end=false;
  unsigned int used=0;
  required_changed=false;

  Pipe * aux_last=board[entry_row][entry_column];
  CardinalPoint con=board[row][column]->getOutput(Void);//Entry output
  changes[last_row][last_column]=true;
  while (!end){
    used++;
    end=true;
    switch (con){
      case East:{
        column++;
	      if (column<BoardColumns)
	        if (board[row][column] != NULL)
   	        if (board[row][column]->hasConnection(West))
	            if (!board[row][column]->isRestrictedAsOutput(West)){
            	  con=board[row][column]->getOutput(West);
            	  end=false;
            	}
        break;
      }
      case South:{
        row++;
	      if (row<BoardRows)
 	        if (board[row][column] != NULL)
            if (board[row][column]->hasConnection(North))
	            if (!board[row][column]->isRestrictedAsOutput(North)){
            	  con=board[row][column]->getOutput(North);
            	  end=false;
            	}
        break;
      }
      case West:{
        column--;
        if (column>=0)
 	        if (board[row][column] != NULL)
            if (board[row][column]->hasConnection(East))
	            if (!board[row][column]->isRestrictedAsOutput(East)){
            	  con=board[row][column]->getOutput(East);	
            	  end=false;
            	}
        break;
      }
      case North:{
        row--;
        if (row>=0)
 	        if (board[row][column] != NULL)
            if (board[row][column]->hasConnection(South))
	            if (!board[row][column]->isRestrictedAsOutput(South)){
            	  con=board[row][column]->getOutput(South);	
            	  end=false;            	
            	}
        break;
      }
      default:{
        used--; //La salida no se cuenta como uno usado
        break;
      }
    }
    if (!end){
      aux_last=board[row][column];            	
      last_row=row;
      last_column=column;
    }
  }//while
  used--; //La entrada no se cuenta como usado
  if (last != aux_last){
    last->setLast(false);
    last=aux_last;
    last->setLast(true);
  }

  if (used>required) return 0;
  return required-used;
}

void Board::addPointer(Pointer * p){
  lps->insert(lps->getFirst(), (Pointer *)p);
}

void Board::removePointer(Pointer * p){
  lps->remove(lps->indexOf((Pointer*)p));
}

void Board::movePointer(Pointer *p, int row, int column){
  if (row>=0 && row<BoardRows && column>=0 && column<BoardColumns){
    if (p->getRow()>=0 && p->getRow()<BoardRows && p->getColumn()>=0 && p->getColumn()<BoardColumns)
      changes[p->getRow()][p->getColumn()]=true;
    p->setRowColumn(row, column);
    p->setMoved(true);
  }
}

void Board::setPos(int x, int y){
  Canvas::setPos(x,y);
  int i,j;
  for (i=0;i<BoardRows;i++)
    for (j=0;j<BoardColumns;j++)
      if (board[i][j])
        board[i][j]->setPos(j*board[i][j]->width()+x, i*board[i][j]->height()+y);
  paint_all=true;
}

void Board::setSpeed(int amount){
  tick_amount=amount;
}

void Board::setMaxSpeed(){
  tick_amount=MAX_SPEED;
}

void Board::setRequired(unsigned int value){
  required=value;
  required_changed=true;
}

void Board::setBackgroundType(BackgroundType type){
  background_type=type;
}

void Board::tick(){
  int aux;

  tank->tick();
  last->tick();

  for (int i=0;i<BoardRows;i++)
    for (int j=0;j<BoardColumns;j++)
      if (board[i][j])
        if (board[i][j]->getBonus()!=NormalBonus){
           board[i][j]->tick();
           changes[i][j]=true;
        }

  changes[last_row][last_column]=true;
  if (!(tank->isEmpty())){//Descuento del tanque
    tank->decFullLevel(tick_amount);
    changes[flow_row][flow_column]=true;
  }
  else{//Aumento el actual
    aux=(getPipe(flow_row, flow_column))->getFullLevel(actual_input);
    if (aux<(getPipe(flow_row, flow_column))->full()){
      (getPipe(flow_row, flow_column))->incFullLevel(actual_input, tick_amount);
      changes[flow_row][flow_column]=true;
    }
    else{//Paso al siguiente
      Pipe * next=NULL;
      Pipe * last=board[flow_row][flow_column];
      actual_input=Void;
      switch (actual_output){
        case North:{
          if (flow_row>0){
            next=getPipe(--flow_row, flow_column);
            actual_input=South;
          }
          break;
        }
        case South:{
          if (flow_row<BoardRows-1){
            next=getPipe(++flow_row, flow_column);
            actual_input=North;
          }
          break;
        }
        case East:{
          if (flow_column<BoardColumns-1){
            next=getPipe(flow_row, ++flow_column);
            actual_input=West;
          }
          break;
        }
        case West:{
          if (flow_column>0){
            next=getPipe(flow_row, --flow_column);
            actual_input=East;
          }
          break;
        }
        default:break;
      }
      if (actual_output==Void)
        if (getStillRequired()==0)
          state=OverWon;
        else
          state=OverLost;
      else
        if (actual_input==Void){
          state=OverLost;
      }else{
        if (next)
          if (next->hasConnection(actual_input) && !(next->isRestrictedAsOutput(actual_input))){
             actual_output=next->getOutput(actual_input);
             next->incFullLevel(actual_input,tick_amount);

             /** Bonus stuff*/
             if (!next->getOwner() && last->getOwner())
                 next->setOwner(last->getOwner());

             Player * owner;
             if ((owner=next->getOwner()))
               switch (next->getBonus()){
                 case LifeBonus:{
                   owner->incLives();
                   next->setBonus(NormalBonus);
                   break;
                 }
                 case TimeBonus:{
                   //Debera incrementarse el valor del tiempo en board.
                   break;
                 }
                 default:{
                   owner->incScore(next->getBonus());
                 }
               }
             /*End of Bonus stuff*/

             changes[flow_row][flow_column]=true;
           }else state=OverLost;
        else state=OverLost;
      }
    }
  }
}

void Board::paint(VideoManager * vm){
  int i,j;
  Index * index;
  Pointer * aux;
  int pc, pr;

  if (!background){
    Image * ima=vm->getImageManager()->getImage(new Str("board_back.jpg"));
    switch (background_type){
      case WallpaperBackground:{
        background=new WallPaper(ima);
        break;
      }
      case MosaicBackground:{
        background=new Mosaic(ima);
        break;
      }
    }
    background->setPos(x, y);
  }

  /** Pinta todos los pipes.*/
  if (paint_all){
    background->paint(vm);
    for (i=0;i<BoardRows;i++)
      for (j=0;j<BoardColumns;j++){
        if (board[i][j]) board[i][j]->paint(vm);
        changes[i][j]=false;
      }
  }else{/** Pinta solo los que han cambiado*/
    for (i=0;i<BoardRows;i++)
      for (j=0;j<BoardColumns;j++){
        if (changes[i][j]){
          background->repaint(vm, j*PipeWidth, i*PipeHeight, PipeWidth, PipeHeight);
          if (board[i][j]) board[i][j]->paint(vm);
          changes[i][j]=false;
        }
      }
  }


  /** Pinta y marca los punteros para la proxima entrada.*/
  index=lps->getFirst();
  for (i=0;i<lps->nObjects();i++){
    aux=(Pointer*)lps->getObject(index);
    pr=aux->getRow();
    pc=aux->getColumn();
    if (aux->moved()
    ||(flow_column==pc && flow_row==pr)
    ||(last_column==pc && last_row==pr)
    ||(board[pr][pc] && board[pr][pc]->getBonus()!=NormalBonus)){
      aux->setPos(pc*aux->width+x, pr*aux->height+y);
      aux->paint(vm);
      if (aux->moved()) aux->setMoved(false); else aux->setMoved(true);
    }
    index=lps->getNext(index);
  }
  paint_all=false;

  /** Pinta la mancha si el juego esta perdido.*/
  if (state==OverLost){
    Image * mancha=vm->getImageManager()->getImage(new Str("splash.png"));
    int mx=flow_column*PipeWidth+x;
    int my=flow_row*PipeHeight+y;
    switch (actual_input){
      case Void:{ //Esta contra un borde
        switch (actual_output){
          case North:my-=PipeHeight/2;break;
          case South:my+=PipeHeight/2;break;
          case West:mx-=PipeWidth/2;break;
          case East:mx+=PipeWidth/2;break;
          default:break;
        }
        break;
      }
      case North:my-=PipeHeight/2;break;
      case South:my+=PipeHeight/2;break;
      case West:mx-=PipeWidth/2;break;
      case East:mx+=PipeWidth/2;break;
      default:break;
    }
    vm->setClipping(x, y, width(), height());
    vm->enableClipping(true);
    vm->blit(mancha, mx, my);
    vm->enableClipping(false);
  }
  if (tank->isChanged()) tank->paint(vm);
}

int Board::width(){
 return BoardColumns*PipeWidth;
}

int Board::height(){
 return BoardRows*PipeHeight;
}

