## vim:ts=4:et:nowrap
##
##---------------------------------------------------------------------------##
##
## PySol -- a Python Solitaire game
##
## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer
## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer
## All Rights Reserved.
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; see the file COPYING.
## If not, write to the Free Software Foundation, Inc.,
## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
##
## Markus F.X.J. Oberhumer
## <markus@oberhumer.com>
## http://www.oberhumer.com/pysol
##
##---------------------------------------------------------------------------##


# imports
import os, re, sys, types, Tkinter
from UserList import UserList

# PySol imports
from mfxtools import *
from mfxutil import destruct, Struct, KwStruct
from util import PACKAGE
from gamedb import GI
from help import helpHTML

# Toolkit imports
from tkutil import unbind_destroy, getFont
from tkwidget import _ToplevelDialog, MfxDialog
from tkcanvas  import MfxCanvasText
from selecttree import SelectDialogTreeLeaf, SelectDialogTreeNode
from selecttree import SelectDialogTreeData, SelectDialogTreeCanvas
from selecttree import SelectDialogPreviewCanvas


# /***********************************************************************
# // Nodes
# ************************************************************************/

class SelectGameLeaf(SelectDialogTreeLeaf):
    pass


class SelectGameNode(SelectDialogTreeNode):
    def _getContents(self):
        contents = []
        if isinstance(self.select_func, UserList):
            # key/value pairs
            for id, name in self.select_func:
                if id and name:
                    node = SelectGameLeaf(self.tree, self, name, key=id)
                    contents.append(node)
        else:
            for gi in self.tree.data.all_games_gi:
                if gi and self.select_func(gi):
                    node = SelectGameLeaf(self.tree, self, gi.name, key=gi.id)
                    contents.append(node)
        return contents or self.tree.data.no_games


# /***********************************************************************
# // Tree database
# ************************************************************************/

class SelectGameData(SelectDialogTreeData):
    def __init__(self, app):
        SelectDialogTreeData.__init__(self)
        self.all_games_gi = map(app.gdb.get, app.gdb.getGamesIdSortedByName())
        self.no_games = [ SelectGameLeaf(None, None, "(no games)", None), ]
        #
        s_by_type = s_special = s_original = s_contrib = None
        g = []
        for data in (GI.SELECT_GAME_BY_TYPE,
                     GI.SELECT_SPECIAL_GAME_BY_TYPE,
                     GI.SELECT_ORIGINAL_GAME_BY_TYPE,
                     GI.SELECT_CONTRIB_GAME_BY_TYPE):
            gg = []
            for name, select_func in data:
                if name is None or not exists(select_func, self.all_games_gi):
                    continue
                name = re.sub(r"&", "", name)
                gg.append(SelectGameNode(None, name, select_func))
            g.append(gg)
        if 1 and g[0]:
            s_by_type = SelectGameNode(None, "by Game Type", tuple(g[0]), expanded=1)
            pass
        if 1 and g[1]:
            s_special = SelectGameNode(None, "Special Games", tuple(g[1]))
            pass
        if 1 and g[2]:
            s_original = SelectGameNode(None, "Original Games", tuple(g[2]))
            pass
        if 1 and g[3]:
            ##s_contrib = SelectGameNode(None, "Contributed Games", tuple(g[3]))
            pass
        #
        s_by_compatibility, gg = None, []
        for name, games in GI.GAMES_BY_COMPATIBILITY:
            select_func = lambda gi, games=games: gi.id in games
            if name is None or not exists(select_func, self.all_games_gi):
               continue
            gg.append(SelectGameNode(None, name, select_func))
        if 1 and gg:
            s_by_compatibility = SelectGameNode(None, "by Compatibility", tuple(gg))
            pass
        #
        s_by_pysol_version, gg = None, []
        for name, games in GI.GAMES_BY_PYSOL_VERSION:
            select_func = lambda gi, games=games: gi.id in games
            if name is None or not exists(select_func, self.all_games_gi):
               continue
            name = "New games in v" + name
            gg.append(SelectGameNode(None, name, select_func))
        if 1 and gg and PACKAGE == "PySol":
            s_by_pysol_version = SelectGameNode(None, "by PySol version", tuple(gg))
            pass
        #
        ul_alternate_names = UserList(list(app.gdb.getGamesTuplesSortedByAlternateName()))
        #
        self.rootnodes = filter(None, (
            SelectGameNode(None, "All Games", lambda gi: 1, expanded=0),
            SelectGameNode(None, "Alternate Names", ul_alternate_names),
            SelectGameNode(None, "Popular Games", lambda gi: gi.si.game_flags & GI.GT_POPULAR, expanded=0),
            s_special,
            s_by_type,
            SelectGameNode(None, "by Game Feature", (
                SelectGameNode(None, "by Number of Cards", (
                    SelectGameNode(None, "32 cards", lambda gi: gi.si.ncards == 32),
                    SelectGameNode(None, "48 cards", lambda gi: gi.si.ncards == 48),
                    SelectGameNode(None, "52 cards", lambda gi: gi.si.ncards == 52),
                    SelectGameNode(None, "64 cards", lambda gi: gi.si.ncards == 64),
                    SelectGameNode(None, "78 cards", lambda gi: gi.si.ncards == 78),
                    SelectGameNode(None, "104 cards", lambda gi: gi.si.ncards == 104),
                    SelectGameNode(None, "144 cards", lambda gi: gi.si.ncards == 144),
                    SelectGameNode(None, "Other number", lambda gi: gi.si.ncards not in (32, 48, 52, 64, 78, 104, 144)),
                )),
                SelectGameNode(None, "by Number of Decks", (
                    SelectGameNode(None, "1 deck games", lambda gi: gi.si.decks == 1),
                    SelectGameNode(None, "2 deck games", lambda gi: gi.si.decks == 2),
                    SelectGameNode(None, "3 deck games", lambda gi: gi.si.decks == 3),
                    SelectGameNode(None, "4 deck games", lambda gi: gi.si.decks == 4),
                )),
                SelectGameNode(None, "by Number of Redeals", (
                    SelectGameNode(None, "No redeal", lambda gi: gi.si.redeals == 0),
                    SelectGameNode(None, "1 redeal", lambda gi: gi.si.redeals == 1),
                    SelectGameNode(None, "2 redeals", lambda gi: gi.si.redeals == 2),
                    SelectGameNode(None, "3 redeals", lambda gi: gi.si.redeals == 3),
                    SelectGameNode(None, "Unlimited redeals", lambda gi: gi.si.redeals == -1),
##                    SelectGameNode(None, "Variable redeals", lambda gi: gi.si.redeals == -2),
                    SelectGameNode(None, "Other number", lambda gi: gi.si.redeals not in (-1, 0, 1, 2, 3)),
                )),
                s_by_compatibility,
            )),
            s_by_pysol_version,
            SelectGameNode(None, "Other Categories", (
                SelectGameNode(None, "Games for Children (very easy)", lambda gi: gi.si.game_flags & GI.GT_CHILDREN),
                SelectGameNode(None, "Games with Scoring",  lambda gi: gi.si.game_flags & GI.GT_SCORE),
                SelectGameNode(None, "Games with Separate Decks",  lambda gi: gi.si.game_flags & GI.GT_SEPARATE_DECKS),
                SelectGameNode(None, "Open Games (all cards visible)", lambda gi: gi.si.game_flags & GI.GT_OPEN),
                SelectGameNode(None, "Relaxed Variants",  lambda gi: gi.si.game_flags & GI.GT_RELAXED),
            )),
            s_original,
            s_contrib,
        ))


# /***********************************************************************
# // Canvas that shows the tree
# ************************************************************************/

class SelectGameTreeWithPreview(SelectDialogTreeCanvas):
    data = None
    html_viewer = None


class SelectGameTree(SelectGameTreeWithPreview):
    def singleClick(self, event=None):
        self.doubleClick(event)


# /***********************************************************************
# // Dialog
# ************************************************************************/

class SelectGameDialog(MfxDialog):
    Tree_Class = SelectGameTree
    TreeDataHolder_Class = SelectGameTreeWithPreview
    TreeData_Class = SelectGameData

    def __init__(self, parent, title, app, gameid, **kw):
        kw = self.initKw(kw)
        _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default)
        top_frame, bottom_frame = self.createFrames(kw)
        self.createBitmaps(top_frame, kw)
        #
        self.app = app
        self.gameid = gameid
        self.random = None
        if self.TreeDataHolder_Class.data is None:
            self.TreeDataHolder_Class.data = self.TreeData_Class(app)
        #
        self.top.wm_minsize(200, 200)
        self.tree = self.Tree_Class(self, top_frame, key=gameid, default=kw.default, font=kw.font)
        self.tree.frame.pack(fill=Tkinter.BOTH, expand=1, padx=kw.padx, pady=kw.pady)
        #
        focus = self.createButtons(bottom_frame, kw)
        focus = self.tree.frame
        self.mainloop(focus, kw.timeout)

    def initKw(self, kw):
        kw = KwStruct(kw,
            strings=(None, None, "Cancel",), default=0,
            separatorwidth=2, resizable=1,
            font=None,
            padx=10, pady=10,
            buttonpadx=10, buttonpady=5,
        )
        return MfxDialog.initKw(self, kw)

    def destroy(self):
        self.app = None
        self.tree.updateNodesWithTree(self.tree.rootnodes, None)
        self.tree.destroy()
        MfxDialog.destroy(self)

    def mDone(self, button):
        if button == 0:                    # Ok or double click
            self.gameid = self.tree.selection_key
            self.tree.n_expansions = 1  # save xyview in any case
        if button == 1:                    # Rules
            doc = self.app.getGameRulesFilename(self.tree.selection_key)
            if not doc:
                return
            dir = os.path.join("html", "rules")
            viewer = SelectGameTreeWithPreview.html_viewer
            if viewer is None:
                SelectGameTreeWithPreview.html_viewer = helpHTML(self.app, doc, dir)
            else:
                url = self.app.dataloader.findFile(doc, dir)
                viewer.updateHistoryXYView()
                viewer.display(url, relpath=0)
            return
        MfxDialog.mDone(self, button)


# /***********************************************************************
# // Dialog
# ************************************************************************/

class SelectGameDialogWithPreview(SelectGameDialog):
    Tree_Class = SelectGameTreeWithPreview

    def __init__(self, parent, title, app, gameid, bookmark=None, **kw):
        kw = self.initKw(kw)
        _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default)
        top_frame, bottom_frame = self.createFrames(kw)
        self.createBitmaps(top_frame, kw)
        #
        self.app = app
        self.gameid = gameid
        self.bookmark = bookmark
        self.random = None
        if self.TreeDataHolder_Class.data is None:
            self.TreeDataHolder_Class.data = self.TreeData_Class(app)
        #
        self.top.wm_minsize(400, 200)
        sw = self.top.winfo_screenwidth()
        if sw >= 1100:
            w1, w2 = 250, 600
        elif sw >= 900:
            w1, w2 = 250, 500
        elif sw >= 800:
            w1, w2 = 220, 480
        else:
            w1, w2 = 200, 300
        ##print sw, w1, w2
        w2 = max(200, min(w2, 10 + 12*(app.subsampled_images.CARDW+10)))
        ##print sw, w1, w2
        self.tree = self.Tree_Class(self, top_frame, key=gameid, default=kw.default, font=kw.font, width=w1)
        self.tree.frame.pack(side="left", fill=Tkinter.BOTH, expand=0, padx=kw.padx, pady=kw.pady)
        self.preview = SelectDialogPreviewCanvas(top_frame, width=w2)
        self.preview.setup(app, kw)
        # set the scale factor
        self.preview.canvas.preview = 2
        # create a preview of the current game
        self.preview_key = -1
        self.preview_game = None
        self.preview_app = None
        self.updatePreview(gameid, animations=0)
        #
        focus = self.createButtons(bottom_frame, kw)
        ##focus = self.tree.frame
        self.mainloop(focus, kw.timeout)

    def initKw(self, kw):
        kw = KwStruct(kw,
            ##strings=("Select", "Rules", "Cancel",), default=0,
            strings=("Select", None, "Cancel",), default=0,
        )
        return SelectGameDialog.initKw(self, kw)

    def destroy(self):
        self.deletePreview(destroy=1)
        self.preview.unbind_all()
        SelectGameDialog.destroy(self)

    def deletePreview(self, destroy=0):
        self.preview_key = -1
        # clean up the canvas
        if self.preview:
            unbind_destroy(self.preview.canvas)
            self.preview.canvas.deleteAllItems()
            if destroy:
                self.preview.canvas.delete("all")
        # destruct the game
        if self.preview_game:
            self.preview_game.endGame()
            self.preview_game.destruct()
            destruct(self.preview_game)
        self.preview_game = None
        # destruct the app
        if destroy:
            if self.preview_app:
                destruct(self.preview_app)
            self.preview_app = None

    def updatePreview(self, gameid, animations=5):
        if gameid == self.preview_key:
            return
        self.deletePreview()
        canvas = self.preview.canvas
        #
        gi = self.app.gdb.get(gameid)
        if not gi:
            self.preview_key = -1
            return
        #
        if self.preview_app is None:
            self.preview_app = Struct(
                # variables
                audio = self.app.audio,
                canvas = canvas,
                cardset = self.app.cardset.copy(),
                comments = self.app.comments.new(),
                debug = 0,
                gamerandom = self.app.gamerandom,
                gdb = self.app.gdb,
                gimages = self.app.gimages,
                images = self.app.subsampled_images,
                menubar = None,
                miscrandom = self.app.miscrandom,
                opt = self.app.opt.copy(),
                startup_opt = self.app.startup_opt,
                stats = self.app.stats.new(),
                top = None,
                top_cursor = self.app.top_cursor,
                toolbar = None,
                # methods
                constructGame = self.app.constructGame,
            )
            self.preview_app.opt.shadow = 0
            self.preview_app.opt.shade = 0
        #
        self.preview_app.audio = None    # turn off audio for intial dealing
        if animations >= 0:
            self.preview_app.opt.animations = animations
        #
        if self.preview_game:
            self.preview_game.endGame()
            self.preview_game.destruct()
        ##self.top.wm_title("Select Game - " + self.app.getGameTitleName(gameid))
        self.top.wm_title("Playable Preview - " + self.app.getGameTitleName(gameid))
        if 1:
            cw, ch = canvas.winfo_width(), canvas.winfo_height()
            if cw >= 100 and ch >= 100:
                MfxCanvasText(canvas, cw / 2, ch - 4,
                              preview=0, anchor="s", text="Playable Area",
                              font=getFont("canvas_large"))
        #
        self.preview_game = gi.gameclass(gi)
        self.preview_game.createPreview(self.preview_app)
        tx, ty = 0, 0
        gw, gh = self.preview_game.width, self.preview_game.height
        canvas.config(scrollregion=(-tx, -ty, -tx, -ty))
        canvas.xview_moveto(0)
        canvas.yview_moveto(0)
        #
        random = None
        if gameid == self.gameid:
            random = self.app.game.random.copy()
        if gameid == self.gameid and self.bookmark:
            self.preview_game.restoreGameFromBookmark(self.bookmark)
        else:
            self.preview_game.newGame(random=random, autoplay=1)
        canvas.config(scrollregion=(-tx, -ty, gw, gh))
        #
        self.preview_app.audio = self.app.audio
        if self.app.opt.animations:
            self.preview_app.opt.animations = 5
        else:
            self.preview_app.opt.animations = 0
        # save seed
        self.random = self.preview_game.random.copy()
        self.random.origin = self.random.ORIGIN_PREVIEW
        self.preview_key = gameid

