/* This file is part of cardwords
   (c) 1998 1999 Tobias Peters
   see file COPYING for the copyright terms.
   
   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// cardwords_dicbot_cardtable.cc

#include <assert.h>
#include "cardwords_iostream.hh"

#include "cardwords_dicbot_cardtable.hh"
#include "cardwords_dictionary.hh"

void 
CardWords_DicBot_CardTable::add_card (CardWords_CardTableLocation loc,

                                      const
                                      CardWords_CardDescription &
                                      carddesc)
{
  size_t parameter;
  
  assert (loc.first < get_width());
  assert (loc.second < get_height());

  assert ((*this)[loc].is_cell_blocked()
          == false); // Should have been checked.

  if ((*this)[loc].get_containsCard() == false) {
    ++no_of_contained_cards;
  }
  (*this)[loc].change_card(carddesc);

  (*this)[loc].clear_must_nots(horizontal);
  (*this)[loc].clear_must_nots(vertikal);

  if (loc.first != 0) {
    parameter = loc.first-1;
    (*this)(parameter,loc.second).unset_endPossible(horizontal);
    do {
      if ((*this)(parameter,loc.second).get_containsCard() == false) {
        (*this)(parameter,loc.second).clear_must_nots(vertikal);
        break;
      }
      if ((*this)(parameter,loc.second).is_cell_blocked() == true) {
        break;
      }
    } while (parameter-- > 0);
  }
  if ( (parameter = loc.first + 1) < get_width() ) {
    (*this)(parameter,loc.second).unset_startPossible(horizontal);
    do {
      if ((*this)(parameter,loc.second).get_containsCard() == false) {
        (*this)(parameter,loc.second).clear_must_nots(vertikal);
        break;
      }
      if ((*this)(parameter,loc.second).is_cell_blocked() == true) {
        break;
      }
    } while (++parameter < get_width());
  }
  if (loc.second != 0) {
    parameter = loc.second-1;
    (*this)(loc.first, parameter).unset_endPossible(vertikal);
    do {
      if ((*this)(loc.first, parameter).get_containsCard() == false) {
        (*this)(loc.first, parameter).clear_must_nots(horizontal);
        break;
      }
      if ((*this)(loc.first, parameter).is_cell_blocked() == true) {
        break;
      }
    } while (parameter-- > 0);
  }
  if ( (parameter = loc.second + 1) < get_height() ) {
    (*this)(loc.first, parameter).unset_startPossible(vertikal);
    do {
      if ((*this)(loc.first, parameter).get_containsCard() == false) {
        (*this)(loc.first, parameter).clear_must_nots(horizontal);
        break;
      }
      if ((*this)(loc.first, parameter).is_cell_blocked() == true) {
        break;
      }
    } while (++parameter < get_height());
  }
}

void 
CardWords_DicBot_CardTable::remove_card (CardWords_CardTableLocation loc)
{
  size_t parameter;
  
  assert (loc.first < get_width());
  assert (loc.second < get_height());

  assert ((*this)[loc].is_cell_blocked() == false);

  if ((*this)[loc].get_containsCard()) {
    --no_of_contained_cards;
  }
  (*this)[loc].remove_card();

  if (loc.first != 0) {
    parameter = loc.first-1;
    (*this)(parameter,loc.second).set_endPossible(horizontal);
    do {
      if ((*this)(parameter,loc.second).get_containsCard() == false) {
        (*this)(parameter,loc.second).clear_must_nots(vertikal);
        break;
      }
    } while (parameter-- > 0);
  }
  if ( (parameter = loc.first + 1) < get_width() ) {
    (*this)(parameter,loc.second).set_startPossible(horizontal);
    do {
      if ((*this)(parameter,loc.second).get_containsCard() == false) {
        (*this)(parameter,loc.second).clear_must_nots(vertikal);
        break;
      }
    } while (++parameter < get_width());
  }
  if (loc.second != 0) {
    parameter = loc.second-1;
    (*this)(loc.first, parameter).set_endPossible(vertikal);
    do {
      if ((*this)(loc.first, parameter).get_containsCard() == false) {
        (*this)(loc.first, parameter).clear_must_nots(horizontal);
        break;
      }
    } while (parameter-- > 0);
  }
  if ( (parameter = loc.second + 1) < get_height() ) {
    (*this)(loc.first, parameter).set_startPossible(vertikal);
    do {
      if ((*this)(loc.first, parameter).get_containsCard() == false) {
        (*this)(loc.first, parameter).clear_must_nots(horizontal);
        break;
      }
    } while (++parameter < get_height());
  }
}
  

// counts the number of free places after this field
size_t
CardWords_DicBot_CardTable::count_free (CardWords_CardTableLocation loc,
                                        CardWords_Direction dir) const
{
  assert (loc.first < get_width());
  assert (loc.second < get_height());

  size_t border = (dir == horizontal) ? get_width() : get_height();
  // size_t &fix = (dir == horizontal) ? loc.second : loc.first;
  size_t &var = (dir == horizontal) ? loc.first : loc.second;

  int count = 0;

  for (;
       var < border
         && (*this)[loc].is_cell_blocked() == false;
       var++)
    {
      if ((*this)[loc].get_containsCard() == false) {
        count++;
      }
    }
  return count;
}

// Returns -1 (MOVE_VALID) if the move is valid.
// Returns -2 (MOVE_INVALID) if the move is invalid because the move is not in
// the dictionary.
// Returns 0<= x < move.size() if the move's x'th card led to an invalid
// side word.
// If the given pointer to CardWords_String is not 0, sets CardWords_String
// to the word that was not in the dictionary.
int
CardWords_DicBot_CardTable::
private_check_move (const CardWords_Move & move,
                    const CardWords_Dictionary * dict,
                    CardWords_String * false_word,
                    bool recursive)
{
  // The recursive flag indicates that this call came from inside check_move()
  // itself.
  // It is therefore unnecessary to check for adjacent words, this (move) is
  // already the adjacent word.

  CardWords_CardTableLocation loc = move.location;

  assert (loc.first < get_width());
  assert (loc.second < get_height());
  
  if (move.size() == 0
      || loc.first >= get_width()
      || loc.second >= get_height())
    return MOVE_INVALID;

  // in case of a recursive call, count_free checking is superfluo-us.
  // we already know that there is one free place at move.location.
  if (!recursive && count_free(loc, move.direction) < move.size())
    return MOVE_INVALID;

  // This reference is a trick to avoid two much direction-checking.
  // By modifying var, the right coordinate in loc is modified.
  // I like references, as long as I remember what is tied with what.
  size_t &var = (move.direction == horizontal) ? loc.first : loc.second;

  // Find the place where the word starts:
  while ((*this)[loc].is_startPossible(move.direction) == false) {
    var--;
  }

  size_t used_cards = 0;
  CardWords_Move side_move;
  CardWords_String word;
  bool touching_card = false;
  bool obeying_start_restrictions = false;
  if (is_empty() == false) {
    // No need to search further.
    obeying_start_restrictions = true;
  }
  
  for(;/*break at end of loop*/;var++) {
    if ( obeying_start_restrictions == false
         &&
         ( starting_pos.first == loc.first ||
           starting_pos.first >= get_width()
           )
         &&
         ( starting_pos.second == loc.second ||
           starting_pos.second >= get_height()
           )
         ) {
      obeying_start_restrictions = true;
    }
    if ((*this)[loc].get_containsCard()) {
      word += (*this)[loc].get_card()->get_meaning();
      touching_card = true;
    }
    else {
      if (!recursive && has_neighbour(loc,!move.direction)) {
	side_move = move[used_cards];
	side_move.location = loc;
	side_move.direction = !move.direction;
        touching_card = true;
	if(dict != 0 &&
           IS_MOVE_INVALID(private_check_move(side_move,dict,false_word,true))
           )
          {
            // The move led to an invalid side move
            (*this)[loc].add_must_not(move[used_cards].get_meaning(),
                                      move.direction);
            return used_cards; // Indication that this side move was invalid.
          }
      }
      word += move[used_cards++].get_meaning();
    }
    if (used_cards == move.size()
	&& (*this)[loc].is_endPossible(move.direction) == true)
      break;
  }
  if ( ( is_empty() == false && touching_card == false)
       ||
       ( is_empty() && obeying_start_restrictions == false)
       ) {
    return MOVE_INVALID;
  }
  if (dict != 0 && dict->contains_word(word))
    {
      return MOVE_VALID;
    }
  if (false_word != 0) {
    *false_word = word;
  }
  return MOVE_INVALID;
}

// Returns -1 (MOVE_VALID) if the move is valid.
// Returns -2 (MOVE_INVALID) if the move is invalid because the move is not
// in the dictionary.
// Returns 0<= x < move.size() if the move's x'th card led to an invalid
// side word.
// Writes the score of that move into the int referenced by the second
// argument, if the move is valid.

// This function does not actually call check_move, because much of the work
// done when only checking words needs also to be done when counting the
// points.
// So instead of calling check_move(), this is a cut and pasted and modified
// private_check_move() function itself.
int
CardWords_DicBot_CardTable::
private_count_move (const CardWords_Move & move,
                    int * score,
                    const CardWords_Dictionary * dict,
                    CardWords_String * false_word,
                    bool recursive)
{
  CardWords_CardTableLocation loc = move.location;

  assert (loc.first < get_width());
  assert (loc.second < get_height());
  
  if (move.size() == 0
      || loc.first >= get_width()
      || loc.second >= get_height())
    return MOVE_INVALID;

  if (!recursive && count_free(loc, move.direction) < move.size())
    return MOVE_INVALID;

  size_t &var = (move.direction == horizontal) ? loc.first : loc.second;
  while ((*this)[loc].is_startPossible(move.direction) == false) {
    var--;
  }

  size_t used_cards = 0;
  CardWords_Move side_move;
  CardWords_String word;

  *score = 0;
  
  int side_score = 0;
  int tmp_side_score;
  int factor = 1;

  int cell_points = 0;

  bool touching_card = false;
  bool obeying_start_restrictions = false;
  if (is_empty() == false) {
    // No need to search further.
    obeying_start_restrictions = true;
  }
  
  for(;/*break at end of loop*/;var++) {
    const CardWords_DicBot_CardTableCell & cell = (*this)[loc];

    if ( obeying_start_restrictions == false
         &&
         ( starting_pos.first == loc.first ||
           starting_pos.first >= get_width()
           )
         &&
         ( starting_pos.second == loc.second ||
           starting_pos.second >= get_height()
           )
         ) {
      obeying_start_restrictions = true;
    }

    if (cell.get_containsCard()) {
      touching_card = true;
      word += cell.get_card()->get_meaning();
      *score += cell.get_card()->get_points();
    }
    else {
      // Place a card here.
      if (!recursive && has_neighbour(loc,!move.direction)) {
        touching_card = true;
	side_move = move[used_cards];
	side_move.location = loc;
	side_move.direction = !move.direction;
        if (IS_MOVE_VALID(private_count_move(side_move,      \
                                             &tmp_side_score,\
                                             dict,           \
                                             false_word,
                                             true))) {
          side_score += tmp_side_score;
        }
        else {
          // side move was invalid
          (*this)[loc].add_must_not(move[used_cards].get_meaning(),
                                    move.direction);
          return used_cards; // Indication that this side move was invalid.
        }
      }
      *score += move[used_cards].get_points() * cell.get_letter_factor();
      factor *= cell.get_word_factor();
      word += move[used_cards++].get_meaning();
      if (!recursive) {
        cell_points += (*this)[loc].get_cell_points();
      }
    }
    if (used_cards == move.size()
	&& cell.is_endPossible(move.direction) == true)
      break;
  }

  if ( ( is_empty() == false && touching_card == false)
       ||
       ( is_empty() && obeying_start_restrictions == false)
       ) {
    return MOVE_INVALID;
  }
  if ( dict != 0 && !(dict->contains_word(word)) )
    {
      if (false_word != 0) {
        *false_word = word;
      }
      return MOVE_INVALID;
    }

  *score *= factor;
  *score += side_score + cell_points;

  if (bonus_table != 0 && move.size() <= bonus_table->get_hand_capacity()) {
    *score += (*bonus_table)[move.size()];
  }
  return MOVE_VALID;
}


// the same as count_move, but actually lays the letters onto the card-table
int
CardWords_DicBot_CardTable::add_move (const CardWords_Move & move,
                                int * score,
				const CardWords_Dictionary * dict = 0,
                                CardWords_String * false_word = 0)
{
  int validity = count_move (move, score, dict, false_word);

  if (IS_MOVE_INVALID(validity)) {
    return validity;
  }
  
  CardWords_CardTableLocation loc = move.location;

  size_t &var = (move.direction == horizontal) ? loc.first : loc.second;
  while ((*this)[loc].is_startPossible(move.direction) == false) {
    var--;
  }

  size_t used_cards = 0;

  for(;/*break at end of loop*/;var++) {
    assert ((*this)[loc].is_cell_blocked() == false);
    if ((*this)[loc].get_containsCard() == false) {
      add_card(loc, move[used_cards++]);
    }
    if (used_cards == move.size()
	&& (*this)[loc].is_endPossible(move.direction) == true)
      break;
  }

  return validity;
}

CardWords_DicBot_CardTable *
read_card_table( class istream & istream )
{
  size_t width, height;
  char ch = ' ';
  CardWords_CardTableLocation start;

  istream >> width;
  if (istream.bad())
    throw CardWords_XBadInput("CardWords_DicBot_CardTable",
                              gettext("number: width"));

  while (istream.good() && (ch == ' ' || ch == '\n' || ch == '\t'))
    istream >> ch;
  if (ch != '*')
    throw CardWords_XBadInput("CardWords_DicBot_CardTable","*");

  istream >> height;
  if (istream.bad())
    throw CardWords_XBadInput("CardWords_DicBot_CardTable",
                              gettext("number: height"));

  istream >> start;
  if (istream.bad())
    throw CardWords_XBadInput("CardWords_DicBot_CardTable",
                            gettext("CardWords_CardTableLocation: Start"));
  
  CardWords_DicBot_CardTable * card_table =
    new CardWords_DicBot_CardTable(width, height, start);
  
  if (card_table == 0) {
    throw CardWords_XLowResources (gettext("creating CardWords_DicBot_"\
                                           "CardTable"));
  }

  CardWords_CardTableLocation loc;
  int value;
  CardWords_CardDescription desc;

  // read all location descriptions:
  for (istream >> loc;
       istream.good() && loc.first < width && loc.second < height;
       istream >> loc) {

    // read the description of this location:
    do {
      ch = ' ';
      while (istream .good() && (ch == ' ' || ch == '\n' || ch == '\t')) {
	istream >> ch;
      }
      if (istream.bad()
          || (ch != 'L'
              && ch != 'W'
              && ch != 'T'
              && ch != ';'
              && ch != 'B'
              && ch != 'C')
          ) {
	throw CardWords_XBadInput("CardWords_DicBot_CardTable",
				gettext("\'L\', \'W\', \'T\',"\
                                        "\'B\', \'C\' or \';\'"));
      }
      switch (ch) {
      case 'L':
	istream >> value;
	if (istream.bad())
	  throw CardWords_XBadInput("CardWords_DicBot_CardTable",
				  gettext("number: letter factor"));
	(*card_table)[loc].set_letter_factor(value);
	break;
      case 'W':
	istream >> value;
	if (istream.bad())
	  throw CardWords_XBadInput("CardWords_DicBot_CardTable",
				  gettext("number: word factor"));
	(*card_table)[loc].set_word_factor(value);
	break;
      case 'T':
	istream >> desc;
	card_table->add_card(loc, desc);
	break;
      case 'C':
        istream >> value;
        if (istream.bad())
          throw CardWords_XBadInput("CardWords_CardTable",
                                    gettext("number: cell points"));
        (*card_table)[loc].set_cell_points(value);
        break;
      case 'B':
        (*card_table)[loc].set_cell_blocked(true);
        break;
        
      default:
	assert (ch == ';');
      }
    } while (ch != ';');
  }
  return card_table;
}

ostream & operator <<(class ostream &ostream,
		      const CardWords_DicBot_CardTable & card_table)
{
  CardWords_CardTableLocation loc;

  ostream << card_table.get_width() << '*' <<  card_table.get_height()
	  << endl
          << card_table.get_starting_pos()
          << endl;

  for (loc.second = 0; loc.second < card_table.get_height(); loc.second++) {
    for (loc.first = 0; loc.first < card_table.get_width(); loc.first++) {
      if (card_table[loc].get_letter_factor() != DEFAULT_LETTER_FACTOR
	  || card_table[loc].get_word_factor() != DEFAULT_WORD_FACTOR
	  || card_table[loc].get_containsCard() == true
          || card_table[loc].get_cell_points() != DEFAULT_CELL_POINTS
          || card_table[loc].is_cell_blocked() == true) {
	
	ostream << loc;
	if (card_table[loc].get_letter_factor() != DEFAULT_LETTER_FACTOR) {
	  ostream << " L" <<  card_table[loc].get_letter_factor();
	}
	if (card_table[loc].get_word_factor() != DEFAULT_WORD_FACTOR) {
	  ostream << " W" <<  card_table[loc].get_word_factor();
	}
	if (card_table[loc].get_containsCard()) {
	  ostream << " T" << card_table[loc].get_card();
	}
        if (card_table[loc].get_cell_points() != DEFAULT_CELL_POINTS) {
          ostream << " C" << card_table[loc].get_cell_points();
        }
        if (card_table[loc].is_cell_blocked() == true) {
          ostream << " B";
        }
	ostream << ';' << endl;
      }
    }
  }
  // mark the end:
  return ostream << CardWords_CardTableLocation(card_table.get_height(),
                                                card_table.get_width())
		 << endl;
}

