## 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 pysollib.mfxutil import destruct, Struct, KwStruct
from pysollib.mfxutil import format_time
from pysollib.gamedb import GI
from pysollib.help import helpHTML
from pysollib.resource import CSI

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

gettext = _

# /***********************************************************************
# // 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:
                    name = gettext(name) # name of game
                    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 is None:
                    # All games
                    ##name = '%s (%s)' % (gi.name, CSI.TYPE_NAME[gi.category])
                    name = gi.name
                    name = gettext(name) # name of game
                    node = SelectGameLeaf(self.tree, self, name, key=gi.id)
                    contents.append(node)
                elif gi and self.select_func(gi):
                    name = gi.name
                    name = gettext(name) # name of game
                    node = SelectGameLeaf(self.tree, self, 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_oriental = s_special = s_original = s_contrib = None
        g = []
        for data in (GI.SELECT_GAME_BY_TYPE,
                     GI.SELECT_ORIENTAL_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 filter(select_func, self.all_games_gi):
                    continue
                name = gettext(name)
                name = name.replace("&", "")
                gg.append(SelectGameNode(None, name, select_func))
            g.append(gg)
        if 1 and g[0]:
            s_by_type = SelectGameNode(None, _("French games"), tuple(g[0]), expanded=1)
            pass
        if 1 and g[1]:
            s_oriental = SelectGameNode(None, _("Oriental Games"), tuple(g[1]))
            pass
        if 1 and g[2]:
            s_special = SelectGameNode(None, _("Special Games"), tuple(g[2]))
            pass
        if 1 and g[3]:
            s_original = SelectGameNode(None, _("Original Games"), tuple(g[3]))
            pass
        if 1 and g[4]:
            ##s_contrib = SelectGameNode(None, "Contributed Games", tuple(g[4]))
            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 filter(select_func, self.all_games_gi):
                continue
            name = gettext(name)
            gg.append(SelectGameNode(None, name, select_func))
        if 1 and gg:
            s_by_compatibility = SelectGameNode(None, _("by Compatibility"), tuple(gg))
            pass
##         #
##         s_mahjongg, 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 filter(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 filter(select_func, self.all_games_gi):
               continue
            name = _("New games in v.") + name
            gg.append(SelectGameNode(None, name, select_func))
        if 1 and gg:
            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, _("All Games"), None, expanded=0),
            SelectGameNode(None, _("Alternate Names"), ul_alternate_names),
            SelectGameNode(None, _("Popular Games"), lambda gi: gi.si.game_flags & GI.GT_POPULAR, expanded=0),
            SelectGameNode(None, _("Mahjongg Games"),
                           lambda gi: gi.si.game_type == GI.GT_MAHJONGG,
                           expanded=0),
            s_oriental,
            s_special,
            s_by_type,
            SelectGameNode(None, _('by Skill Level'), (
                SelectGameNode(None, _('Luck only'), lambda gi: gi.skill_level == GI.SL_LUCK),
                SelectGameNode(None, _('Mostly luck'), lambda gi: gi.skill_level == GI.SL_MOSTLY_LUCK),
                SelectGameNode(None, _('Balanced'), lambda gi: gi.skill_level == GI.SL_BALANCED),
                SelectGameNode(None, _('Mostly skill'), lambda gi: gi.skill_level == GI.SL_MOSTLY_SKILL),
                SelectGameNode(None, _('Skill only'), lambda gi: gi.skill_level == GI.SL_SKILL),
                )),
            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 of redeals"), 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)
        MfxDialog.__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)
        font = app.getFont("default")
        self.tree = self.Tree_Class(self, top_frame, key=gameid,
                                    font=font,
                                    default=kw.default)
        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,
                      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")
            helpHTML(self.app, doc, dir, self.top)
            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)
        MfxDialog.__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
        ##padx, pady = kw.padx, kw.pady
        padx, pady = kw.padx/2, kw.pady/2
        # PanedWindow
        if Tkinter.TkVersion >= 8.4:
            paned_window = Tkinter.PanedWindow(top_frame)
            paned_window.pack(expand=1, fill='both')
            left_frame = Tkinter.Frame(paned_window)
            right_frame = Tkinter.Frame(paned_window)
            paned_window.add(left_frame)
            paned_window.add(right_frame)
        else:
            left_frame = Tkinter.Frame(top_frame)
            right_frame = Tkinter.Frame(top_frame)
            left_frame.pack(side='left', expand=1, fill='both')
            right_frame.pack(side='right', expand=1, fill='both')
        # Tree
        font = app.getFont("default")
        self.tree = self.Tree_Class(self, left_frame, key=gameid,
                                    default=kw.default, font=font, width=w1)
        self.tree.frame.pack(padx=padx, pady=pady, expand=1, fill='both')
        # LabelFrame
        if Tkinter.TkVersion >= 8.4:
            info_frame = Tkinter.LabelFrame(right_frame, text=_('About game'))
            stats_frame = Tkinter.LabelFrame(right_frame, text=_('Statistics'))
        else:
            info_frame = Tkinter.Frame(right_frame)
            stats_frame = Tkinter.Frame(right_frame)
        info_frame.grid(row=0, column=0, padx=padx, pady=pady,
                        ipadx=padx, ipady=pady, sticky='nws')
        stats_frame.grid(row=0, column=1, padx=padx, pady=pady,
                         ipadx=padx, ipady=pady, sticky='nws')
        # Info
        self.info_labels = {}
        i = 0
        for n, t, f, row in (
            ('name',      _('Name:'),             info_frame,   0),
            ('altnames',  _('Alternate names:'),  info_frame,   1),
            ('category',  _('Category:'),         info_frame,   2),
            ('type',      _('Type:'),             info_frame,   3),
            ('decks',     _('Decks:'),            info_frame,   4),
            ('redeals',   _('Redeals:'),          info_frame,   5),
            #
            ('played',    _('Played:'),           stats_frame,  0),
            ('won',       _('Won:'),              stats_frame,  1),
            ('lost',      _('Lost:'),             stats_frame,  2),
            ('time',      _('Playing time:'),     stats_frame,  3),
            ('moves',     _('Moves:'),            stats_frame,  4),
            ('percent',   _('% won:'),            stats_frame,  5),
            ):
            title_label = Tkinter.Label(f, text=t, justify='left', anchor='w')
            title_label.grid(row=row, column=0, sticky='nw')
            text_label = Tkinter.Label(f, justify='left', anchor='w')
            text_label.grid(row=row, column=1, sticky='nw')
            self.info_labels[n] = (title_label, text_label)
        ##info_frame.columnconfigure(1, weight=1)
        info_frame.rowconfigure(6, weight=1)
        stats_frame.rowconfigure(6, weight=1)
        # Canvas
        self.preview = MfxScrolledCanvas(right_frame, width=w2)
        self.preview.setTile(app, app.tabletile_index, force=True)
        self.preview.grid(row=1, column=0, columnspan=3,
                          padx=padx, pady=pady, sticky='nsew')
        right_frame.columnconfigure(1, weight=1)
        right_frame.rowconfigure(1, weight=1)

        # 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
        SelectGameTreeWithPreview.html_viewer = None
        self.mainloop(focus, kw.timeout)

    def initKw(self, kw):
        kw = KwStruct(kw,
                      strings=(_("&Select"), _("&Rules"), _("&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")
        #
        #for l in self.info_labels.values():
        #    l.config(text='')
        # 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,
                getFont = self.app.getFont,
            )
            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))
        title = self.app.getGameTitleName(gameid)
        self.top.wm_title(_("Playable Preview - ") + title)
##         if False:
##             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=self.app.getFont("canvas_large"))
##                 if self.app.opt.table_text_color:
##                     canvas.setTextColor(self.app.opt.table_text_color_value)
        #
        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
        #
        self.updateInfo(gameid)

    def updateInfo(self, gameid):
        gi = self.app.gdb.get(gameid)
        # info
        name = gettext(gi.name)
        altnames = '\n'.join([gettext(n) for n in gi.altnames])
        category = gettext(CSI.TYPE[gi.category])
        type = ''
        if GI.TYPE_NAMES.has_key(gi.si.game_type):
            type = gettext(GI.TYPE_NAMES[gi.si.game_type])
        if    gi.redeals == -2: redeals = _('variable')
        elif  gi.redeals == -1: redeals = _('unlimited')
        else:                   redeals = str(gi.redeals)
        # stats
        won, lost, time, moves = self.app.stats.getFullStats(self.app.opt.player, gameid)
        if won+lost > 0: percent = "%.1f" % (100.0*won/(won+lost))
        else: percent = "0.0"
        time = format_time(time)
        moves = str(round(moves, 1))
        for n, t in (
            ('name',      name),
            ('altnames',  altnames),
            ('category',  category),
            ('type',      type),
            ('decks',     gi.decks),
            ('redeals',   redeals),
            ('played',    won+lost),
            ('won',       won),
            ('lost',      lost),
            ('time',      time),
            ('moves',     moves),
            ('percent',   percent),
            ):
            title_label, text_label = self.info_labels[n]
            if t == '':
                title_label.grid_remove()
                text_label.grid_remove()
            else:
                title_label.grid()
                text_label.grid()
            text_label.config(text=t)
            #self.info_labels[n].config(text=t)