#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
# ---------------------------------------------------------------------------##
#
# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
# Copyright (C) 2003 Mt. Hood Playing Card Co.
# Copyright (C) 2005-2009 Skomoroh
#
# 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 3 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/>.
#
# ---------------------------------------------------------------------------##

from pysollib.game import Game
from pysollib.gamedb import GI, GameInfo, registerGame
from pysollib.games.golf import BlackHole_Foundation
from pysollib.hint import CautiousDefaultHint
from pysollib.layout import Layout
from pysollib.stack import \
        AC_RowStack, \
        AbstractFoundationStack, \
        RK_FoundationStack, \
        RK_RowStack, \
        ReserveStack, \
        SS_FoundationStack, \
        Stack, \
        StackWrapper, \
        UD_RK_RowStack, \
        WasteStack, \
        WasteTalonStack
from pysollib.util import ACE, ANY_SUIT, KING, UNLIMITED_CARDS

# ************************************************************************
# *
# ************************************************************************


class Windmill_Foundation(RK_FoundationStack):
    def getBottomImage(self):
        if self.cap.base_rank == ACE:
            return self.game.app.images.getLetter(ACE)
        return RK_FoundationStack.getBottomImage(self)


class Windmill_RowStack(ReserveStack):
    def acceptsCards(self, from_stack, cards):
        if not ReserveStack.acceptsCards(self, from_stack, cards):
            return False
        # this stack accepts one card from the Waste pile
        return from_stack is self.game.s.waste


# ************************************************************************
# * Windmill
# * Dutch Solitaire
# ************************************************************************

class Windmill(Game):

    Foundation_Classes = [
        StackWrapper(Windmill_Foundation, mod=13, min_cards=1, max_cards=52),
        StackWrapper(Windmill_Foundation, base_rank=KING, dir=-1),
        ]
    RowStack_Class = Windmill_RowStack

    FOUNDATIONS_LAYOUT = ((1, 0.6), (3, 0.6), (1, 3.4), (3, 3.4))
    ROWS_LAYOUT = ((2, 0), (2, 1), (0, 2), (1, 2), (3, 2), (4, 2),
                   (2, 3), (2, 4))
    FILL_STACK = True

    #
    # game layout
    #

    def createGame(self, card_x_space=20):
        # create layout
        l, s = Layout(self, card_x_space=card_x_space), self.s

        # set window
        max_x = max([i[0] for i in self.FOUNDATIONS_LAYOUT+self.ROWS_LAYOUT])
        max_y = max([i[1] for i in self.FOUNDATIONS_LAYOUT+self.ROWS_LAYOUT])
        self.setSize((3+max_x)*l.XS+l.XM, (1+max_y)*l.YS+l.YM+l.YM)

        # create stacks
        x = l.XM
        y = l.YM
        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
        l.createText(s.talon, "s")
        x = x + l.XS
        s.waste = WasteStack(x, y, self)
        l.createText(s.waste, "s")
        x0, y0 = x + l.XS, y
        for d in self.ROWS_LAYOUT:
            x, y = x0 + d[0] * l.XS, y0 + d[1] * l.YS
            stack = self.RowStack_Class(x, y, self)
            s.rows.append(stack)
            stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0
        x, y = x0 + 2 * l.XS, y0 + 2 * l.YS
        fnd_cls = self.Foundation_Classes[0]
        s.foundations.append(fnd_cls(x, y, self))
        fnd_cls = self.Foundation_Classes[1]
        for d in self.FOUNDATIONS_LAYOUT:
            x, y = x0 + d[0] * l.XS, y0 + d[1] * l.YS
            s.foundations.append(fnd_cls(x, y, self))

        # define stack-groups
        l.defaultStackGroups()

    #
    # game overrides
    #

    def _shuffleHook(self, cards):
        for c in cards:
            if c.id == 0:
                break
        cards.remove(c)
        return cards + [c]

    def startGame(self):
        self.startDealSample()
        self.s.talon.dealRow(rows=(self.s.foundations[0],))
        self.s.talon.dealRow()
        self.s.talon.dealCards()          # deal first card to WasteStack

    def fillStack(self, stack):
        if self.FILL_STACK and len(stack.cards) == 0:
            if stack is self.s.waste and self.s.talon.cards:
                self.s.talon.dealCards()
            elif stack in self.s.rows and self.s.waste.cards:
                self.s.waste.moveMove(1, stack)

    shallHighlightMatch = Game._shallHighlightMatch_RK

    def getHighlightPilesStacks(self):
        return ()

    def getAutoStacks(self, event=None):
        # disable auto drop - this would ruin the whole gameplay
        return ((), (), ())


class DutchSolitaire_RowStack(UD_RK_RowStack):
    getBottomImage = Stack._getReserveBottomImage


class DutchSolitaire(Windmill):
    Hint_Class = CautiousDefaultHint
    Foundation_Classes = [
        StackWrapper(BlackHole_Foundation, suit=ANY_SUIT, mod=13,
                     max_cards=UNLIMITED_CARDS, min_cards=1),
        StackWrapper(BlackHole_Foundation, suit=ANY_SUIT, mod=13,
                     max_cards=UNLIMITED_CARDS, min_cards=1),
        ]
    RowStack_Class = DutchSolitaire_RowStack

    FOUNDATIONS_LAYOUT = ((1, 1), (3, 1), (1, 3), (3, 3))
    ROWS_LAYOUT = ((2, 0.5), (-0.5, 2), (0.5, 2), (3.5, 2), (4.5, 2), (2, 3.5))
    FILL_STACK = False

    def createGame(self):
        Windmill.createGame(self, card_x_space=10)

    def _shuffleHook(self, cards):
        # move 5 Aces to top of the Talon (i.e. first cards to be dealt)
        def select_cards(c):
            if c.rank == ACE:
                if c.suit in (0, 1):
                    return True, c.suit
                if c.suit == 3 and c.deck == 0:
                    return True, c.suit
            return False, None
        return self._shuffleHookMoveToTop(cards, select_cards)

    def startGame(self):
        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
        self._startDealNumRows(8)
        self.s.talon.dealRow()
        self.s.talon.dealRow()
        self.s.talon.dealCards()          # deal first card to WasteStack

    def getAutoStacks(self, event=None):
        return (self.sg.dropstacks, self.sg.dropstacks, self.sg.dropstacks)


# ************************************************************************
# * Napoleon's Tomb
# ************************************************************************

class NapoleonsTomb_Foundation(AbstractFoundationStack):
    def acceptsCards(self, from_stack, cards):
        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
            return False
        if self.cards:
            # check the rank - an ACE equals a seven
            rank = self.cards[-1].rank
            if rank == ACE:
                rank = 6
            if (rank + self.cap.dir) % self.cap.mod != cards[0].rank:
                return False
        return True


class NapoleonsTomb(Game):

    #
    # game layout
    #

    def createGame(self):
        # create layout
        l, s = Layout(self, card_x_space=20, card_y_space=20), self.s

        # set window
        self.setSize(int(5.1 * l.XS) + l.XM, int(3.05 * l.YS) + (2 * l.YM))

        # create stacks
        x = l.XM
        y = l.YM
        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
        l.createText(s.talon, "s")
        x = x + l.XS
        s.waste = WasteStack(x, y, self)
        l.createText(s.waste, "s")
        x0, y0 = x + l.XS + (0.15 * l.XS), y + (0.05 * l.YS)
        for d in ((0, 1), (1, 0), (1, 2), (2, 1)):
            x, y = x0 + d[0] * l.XS, y0 + d[1] * l.YS
            s.rows.append(Windmill_RowStack(x, y, self))
        x, y = x0 + l.XS, y0 + l.YS
        s.foundations.append(NapoleonsTomb_Foundation(x, y, self, base_rank=5,
                             mod=13, max_cards=24, dir=-1, suit=ANY_SUIT))
        for d in ((0.05, 0.05), (-2.05, 0.05), (0.05, -2.05), (-2.05, -2.05)):
            x, y = x0 - d[0] * l.XS, y0 - d[1] * l.YS
            s.foundations.append(Windmill_Foundation(x, y, self,
                                 max_cards=7, base_rank=6, mod=13))

        # define stack-groups
        l.defaultStackGroups()

    def startGame(self):
        self.startDealSample()
        self.s.talon.dealCards()


# ************************************************************************
# * Corners
# ************************************************************************

class Corners(Game):
    RowStack_Class = ReserveStack

    def createGame(self, max_rounds=3):
        # create layout
        l, s = Layout(self, card_x_space=20, card_y_space=20), self.s

        # set window
        self.setSize(5*l.XS+l.XM, 4*l.YS+3*l.YM)

        # create stacks
        x, y = l.XM+1.5*l.XS, l.YM
        s.talon = WasteTalonStack(x, y, self, max_rounds=max_rounds)
        l.createText(s.talon, "sw")
        if max_rounds > 1:
            l.createRoundText(self.s.talon, 'nw')
        x += l.XS
        s.waste = WasteStack(x, y, self)
        l.createText(s.waste, "se")
        x0, y0 = l.XM, l.YM+l.YS
        i = 0
        for d in ((0, 0), (4, 0), (0, 2), (4, 2)):
            x, y = x0+d[0]*l.XS, y0+d[1]*l.YS
            s.foundations.append(SS_FoundationStack(x, y, self, suit=i,
                                                    max_move=0, mod=13))
            i += 1
        for d in ((2, 0), (1, 1), (2, 1), (3, 1), (2, 2)):
            x, y = x0+d[0]*l.XS, y0+d[1]*l.YS
            stack = self.RowStack_Class(x, y, self)
            s.rows.append(stack)
            stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0

        # define stack-groups
        l.defaultStackGroups()

    def fillStack(self, stack):
        if len(stack.cards) == 0:
            if stack is self.s.waste and self.s.talon.cards:
                self.s.talon.dealCards()
            elif stack in self.s.rows and self.s.waste.cards:
                self.s.waste.moveMove(1, stack)

    def _shuffleHook(self, cards):
        suits = []
        top_cards = []
        for c in cards[:]:
            if c.suit not in suits:
                suits.append(c.suit)
                cards.remove(c)
                top_cards.append(c)
                if len(suits) == 4:
                    break
        top_cards = sorted(top_cards, key=lambda x: -x.suit)
        return cards+top_cards

    def startGame(self):
        self.startDealSample()
        self.s.talon.dealRow(rows=self.s.foundations)
        self.s.talon.dealRow()
        self.s.talon.dealCards()          # deal first card to WasteStack


# ************************************************************************
# * Czarina
# * Four Seasons
# * Florentine Patience
# ************************************************************************

class Czarina_RowStack(RK_RowStack):
    getBottomImage = Stack._getReserveBottomImage


class Czarina(Corners):
    Hint_Class = CautiousDefaultHint
    RowStack_Class = StackWrapper(Czarina_RowStack, mod=13, max_move=1)

    def createGame(self, max_rounds=1):
        # extra settings
        self.base_card = None
        Corners.createGame(self, max_rounds=max_rounds)

    def startGame(self):
        self.startDealSample()
        self.base_card = None
        # deal base_card to Foundations, update foundations cap.base_rank
        self.base_card = self.s.talon.getCard()
        for s in self.s.foundations:
            s.cap.base_rank = self.base_card.rank
        self.flipMove(self.s.talon)
        self.moveMove(1, self.s.talon, self.s.foundations[self.base_card.suit])
        self.s.talon.dealRow()
        self.s.talon.dealCards()          # deal first 3 cards to WasteStack

    def _shuffleHook(self, cards):
        return cards

    shallHighlightMatch = Game._shallHighlightMatch_RKW

    def _restoreGameHook(self, game):
        self.base_card = self.cards[game.loadinfo.base_card_id]
        for s in self.s.foundations:
            s.cap.base_rank = self.base_card.rank

    def _loadGameHook(self, p):
        self.loadinfo.addattr(base_card_id=None)    # register extra load var.
        self.loadinfo.base_card_id = p.load()

    def _saveGameHook(self, p):
        p.dump(self.base_card.id)


class FourSeasons(Czarina):
    def fillStack(self, stack):
        pass


class FlorentinePatience(FourSeasons):
    def createGame(self):
        Czarina.createGame(self, max_rounds=2)


# ************************************************************************
# * Simplicity
# ************************************************************************

class Simplicity(Game):
    Hint_Class = CautiousDefaultHint

    def createGame(self, max_rounds=2):

        l, s = Layout(self), self.s
        self.setSize(l.XM+8*l.XS, l.YM+4*l.YS)

        self.base_card = None

        i = 0
        for x, y in ((l.XM,        l.YM),
                     (l.XM+7*l.XS, l.YM),
                     (l.XM,        l.YM+3*l.YS),
                     (l.XM+7*l.XS, l.YM+3*l.YS),
                     ):
            s.foundations.append(
                SS_FoundationStack(x, y, self, suit=i, mod=13))
            i += 1
        y = l.YM+l.YS
        for i in range(2):
            x = l.XM+l.XS
            for j in range(6):
                stack = AC_RowStack(x, y, self, max_move=1, mod=13)
                stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0
                s.rows.append(stack)
                x += l.XS
            y += l.YS
        x, y = l.XM+3*l.XS, l.YM
        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
        l.createText(s.talon, 'sw')
        x += l.XS
        s.waste = WasteStack(x, y, self)
        l.createText(s.waste, 'se')

        l.defaultStackGroups()

    def startGame(self):
        self.startDealSample()
        # deal base_card to Foundations, update foundations cap.base_rank
        self.base_card = self.s.talon.getCard()
        for s in self.s.foundations:
            s.cap.base_rank = self.base_card.rank
        self.flipMove(self.s.talon)
        self.moveMove(1, self.s.talon, self.s.foundations[self.base_card.suit])
        self.s.talon.dealRow()
        self.s.talon.dealCards()

    shallHighlightMatch = Game._shallHighlightMatch_ACW

    def _restoreGameHook(self, game):
        self.base_card = self.cards[game.loadinfo.base_card_id]
        for s in self.s.foundations:
            s.cap.base_rank = self.base_card.rank

    def _loadGameHook(self, p):
        self.loadinfo.addattr(base_card_id=None)    # register extra load var.
        self.loadinfo.base_card_id = p.load()

    def _saveGameHook(self, p):
        p.dump(self.base_card.id)


# register the game
registerGame(GameInfo(30, Windmill, "Windmill",
                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_MOSTLY_LUCK,
                      altnames=('Propeller',)))
registerGame(GameInfo(277, NapoleonsTomb, "Napoleon's Tomb",
                      GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
registerGame(GameInfo(417, Corners, "Corners",
                      GI.GT_1DECK_TYPE, 1, 2, GI.SL_MOSTLY_LUCK))
registerGame(GameInfo(437, Simplicity, "Simplicity",
                      GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
registerGame(GameInfo(483, Czarina, "Czarina",
                      GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
registerGame(GameInfo(484, FourSeasons, "Four Seasons",
                      GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK,
                      altnames=('Corner Card', 'Vanishing Cross',
                                'Cross Currents')))
registerGame(GameInfo(561, DutchSolitaire, "Dutch Solitaire",
                      GI.GT_2DECK_TYPE, 2, 0, GI.SL_MOSTLY_SKILL))
registerGame(GameInfo(696, FlorentinePatience, "Florentine Patience",
                      GI.GT_1DECK_TYPE, 1, 1, GI.SL_MOSTLY_LUCK))