## 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, glob

# PySol imports
from mfxutil import Struct, KwStruct, EnvError
from settings import DEBUG


# /***********************************************************************
# // Abstract
# ************************************************************************/

class Resource(Struct):
    def __init__(self, **kw):
        kw = KwStruct(kw,
            name = "",
            filename = "",
            basename = "",      # basename of filename
            absname = "",       # absolute filename
            # implicit
            index = -1,
            error = 0,          # error while loading this resource
        )
        Struct.__init__(self, **kw.getKw())

    def getSortKey(self):
        return self.name.lower()
        #return latin1_to_ascii(self.name).lower()


class ResourceManager:
    def __init__(self):
        self._selected_key = -1
        self._objects = []
        self._objects_by_name = None
        self._objects_cache_name = {}
        self._objects_cache_filename = {}
        self._objects_cache_basename = {}
        self._objects_cache_absname = {}

    def getSelected(self):
        return self._selected_key

    def setSelected(self, index):
        assert -1 <= index < len(self._objects)
        self._selected_key = index

    def len(self):
        return len(self._objects)

    def register(self, obj):
        assert obj.index == -1
        assert obj.name and obj.name not in self._objects_cache_name
        self._objects_cache_name[obj.name] = obj
        if obj.filename:
            obj.absname = os.path.abspath(obj.filename)
            obj.basename = os.path.basename(obj.filename)
            self._objects_cache_filename[obj.filename] = obj
            self._objects_cache_basename[obj.basename] = obj
            self._objects_cache_absname[obj.absname] = obj
        obj.index = len(self._objects)
        self._objects.append(obj)
        self._objects_by_name = None    # invalidate

    def get(self, index):
        if 0 <= index < len(self._objects):
            return self._objects[index]
        return None

    def getByName(self, key):
        return self._objects_cache_name.get(key)

    def getByBasename(self, key):
        return self._objects_cache_basename.get(key)

    def getAll(self):
        return tuple(self._objects)

    def getAllSortedByName(self):
        if self._objects_by_name is None:
            l = map(lambda obj: (obj.getSortKey(), obj), self._objects)
            l.sort()
            self._objects_by_name = tuple(map(lambda item: item[1], l))
        return self._objects_by_name

    #
    # static methods
    #

    def _addDir(self, result, dir):
        try:
            if dir:
                dir = os.path.normpath(dir)
                if dir and os.path.isdir(dir) and dir not in result:
                    result.append(dir)
        except EnvError, ex:
            pass

    def getSearchDirs(self, app, search, env=None):
        if isinstance(search, str):
            search = (search,)
        result = []
        if env:
            for d in os.environ.get(env, "").split(os.pathsep):
                self._addDir(result, d.strip())
        for dir in (app.dataloader.dir, app.dn.maint, app.dn.config):
            if not dir:
                continue
            dir = os.path.normpath(dir)
            if not dir or not os.path.isdir(dir):
                continue
            for s in search:
                try:
                    if s[-2:] == "-*":
                        d = os.path.normpath(os.path.join(dir, s[:-2]))
                        self._addDir(result, d)
                        globdirs = glob.glob(d + "-*")
                        globdirs.sort()
                        for d in globdirs:
                            self._addDir(result, d)
                    else:
                        self._addDir(result, os.path.join(dir, s))
                except EnvError, ex:
                    pass
        if DEBUG >= 6:
            print "getSearchDirs", env, search, "->", result
        return result


# /***********************************************************************
# // Cardset
# ************************************************************************/

# CardsetInfo constants
class CSI:
    # cardset size
    SIZE_TINY   = 1
    SIZE_SMALL  = 2
    SIZE_MEDIUM = 3
    SIZE_LARGE  = 4
    SIZE_XLARGE = 5

    # cardset types
    TYPE_FRENCH   = 1
    TYPE_HANAFUDA = 2
    TYPE_TAROCK   = 3
    TYPE_MAHJONGG = 4
    TYPE_HEXADECK = 5
    TYPE_MUGHAL_GANJIFA = 6
    TYPE_NAVAGRAHA_GANJIFA = 7
    TYPE_DASHAVATARA_GANJIFA = 8
    TYPE_TRUMP_ONLY = 9

    TYPE = {
        1:  _("French type (52 cards)"),
        2:  _("Hanafuda type (48 cards)"),
        3:  _("Tarock type (78 cards)"),
        4:  _("Mahjongg type (42 tiles)"),
        5:  _("Hex A Deck type (68 cards)"),
        6:  _("Mughal Ganjifa type (96 cards)"),
        7:  _("Navagraha Ganjifa type (108 cards)"),
        8:  _("Dashavatara Ganjifa type (120 cards)"),
        9:  _("Trumps only type (variable cards)"),
    }

    TYPE_NAME = {
        1:  _("French"),
        2:  _("Hanafuda"),
        3:  _("Tarock"),
        4:  _("Mahjongg"),
        5:  _("Hex A Deck"),
        6:  _("Mughal Ganjifa"),
        7:  _("Navagraha Ganjifa"),
        8:  _("Dashavatara Ganjifa"),
        9:  _("Trumps only"),
    }

    # cardset styles
    STYLE = {
        1:  _("Adult"),                #
        2:  _("Animals"),              #
        3:  _("Anime"),                #
        4:  _("Art"),                  #
        5:  _("Cartoons"),             #
        6:  _("Children"),             #
        7:  _("Classic look"),         #
        8:  _("Collectors"),           # scanned collectors cardsets
        9:  _("Computers"),            #
       10:  _("Engines"),              #
       11:  _("Fantasy"),              #
       30:  _("Ganjifa"),              #
       12:  _("Hanafuda"),             #
       29:  _("Hex A Deck"),           #
       13:  _("Holiday"),              #
       28:  _("Mahjongg"),             #
       14:  _("Movies"),               #
       31:  _("Matrix"),               #
       15:  _("Music"),                #
       16:  _("Nature"),               #
       17:  _("Operating Systems"),    # e.g. cards with Linux logos
       19:  _("People"),               # famous people
       20:  _("Places"),               #
       21:  _("Plain"),                #
       22:  _("Products"),             #
       18:  _("Round cardsets"),       #
       23:  _("Science Fiction"),      #
       24:  _("Sports"),               #
       27:  _("Tarock"),               #
       25:  _("Vehicels"),             #
       26:  _("Video Games"),          #
    }

    # cardset nationality (suit and rank symbols)
    NATIONALITY = {
        1021:  _("Australia"),         #
        1001:  _("Austria"),           #
        1019:  _("Belgium"),           #
        1010:  _("Canada"),            #
        1011:  _("China"),             #
        1012:  _("Czech Republic"),    #
        1013:  _("Denmark"),           #
        1003:  _("England"),           #
        1004:  _("France"),            #
        1006:  _("Germany"),           #
        1014:  _("Great Britain"),     #
        1015:  _("Hungary"),           #
        1020:  _("India"),             #
        1005:  _("Italy"),             #
        1016:  _("Japan"),             #
        1002:  _("Netherlands"),       #
        1007:  _("Russia"),            #
        1008:  _("Spain"),             #
        1017:  _("Sweden"),            #
        1009:  _("Switzerland"),       #
        1018:  _("USA"),               #
    }

    # cardset creation date
    DATE = {
        10:  "1000 - 1099",
        11:  "1100 - 1199",
        12:  "1200 - 1299",
        13:  "1300 - 1399",
        14:  "1400 - 1499",
        15:  "1500 - 1599",
        16:  "1600 - 1699",
        17:  "1700 - 1799",
        18:  "1800 - 1899",
        19:  "1900 - 1999",
        20:  "2000 - 2099",
        21:  "2100 - 2199",
        22:  "2200 - 2299",
    }


class CardsetConfig(Struct):
    # see config.txt and _readCardsetConfig()
    def __init__(self):
        Struct.__init__(self,
            # line[0]
            version = 1,
            ext = ".gif",
            type = CSI.TYPE_FRENCH,
            ncards = -1,
            styles = [],
            year = 0,
            # line[1]
            ident = "",
            name = "",
            # line[2]
            CARDW = 0,
            CARDH = 0,
            CARDD = 0,
            # line[3]
            CARD_XOFFSET = 0,
            CARD_YOFFSET = 0,
            SHADOW_XOFFSET = 0,
            SHADOW_YOFFSET = 0,
            # line[4]
            backindex = 0,
            # line[5]
            backnames = (),
            # other
            CARD_DX = 0,        # relative pos of real card image within Card
            CARD_DY = 0,
        )


class Cardset(Resource):
    def __init__(self, **kw):
        # start with all fields from CardsetConfig
        config = CardsetConfig()
        kw = KwStruct(config.__dict__, **kw)
        # si is the SelectionInfo struct that will be queried by
        # the "select cardset" dialogs. It can be freely modified.
        si = Struct(type=0, size=0, styles=[], nationalities=[], dates=[])
        kw = KwStruct(kw,
            # essentials
            ranks = (),
            suits = (),
            trumps = (),
            nbottoms = 7,
            nletters = 4,
            nshadows = 1 + 13,
            # selection criterias
            si = si,
            # implicit
            backname = None,
            dir = "",
        )
        Resource.__init__(self, **kw.getKw())

    def getFaceCardNames(self):
        names = []
        for suit in self.suits:
            for rank in self.ranks:
                names.append("%02d%s" % (rank + 1, suit))
        for trump in self.trumps:
            names.append("%02d%s" % (trump + 1, "z"))
        assert len(names) == self.ncards
        return names

    def getPreviewCardNames(self):
        names = self.getFaceCardNames()
        pnames = []
        ranks, suits = self.ranks, self.suits
        lr, ls = len(ranks), len(suits)
        if lr == 0 or ls == 0:     # TYPE_TRUMP_ONLY
            return names[:16], 4
        if lr >= 4:
            ls = min(ls, 4)
        low_ranks, high_ranks = 1, 3
        ###if self.type == 3: high_ranks = 4
        for rank in range(0, low_ranks) + range(lr-high_ranks, lr):
            for suit in range(ls):
                index = suit * len(self.ranks) + rank
                pnames.append(names[index % len(names)])
        return pnames, ls

    def updateCardback(self, backname=None, backindex=None):
        # update default back
        if isinstance(backname, str):
            if backname in self.backnames:
                backindex = self.backnames.index(backname)
        if isinstance(backindex, int):
            self.backindex = backindex % len(self.backnames)
        self.backname = self.backnames[self.backindex]


class CardsetManager(ResourceManager):
    def __init__(self):
        ResourceManager.__init__(self)
        self.registered_types = {}
        self.registered_sizes = {}
        self.registered_styles = {}
        self.registered_nationalities = {}
        self.registered_dates = {}

    def _check(self, cs):
        s = cs.type
        if s not in CSI.TYPE:
            return 0
        cs.si.type = s
        if s == CSI.TYPE_FRENCH:
            cs.ranks = range(13)
            cs.suits = "cshd"
        elif s == CSI.TYPE_HANAFUDA:
            cs.ranks = range(12)
            cs.suits = "cshd"
        elif s == CSI.TYPE_TAROCK:
            cs.nbottoms = 8
            cs.ranks = range(14)
            cs.suits = "cshd"
            cs.trumps = range(22)
        elif s == CSI.TYPE_MAHJONGG:
            cs.ranks = range(10)
            cs.suits = "abc"
            cs.trumps = range(12)
            #
            cs.nbottoms = 0
            cs.nletters = 0
            cs.nshadows = 0
        elif s == CSI.TYPE_HEXADECK:
            cs.nbottoms = 8
            cs.ranks = range(16)
            cs.suits = "cshd"
            cs.trumps = range(4)
        elif s == CSI.TYPE_MUGHAL_GANJIFA:
            cs.nbottoms = 11
            cs.ranks = range(12)
            cs.suits = "abcdefgh"
        elif s == CSI.TYPE_NAVAGRAHA_GANJIFA:
            #???return 0                            ## FIXME
            cs.nbottoms = 12
            cs.ranks = range(12)
            cs.suits = "abcdefghi"
        elif s == CSI.TYPE_DASHAVATARA_GANJIFA:
            cs.nbottoms = 13
            cs.ranks = range(12)
            cs.suits = "abcdefghij"
        elif s == CSI.TYPE_TRUMP_ONLY:
            #???return 0                            ## FIXME
            #cs.nbottoms = 7
            #cs.ranks = ()
            #cs.suits = ""
            #cs.trumps = range(cs.ncards)
            cs.nbottoms = 1
            cs.nletters = 0
            cs.nshadows = 0
            cs.ranks = ()
            cs.suits = ""
            cs.trumps = range(cs.ncards)

        else:
            return 0
        return 1

    def register(self, cs):
        if not self._check(cs):
            return
        cs.ncards = len(cs.ranks) * len(cs.suits) + len(cs.trumps)
        cs.name = cs.name[:25]
        if not (1 <= cs.si.size <= 5):
            CW, CH = cs.CARDW, cs.CARDH
            if CW <= 55 and CH <= 72:
                cs.si.size = CSI.SIZE_TINY
            elif CW <= 60 and CH <= 85:
                cs.si.size = CSI.SIZE_SMALL
            elif CW <= 75 and CH <= 105:
                cs.si.size = CSI.SIZE_MEDIUM
            elif CW <= 90 and CH <= 125:
                cs.si.size = CSI.SIZE_LARGE
            else:
                cs.si.size = CSI.SIZE_XLARGE
        #
        keys = cs.styles[:]
        cs.si.styles = tuple(filter(lambda s: s in CSI.STYLE, keys))
        for s in cs.si.styles:
            self.registered_styles[s] = self.registered_styles.get(s, 0) + 1
        cs.si.nationalities = tuple(filter(lambda s: s in CSI.NATIONALITY, keys))
        for s in cs.si.nationalities:
            self.registered_nationalities[s] = self.registered_nationalities.get(s, 0) + 1
        keys = (cs.year / 100,)
        cs.si.dates = tuple(filter(lambda s: s in CSI.DATE, keys))
        for s in cs.si.dates:
            self.registered_dates[s] = self.registered_dates.get(s, 0) + 1
        #
        s = cs.si.type
        self.registered_types[s] = self.registered_types.get(s, 0) + 1
        s = cs.si.size
        self.registered_sizes[s] = self.registered_sizes.get(s, 0) + 1
        cs.updateCardback()
        ResourceManager.register(self, cs)


# /***********************************************************************
# // Tile
# ************************************************************************/

class Tile(Resource):
    def __init__(self, **kw):
        kw['color'] = None
        kw['stretch'] = 0
        Resource.__init__(self, **kw)


class TileManager(ResourceManager):
    pass


# /***********************************************************************
# // Sample
# ************************************************************************/

class Sample(Resource):
    def __init__(self, **kw):
        kw['volume'] = -1
        Resource.__init__(self, **kw)


class SampleManager(ResourceManager):
    pass


# /***********************************************************************
# // Music
# ************************************************************************/

class Music(Sample):
    pass


class MusicManager(SampleManager):
    pass