/* This file is part of cardwords
   (c) 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_cardtable.cc

#include "cardwords_cardtable.hh"
#include "cardwords_exceptions.hh"
#include "cardwords_move.hh"

CardWords_CardTable::CardWords_CardTable (size_t columns,
                                          size_t rows,
                                          CardWords_CardTableLocation start)
  : CardWords_CardContainer2Dim (columns, rows),
    starting_pos(start),
    letter_factors(columns, rows, 1),
    word_factors  (columns, rows, 1),
    cell_points   (columns, rows, 0),
    cell_blocked  (columns, rows, false),
    number_of_cards(0U)
{}

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

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

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

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

  istream >> start;
  if (istream.bad())
    throw CardWords_XBadInput("CardWords_CardTable",
                              gettext("CardWords_CardTableLocation: "\
                                      "Start condition"));
  
  card_table = new CardWords_CardTable(width, height, start);
  
  CardWords_CardTableLocation loc;
  int value;

  // 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 != 'B'
              && ch != 'C'
              && ch != ';')
          ) {
        throw CardWords_XBadInput("CardWords_CardTable",
                                  gettext("\'L\', \'W\',"\
                                          "\'B\', \'C\' or \';\'"));
      }
      switch (ch) {
      case 'L':
        istream >> value;
        if (istream.bad())
          throw CardWords_XBadInput("CardWords_CardTable",
                                    gettext("number: letter factor"));
        card_table->letter_factors[loc] = value;
        break;
      case 'W':
        istream >> value;
        if (istream.bad())
          throw CardWords_XBadInput("CardWords_CardTable",
                                    gettext("number: word factor"));
        card_table->word_factors[loc] = value;
        break;
      case 'T':
        // only empty card-tables allowed
        throw CardWords_XBadInput("CardWords_CardTable * "\
                                  "read_card_table(istream &):",
                                  gettext("Description contains card"));
      case 'C':
        istream >> value;
        if (istream.bad())
          throw CardWords_XBadInput("CardWords_CardTable",
                                    gettext("number: cell points"));
        card_table->cell_points[loc] = value;
        break;
      case 'B':
        card_table->cell_blocked[loc] = true;
        break;
      default:
        assert (ch == ';');
      }
    } while (ch != ';');
  }
  return card_table;
}

ostream & operator <<(class ostream &ostream,
                      const CardWords_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.get_letter_factor(loc) != 1
          || card_table.get_word_factor(loc) != 1
          || card_table.get_cell_points(loc) != 0
          || card_table.is_cell_blocked(loc) == true
          || card_table[loc] != 0  ) {
        
        ostream << loc;
        if (card_table.get_letter_factor(loc) != 1) {
          ostream << " L" <<  card_table.get_letter_factor(loc);
        }
        if ( card_table.get_word_factor(loc) != 1 ) {
          ostream << " W" <<  card_table.get_word_factor(loc);
        }
        if (card_table[loc] != 0) {
          ostream << " T" << card_table[loc];
        }
        if (card_table.is_cell_blocked(loc) == true) {
          ostream << " B";
        }
        if (card_table.get_cell_points(loc) != 0) {
          ostream << " C" << card_table.get_cell_points(loc);
        }
        ostream << ';' << endl;
      }
    }
  }
  // mark the end:
  return
    ostream << CardWords_CardTableLocation(card_table.get_height(),
                                           card_table.get_width())
            << endl;
}

void
CardWords_CardTable::add_move(const CardWords_Move &move,
                              CardWords_CardContainer1Dim * pile)
{
  CardWords_CardTableLocation loc = move.location;
  size_t & var = (move.direction == horizontal) ? loc.first : loc.second;

  size_t used_cards = 0;
  while (used_cards < move.size()) {
    assert(is_cell_blocked(loc) == false);// should have been tested before

    if ((*this)[loc] == 0) {
      add_card (pile->matching_card(move[used_cards]), loc);
      ++used_cards;
    }
    ++var;
  }
}

inline
bool 
CardWords_CardTable::
has_cell_neighbour_card(CardWords_CardTableLocation loc) const
{
  return (( loc.first > 0
            && (*this)(loc.first - 1, loc.second) != 0)
          ||
          ( loc.first < get_width() - 1
            && (*this)(loc.first + 1, loc.second) != 0)
          ||
          ( loc.second > 0
            && (*this)(loc.first, loc.second - 1) != 0)
          ||
          ( loc.second < get_height() - 1
            && (*this)(loc.first, loc.second + 1) != 0)
          );
}
                       
// Check if such a move would fit on the card-table
// Moves creating 1-letters-words are marked as not-fitting
bool
CardWords_CardTable::check_move_fits(const CardWords_Move &move) const
{
  CardWords_CardTableLocation loc = move.location;

  if (move.size() == 1 && (*this)[loc] == 0) {
    bool found_card_in_move_direction = false;
    // Check for 1-letter-words
    if (move.direction == horizontal) {
      if (loc.first > 0
          && (*this)(loc.first-1, loc.second) != 0 ) {
        found_card_in_move_direction = true;
      }
      if (loc.first < get_width()-1
          && (*this)(loc.first+1, loc.second) != 0 ) {
        found_card_in_move_direction = true;
      }
    }
    else {
      // move.direction != horizontal
      if (loc.second > 0
          && (*this)(loc.first, loc.second-1) != 0 ) {
        found_card_in_move_direction = true;
      }
      if (loc.second < get_height()-1
          && (*this)(loc.first, loc.second+1) != 0 ) {
        found_card_in_move_direction = true;
      }
    }
    if (found_card_in_move_direction == false) {
      // This is a move that would create a one-letter-word
      return false;
    }
  }

  // Check if the start condition is obeyed:
  if (get_number_of_cards() == 0U) {
    // empty card-table
    if (starting_pos.first < get_width()) {
      if (move.direction == vertikal) {
        if (loc.first != starting_pos.first) {
          return false;
        }
      }
      else {
        // move.direction == horizontal
        if (loc.first > starting_pos.first
            || loc.first + move.size() - 1 < starting_pos.first) {
          return false;
        }
      }
    }

    if (starting_pos.second < get_height()) {
      if (move.direction == horizontal) {
        if (loc.second != starting_pos.second) {
          return false;
        }
      }
      else {
        // move.direction == vertikal
        if (loc.second > starting_pos.second
            || loc.second + move.size() -1 < starting_pos.second) {
          return false;
        }
      }
    }
  }

  bool touching_card = false;
  
  size_t & var = (move.direction == horizontal) ? loc.first : loc.second;

  size_t used_cards = 0;
  while (used_cards < move.size()
         && loc.first < get_width()
         && loc.second < get_height()
         && is_cell_blocked(loc) == false) {
    if ((*this)[loc] == 0) {
      ++used_cards;
      touching_card = touching_card || has_cell_neighbour_card(loc);
    }
    else {
      touching_card = true; // there is a card in loc
    }
    ++var;
  }
  if (get_number_of_cards() != 0U && touching_card == false) {
    return false;
  }
  return used_cards == move.size();
}

size_t
CardWords_CardTable::get_number_of_cards(void) const
{
  return number_of_cards;
}
// These functions are here for counting the cards on the card-table:
// remove a special card from the container:
void
CardWords_CardTable::remove_card (CardWords_Card *card)
{
  assert(get_number_of_cards() >0U);
  CardWords_CardContainer2Dim::remove_card(card);
  --number_of_cards;
}

// add a card to the container:
void
CardWords_CardTable::add_card (CardWords_Card *card,
                               CardWords_CardTableLocation loc)
{
  CardWords_CardContainer2Dim::add_card(card, loc);
  ++number_of_cards;
}
