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


# imports

# PySol imports
from pysollib.mfxutil import Struct
from pysollib.pysoltk import MfxCanvasText


# ************************************************************************
# * a helper class to create common layouts
# ************************************************************************

# a layout stack
class _LayoutStack:
    def __init__(self, x, y, suit=None):
        self.x = int(round(x))
        self.y = int(round(y))
        self.suit = suit
        self.text_args = {}
        self.text_format = "%d"

    def setText(self, x, y, anchor="center", format=None, **kw):
        self.text_args["x"] = x
        self.text_args["y"] = y
        self.text_args["anchor"] = anchor
        self.text_args.update(kw)
        if format is not None:
            self.text_format = format


class Layout:
    def __init__(self, game, card_x_space=None, card_y_space=None, **kw):
        self.game = game
        self.canvas = self.game.canvas
        self.size = None
        self.s = Struct(
            talon=None,
            waste=None,
            foundations=[],
            rows=[],
            reserves=[],
        )
        self.stackmap = {}
        self.regions = []
        # set visual constants
        images = self.game.app.images

        layout_x_margin = images.CARDW // 9
        layout_y_margin = layout_x_margin
        layout_card_x_space = images.CARDW // 9
        layout_card_y_space = images.CARDH // 8

        self.CW = images.CARDW
        self.CH = images.CARDH
        self.XOFFSET = images.CARD_XOFFSET
        self.YOFFSET = images.CARD_YOFFSET
        self.XM = layout_x_margin       # XMARGIN
        self.YM = layout_y_margin       # YMARGIN

        if card_x_space is None:
            self.XS = self.CW + layout_card_x_space          # XSPACE
        else:
            self.XS = self.CW + card_x_space
        if card_y_space is None:
            self.YS = self.CH + layout_card_y_space          # YSPACE
        else:
            self.YS = self.CH + card_y_space

        # self.CARD_X_SPACE = layout_card_x_space
        # self.CARD_Y_SPACE = layout_card_y_space
        # self.RIGHT_MARGIN = layout_x_margin-layout_card_x_space
        # self.BOTTOM_MARGIN = layout_y_margin-layout_card_y_space

        font = game.app.getFont("canvas_default")
        # self.TEXT_MARGIN = 10
        self.TEXT_MARGIN = font[1]
        # self.TEXT_HEIGHT = 30
        self.TEXT_HEIGHT = 18+font[1]

        self.__dict__.update(kw)
        if self.game.preview > 1:
            if "XOFFSET" in kw:
                self.XOFFSET //= self.game.preview
            if "YOFFSET" in kw:
                self.YOFFSET //= self.game.preview
            self.TEXT_HEIGHT = 10

    def __createStack(self, x, y, suit=None):
        stack = _LayoutStack(x, y, suit)
        mapkey = (stack.x, stack.y)
        # from pprint import pprint
        # print mapkey
        # pprint(self.stackmap)
        assert mapkey not in self.stackmap
        self.stackmap[mapkey] = stack
        return stack

    def _setText(self, stack, anchor="center"):
        tx, ty, ta, tf = self.getTextAttr(stack, anchor)
        stack.setText(tx, ty, ta, tf)

    #
    #
    #

    def createGame(self, layout_method,
                   talon_class=None,
                   waste_class=None,
                   foundation_class=None,
                   row_class=None,
                   reserve_class=None,
                   **kw
                   ):
        # create layout
        game = self.game
        s = game.s
        layout_method(self, **kw)
        game.setSize(self.size[0], self.size[1])
        # create stacks
        if talon_class:
            s.talon = talon_class(self.s.talon.x, self.s.talon.y, game)
        if waste_class:
            s.waste = waste_class(self.s.waste.x, self.s.waste.y, game)
        if foundation_class:
            if isinstance(foundation_class, (list, tuple)):
                n = len(self.s.foundations)//len(foundation_class)
                i = 0
                for j in range(n):
                    for cls in foundation_class:
                        r = self.s.foundations[i]
                        s.foundations.append(cls(r.x, r.y, game, suit=r.suit))
                        i += 1

            else:
                for r in self.s.foundations:
                    s.foundations.append(foundation_class(r.x, r.y, game,
                                                          suit=r.suit))
        if row_class:
            for r in self.s.rows:
                s.rows.append(row_class(r.x, r.y, game))
        if reserve_class:
            for r in self.s.reserves:
                s.reserves.append(reserve_class(r.x, r.y, game))
        # default
        self.defaultAll()
        # reserves texts
        if self.s.reserves and ('reserve_texts' in kw) and kw['reserve_texts']:
            game = self.game
            for i in range(len(game.s.reserves)):
                s1 = game.s.reserves[i]
                s2 = self.s.reserves[i]
                s1.texts.ncards = self.defaultText(s2)

    #
    # public util for use by class Game
    #

    def getTextAttr(self, stack, anchor):
        x, y = 0, 0
        if stack is not None:
            x, y = stack.x, stack.y
        delta_x, delta_y = 4, 4
        delta_yy = 10
        d = {
            "n": (x+self.CW//2,       y-delta_y,          "s",  "%d"),
            "nn": (x+self.CW//2,       y-delta_yy,         "s",  "%d"),
            "s": (x+self.CW//2,       y+self.CH+delta_y,  "n",  "%d"),
            "ss": (x+self.CW//2,       y+self.CH+delta_yy, "n",  "%d"),
            "nw": (x-delta_x,         y,                  "ne", "%d"),
            "sw": (x-delta_x,         y+self.CH,          "se", "%d"),
            "ne": (x+self.CW+delta_x, y,                  "nw", "%d"),
            "se": (x+self.CW+delta_x, y+self.CH,          "sw", "%d"),
            "w": (x-delta_x,         y+self.CH//2,        "e",  "%d"),
            "e": (x+self.CW+delta_x, y+self.CH//2,        "w",  "%d"),
            }
        return d[anchor]

    def createText(self, stack, anchor, dx=0, dy=0, text_format=""):
        if self.canvas.preview > 1:
            return
        assert stack.texts.ncards is None
        tx, ty, ta, tf = self.getTextAttr(stack, anchor)
        font = self.game.app.getFont("canvas_default")
        stack.texts.ncards = MfxCanvasText(self.canvas, tx+dx, ty+dy,
                                           anchor=ta, font=font)
        stack.texts.ncards.text_format = text_format or tf

    def createRoundText(self, stack, anchor, dx=0, dy=0):
        if self.canvas.preview > 1:
            return
        assert stack.texts.rounds is None
        delta_x, delta_y = 0, 0
        if anchor == 'nnn':
            anchor = 'nn'
            delta_y = -self.TEXT_MARGIN
        elif anchor == 'sss':
            anchor = 'ss'
            delta_y = self.TEXT_MARGIN
        tx, ty, ta, tf = self.getTextAttr(stack, anchor)
        tx += delta_x + dx
        ty += delta_y + dy
        font = self.game.app.getFont("canvas_default")
        stack.texts.rounds = MfxCanvasText(self.canvas, tx, ty,
                                           anchor=ta, font=font)

    def setRegion(self, stacks, rects):
        self.regions.append((stacks, rects))

    #
    # util for use by a Game
    #

    def defaultAll(self):
        game = self.game
        # create texts
        if game.s.talon:
            game.s.talon.texts.ncards = self.defaultText(self.s.talon)
        if game.s.waste:
            game.s.waste.texts.ncards = self.defaultText(self.s.waste)
        # define stack-groups
        self.defaultStackGroups()
        # set regions
        self.defaultRegions()

    def defaultText(self, layout_stack):
        if self.canvas.preview > 1:
            return None
        # print layout_stack, layout_stack.text_args
        if layout_stack is None or not layout_stack.text_args:
            return None
        layout_stack.text_args["font"] = \
            self.game.app.getFont("canvas_default")
        t = MfxCanvasText(self.game.canvas, **layout_stack.text_args)
        t.text_format = layout_stack.text_format
        return t

    # define stack-groups
    def defaultStackGroups(self):
        game = self.game
        waste = []
        if game.s.waste is not None:
            waste = [game.s.waste]
        game.sg.talonstacks = [game.s.talon] + waste
        game.sg.dropstacks = game.s.rows + game.s.reserves + waste
        game.sg.openstacks = game.s.foundations + game.s.rows + game.s.reserves
        game.sg.reservestacks = game.s.reserves

    def defaultRegions(self):
        for region in self.regions:
            # convert layout-stacks to corresponding game-stacks
            stacks = []
            for s in region[0]:
                mapkey = (s.x, s.y)
                id = self.game.stackmap[mapkey]
                stacks.append(self.game.allstacks[id])
            # print stacks, region[1]
            self.game.setRegion(stacks, region[1])

    #
    # Baker's Dozen layout
    #  - left: 2 rows
    #  - right: foundations, talon
    #

    def bakersDozenLayout(self, rows, texts=0, playcards=9):
        S = self.__createStack
        CW, CH = self.CW, self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
        halfrows = (rows + 1) // 2

        # set size so that at least 9 cards are fully playable
        h = YS + min(2*YS, (playcards-1)*self.YOFFSET)
        h = max(h, 5*YS//2, 3*YS//2+CH)
        h = min(h, 3*YS)

        # create rows
        x, y = XM, YM
        for i in range(halfrows):
            self.s.rows.append(S(x+i*XS, y))
        for i in range(rows-halfrows):
            self.s.rows.append(S(x+i*XS, y+h))

        # create foundations
        x, y = XM + halfrows * XS, YM
        self.setRegion(self.s.rows, (-999, -999, x - CW // 2, 999999))
        for suit in range(suits):
            for i in range(decks):
                self.s.foundations.append(S(x+i*XS, y, suit=suit))
            y += YS

        # create talon
        h = YM + 2*h
        self.s.talon = S(x, h - YS)
        if texts:
            assert 0

        # set window
        self.size = (XM + (halfrows+decks)*XS, h)

    #
    # FreeCell layout
    #  - top: free cells, foundations
    #  - below: rows
    #  - left bottom: talon, waste
    #

    def freeCellLayout(self, rows=0, reserves=0, waste=0,
                       texts=0, reserve_texts=False, playcards=18):
        S = self.__createStack
        CH = self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
        toprows = suits*decks
        if reserves:
            toprows += reserves+1
        maxrows = max(rows, toprows)

        w = XM + maxrows*XS

        # set size so that at least 2//3 of a card is visible with 18 cards
        h = CH*2//3 + (playcards-1)*self.YOFFSET
        h = YM + YS + max(h, 3*YS)
        if reserves and reserve_texts:
            h += self.TEXT_HEIGHT

        # create reserves & foundations
        x, y = (w - (toprows*XS - XM))//2, YM
        if reserves:
            for i in range(reserves):
                s = S(x, y)
                self.s.reserves.append(s)
                if reserve_texts:
                    self._setText(s, anchor="s")
                x += XS
            x += XS
        for suit in range(suits):
            for i in range(decks):
                self.s.foundations.append(S(x, y, suit=suit))
                x += XS

        # create rows
        x, y = (w - (rows*XS - XM))//2, YM + YS
        if reserves and reserve_texts:
            y += self.TEXT_HEIGHT
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS
        self.setRegion(self.s.rows, (-999, y - CH // 2, 999999, 999999))

        # create talon
        x, y = XM, h - YS
        self.s.talon = s = S(x, y)
        if texts:
            if waste:
                # place text top of stack
                self._setText(s, anchor="n")
            else:
                # place text right of stack
                self._setText(s, anchor="se")
        if waste:
            x += XS
            self.s.waste = s = S(x, y)
            if texts:
                # place text top of stack
                self._setText(s, anchor="n")

        # set window
        self.size = (w, h)

    #
    # Gypsy layout
    #  - left: rows
    #  - right: foundations, talon
    #  - bottom: reserves
    #

    def gypsyLayout(self, rows, waste=0, reserves=0,
                    texts=1, reserve_texts=False, round_text=False,
                    playcards=25):
        S = self.__createStack
        CW, CH = self.CW, self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)

        w = XM + max(rows+decks, reserves+2+waste)*XS
        if reserves:
            h = YS+(playcards-1)*self.YOFFSET+YS
        else:
            # set size so that at least 2//3 of a card is visible with 25 cards
            h = CH*2//3 + (playcards-1)*self.YOFFSET
        h = YM + max(h, (suits+1)*YS)
        if reserves and reserve_texts:
            h += self.TEXT_HEIGHT

        # create rows
        x, y = XM, YM
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS
        if reserves:
            yy = h - YS - CH//2
        else:
            yy = 999999
        self.setRegion(self.s.rows, (-999, -999, x - CW // 2, yy))

        # create foundations
        x = w - decks*XS
        for suit in range(suits):
            for i in range(decks):
                self.s.foundations.append(S(x+i*XS, y, suit=suit))
            y += YS

        # create talon and waste
        x, y = x + (decks-1)*XS, h - YS
        if texts:
            x -= XS//2
        self.s.talon = s = S(x, y)
        anchor = 's'
        if round_text:
            anchor = 'n'
        if texts:
            # place text right of stack
            self._setText(s, anchor=anchor+"e")
        if waste:
            x -= XS
            self.s.waste = s = S(x, y)
            if texts:
                # place text left of stack
                self._setText(s, anchor=anchor+"w")
        # create reserves
        x, y = XM, h-YS
        for i in range(reserves):
            s = S(x, y)
            self.s.reserves.append(s)
            if reserve_texts:
                self._setText(s, anchor="n")
            x += XS

        # set window
        self.size = (w, h)

    #
    # Harp layout
    #  - top: reserves, rows
    #  - bottom: foundations, waste, talon
    #

    def harpLayout(self, rows, waste, reserves=0,
                   texts=1, reserve_texts=False, playcards=19):
        S = self.__createStack
        CH = self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)

        w = max(reserves*XS, rows*XS, (suits*decks+waste+1)*XS,
                (suits*decks+1)*XS+2*XM)
        w += XM

        # set size so that at least 19 cards are fully playable
        h = YS + (playcards-1)*self.YOFFSET
        h = max(h, 3*YS)
        if texts:
            h += self.TEXT_HEIGHT
        if reserves:
            h += YS
        if reserves and reserve_texts:
            h += self.TEXT_HEIGHT

        # top
        y = YM
        if reserves:
            if reserve_texts:
                y += self.TEXT_HEIGHT
            x = (w - (reserves*XS - XM))//2
            for i in range(reserves):
                s = S(x, y)
                self.s.reserves.append(s)
                x += XS
                if reserve_texts:
                    self._setText(s, anchor="n")
            y += YS
        x = (w - (rows*XS - XM))//2
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS

        # bottom
        x, y = XM, YM + h
        for suit in range(suits):
            for i in range(decks):
                self.s.foundations.append(S(x, y, suit=suit))
                x += XS
        if reserves:
            yy = YM + YS - CH//2
            if reserve_texts:
                yy += self.TEXT_HEIGHT
        else:
            yy = -999
        self.setRegion(self.s.rows, (-999, yy, 999999, y - YS // 2))
        if waste:
            x = w - 2*XS
            self.s.waste = s = S(x, y)
            if texts:
                # place text above stack
                self._setText(s, 'n')
        x = w - XS
        self.s.talon = s = S(x, y)
        if texts:
            # place text above stack
            self._setText(s, 'n')

        # set window
        self.size = (w, YM + h + YS)

    #
    # Klondike layout
    #  - top: talon, waste, foundations
    #  - below: rows
    #  - bottom: reserves
    #

    def klondikeLayout(self, rows=0, waste=0, reserves=0,
                       texts=1, reserve_texts=False, round_text=False,
                       playcards=16, center=1, text_height=0):
        S = self.__createStack
        CH = self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
        foundrows = 1 + (suits > 5)
        frows = decks * suits // foundrows
        toprows = 1 + waste + frows
        if round_text:
            toprows += 1
        maxrows = max(rows, toprows, reserves)

        w = XM + maxrows * XS
        # set size so that at least 2//3 of a card is visible with 16 cards
        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
        h = max(h, 2 * YS)
        h += YM + YS * foundrows
        if reserves and reserve_texts:
            h += self.TEXT_HEIGHT

        # top
        # text_height = 0
        x, y = XM, YM
        self.s.talon = s = S(x, y)
        if texts:
            if waste or not center or maxrows - frows <= 1:
                # place text below stack
                self._setText(s, 's')
                text_height = self.TEXT_HEIGHT
            else:
                # place text right of stack
                self._setText(s, 'ne')
        if waste:
            x += XS
            self.s.waste = s = S(x, y)
            if texts:
                # place text below stack
                self._setText(s, 's')
                text_height = self.TEXT_HEIGHT

        for row in range(foundrows):
            x = w - frows * XS
            if center and frows + 2 * (1 + waste + 1) <= maxrows:
                # center the foundations
                x = XM + (maxrows - frows) * XS // 2
            for suit in range(suits // foundrows):
                for i in range(decks):
                    self.s.foundations.append(
                        S(x, y, suit=suit + (row * (suits // 2))))
                    x += XS
            y += YS

        # below
        x = XM
        if rows < maxrows:
            x += (maxrows-rows) * XS//2
        # y += YM * (3 - foundrows)
        y += text_height
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS
        if reserves:
            yy = h - CH//2
        else:
            yy = 999999
        self.setRegion(self.s.rows, (-999, y-CH//2, 999999, yy))

        # bottom
        if reserves:
            x = (maxrows-reserves)*XS//2
            y = h
            h += YS
            for i in range(reserves):
                s = S(x, y)
                self.s.reserves.append(s)
                x += XS
                if reserve_texts:
                    self._setText(s, anchor="n")

        # set window
        self.size = (w, h)

    #
    # Yukon layout
    #  - left: rows
    #  - right: foundations
    #  - left bottom: talon
    #

    def yukonLayout(self, rows, texts=0, playcards=20):
        S = self.__createStack
        CW, CH = self.CW, self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)

        # set size so that at least 2//3 of a card is visible with 20 cards
        h = CH*2//3 + (playcards-1)*self.YOFFSET
        h = YM + max(h, suits*YS)

        # create rows
        x, y = XM, YM
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS
        self.setRegion(self.s.rows, (-999, -999, x - CW // 2, 999999))

        # create foundations
        for suit in range(suits):
            for i in range(decks):
                self.s.foundations.append(S(x+i*XS, y, suit=suit))
            y += YS

        # create talon
        x, y = XM, h - YS
        self.s.talon = s = S(x, y)
        if texts:
            # place text right of stack
            self._setText(s, 'se')

        # set window
        self.size = (XM + (rows+decks)*XS,  h)

    #
    # Easy layout
    #  - top: talon, waste, foundations
    #  - bottom: rows
    #

    def easyLayout(self, rows, waste, texts=1, playcards=10, center=1):
        S = self.__createStack
        CH = self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        ranks = len(self.game.gameinfo.ranks)
        frows = 4 * decks // (1 + (decks >= 3))
        toprows = 1 + waste + frows
        maxrows = max(rows, toprows)
        yextra = 0

        # set size so that at least 2//3 of a card is visible with 10 cards
        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
        h = max(h, 2 * YS)

        # top
        x, y = XM, YM
        self.s.talon = s = S(x, y)
        if texts:
            if waste or not center or maxrows - frows <= 1:
                # place text below stack
                self._setText(s, 's')
                yextra = 20
            else:
                # place text right of stack
                self._setText(s, 'ne')
        if waste:
            x += XS
            self.s.waste = s = S(x, y)
            if texts:
                # place text below stack
                self._setText(s, 's')
        x = XM + (maxrows - frows) * XS
        if center and frows + 2 * (1 + waste + 1) <= maxrows:
            # center the foundations
            x = XM + (maxrows - frows) * XS // 2

        x0, y0 = x, y
        for i in range(decks):
            for rank in range(ranks):
                self.s.foundations.append(S(x0, y0, suit=rank))
                x0 += XS
            if i == 1 and decks > 2:
                x0, y0 = x, y + YS
                y = y0

        # bottom
        x, y = XM, y + YS + yextra * (decks <= 2)
        self.setRegion(self.s.rows, (-999, y - YM // 2, 999999, 999999))
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS

        # set window
        self.size = (XM + maxrows * XS, YM + YS + yextra + h)

    #
    # Samuri layout
    #  - top center: rows
    #  - left & right: foundations
    #  - bottom center: talon
    #

    def samuriLayout(self, rows, waste, texts=1, playcards=20, center=1):
        S = self.__createStack
        CH = self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        toprows = 2 * decks + rows
        yextra = 0

        # set size so that at least 2//3 of a card is visible with 20 cards
        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
        h = max(h, 2 * YS)

        # bottom center
        x = (XM + (toprows * XS) // 2) - XS
        y = h
        self.s.talon = s = S(x, y)
        if texts:
            if waste or not center or toprows - rows <= 1:
                # place text below stack
                self._setText(s, 's')
                yextra = 20
            else:
                # place text right of stack
                self._setText(s, 'ne')
        if waste:
            x += XS
            self.s.waste = s = S(x, y)
            if texts:
                # place text below stack
                self._setText(s, 's')

        # left & right
        x, y = XM, YM
        d, x0, y0 = 0, x, y
        for suit in range(12):
            for i in range(decks):
                x0, y0 = x + XS * i, y + YS * d
                self.s.foundations.append(S(x0, y0, suit=suit))
                if i == decks - 1 and suit == 5:
                    x0, y0 = x + XS * (toprows - decks), YM
                    d, x, y = -1, x0, y0
            d += 1

        # top center
        x, y = XM + XS * decks, YM
        self.setRegion(self.s.rows, (x - XM // 2, 0, x + XS * rows, 999999))
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS

        # set window
        self.size = (XM + toprows * XS, YM + YS + yextra + h)

    #
    # Sumo layout
    #  - top center: rows
    #  - left & right: foundations
    #  - bottom center: talon
    #

    def sumoLayout(self, rows, reserves, texts=0, playcards=12, center=1):
        S = self.__createStack
        CH = self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        assert reserves % 2 == 0
        toprows = 12
        maxrows = max(rows, toprows)
        w = XM + maxrows * XS

        # set size so that at least 2//3 of a card is visible with 12 cards
        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
        h = max(h, 2 * YS)

        # create foundations
        x, y = XM, YM
        for i in range(decks):
            for suit in range(12):
                self.s.foundations.append(S(x, y, suit=suit))
                x += XS
            x, y = XM, y + YS

        # create rows
        x, y = XM + XS * ((toprows - rows) // 2), YM + YS * decks
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS
        self.setRegion(
            self.s.rows,
            (XS + XM // 2, YS * decks + YM // 2, XS * 11 - XM // 2, 999999))

        # create reserves
        x, y = XM, YM + YS * decks
        for i in range(reserves // 2):
            self.s.reserves.append(S(x, y))
            y += YS
        x, y = w - XS, YM + YS * decks
        for i in range(reserves // 2):
            self.s.reserves.append(S(x, y))
            y += YS

        # create talon
        x, y = XM, h + YM
        self.s.talon = s = S(x, y)
        if texts:
            # place text right of stack
            self._setText(s, 'se')

        # set window
        self.size = (XM + toprows * XS, YM + YS + h)

    #
    # Fun layout
    #  - top: rows
    #  - right: foundations
    #  - bottom right: reserves
    #

    def funLayout(self, rows, reserves, texts=0, playcards=12, center=1):
        S = self.__createStack
        CH = self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        ranks = len(self.game.gameinfo.ranks)
        assert rows % 2 == 0
        assert reserves % decks == 0
        toprows = decks + rows // 2
        w = XM * 2 + toprows * XS

        # set size so that at least 2//3 of a card is visible with 12 cards
        h1 = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
        h2 = (3 + reserves // decks) * YS
        h = max(h1, h2)

        # create foundations
        x, y = w - XS * decks, YM
        for i in range(decks):
            for rank in range(ranks):
                self.s.foundations.append(S(x, y, suit=rank))
                y += YS
            x, y = x + XS, YM

        # create rows
        x, y = XM, YM
        for i in range(rows // 2):
            self.s.rows.append(S(x, y))
            x += XS
        x, y = XM, (YS + h) // 2
        for i in range(rows // 2):
            self.s.rows.append(S(x, y))
            x += XS
        self.setRegion(self.s.rows, (0, 0, XS * rows // 2 + XM // 2, 999999))

        # create reserves
        x, y = w - XS * decks, YM + YS * 4
        for i in range(decks):
            for i in range(reserves // decks):
                self.s.reserves.append(S(x, y))
                y += YS
            x, y = x + XS, YM + YS * 4

        # create talon
        x, y = XM, h
        self.s.talon = s = S(x, y)
        if texts:
            # place text right of stack
            self._setText(s, 'se')

        # set window
        self.size = (w, YM + YS + h)

    #
    # Oonsoo layout
    #  - top: talon & rows
    #  - left: reserves
    #  - center right: rows
    #

    def oonsooLayout(self, rows, reserves, texts=0, playcards=12, center=1):
        S = self.__createStack
        CH = self.CH
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        assert rows % 2 == 0
        toprows = decks + rows // 2
        w = XM * 2 + toprows * (XS + XM)

        # set size so that at least 2//3 of a card is visible with 12 cards
        h = CH * 2 // 3 + (playcards - 1) * self.YOFFSET
        h = max(h, 2 * YS)

        # create talon
        x, y = XM, YM
        self.s.talon = s = S(x, y)
        if texts:
            # place text below stack
            self._setText(s, 's')

        # create rows
        x, y = XS + XM * 3, YM
        for i in range(rows // 2):
            self.s.rows.append(S(x, y))
            x += XS + XM
        x, y = XS + XM * 3, (YS + h) // 2
        for i in range(rows // 2):
            self.s.rows.append(S(x, y))
            x += XS + XM
        self.setRegion(self.s.rows, (XS + XM, -999, 999999, 999999))

        # create reserves
        x, y = XM, YM + YS + self.TEXT_HEIGHT
        for i in range(decks):
            for i in range(reserves // decks):
                self.s.reserves.append(S(x, y))
                y += YS
            x, y = x + XS, YM + YS * 4

        # set window
        self.size = (w, YM + YS + h)

    #
    # Ghulam layout
    #  - left & right: foundations & reserves
    #  - center: two groups of rows
    #  - lower right: talon
    #

    def ghulamLayout(self, rows, reserves=0, texts=0):
        S = self.__createStack
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        suits = len(self.game.gameinfo.suits)
        assert rows % 2 == 0
        assert reserves % 2 == 0

        # set size
        w, h = XM * 3 + XS * ((rows // 2) + 2), YM + YS * ((suits // 2) + 2)

        # create foundations
        x, y = XM, YM
        for i in range(suits):
            self.s.foundations.append(S(x, y, suit=i))
            y += YS
            if i == suits // 2 - 1:
                x, y = w - XS, YM

        # create rows
        x = XM * 2 + XS
        for i in range(rows // 2):
            self.s.rows.append(S(x + i * XS, YM))
        for i in range(rows // 2):
            self.s.rows.append(S(x + i * XS, h // 2))
        self.setRegion(self.s.rows, (XM + XS, -999, w - XM - XS, 999999))

        # create reserves
        for i in range(reserves // 2):
            self.s.reserves.append(S(XM, h - YS * (i + 1)))
        for i in range(reserves // 2):
            self.s.reserves.append(S(w - XS, h - YS * (i + 1)))

        # create talon
        self.s.talon = S(w - XS * 2, h - YS)
        if texts:
            assert 0

        # set window
        self.size = (w, h)

    #
    # Generiklon layout
    #  - top: talon & foundations
    #  - bottom: rows
    #

    def generiklonLayout(self, rows, waste=1, height=6):
        S = self.__createStack
        XM, YM = self.XM, self.YM
        XS, YS = self.XS, self.YS

        decks = self.game.gameinfo.decks
        suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps)
        frows = suits * decks // 2
        fspace = XS * (rows - 1) // 2

        # Set window size
        w, h = XM + XS * rows, YM * 2 + YS * height
        self.size = (w, h)

        # Talon
        x, y = XM, YM
        self.s.talon = s = S(x, y)
        self._setText(s, 'se')
        self.s.waste = s = S(x, y + YS)
        self._setText(s, 'se')

        # Create foundations
        x = w - fspace - XS * frows // 2
        for suit in range(suits // 2):
            for i in range(decks):
                self.s.foundations.append(S(x, y, suit=suit))
                x += XS
        x = w - fspace - XS * frows // 2
        y += YS
        for suit in range(suits // 2):
            for i in range(decks):
                self.s.foundations.append(S(x, y, suit=(suit + suits // 20)))
                x += XS

        # bottom
        x, y = XM, YM * 2 + YS * 2
        for i in range(rows):
            self.s.rows.append(S(x, y))
            x += XS
        self.setRegion(self.s.rows, (-999, y - YM, 999999, 999999))
