#!/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/>.
#
# ---------------------------------------------------------------------------

import math
import time

from pysollib.game import Game
from pysollib.gamedb import GI, GameInfo, registerGame
from pysollib.hint import AbstractHint, CautiousDefaultHint, DefaultHint
from pysollib.layout import Layout
from pysollib.mfxutil import kwdefault
from pysollib.mygettext import _
from pysollib.pysoltk import MfxCanvasText
from pysollib.stack import \
        AC_RowStack, \
        AbstractFoundationStack, \
        BasicRowStack, \
        DealRowTalonStack, \
        InitialDealTalonStack, \
        OpenStack, \
        RK_RowStack, \
        ReserveStack, \
        SS_FoundationStack, \
        SS_RowStack, \
        StackWrapper, \
        WasteStack, \
        WasteTalonStack, \
        isSameSuitSequence
from pysollib.util import ANY_RANK, ANY_SUIT, NO_RANK, \
        UNLIMITED_ACCEPTS, \
        UNLIMITED_CARDS, \
        UNLIMITED_MOVES

# ************************************************************************
#  * Dashavatara Foundation Stacks
#  ***********************************************************************/


class Dashavatara_FoundationStack(AbstractFoundationStack):

    def __init__(self, x, y, game, suit, **cap):
        kwdefault(cap, max_move=0, max_cards=12)
        SS_FoundationStack.__init__(self, x, y, game, suit, **cap)

    def updateText(self):
        AbstractFoundationStack.updateText(self)
        self.game.updateText()


class Journey_Foundation(AbstractFoundationStack):

    def __init__(self, x, y, game, suit, **cap):
        kwdefault(cap, mod=12, dir=0, base_rank=NO_RANK, max_move=0)
        AbstractFoundationStack.__init__(self, x, y, game, suit, **cap)

    def acceptsCards(self, from_stack, cards):
        if not AbstractFoundationStack.acceptsCards(self, from_stack, cards):
            return 0
        if not self.cards:
            return 1
        stack_dir = self.game.getFoundationDir()
        if stack_dir == 0:
            card_dir = (cards[0].rank - self.cards[-1].rank) % self.cap.mod
            return card_dir in (1, 11)
        return (self.cards[-1].rank + stack_dir) % \
            self.cap.mod == cards[0].rank


class AppachansWaterfall_Foundation(AbstractFoundationStack):

    def __init__(self, x, y, game, suit, **cap):
        kwdefault(cap, base_suit=0, mod=12, max_cards=120, max_move=0)
        AbstractFoundationStack.__init__(self, x, y, game, suit, **cap)

    def acceptsCards(self, from_stack, cards):
        if not (from_stack in self.game.s.rows and
                AbstractFoundationStack.acceptsCards(self, from_stack, cards)):
            return 0
        pile, rank, suit = from_stack.getPile(), 0, 0
        if self.cards:
            rank = (self.cards[-1].rank + 1) % 12
            suit = self.cards[-1].suit + (rank == 0)
        if (not pile or len(pile) <= 11 - rank or
                not isSameSuitSequence(pile[-(12 - rank):])):
            return 0
        return cards[0].suit == suit and cards[0].rank == rank


# ************************************************************************
#  * Dashavatara Row Stacks
#  ***********************************************************************/

class Dashavatara_OpenStack(OpenStack):

    def __init__(self, x, y, game, yoffset, **cap):
        kwdefault(cap, max_move=UNLIMITED_MOVES, max_cards=UNLIMITED_CARDS,
                  max_accept=UNLIMITED_ACCEPTS, base_rank=0, dir=-1)
        OpenStack.__init__(self, x, y, game, **cap)
        self.CARD_YOFFSET = yoffset

    def currentForce(self, card):
        force = self._getForce(card)
        hour = time.localtime(time.time())[3]
        if not (7 <= hour <= 19):
            force = not force
        return force

    def _getForce(self, card):
        return int(card.suit >= 5)

    def isRankSequence(self, cards, dir=None):
        if not dir:
            dir = self.cap.dir
        c1 = cards[0]
        for c2 in cards[1:]:
            if not c1.rank + dir == c2.rank:
                return 0
            c1 = c2
        return 1

    def isAlternateColorSequence(self, cards, dir=None):
        if not dir:
            dir = self.cap.dir
        c1 = cards[0]
        for c2 in cards[1:]:
            if not ((c1.suit + c2.suit) % 2 and
                    c1.rank + dir == c2.rank):
                return 0
            c1 = c2
        return 1

    def isAlternateForceSequence(self, cards, dir=None):
        if not dir:
            dir = self.cap.dir
        c1 = cards[0]
        f1 = self._getForce(c1)
        for c2 in cards[1:]:
            f2 = self._getForce(c2)
            if f1 == f2 or c1.rank + dir != c2.rank:
                return 0
            c1 = c2
            f1 = f2
        return 1

    def isSuitSequence(self, cards, dir=None):
        if not dir:
            dir = self.cap.dir
        c1 = cards[0]
        for c2 in cards[1:]:
            if not (c1.suit == c2.suit and c1.rank + dir == c2.rank):
                return 0
            c1 = c2
        return 1


class Dashavatara_AC_RowStack(Dashavatara_OpenStack):

    def acceptsCards(self, from_stack, cards):
        if not self.basicAcceptsCards(from_stack, cards) \
                or not self.isAlternateColorSequence(cards):
            return 0
        stackcards = self.cards
        if not stackcards:
            return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK
        return self.isAlternateColorSequence([stackcards[-1], cards[0]])


class Dashavatara_AF_RowStack(Dashavatara_OpenStack):

    def acceptsCards(self, from_stack, cards):
        if not self.basicAcceptsCards(from_stack, cards) \
                or not self.isAlternateForceSequence(cards):
            return 0
        stackcards = self.cards
        if not stackcards:
            return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK
        return self.isAlternateForceSequence([stackcards[-1], cards[0]])


class Dashavatara_RK_RowStack(Dashavatara_OpenStack):

    def acceptsCards(self, from_stack, cards):
        if not self.basicAcceptsCards(from_stack, cards) \
                or not self.isRankSequence(cards):
            return 0
        stackcards = self.cards
        if not stackcards:
            return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK
        return self.isRankSequence([stackcards[-1], cards[0]])


class Dashavatara_SS_RowStack(Dashavatara_OpenStack):

    def acceptsCards(self, from_stack, cards):
        if not self.basicAcceptsCards(from_stack, cards) \
                or not self.isSuitSequence(cards):
            return 0
        stackcards = self.cards
        if not stackcards:
            return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK
        return self.isSuitSequence([stackcards[-1], cards[0]])


class Circles_RowStack(SS_RowStack):

    def __init__(self, x, y, game, base_rank):
        SS_RowStack.__init__(self, x, y, game, base_rank=base_rank,
                             max_accept=1, max_move=1)
        self.CARD_YOFFSET = 1


class Journey_BraidStack(OpenStack):

    def __init__(self, x, y, game, xoffset, yoffset):
        OpenStack.__init__(self, x, y, game)
        self.CARD_YOFFSET = int(self.game.app.images.CARD_YOFFSET * yoffset)
        # use a sine wave for the x offsets
        self.CARD_XOFFSET = []
        j = 1
        for i in range(30):
            self.CARD_XOFFSET.append(int(math.sin(j) * xoffset))
            j = j + .9


class Journey_StrongStack(ReserveStack):

    def fillStack(self):
        if not self.cards:
            if self.game.s.braidstrong.cards:
                self.game.moveMove(1, self.game.s.braidstrong, self)
            elif self.game.s.braidweak.cards:
                self.game.moveMove(1, self.game.s.braidweak, self)

    def getBottomImage(self):
        return self.game.app.images.getBraidBottom()


class Journey_WeakStack(ReserveStack):

    def fillStack(self):
        if not self.cards:
            if self.game.s.braidweak.cards:
                self.game.moveMove(1, self.game.s.braidweak, self)
            elif self.game.s.braidstrong.cards:
                self.game.moveMove(1, self.game.s.braidstrong, self)

    def getBottomImage(self):
        return self.game.app.images.getBraidBottom()


class Journey_ReserveStack(ReserveStack):

    def acceptsCards(self, from_stack, cards):
        if (from_stack is self.game.s.braidstrong or
                from_stack is self.game.s.braidweak or
                from_stack in self.game.s.rows):
            return 0
        return ReserveStack.acceptsCards(self, from_stack, cards)

    def getBottomImage(self):
        return self.game.app.images.getTalonBottom()


class AppachansWaterfall_RowStack(RK_RowStack):

    def canDropCards(self, stacks):
        pile, stack, rank = self.getPile(), stacks[0], 0
        if stack.cards:
            rank = (stack.cards[-1].rank + 1) % 12
        if (not pile or len(pile) <= 11 - rank or
                not isSameSuitSequence(pile[-(12 - rank):]) or
                not stack.acceptsCards(self, pile[-1:])):
            return (None, 0)
        return (stack, 1)


# ************************************************************************
# * Dashavatara Game Stacks
# ************************************************************************

class Dashavatara_TableauStack(Dashavatara_OpenStack):

    def __init__(self, x, y, game, base_rank, yoffset, **cap):
        kwdefault(cap, dir=3, max_move=99, max_cards=4, max_accept=1,
                  base_rank=base_rank)
        OpenStack.__init__(self, x, y, game, **cap)
        self.CARD_YOFFSET = yoffset

    def acceptsCards(self, from_stack, cards):
        if not self.basicAcceptsCards(from_stack, cards):
            return 0
        # check that the base card is correct
        if self.cards and self.cards[0].rank != self.cap.base_rank:
            return 0
        if not self.cards:
            return cards[0].rank == self.cap.base_rank
        return (self.cards[-1].suit == cards[0].suit and
                self.cards[-1].rank + self.cap.dir == cards[0].rank)

    def getBottomImage(self):
        return self.game.app.images.getLetter(self.cap.base_rank)


class Dashavatara_ReserveStack(ReserveStack):

    def __init__(self, x, y, game, **cap):
        kwdefault(cap, max_cards=1, max_accept=1, base_rank=ANY_RANK)
        OpenStack.__init__(self, x, y, game, **cap)

    def acceptsCards(self, from_stack, cards):
        return (ReserveStack.acceptsCards(self, from_stack, cards) and
                self.game.s.talon.cards)


class Dashavatara_RowStack(BasicRowStack):

    def acceptsCards(self, from_stack, cards):
        if not BasicRowStack.acceptsCards(self, from_stack, cards):
            return 0
        # check
        return not (self.cards or self.game.s.talon.cards)

    def canMoveCards(self, cards):
        return 1

    def getBottomImage(self):
        return self.game.app.images.getTalonBottom()


# ************************************************************************
#  *
#  ***********************************************************************/

class AbstractDashavataraGame(Game):

    SUITS = (_("Fish"), _("Tortoise"), _("Boar"), _("Lion"), _("Dwarf"),
             _("Axe"), _("Arrow"), _("Plow"), _("Lotus"), _("Horse"))
    RANKS = (_("Ace"), "2", "3", "4", "5", "6", "7", "8", "9", "10",
             _("Pradhan"), _("Raja"))
    COLORS = (_("Black"), _("Red"), _("Yellow"), _("Green"), _("Brown"),
              _("Orange"), _("Grey"), _("White"), _("Olive"), _("Crimson"))
    FORCE = (_("Strong"), _("Weak"))

    def updateText(self):
        pass

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.suit == card2.suit and
                (card1.rank + 1 == card2.rank or card1.rank - 1 == card2.rank))

    def parseCard(self, card):
        if not card.face_up:
            return _("Face-down")
        suit = self.SUITS[card.suit]
        rank = self.RANKS[card.rank]
        return rank + " - " + suit


class Journey_Hint(DefaultHint):
    # FIXME: demo is not too clever in this game
    pass


# ************************************************************************
#  * Dashavatara Circles
#  ***********************************************************************/

class DashavataraCircles(AbstractDashavataraGame):
    Hint_Class = CautiousDefaultHint

    #
    # Game layout
    #

    def createGame(self):
        l, s = Layout(self), self.s

        # Set window size
        w, h = l.XM + l.XS * 9, l.YM + l.YS * 7
        self.setSize(w, h)

        # Create row stacks
        x = w // 2 - l.CW // 2
        y = h // 2 - l.YS // 2
        x0 = (-.7, .3, .7, -.3,
              -1.7, -1.5, -.6, .6, 1.5, 1.7, 1.5, .6, -.6, -1.5,
              -2.7, -2.5, -1.9, -1, 0, 1, 1.9, 2.5, 2.7, 2.5, 1.9,
              1, 0, -1, -1.9, -2.5)
        y0 = (-.3, -.45, .3, .45,
              0, -.8, -1.25, -1.25, -.8, 0, .8, 1.25, 1.25, .8,
              0, -.9, -1.6, -2, -2.2, -2, -1.6, -.9, 0, .9, 1.6,
              2, 2.2, 2, 1.6, .9)
        for i in range(30):
            # FIXME:
            _x, _y = x+l.XS*x0[i], y+l.YS*y0[i]+l.YM*y0[i]*2
            _x = max(_x, 0)
            _y = max(_y, 0)
            s.rows.append(Circles_RowStack(_x, _y, self, base_rank=ANY_RANK))

        # Create reserve stacks
        s.reserves.append(ReserveStack(l.XM, h - l.YS, self))
        s.reserves.append(ReserveStack(w - l.XS, h - l.YS, self))

        # Create foundations
        x, y = l.XM, l.YM
        for j in range(2):
            for i in range(5):
                s.foundations.append(
                    SS_FoundationStack(x, y, self, i + j * 5, mod=12,
                                       max_move=0, max_cards=12))
                y = y + l.YS
            x, y = w - l.XS, l.YM
        #        from pprint import pprint
        #        pprint(s.rows)
        #        print (l.XM + l.XS, 0, w - l.XS - l.XM, 999999)
        self.setRegion(s.rows, (l.XM + l.XS, 0, w - l.XS - l.XM, 999999))

        # Create talon
        s.talon = InitialDealTalonStack(l.XM + l.XS, l.YM, self)

        # Define stack groups
        l.defaultStackGroups()

    #
    # Game over rides
    #

    def startGame(self):
        assert len(self.s.talon.cards) == 120
        for i in range(3):
            self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=0)
        self.startDealSample()
        self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=3)
        self.s.talon.dealCards()


# ************************************************************************
#  * Ten Avatars
#  ***********************************************************************/

class TenAvatars(AbstractDashavataraGame):

    #
    # Game layout
    #

    def createGame(self):
        l, s = Layout(self), self.s

        # Set window size
        self.setSize(l.XM * 3 + l.XS * 11, l.YM + l.YS * 6)

        # Create row stacks
        x = l.XM
        y = l.YM
        for i in range(10):
            s.rows.append(RK_RowStack(x, y, self, base_rank=11,
                                      max_move=12, max_cards=99))
            x = x + l.XS

        # Create reserve stacks
        x = self.width - l.XS
        y = l.YM
        for i in range(6):
            s.reserves.append(ReserveStack(x, y, self))
            y = y + l.YS
        y = y - l.YS
        for i in range(6):
            x = x - l.XS
            s.reserves.append(ReserveStack(x, y, self))

        self.setRegion(s.rows, (0, 0, l.XM + l.XS * 10, l.YS * 5))

        # Create talon
        s.talon = DealRowTalonStack(l.XM, self.height - l.YS, self)
        l.createText(s.talon, "n")

        # Define stack groups
        l.defaultStackGroups()

    #
    # Game over rides
    #

    def startGame(self):
        assert len(self.s.talon.cards) == 120
        for i in range(4):
            self.s.talon.dealRow(flip=1, frames=0)
        self.startDealSample()
        self.s.talon.dealCards()

    def isGameWon(self):
        if len(self.s.talon.cards):
            return 0
        for s in self.s.rows:
            if len(s.cards) != 12 or not isSameSuitSequence(s.cards):
                return 0
        return 1


# ************************************************************************
#  * Balarama
#  ***********************************************************************/

class Balarama(AbstractDashavataraGame):
    Layout_Method = staticmethod(Layout.ghulamLayout)
    Talon_Class = InitialDealTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = Dashavatara_AC_RowStack
    BASE_RANK = ANY_RANK

    #
    # Game layout
    #

    def createGame(self, **layout):
        l, s = Layout(self), self.s
        kwdefault(layout, rows=16, reserves=4, texts=0)
        self.Layout_Method(l, **layout)
        self.setSize(l.size[0], l.size[1])

        # Create foundations
        for r in l.s.foundations:
            s.foundations.append(self.Foundation_Class(r.x, r.y, self,
                                 r.suit, mod=12, max_cards=12))

        # Create reserve stacks
        for r in l.s.reserves:
            s.reserves.append(ReserveStack(r.x, r.y, self, ))

        # Create row stacks
        for r in l.s.rows:
            s.rows.append(self.RowStack_Class(r.x, r.y, self, l.YOFFSET,
                          suit=ANY_SUIT, base_rank=self.BASE_RANK,
                          max_cards=12))

        # Create talon
        s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self)

        # Define stack groups
        l.defaultAll()

    #
    # Game over rides
    #

    def startGame(self):
        assert len(self.s.talon.cards) == 120
        for i in range(6):
            self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=0)
        self.startDealSample()
        self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=3)
        self.s.talon.dealRow(rows=self.s.rows[:8], flip=1, frames=3)
        self.s.talon.dealCards()

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.color % 2 != card2.color % 2 and
                (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank))


# ************************************************************************
#  * Hayagriva
#  ***********************************************************************/

class Hayagriva(Balarama):
    Layout_Method = staticmethod(Layout.ghulamLayout)
    Talon_Class = InitialDealTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = Dashavatara_RK_RowStack
    BASE_RANK = 11

    #
    # Game layout
    #

    def createGame(self, **layout):
        Balarama.createGame(self)

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)


# ************************************************************************
#  * Shanka
#  ***********************************************************************/

class Shanka(Balarama):
    Layout_Method = staticmethod(Layout.ghulamLayout)
    Talon_Class = InitialDealTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = Dashavatara_RK_RowStack
    BASE_RANK = 11

    #
    # Game layout
    #

    def createGame(self, **layout):
        Balarama.createGame(self, reserves=0)

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        if stack1 in self.s.foundations:
            return (card1.suit == card2.suit and
                    (card1.rank + 1 == card2.rank or
                     card2.rank + 1 == card1.rank))
        return (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)


# ************************************************************************
#  * Surukh
#  ***********************************************************************/

class Surukh(Balarama):
    Layout_Method = staticmethod(Layout.ghulamLayout)
    Talon_Class = InitialDealTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = Dashavatara_AF_RowStack
    BASE_RANK = ANY_RANK

    #
    # Game layout
    #

    def createGame(self, **layout):
        Balarama.createGame(self, reserves=4)

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        if card1.suit <= 4:
            force0 = 0
        else:
            force0 = 1
        if card2.suit <= 4:
            force1 = 0
        else:
            force1 = 1
        return (force0 != force1 and
                (card1.rank + 1 == card2.rank or
                 card2.rank + 1 == card1.rank))


# ************************************************************************
#  * Matsya
#  ***********************************************************************/

class Matsya(AbstractDashavataraGame):
    Layout_Method = staticmethod(Layout.klondikeLayout)
    Talon_Class = WasteTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = RK_RowStack
    BASE_RANK = 11

    #
    # Game layout
    #

    def createGame(self, max_rounds=1, num_deal=1, **layout):
        l, s = Layout(self), self.s
        kwdefault(layout, rows=10, waste=1)
        self.Layout_Method(l, **layout)
        self.setSize(l.size[0], l.size[1])

        # Create talon
        s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self,
                                   max_rounds=max_rounds, num_deal=num_deal)
        s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self)

        # Create foundations
        for r in l.s.foundations:
            s.foundations.append(self.Foundation_Class(r.x, r.y, self,
                                 r.suit, mod=12, max_cards=12, max_move=0))

        # Create row stacks
        for r in l.s.rows:
            s.rows.append(self.RowStack_Class(r.x, r.y, self,
                          suit=ANY_SUIT, base_rank=self.BASE_RANK))

        # Define stack groups
        l.defaultAll()

    #
    # Game over rides
    #

    def startGame(self):
        assert len(self.s.talon.cards) == 120
        for i in range(10):
            self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0)
        self.startDealSample()
        self.s.talon.dealRow()
        self.s.talon.dealCards()

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)


# ************************************************************************
#  * Kurma
#  ***********************************************************************/

class Kurma(Matsya):
    Layout_Method = staticmethod(Layout.klondikeLayout)
    Talon_Class = WasteTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = SS_RowStack
    BASE_RANK = ANY_RANK

    #
    # Game layout
    #

    def createGame(self, **layout):
        Matsya.createGame(self, max_rounds=-1)


# ************************************************************************
#  * Varaha
#  ***********************************************************************/

class Varaha(Matsya):
    Layout_Method = staticmethod(Layout.klondikeLayout)
    Talon_Class = WasteTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = SS_RowStack
    BASE_RANK = ANY_RANK

    #
    # Game layout
    #

    def createGame(self, **layout):
        Matsya.createGame(self, max_rounds=-1, num_deal=3)


# ************************************************************************
#  * Narasimha
#  ***********************************************************************/

class Narasimha(Matsya):
    Layout_Method = staticmethod(Layout.klondikeLayout)
    Talon_Class = WasteTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = AC_RowStack
    BASE_RANK = 11

    #
    # Game layout
    #

    def createGame(self, **layout):
        Matsya.createGame(self, max_rounds=1, num_deal=1)

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.color % 2 != card2.color % 2 and
                (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank))


# ************************************************************************
#  * Vamana
#  ***********************************************************************/

class Vamana(Matsya):
    Layout_Method = staticmethod(Layout.klondikeLayout)
    Talon_Class = WasteTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = AC_RowStack
    BASE_RANK = 11

    #
    # Game layout
    #

    def createGame(self, **layout):
        Matsya.createGame(self, max_rounds=-1, num_deal=3)

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.color % 2 != card2.color % 2 and
                (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank))


# ************************************************************************
#  * Parashurama
#  ***********************************************************************/

class Parashurama(Matsya):
    Layout_Method = staticmethod(Layout.klondikeLayout)
    Talon_Class = WasteTalonStack
    Foundation_Class = SS_FoundationStack
    RowStack_Class = RK_RowStack
    BASE_RANK = 11

    #
    # Game layout
    #

    def createGame(self, **layout):
        Matsya.createGame(self, max_rounds=2, num_deal=3)

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)


# ************************************************************************
# * Journey to Cuddapah
# ************************************************************************

class Journey(AbstractDashavataraGame):
    Hint_Class = Journey_Hint

    BRAID_CARDS = 15
    BRAID_OFFSET = 1

    #
    # game layout
    #

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

        # set window
        # (piles up to 20 cards are playable - needed for Braid_BraidStack)
        decks = self.gameinfo.decks
        h = max(5 * l.YS + 35, 2*l.YM + 2*l.YS +
                (self.BRAID_CARDS - 1) * l.YOFFSET*self.BRAID_OFFSET)
        self.setSize(l.XM + l.XS * (7 + decks * 2), l.YM + h)

        # extra settings
        self.base_card = None

        # Create foundations, rows, reserves
        s.addattr(braidstrong=None)      # register extra stack variable
        s.addattr(braidweak=None)      # register extra stack variable
        x, y = l.XM, l.YM
        for j in range(5):
            for i in range(decks):
                s.foundations.append(Journey_Foundation(x + l.XS * i, y, self,
                                     j, mod=12, max_cards=12))
            s.rows.append(Journey_StrongStack(x + l.XS * decks, y, self))
            s.rows.append(
                Journey_ReserveStack(x + l.XS * (1 + decks), y, self))
            y = y + l.YS
        x, y = x + l.XS * (5 + decks), l.YM
        for j in range(5):
            s.rows.append(Journey_ReserveStack(x, y, self))
            s.rows.append(Journey_WeakStack(x + l.XS, y, self))
            for i in range(decks, 0, -1):
                s.foundations.append(
                    Journey_Foundation(x + l.XS * (1 + i), y, self,
                                       j + 5, mod=12, max_cards=12))
            y = y + l.YS
        self.texts.info = MfxCanvasText(
            self.canvas,
            self.width // 2, h - l.YM // 2,
            anchor="center",
            font=self.app.getFont("canvas_default"))

        # Create braids
        x, y = l.XM + l.XS * 2.15 + l.XS * decks, l.YM
        s.braidstrong = Journey_BraidStack(
            x, y, self, xoffset=12, yoffset=self.BRAID_OFFSET)
        x = x + l.XS * 1.7
        s.braidweak = Journey_BraidStack(
            x, y, self, xoffset=-12, yoffset=self.BRAID_OFFSET)

        # Create talon
        x, y = l.XM + l.XS * 2 + l.XS * decks, h - l.YS - l.YM
        s.talon = WasteTalonStack(x, y, self, max_rounds=3)
        l.createText(s.talon, "s")
        s.talon.texts.rounds = MfxCanvasText(
            self.canvas,
            self.width // 2, h - l.YM * 2.5,
            anchor="center",
            font=self.app.getFont("canvas_default"))
        x = x + l.XS * 2
        s.waste = WasteStack(x, y, self)
        l.createText(s.waste, "s")

        # define stack-groups
        self.sg.talonstacks = [s.talon] + [s.waste]
        self.sg.openstacks = s.foundations + s.rows
        self.sg.dropstacks = [s.braidstrong] + [s.braidweak] + s.rows \
            + [s.waste]

    #
    # game overrides
    #

    def startGame(self):
        self.startDealSample()
        self.base_card = None
        self.updateText()
        for i in range(self.BRAID_CARDS):
            self.s.talon.dealRow(rows=[self.s.braidstrong])
        for i in range(self.BRAID_CARDS):
            self.s.talon.dealRow(rows=[self.s.braidweak])
        self.s.talon.dealRow()
        # deal base_card to foundations, update cap.base_rank
        self.base_card = self.s.talon.getCard()
        to_stack = self.s.foundations[
            self.base_card.suit * self.gameinfo.decks]
        self.flipMove(self.s.talon)
        self.moveMove(1, self.s.talon, to_stack)
        self.updateText()
        for s in self.s.foundations:
            s.cap.base_rank = self.base_card.rank
        # deal first card to WasteStack
        self.s.talon.dealCards()

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.suit == card2.suit and
                ((card1.rank + 1) % 12 == card2.rank or
                 (card2.rank + 1) % 12 == card1.rank))

    def getHighlightPilesStacks(self):
        return ()

    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)

    #
    # game extras
    #

    def updateText(self):
        if self.preview > 1 or not self.texts.info:
            return
        if not self.base_card:
            t = ""
        else:
            t = self.RANKS[self.base_card.rank]
            dir = self.getFoundationDir() % 12
            if dir == 1:
                t = t + _(" Ascending")
            elif dir == 11:
                t = t + _(" Descending")
        self.texts.info.config(text=t)


# ************************************************************************
# * Long Journey to Cuddapah
# ************************************************************************

class LongJourney(Journey):

    BRAID_CARDS = 20
    BRAID_OFFSET = .7


# ************************************************************************
#  * Appachan's Waterfall
#  ***********************************************************************/

class AppachansWaterfall(AbstractDashavataraGame):

    #
    # Game layout
    #

    def createGame(self):
        l, s = Layout(self), self.s

        # Set window size
        w, h = l.XM + l.XS * 10, l.YM + l.YS * 6
        self.setSize(w, h)

        # Create row stacks
        x, y = l.XM, l.YM
        for i in range(10):
            s.rows.append(AppachansWaterfall_RowStack(x, y, self,
                                                      base_rank=ANY_RANK,
                                                      max_move=12,
                                                      max_cards=99))
            x = x + l.XS
        self.setRegion(s.rows, (-999, -999, 999999, l.YM + l.YS * 5))

        # Create foundation
        x, y = w // 2 - l.CW // 2, h - l.YS
        s.foundations.append(AppachansWaterfall_Foundation(x, y, self, -1))

        # Create reserves
        s.reserves.append(ReserveStack(x - l.XS * 2, y, self))
        s.reserves.append(ReserveStack(x + l.XS * 2, y, self))

        # Create talon
        s.talon = DealRowTalonStack(l.XM, y, self)
        l.createText(s.talon, "n")

        # Define stack groups
        l.defaultStackGroups()

    #
    # Game over rides
    #

    def startGame(self):
        assert len(self.s.talon.cards) == 120
        for i in range(4):
            self.s.talon.dealRow(flip=1, frames=0)
        self.startDealSample()
        self.s.talon.dealCards()

    def isGameWon(self):
        return len(self.s.foundations[0].cards) == 120


# ************************************************************************
# * Hiranyaksha
# ************************************************************************

class Hiranyaksha(AbstractDashavataraGame):
    RowStack_Class = StackWrapper(Dashavatara_RK_RowStack, base_rank=NO_RANK)

    #
    # game layout
    #

    def createGame(self, rows=11, reserves=10):
        # create layout
        l, s = Layout(self), self.s

        # set size
        maxrows = max(rows, reserves)
        self.setSize(l.XM + (maxrows + 2) * l.XS, l.YM + 6 * l.YS)

        #
        playcards = 4 * l.YS // l.YOFFSET
        xoffset, yoffset = [], []
        for i in range(playcards):
            xoffset.append(0)
            yoffset.append(l.YOFFSET)
        for i in range(96 * self.gameinfo.decks - playcards):
            xoffset.append(l.XOFFSET)
            yoffset.append(0)

        # create stacks
        x, y = l.XM + (maxrows - reserves) * l.XS // 2, l.YM
        for i in range(reserves):
            s.reserves.append(ReserveStack(x, y, self))
            x = x + l.XS
        x, y = l.XM + (maxrows - rows) * l.XS // 2, l.YM + l.YS
        self.setRegion(s.reserves, (-999, -999, 999999, y - l.YM // 2))
        for i in range(rows):
            stack = self.RowStack_Class(x, y, self, yoffset=l.YOFFSET)
            stack.CARD_XOFFSET = xoffset
            stack.CARD_YOFFSET = yoffset
            s.rows.append(stack)
            x = x + l.XS
        x, y = l.XM + maxrows * l.XS, l.YM
        for i in range(2):
            for suit in range(5):
                s.foundations.append(SS_FoundationStack(x, y, self,
                                                        suit=suit + (5 * i)))
                y = y + l.YS
            x, y = x + l.XS, l.YM
        self.setRegion(self.s.foundations, (x - l.XS * 2, -999, 999999,
                       self.height - (l.YS + l.YM)), priority=1)
        s.talon = InitialDealTalonStack(
            self.width - 3 * l.XS // 2, self.height - l.YS, self)

        # define stack-groups
        l.defaultStackGroups()

    #
    # game overrides
    #

    def startGame(self):
        self.startDealSample()
        i = 0
        while self.s.talon.cards:
            if self.s.talon.cards[-1].rank == 11:
                if self.s.rows[i].cards:
                    i = i + 1
            self.s.talon.dealRow(rows=[self.s.rows[i]], frames=4)

    # must look at cards
    def _getClosestStack(self, cx, cy, stacks, dragstack):
        closest, cdist = None, 999999999
        for stack in stacks:
            if stack.cards and stack is not dragstack:
                dist = (stack.cards[-1].x - cx)**2 + \
                    (stack.cards[-1].y - cy)**2
            else:
                dist = (stack.x - cx)**2 + (stack.y - cy)**2
            if dist < cdist:
                closest, cdist = stack, dist
        return closest

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        row = self.s.rows[0]
        sequence = row.isRankSequence
        return (sequence([card1, card2]) or sequence([card2, card1]))


# ************************************************************************
# * Dashavatara Hint
# ************************************************************************

class Dashavatara_Hint(AbstractHint):
    def computeHints(self):
        game = self.game

        # 2)See if we can move a card to the tableaux
        if not self.hints:
            for r in game.sg.dropstacks:
                pile = r.getPile()
                if not pile or len(pile) != 1:
                    continue
                if r in game.s.tableaux:
                    rr = self.ClonedStack(r, stackcards=r.cards[:-1])
                    if rr.acceptsCards(None, pile):
                        # do not move a card that is already in correct place
                        continue
                    base_score = 80000 + (4 - r.cap.base_suit)
                else:
                    base_score = 80000
                # find a stack that would accept this card
                for t in game.s.tableaux:
                    if t is not r and t.acceptsCards(r, pile):
                        score = base_score + 100 * (self.K - pile[0].rank)
                        self.addHint(score, 1, r, t)
                        break

        # 3)See if we can move a card from the tableaux
        #    to a row stack. This can only happen if there are
        #    no more cards to deal.
        if not self.hints:
            for r in game.s.tableaux:
                pile = r.getPile()
                if not pile or len(pile) != 1:
                    continue
                rr = self.ClonedStack(r, stackcards=r.cards[:-1])
                if rr.acceptsCards(None, pile):
                    # do not move a card that is already in correct place
                    continue
                # find a stack that would accept this card
                for t in game.s.rows:
                    if t is not r and t.acceptsCards(r, pile):
                        score = 70000 + 100 * (self.K - pile[0].rank)
                        self.addHint(score, 1, r, t)
                        break

        # 4)See if we can move a card within the row stacks
        if not self.hints:
            for r in game.s.rows:
                pile = r.getPile()
                if not pile or len(pile) != 1 or len(pile) == len(r.cards):
                    continue
                base_score = 60000
                # find a stack that would accept this card
                for t in game.s.rows:
                    if t is not r and t.acceptsCards(r, pile):
                        score = base_score + 100 * (self.K - pile[0].rank)
                        self.addHint(score, 1, r, t)
                        break

        # 5)See if we can deal cards
        if self.level >= 2:
            if game.canDealCards():
                self.addHint(self.SCORE_DEAL, 0, game.s.talon, None)


# ************************************************************************
# * Dashavatara
# ************************************************************************

class Dashavatara(AbstractDashavataraGame):
    Hint_Class = Dashavatara_Hint

    #
    # game layout
    #

    def createGame(self):
        # create layout
        l, s = Layout(self), self.s
        TABLEAU_YOFFSET = min(9, max(3, l.YOFFSET // 3))

        # set window
        th = l.YS + 3 * TABLEAU_YOFFSET
        # (set piles so that at least 2/3 of a card is visible with 10 cards)
        h = 10 * l.YOFFSET + l.CH * 2//3
        self.setSize(11 * l.XS + l.XM * 2, l.YM + 3 * th + l.YM + h)

        # create stacks
        s.addattr(tableaux=[])     # register extra stack variable
        x = l.XM + 8 * l.XS + l.XS // 2
        y = l.YM
        for i in range(3, 0, -1):
            x = l.XM
            for j in range(10):
                s.tableaux.append(
                    Dashavatara_TableauStack(
                        x, y, self, i - 1, TABLEAU_YOFFSET))
                x = x + l.XS
            x = x + l.XM
            s.reserves.append(Dashavatara_ReserveStack(x, y, self))
            y = y + th
        x, y = l.XM, y + l.YM
        for i in range(10):
            s.rows.append(Dashavatara_RowStack(x, y, self, max_accept=1))
            x = x + l.XS
        x = self.width - l.XS
        y = self.height - l.YS
        s.talon = DealRowTalonStack(x, y, self)
        l.createText(s.talon, "sw")

        # define stack-groups
        self.sg.openstacks = s.tableaux + s.rows + s.reserves
        self.sg.talonstacks = [s.talon]
        self.sg.dropstacks = s.tableaux + s.rows

    #
    # game overrides
    #

    def startGame(self):
        self.s.talon.dealRow(rows=self.s.tableaux, frames=0)
        self._startAndDealRow()

    def isGameWon(self):
        for stack in self.s.tableaux:
            if len(stack.cards) != 4:
                return 0
        return 1

    def fillStack(self, stack):
        if self.s.talon.cards:
            if stack in self.s.rows and len(stack.cards) == 0:
                self.s.talon.dealRow(rows=[stack])

    def shallHighlightMatch(self, stack1, card1, stack2, card2):
        return (card1.suit == card2.suit and
                (card1.rank + 3 == card2.rank or card2.rank + 3 == card1.rank))

    def getHighlightPilesStacks(self):
        return ()


# ************************************************************************
#  *
#  ***********************************************************************/

def r(id, gameclass, name, game_type, decks, redeals, skill_level,
      altnames=()):
    game_type = game_type | GI.GT_DASHAVATARA_GANJIFA
    gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level,
                  suits=list(range(10)), ranks=list(range(12)),
                  altnames=altnames)
    registerGame(gi)
    return gi


r(15406, Matsya, "Matsya", GI.GT_DASHAVATARA_GANJIFA, 1, 0, GI.SL_BALANCED)
r(15407, Kurma, "Kurma", GI.GT_DASHAVATARA_GANJIFA, 1, -1, GI.SL_BALANCED)
r(15408, Varaha, "Varaha", GI.GT_DASHAVATARA_GANJIFA, 1, -1, GI.SL_BALANCED)
r(15409, Narasimha, "Narasimha", GI.GT_DASHAVATARA_GANJIFA, 1, 0,
  GI.SL_BALANCED)
r(15410, Vamana, "Vamana", GI.GT_DASHAVATARA_GANJIFA, 1, -1, GI.SL_BALANCED)
r(15411, Parashurama, "Parashurama", GI.GT_DASHAVATARA_GANJIFA, 1, 1,
  GI.SL_BALANCED)
r(15412, TenAvatars, "Ten Avatars", GI.GT_DASHAVATARA_GANJIFA, 1, 0,
  GI.SL_MOSTLY_SKILL)
r(15413, DashavataraCircles, "Dashavatara Circles", GI.GT_DASHAVATARA_GANJIFA,
  1, 0, GI.SL_MOSTLY_SKILL)
r(15414, Balarama, "Balarama", GI.GT_DASHAVATARA_GANJIFA, 1, 0,
  GI.SL_MOSTLY_SKILL)
r(15415, Hayagriva, "Hayagriva", GI.GT_DASHAVATARA_GANJIFA, 1, 0,
  GI.SL_MOSTLY_SKILL)
r(15416, Shanka, "Shanka", GI.GT_DASHAVATARA_GANJIFA, 1, 0, GI.SL_MOSTLY_SKILL)
r(15417, Journey, "Journey to Cuddapah", GI.GT_DASHAVATARA_GANJIFA, 1, 2,
  GI.SL_BALANCED)
r(15418, LongJourney, "Long Journey to Cuddapah", GI.GT_DASHAVATARA_GANJIFA,
  2, 2, GI.SL_BALANCED)
r(15419, Surukh, "Surukh", GI.GT_DASHAVATARA_GANJIFA, 1, 0, GI.SL_BALANCED)
r(15420, AppachansWaterfall, "Appachan's Waterfall", GI.GT_DASHAVATARA_GANJIFA,
  1, 0, GI.SL_MOSTLY_SKILL)
r(15421, Hiranyaksha, 'Hiranyaksha', GI.GT_DASHAVATARA_GANJIFA, 1, 0,
  GI.SL_MOSTLY_SKILL)
r(15422, Dashavatara, 'Dashavatara', GI.GT_DASHAVATARA_GANJIFA, 1, 0,
  GI.SL_BALANCED)

del r
