/*
 * 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 "grandfsolver.h"

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

#define PRINT 0

/* These two routines make and unmake moves. */

void GrandfSolver::make_move(MOVE *m)
{
#if PRINT
    if (m->totype == O_Type)
        fprintf(stderr, "\nmake move %d from %d out (at %d)\n\n", m->card_index, m->from, m->turn_index);
    else if (m->from == offs)
        fprintf(stderr, "\nmake redeal\n\n");
    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();
#else
    // print_layout();
#endif

    card_t card = NONE;

    auto from = m->from;
    auto to = m->to;

    if (from == offs) {
        // redeal
        m_redeal++;

        card_t deck[52];
        int len = 0;

        for (int i = m_redeal * 7; i < m_redeal * 7 + 7; ++i) {
            Wlen[i] = 0;
            Wp[i] = &W[i][-1];
        }

        for (int pos = 6; pos >= 0; --pos) {
            int oldpile = (m_redeal - 1) * 7 + pos;
            for (int l = 0; l < Wlen[oldpile]; ++l) {
                card_t card = W[oldpile][l];
                deck[len++] = (SUIT(card) << 4) + RANK(card);
            }
        }

        int start = 0;
        int stop = 7 - 1;
        int dir = 1;

        for (int round = 0; round < 7; ++round) {
            int i = start;
            do {
                card_t card = NONE;
                if (len > 0) {
                    card = deck[--len];
                    int currentpile = m_redeal * 7 + i;
                    Wp[currentpile]++;
                    *Wp[currentpile] = card;
                    if (i != start)
                        *Wp[currentpile] += (1 << 7);
                    Wlen[currentpile]++;
                }
                i += dir;
            } while (i != stop + dir);
            int t = start;
            start = stop;
            stop = t + dir;
            dir = -dir;
        }

        int j = 0;
        while (len > 0) {
            card_t card = deck[--len];
            int currentpile = m_redeal * 7 + (j + 1);
            Wp[currentpile]++;
            *Wp[currentpile] = card;
            Wlen[currentpile]++;
            j = (j + 1) % 6;
        }

        for (int round = 0; round < 7; ++round) {
            int currentpile = m_redeal * 7 + round;
            if (Wlen[currentpile]) {
                card_t card = W[currentpile][Wlen[currentpile] - 1];
                W[currentpile][Wlen[currentpile] - 1] = (SUIT(card) << 4) + RANK(card);
            }
            hashpile(currentpile);
            hashpile(currentpile - 7);
        }

#if PRINT
        print_layout();
#endif
        return;
    }

    for (int l = m->card_index; l >= 0; --l) {
        card = W[from][Wlen[from] - l - 1];
        Wp[from]--;
        if (m->totype != O_Type) {
            Wp[to]++;
            *Wp[to] = card;
            Wlen[to]++;
        }
    }
    Wlen[from] -= m->card_index + 1;

    if (m->turn_index == 0) {
        if (DOWN(card))
            card = (SUIT(card) << 4) + RANK(card);
        else
            card += (1 << 7);
        W[to][Wlen[to] - m->card_index - 1] = card;
    } else if (m->turn_index != -1) {
        card_t card2 = *Wp[from];
        if (DOWN(card2))
            card2 = (SUIT(card2) << 4) + RANK(card2);
        *Wp[from] = card2;
    }

    hashpile(from);
    /* Add to pile. */

    if (m->totype == O_Type) {
        if (DOWN(W[offs][to]))
            W[offs][to] = (SUIT(card) << 4) + PS_ACE;
        else
            W[offs][to]++;
        Q_ASSERT(m->card_index == 0);
        hashpile(offs);
    } else {
        hashpile(to);
    }
#if PRINT
    print_layout();
#endif
}

void GrandfSolver::undo_move(MOVE *m)
{
#if PRINT
    if (m->totype == O_Type)
        fprintf(stderr, "\nundo move %d from %d out (at %d)\n\n", m->card_index, m->from, m->turn_index);
    else if (m->from == offs)
        fprintf(stderr, "\nundo redeal\n\n");
    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, to;
    card_t card;

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

    if (from == offs) {
        /* just erase */

        for (int i = 0; i < 7; ++i) {
            int pile = m_redeal * 7 + i;
            Wlen[pile] = 0;
            Wp[pile] = &W[pile][0];
            hashpile(pile);
        }
        m_redeal--;
#if PRINT
        print_layout();
#endif
        return;
    }

    /* Add to 'from' pile. */
    if (m->turn_index > 0) {
        card_t card2 = *Wp[from];
        if (!DOWN(card2))
            card2 = (SUIT(card2) << 4) + RANK(card2) + (1 << 7);
        *Wp[from] = card2;
    }

    if (m->totype == O_Type) {
        card = W[offs][Osuit[to]];
        W[offs][to]--;
        Wp[from]++;
        *Wp[from] = card;
        Wlen[from]++;
    } else {
        for (int l = m->card_index; l >= 0; --l) {
            card = W[to][Wlen[to] - l - 1];
            Wp[from]++;
            *Wp[from] = card;
            Wlen[from]++;
            Wp[to]--;
        }
        Wlen[to] -= m->card_index + 1;
        hashpile(to);
    }

    if (m->turn_index == 0) {
        card_t card = *Wp[from];
        if (DOWN(card))
            card = (SUIT(card) << 4) + RANK(card);
        else
            card += (1 << 7);
        *Wp[from] = card;
    }

    hashpile(from);
#if PRINT
    print_layout();
#endif
}

/* Get the possible moves from a position, and store them in Possible[]. */

int GrandfSolver::get_possible_moves(int *a, int *numout)
{
    int w, o, empty;
    card_t card;
    MOVE *mp;

    /* Check for moves from W to O. */

    int n = 0;
    mp = Possible;

    for (w = m_redeal * 7 + 0; w < m_redeal * 7 + 7; ++w) {
        if (Wlen[w] > 0) {
            card = *Wp[w];
            o = SUIT(card);
            empty = DOWN(W[offs][o]);
            if ((empty && (RANK(card) == PS_ACE)) || (!empty && (RANK(card) == RANK(W[offs][o]) + 1))) {
                mp->card_index = 0;
                mp->from = w;
                mp->to = o;
                mp->totype = O_Type;
                mp->pri = 127; /* unused */
                mp->turn_index = -1;
                if (Wlen[w] > 1 && DOWN(W[w][Wlen[w] - 2]))
                    mp->turn_index = 1;
                n++;
                mp++;

                *a = true;
                return n;
            }
        }
    }

    /* No more automoves, but remember if there were any moves out. */

    *a = false;
    *numout = n;

    for (int i = m_redeal * 7 + 0; i < m_redeal * 7 + 7; ++i) {
        int len = Wlen[i];
        for (int l = 0; l < len; ++l) {
            card_t card = W[i][Wlen[i] - 1 - l];
            if (DOWN(card))
                break;

            for (int j = m_redeal * 7 + 0; j < m_redeal * 7 + 7; ++j) {
                if (i == j)
                    continue;

                int allowed = 0;

                if (Wlen[j] > 0 && RANK(card) == RANK(*Wp[j]) - 1 && SUIT(card) == SUIT(*Wp[j])) {
                    allowed = 1;
                }
                if (RANK(card) == PS_KING && Wlen[j] == 0) {
                    if (l != Wlen[i] - 1)
                        allowed = 4;
                    else if (m_redeal < 2)
                        allowed = 1;
                }
                // TODO: there is no point in moving if we're not opening anything
                // e.g. if both i and j have perfect runs below the cards
#if 0
                fprintf( stderr, "%d %d %d\n", i, l, j );
                printcard( card, stderr );
                printcard( *Wp[j], stderr );
                fprintf( stderr, " allowed %d\n",allowed );
#endif
                if (allowed) {
                    mp->card_index = l;
                    mp->from = i;
                    mp->to = j;
                    mp->totype = W_Type;
                    mp->turn_index = -1;
                    if (Wlen[i] > l + 1 && DOWN(W[i][Wlen[i] - l - 2]))
                        mp->turn_index = 1;
                    if (mp->turn_index > 0 || Wlen[i] == l + 1)
                        mp->pri = 30;
                    else
                        mp->pri = 1;
                    /*if (mp->pri == 30 && mp->from == 9 && mp->to == 10)
                      abort();*/
                    n++;
                    mp++;
                }
            }
        }
    }

    if (m_redeal < 2) {
        mp->card_index = 0;
        mp->from = offs;
        mp->to = 0; // unused
        mp->totype = W_Type;
        mp->turn_index = -1;
        mp->pri = -1;
        n++;
        mp++;
    }

    return n;
}

unsigned int GrandfSolver::getClusterNumber()
{
    return m_redeal;
}

void GrandfSolver::unpack_cluster(unsigned int k)
{
    m_redeal = k;
}

bool GrandfSolver::isWon()
{
    // maybe won?
    for (int o = 0; o < 4; ++o) {
        if (RANK(W[offs][o]) != PS_KING) {
            return false;
        }
    }

    return true;
}

int GrandfSolver::getOuts()
{
    int outs = 0;
    for (int i = 0; i < 4; ++i)
        if (!DOWN(W[offs][i]))
            outs += RANK(W[offs][i]);
    return outs;
}

GrandfSolver::GrandfSolver(const Grandf *dealer)
    : Solver()
{
    Osuit[0] = PS_DIAMOND;
    Osuit[1] = PS_CLUB;
    Osuit[2] = PS_HEART;
    Osuit[3] = PS_SPADE;

    offs = 7 * 3;
    deal = dealer;
    m_redeal = -1;
}

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

    m_redeal = deal->numberOfDeals - 1;

    for (int w = 0; w < 7 * 3; ++w)
        Wlen[w] = 0;

    for (int w = 0; w < 7; ++w) {
        int i = translate_pile(deal->store[w], W[w + m_redeal * 7], 52);
        Wp[w + m_redeal * 7] = &W[w + m_redeal * 7][i - 1];
        Wlen[w + m_redeal * 7] = i;
    }

    Wlen[offs] = 4;

    for (int i = 0; i < 4; ++i)
        W[offs][i] = NONE;

    for (int i = 0; i < 4; ++i) {
        KCard *c = deal->target[i]->topCard();
        if (c)
            W[offs][translateSuit(c->suit()) >> 4] = translateSuit(c->suit()) + c->rank();
    }
    for (int i = 0; i < 4; ++i)
        if (W[offs][i] == NONE)
            W[offs][i] = (i << 4) + PS_ACE + (1 << 7);

    Wp[offs] = &W[offs][3];
}

MoveHint GrandfSolver::translateMove(const MOVE &m)
{
    if (m.from == offs)
        return MoveHint();

    PatPile *frompile = nullptr;
    frompile = deal->store[m.from % 7];

    KCard *card = frompile->at(frompile->count() - m.card_index - 1);

    if (m.totype == O_Type) {
        PatPile *target = nullptr;
        PatPile *empty = nullptr;
        for (int i = 0; i < 4; ++i) {
            KCard *c = deal->target[i]->topCard();
            if (c) {
                if (c->suit() == card->suit()) {
                    target = deal->target[i];
                    break;
                }
            } else if (!empty)
                empty = deal->target[i];
        }
        if (!target)
            target = empty;
        return MoveHint(card, target, m.pri);
    } else {
        return MoveHint(card, deal->store[m.to % 7], m.pri);
    }
}

void GrandfSolver::print_layout()
{
    int i, w, o;

    fprintf(stderr, "print-layout-begin\n");
    for (w = 0; w < 21; ++w) {
        fprintf(stderr, "Play%d-%d(%d): ", w / 7, w % 7, w);
        for (i = 0; i < Wlen[w]; ++i) {
            printcard(W[w][i], stderr);
        }
        fputc('\n', stderr);
    }
    fprintf(stderr, "Off: ");
    for (o = 0; o < 4; ++o) {
        if (!DOWN(W[offs][o]))
            printcard(RANK(W[offs][o]) + Osuit[o], stderr);
    }
    fprintf(stderr, "\nRedeals: %d", m_redeal);
    fprintf(stderr, "\nprint-layout-end\n");
}
