/*
 * Copyright (C) 2006-2009 Stephan Kulow <coolo@kde.org>
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include "fortyeightsolver.h"

// own
#include "../fortyeight.h"
#include "../kpat_debug.h"

namespace {
constexpr auto NUM_PILE = 8;
constexpr auto NUM_DECK = 9;
}

#define PRINT 0

class FortyeightSolverState {
public:
  bool empty[8];
  bool linedup[8];
  int  highestbreak[8];
  int  firstempty;
  int  freestores;
  int  fromscore[8];
};

void FortyeightSolver::checkState(FortyeightSolverState &d)
{
    d.firstempty = -1;
    d.freestores = 0;

    for ( int w = 0; w < 8; w++ )
      {
	d.empty[w] = (Wlen[w] == 0);
	d.linedup[w] = true;
	if ( !Wlen[w] ) 
	  {
	    d.freestores++;
	    if (d.firstempty < 0) d.firstempty = w;
	  }
	d.highestbreak[w] = 0;
	for (int i = 1; i < Wlen[w]; i++) 
	  {
	    card_t card1 = W[w][Wlen[w]-i-1];
	    card_t card2 = W[w][Wlen[w]-i];
	    if ( SUIT( card1 ) != SUIT( card2 ) ||
		 RANK( card1 ) != RANK( card2 ) + 1 ) 
	      {
		d.highestbreak[w] = i - 1;
		d.linedup[w] = false;
		break;
	      }
	  }
	d.fromscore[w] = 0;
	for (int i = d.highestbreak[w] + 1; i < Wlen[w]; i++) 
	  {
	    card_t card = W[w][Wlen[w]-i-1];
	    if ( RANK(card) < 5 || RANK(card) == PS_ACE) {
	      d.fromscore[w] += (13 - RANK(card)) * 20;
	    }
	  }
	if (Wlen[w])
	  d.fromscore[w] /= Wlen[w];
      }
}

void FortyeightSolver::make_move(MOVE *m)
{
#if PRINT
    if ( m->totype == O_Type )
      fprintf( stderr, "\nmake move %d from %d to %d out (at %d)\n\n", m->card_index, m->from, m->to, m->turn_index );
    else
        fprintf( stderr, "\nmake move %d from %d to %d (%d)\n\n", m->card_index, m->from, m->to, m->turn_index );
    print_layout();
#endif

    int from = m->from;
    int to = m->to;

    // move to pile
    if ( from == NUM_DECK && to == NUM_PILE )
    {
        card_t card = *Wp[NUM_DECK];
        Wp[NUM_DECK]--;
        Wlen[NUM_DECK]--;
        card = ( SUIT( card ) << 4 ) + RANK( card );
        Wp[NUM_PILE]++;
        *Wp[NUM_PILE] = card;
        Wlen[NUM_PILE]++;
        hashpile( NUM_DECK );
        hashpile( NUM_PILE );
#if PRINT
        print_layout();
#endif
        return;
    }

    // move to deck
    if ( from == NUM_PILE && to == NUM_DECK )
    {
        while ( Wlen[NUM_PILE] > 1 )
        {
            Wlen[NUM_DECK]++;
            Wp[NUM_DECK]++;
            card_t card = *Wp[NUM_PILE] + ( 1 << 7 );
            *Wp[NUM_DECK] = card;
            Wlen[NUM_PILE]--;
            Wp[NUM_PILE]--;
        }
        hashpile( NUM_DECK );
        hashpile( NUM_PILE );
        lastdeal = true;
#if PRINT
        print_layout();
#endif
        return;
    }

    for ( int i = m->card_index + 1; i > 0; --i )
    {
        card_t card = W[from][Wlen[from]-i];
        Wp[from]--;

	if ( m->totype == O_Type )
	  {
	    O[to]++;
	  }
	else 
	  {
	    Wp[to]++;
	    *Wp[to] = card;
	    Wlen[to]++;
	  }
    }
    Wlen[from] -= m->card_index+1;

    hashpile(from);
    if ( m->totype != O_Type )
      hashpile(to);
#if PRINT
    print_layout();
#endif
}

void FortyeightSolver::undo_move(MOVE *m)
{
#if PRINT
    if ( m->totype == O_Type )
      fprintf( stderr, "\nundo move %d from %d to %d out (at %d)\n\n", m->card_index, m->from, m->to, m->turn_index );
    else
        fprintf( stderr, "\nundo move %d from %d to %d (%d)\n\n", m->card_index, m->from, m->to, m->turn_index );
    print_layout();
#endif

    int from = m->from;
    int to = m->to;

    // move to pile
    if ( from == NUM_DECK && to == NUM_PILE )
    {
        card_t card = *Wp[NUM_PILE];
        Wp[NUM_PILE]--;
        Wlen[NUM_PILE]--;
        card = ( SUIT( card ) << 4 ) + RANK( card ) + ( 1 << 7 );
        Wp[NUM_DECK]++;
        *Wp[NUM_DECK] = card;
        Wlen[NUM_DECK]++;
        hashpile( NUM_DECK );
        hashpile( NUM_PILE );
#if PRINT
        print_layout();
#endif
        return;
    }

    // move to deck
    if ( from == NUM_PILE && to == NUM_DECK )
    {
        while ( Wlen[NUM_DECK] )
        {
            Wlen[NUM_PILE]++;
            Wp[NUM_PILE]++;
            *Wp[NUM_PILE] = ( SUIT( *Wp[NUM_DECK] ) << 4 ) + RANK( *Wp[NUM_DECK] );
            Wlen[NUM_DECK]--;
            Wp[NUM_DECK]--;
        }
        hashpile( NUM_DECK );
        hashpile( NUM_PILE );
        lastdeal = false;
#if PRINT
        print_layout();
#endif
        return;
    }

    for ( int i = m->card_index + 1; i > 0; --i )
    {
      
      card_t card;
      if ( m->totype != O_Type ) 
	{
	  card = W[to][Wlen[to]-i];
	  Wp[to]--;
	} 
      else 
	{
	  card = Osuit[to] + O[to];
	  O[to]--;
	}

      Wp[from]++;
      *Wp[from] = card;
      Wlen[from]++;
    }

    hashpile(from);
    if ( m->totype != O_Type ) {
      Wlen[to] -= m->card_index+1;
      hashpile(to);
    }
#if PRINT
    print_layout();
#endif
}

bool FortyeightSolver::checkMoveOut( int from, MOVE *mp, int *dropped)
{
  if ( !Wlen[from] )
    return false;

  card_t card = *Wp[from];
  int suit = SUIT( card );
  
  int target = suit * 2;

  // aces need to fit on empty
  if ( RANK(card) == PS_ACE )
    {
      if (O[target++] != NONE)
	target--;
      else if ( O[target] != NONE)
	return false;
    }
  else 
    {
      if ( RANK(card) == O[target++] + 1 ) 
	target--;
      else if ( RANK(card) != O[target] + 1 )
	return false;
    }
  
  dropped[target]++;
  mp->card_index = 0;
  mp->from = from;
  mp->to = target;
  mp->totype = O_Type;
  mp->pri = 127;
  mp->turn_index = -1;
  return true;

}

/* Get the possible moves from a position, and store them in Possible[]. */
bool FortyeightSolver::checkMove( int from, int to, MOVE *mp )
{
    if ( !Wlen[from] )
        return false;

    card_t card = *Wp[from];
    Q_ASSERT( to < 8 );

    if ( Wlen[to] )
      {
	card_t top = *Wp[to];
	if ( SUIT( top ) != SUIT( card ) ||
	     RANK( top ) != RANK( card ) + 1 )
	  return false;
      }

    mp->card_index = 0;
    mp->from = from;
    mp->to = to;
    mp->totype = W_Type;
    mp->pri = 70;
    mp->turn_index = -1;
    return true;
}

int FortyeightSolver::get_possible_moves(int *a, int *numout)
{
    int n = 0;
    MOVE *mp = Possible;

    *a = false;

    FortyeightSolverState d;
    checkState(d);

    //print_layout();
    // if a specific target got two possible drops, we don't make it auto drop
    int dropped[8] = { 0, 0, 0, 0, 0, 0, 0, 0};

    // off
    for ( int w = 0; w < 8; w++ )
      {
	if ( checkMoveOut( w, mp, dropped ) )
	  {
	    n++;
	    mp++;
	    break;
	  }
      }
    if ( checkMoveOut( NUM_PILE, mp, dropped ) )
      {
	n++;
	mp++;
      }

    for ( int i = 0; i < 8; i++ )
    {
        if ( dropped[i] > 1 )
        {
            for ( int j = 0; j < n; j++ )
                if ( Possible[j].to == i )
                    Possible[j].pri = qBound( 121, 110 + Wlen[Possible[j].from], 126 );
        }
        if ( dropped[i] == 1 )
        {
            // good automove
            if (n != 1)
            {
                for ( int j = 0; j < n; j++ )
                {
                    if ( Possible[j].to == i )
                    {
		      Possible[0] = Possible[j];
		      Possible[0].pri = 127;
                        break;
                    }
                }
            }
            *a = true;
            *numout = 1;
            return 1;
        }
    }

    //fprintf(stderr, "\n");
    *numout = n;

    for (int w = 0; w < 8; w++)
    {
        for ( int j = 0; j < 8; j++ )
        {
            if ( j == w )
                continue;
            if ( checkMove( w, j, mp ) )
            {
                if ( !Wlen[j] )
                {
		  if (d.linedup[w])
		    continue; // ignore it

		  if ( j != d.firstempty ) // one is enough
		    continue;
		  if ( d.highestbreak[w] >= 1)
		    continue;
		  mp->pri = qMin( 115, 11 + d.fromscore[w]);
                } else {
		  if (d.linedup[w] && Wlen[w] > 1)
		    continue;
		  if (d.linedup[j] && Wlen[j] > 1)
		    mp->pri = 110 + Wlen[j];
		  else {
		    if (d.fromscore[w] < d.fromscore[j])
		      mp->pri = 2;
		    else
		      mp->pri = qMin(115, 20 + d.highestbreak[w] + RANK(*Wp[w]) + d.fromscore[w]);
		  }
		}

                n++;
                mp++;
            }
        }
        if ( checkMove( NUM_PILE, w, mp ) && (!d.empty[w] || w == d.firstempty))
        {
	  if (d.empty[w])
	    mp->pri = 15;
	  else {
	    if (d.linedup[w])
	      mp->pri = 100;
	    else
	      mp->pri = qMax(50 - d.fromscore[w], 0);
	  }
	  n++;
	  mp++;
        }
        if ( Wlen[w] > 1 && d.freestores )
        {
            // Column w has two or more cards and there is at least one empty
            // column, so we can try for multi-card moves from w to other cols.
            // This is valid only if ALL the cards to move are of the same
            // suit and in ascending sequence, starting with the top two cards.
            const card_t nextw = W[w][Wlen[w]-2];	// Next card after top.
            const bool possMultiMove = ( SUIT( nextw ) == SUIT( *Wp[w] ) ) &&
                                       ( ( RANK( nextw ) - RANK( *Wp[w] ) ) == 1 );
            if ( possMultiMove )
            {
                //print_layout();

                for ( int to = 0; to < 8; to++ )
                {
                    if ( to == w )
                        continue;
                    if ( Wlen[to] && SUIT( *Wp[to] ) != SUIT( *Wp[w] ) )
                        continue;
                    if ( d.empty[to] && to != d.firstempty )
                        continue;
                    int moves = 1 << d.freestores;
                    if ( !Wlen[to] )
                        moves = 1 << ( d.freestores - 1 );

                    if ( moves >= Wlen[w] )
                        moves = Wlen[w];

                    bool switched = false;
                    for ( int i = 2; i < moves; ++i )
                    {
                        /*      printcard( W[w][Wlen[w]-i-1], stderr );
                        printcard( *Wp[w], stderr );
                        fprintf( stderr, " switch? %d\n", i ); */
		      if ( SUIT( W[w][Wlen[w]-i-1] ) != SUIT( *Wp[w] ) ||
			   RANK ( W[w][Wlen[w]-i-1] ) != RANK ( W[w][Wlen[w]-i] ) + 1)
			{
                            switched = true;
                            moves = i;
                            break;
                        }
                    }
                    if ( !Wlen[to] )
		      {
			if ( moves < 2 || moves == Wlen[w] || !switched)
                            continue;
                        //print_layout();
                        mp->card_index = moves-1;
                        mp->from = w;
                        mp->to = to;
                        mp->totype = W_Type;
                        mp->pri = 60;
                        mp->turn_index = -1;
                        n++;
                        mp++;
                        continue;
                    }
		    if (d.linedup[w] && moves < Wlen[w])
		      continue;
                    card_t top = *Wp[to];
                    for ( int i = 2; i <= moves && i <= Wlen[w]; i++ )
                    {
                        card_t cur = W[w][Wlen[w]-i];
                        /* printcard( top, stderr );
                        printcard( cur, stderr );
                        fprintf( stderr, " %d\n", i ); */
                        Q_ASSERT( SUIT( top ) == SUIT( cur ) );
                        if ( RANK( top ) == RANK( cur ) + 1 )
                        {
                            mp->card_index = i-1;
                            mp->from = w;
                            mp->to = to;
                            mp->totype = W_Type;
                            mp->pri = 80;
                            mp->turn_index = -1;
                            n++;
                            mp++;
                        }
                    }
                }
            }
        }
    }
    /* check for deck->pile */
    if ( Wlen[NUM_DECK] ) {
        mp->card_index = 1;
        mp->from = NUM_DECK;
        mp->to = NUM_PILE;
        mp->totype = W_Type;
        mp->pri = 19;
        mp->turn_index = 0;
        n++;
        mp++;
    } else if ( !lastdeal )
    {
        mp->card_index = 1;
        mp->from = NUM_PILE;
        mp->to = NUM_DECK;
        mp->totype = W_Type;
        mp->pri = 19;
        mp->turn_index = 0;
        n++;
        mp++;
    }

    return n;
}

void FortyeightSolver::unpack_cluster( unsigned int k )
{
    O[0] = k & 0xF;
    k >>= 4;
    O[1] = k & 0xF;
    k >>= 4;
    O[2] = k & 0xF;
    k >>= 4;
    O[3] = k & 0xF;
    k >>= 4;
    O[4] = k & 0xF;
    k >>= 4;
    O[5] = k & 0xF;
    k >>= 4;
    O[6] = k & 0xF;
    k >>= 4;
    O[7] = k & 0xF;
}

bool FortyeightSolver::isWon()
{
    for ( int i = 0; i < 8; ++i )
        if ( O[i] != PS_KING )
            return false;
    //qCDebug(KPAT_LOG) << "isWon" << getOuts();
    return true;
}

int FortyeightSolver::getOuts()
{
    int total = 0;
    for ( int i = 0; i < 8; ++i )
        total += O[i];
    return total;
}

FortyeightSolver::FortyeightSolver(const Fortyeight *dealer)
    : Solver()
{
    deal = dealer;
}

void FortyeightSolver::translate_layout()
{
    /* Read the workspace. */

    int total = 0;
    for ( int w = 0; w < 8; ++w ) {
        int i = translate_pile(deal->stack[w], W[w], 52);
        Wp[w] = &W[w][i - 1];
        Wlen[w] = i;
        total += i;
    }

    /* Output piles, if any. */
    for (int i = 0; i < 8; ++i) {
        O[i] = NONE;
    }
    Osuit[0] = PS_DIAMOND;
    Osuit[1] = PS_DIAMOND;
    Osuit[2] = PS_CLUB;
    Osuit[3] = PS_CLUB;
    Osuit[4] = PS_HEART;
    Osuit[5] = PS_HEART;
    Osuit[6] = PS_SPADE;
    Osuit[7] = PS_SPADE;

    for (int i = 0; i < 8; ++i) {
      KCard *c = deal->target[i]->topCard();
      if (c) {
	int suit = translateSuit( c->suit() ) >> 4;
	if (O[suit * 2] == NONE)
	  O[suit * 2] = c->rank();
	else {
	  if (O[suit * 2] < c->rank()) {
	    O[suit * 2 + 1] = O[suit * 2];
	    O[suit * 2] = c->rank();
	  } else {
	    O[suit * 2 + 1] = c->rank();
	  }
	}
	total += c->rank();
      }
    }
    int i = translate_pile( deal->pile, W[NUM_PILE], 80 );
    Wp[NUM_PILE] = &W[NUM_PILE][i-1];
    Wlen[NUM_PILE] = i;
    total += i;

    i = translate_pile( deal->talon, W[NUM_DECK], 80 );
    Wp[NUM_DECK] = &W[NUM_DECK][i-1];
    Wlen[NUM_DECK] = i;
    total += i;

    lastdeal = deal->lastdeal;

    Q_ASSERT( total == 104 );
}

unsigned int FortyeightSolver::getClusterNumber()
{
    unsigned int i = O[0] + (O[1] << 4);
    unsigned int k = i;
    i = O[2] + (O[3] << 4);
    k |= i << 8;
    i = O[4] + (O[5] << 4);
    k |= i << 16;
    i = O[6] + (O[7] << 4);
    k |= i << 24;
    return k;
}

MoveHint FortyeightSolver::translateMove( const MOVE &m )
{
    if ( m.from == NUM_DECK || m.to == NUM_DECK )
        return MoveHint();
    PatPile *frompile = nullptr;
    if ( m.from < 8 )
        frompile = deal->stack[m.from];
    else
        frompile = deal->pile;

    Q_ASSERT( frompile );
    KCard *card = frompile->at( frompile->count() - m.card_index - 1);
    Q_ASSERT( card );
    Q_ASSERT( m.to < NUM_PILE );
    if ( m.totype == W_Type)
      return MoveHint( card, deal->stack[m.to], m.pri );

    for (int i = 0; i < 8; i++) {
      KCard *top = deal->target[i]->topCard(); 
      if (top) {
	if (top->suit() == card->suit() && top->rank() + 1 == card->rank())
	  return MoveHint( card, deal->target[i], m.pri );
      } else {
	if (card->rank() == PS_ACE)
	  return MoveHint( card, deal->target[i], m.pri );
      }
    }
    Q_ASSERT( false);
    return MoveHint();
}

void FortyeightSolver::print_layout()
{
    FortyeightSolverState d;
    checkState(d);

    fprintf(stderr, "print-layout-begin\n");
    for (int w = 0; w < 10; w++) {
        if ( w == NUM_DECK )
            fprintf( stderr, "Deck(9): " );
        else if ( w == 8 )
            fprintf( stderr, "Pile(8): " );
        else if ( w < 8 )
            fprintf( stderr, "Play%d: ", w );
        for (int i = 0; i < Wlen[w]; i++) {
            printcard(W[w][i], stderr);
        }
        fputc('\n', stderr);
    }
    fprintf( stderr, "Off: " );
    for (int o = 0; o < 8; ++o) {
      printcard(O[o] + Osuit[o], stderr);
    }
    fprintf( stderr, "\nLast-Deal: %d\n", lastdeal );
#if 1
    fprintf( stderr, "FE: %d FS: %d\n", d.firstempty, d.freestores);
    for (int o = 0; o < 8; ++o) {
      fprintf( stderr, "Pile%d: empty:%d linedup:%d highestbreak:%d fromscore:%d\n", o, d.empty[o], d.linedup[o], d.highestbreak[o], d.fromscore[o]);
    }
#endif
    fprintf(stderr, "print-layout-end\n");
}
