"""
"""
import os, re
import pygame

from .const import *
from . import surface


def _list_themes(dir):
    d = {}
    for entry in os.listdir(dir):
        if os.path.exists(os.path.join(dir, entry, "config.txt")):
            d[entry] = os.path.join(dir, entry)
    return d


class Theme:
    """Theme interface.

    <p>If you wish to create your own theme, create a class with this interface, and
    pass it to gui.App via <tt>gui.App(theme=MyTheme())</tt>.</p>

    <strong>Default Theme</strong>

    <pre>Theme(dirs='default')</pre>
    <dl>
    <dt>dirs<dd>Name of the theme dir to load a theme from.  May be an absolute path to a theme, if pgu is not installed, or if you created your own theme.  May include several dirs in a list if data is spread across several themes.
    </dl>

    <strong>Example</strong>

    <code>
    theme = gui.Theme("default")
    theme = gui.Theme(["mytheme","mytheme2"])
    </code>
    """

    def __init__(self, dirs="default"):
        self.config = {}
        self.dict = {}
        self._loaded = []
        self.cache = {}
        self._preload(dirs)
        pygame.font.init()

    def _preload(self, ds):
        if not isinstance(ds, list):
            ds = [ds]
        for d in ds:
            if d not in self._loaded:
                self._load(d)
            self._loaded.append(d)

    def _load(self, name):
        # theme_dir = themes[name]

        # try to load the local dir, or absolute path
        dnames = [name]

        dnames.append(os.path.join("data", "themes", name))
        # if the package isn't installed and people are just
        # trying out the scripts or examples
        dnames.append(
            os.path.join(os.path.dirname(__file__), "..", "..", "data", "themes", name)
        )

        # if the package is installed, and the package is installed
        # in /usr/lib/python2.3/site-packages/pgu/
        # or c:\python23\lib\site-packages\pgu\
        # the data is in ... lib/../share/ ...
        dnames.append(
            os.path.join(
                os.path.dirname(__file__),
                "..",
                "..",
                "..",
                "..",
                "share",
                "pgu",
                "themes",
                name,
            )
        )
        dnames.append(
            os.path.join(
                os.path.dirname(__file__),
                "..",
                "..",
                "..",
                "..",
                "..",
                "share",
                "pgu",
                "themes",
                name,
            )
        )

        for dname in dnames:
            if os.path.isdir(dname):
                break
        if not os.path.isdir(dname):
            raise "could not find theme " + name

        fname = os.path.join(dname, "config.txt")
        try:
            f = open(fname)
            for line in f.readlines():
                vals = line.strip().split()
                if len(vals) < 3:
                    continue
                cls = vals[0]
                del vals[0]
                pcls = ""
                if cls.find(":") >= 0:
                    cls, pcls = cls.split(":")
                attr = vals[0]
                del vals[0]
                self.config[cls + ":" + pcls + " " + attr] = (dname, vals)
        finally:
            f.close()

    is_image = re.compile("\.(gif|jpg|bmp|png|tga)$", re.I)

    def _get(self, key):
        if not key in self.config:
            return
        if key in self.dict:
            return self.dict[key]
        dvals = self.config[key]
        dname, vals = dvals
        # theme_dir = themes[name]
        v0 = vals[0]
        if v0[0] == "#":
            v = pygame.color.Color(v0)
        elif v0.endswith(".ttf") or v0.endswith(".TTF"):
            v = pygame.font.Font(os.path.join(dname, v0), int(vals[1]))
        elif self.is_image.search(v0) is not None:
            v = pygame.image.load(os.path.join(dname, v0))
        else:
            try:
                v = int(v0)
            except:
                v = pygame.font.SysFont(v0, int(vals[1]))
        self.dict[key] = v
        return v

    def get(self, cls, pcls, attr):
        """Interface method -- get the value of a style attribute.

        <pre>Theme.get(cls,pcls,attr): return value</pre>

        <dl>
        <dt>cls<dd>class, for example "checkbox", "button", etc.
        <dt>pcls<dd>pseudo class, for example "hover", "down", etc.
        <dt>attr<dd>attribute, for example "image", "background", "font", "color", etc.
        </dl>

        <p>returns the value of the attribute.</p>

        <p>This method is called from [[gui-style]].</p>
        """

        if not self._loaded:
            self._preload("default")

        o = cls + ":" + pcls + " " + attr

        # if not hasattr(self,'_count'):
        #    self._count = {}
        # if o not in self._count: self._count[o] = 0
        # self._count[o] += 1

        if o in self.cache:
            return self.cache[o]

        v = self._get(cls + ":" + pcls + " " + attr)
        if v:
            self.cache[o] = v
            return v

        pcls = ""
        v = self._get(cls + ":" + pcls + " " + attr)
        if v:
            self.cache[o] = v
            return v

        cls = "default"
        v = self._get(cls + ":" + pcls + " " + attr)
        if v:
            self.cache[o] = v
            return v

        v = 0
        self.cache[o] = v
        return v

    def box(self, w, s):
        style = w.style
        style.cache(1)

        c = (0, 0, 0)
        if style.border_color != 0:
            c = style.border_color
        w, h = s.get_width(), s.get_height()

        s.fill(c, (0, 0, w, style.border_top))
        s.fill(c, (0, h - style.border_bottom, w, style.border_bottom))
        s.fill(c, (0, 0, style.border_left, h))
        s.fill(c, (w - style.border_right, 0, style.border_right, h))

        style.cache(0)

    def getspacing(self, w):
        # return the top, right, bottom, left spacing around the widget
        if not hasattr(w, "_spacing"):  # HACK: assume spacing doesn't change re pcls
            s = w.style
            s.cache(1)
            xt = s.margin_top + s.border_top + s.padding_top
            xr = s.padding_right + s.border_right + s.margin_right
            xb = s.padding_bottom + s.border_bottom + s.margin_bottom
            xl = s.margin_left + s.border_left + s.padding_left
            s.cache(0)
            w._spacing = xt, xr, xb, xl
        return w._spacing

    def resize(self, w, m):
        def func(width=None, height=None):
            # ww,hh = m(width,height)
            ow, oh = width, height

            s = w.style

            s.cache(1)
            pt, pr, pb, pl = (
                s.padding_top,
                s.padding_right,
                s.padding_bottom,
                s.padding_left,
            )
            bt, br, bb, bl = (
                s.border_top,
                s.border_right,
                s.border_bottom,
                s.border_left,
            )
            mt, mr, mb, ml = (
                s.margin_top,
                s.margin_right,
                s.margin_bottom,
                s.margin_left,
            )
            s.cache(0)

            xt = pt + bt + mt
            xr = pr + br + mr
            xb = pb + bb + mb
            xl = pl + bl + ml
            ttw = xl + xr
            tth = xt + xb

            ww, hh = None, None
            if width != None:
                ww = width - ttw
            if height != None:
                hh = height - tth
            ww, hh = m(ww, hh)

            rect = pygame.Rect(0 + xl, 0 + xt, ww, hh)
            w._rect_content = rect  # pygame.Rect(0 + xl, 0 + xt, width, height)
            # r = rect

            if width == None:
                width = ww
            if height == None:
                height = hh
            # if the widget hasn't respected the style.width,
            # style height, we'll add in the space for it...
            width = max(width - ttw, ww, w.style.width)
            height = max(height - tth, hh, w.style.height)

            # width = max(ww,w.style.width-tw)
            # height = max(hh,w.style.height-th)

            r = pygame.Rect(rect.x, rect.y, width, height)

            w._rect_padding = pygame.Rect(
                r.x - pl, r.y - pt, r.w + pl + pr, r.h + pt + pb
            )
            r = w._rect_padding
            w._rect_border = pygame.Rect(
                r.x - bl, r.y - bt, r.w + bl + br, r.h + bt + bb
            )
            r = w._rect_border
            w._rect_margin = pygame.Rect(
                r.x - ml, r.y - mt, r.w + ml + mr, r.h + mt + mb
            )

            # align it within it's zone of power.
            dx = width - rect.w
            dy = height - rect.h
            # rect.x += (1)*dx/2
            # rect.y += (1)*dy/2
            rect.x += (w.style.align + 1) * dx / 2
            rect.y += (w.style.valign + 1) * dy / 2

            # print w,ow, w._rect_margin.w,  ttw
            return w._rect_margin.w, w._rect_margin.h

        return func

    def paint(self, w, m):
        def func(s):
            if w.disabled:
                orig = s
                s = pygame.Surface((s.get_width(), s.get_height()), 0, s)
                s.blit(orig, (0, 0))

            if hasattr(w, "background"):
                w.background.paint(surface.subsurface(s, w._rect_border))
            self.box(w, surface.subsurface(s, w._rect_border))
            r = m(surface.subsurface(s, w._rect_content))

            if w.disabled:
                s.set_alpha(128)
                orig.blit(s, (0, 0))

            return r

        return func

    def event(self, w, m):
        def func(e):
            rect = w._rect_content
            if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN:
                sub = pygame.event.Event(
                    e.type,
                    {"button": e.button, "pos": (e.pos[0] - rect.x, e.pos[1] - rect.y)},
                )
            elif e.type == CLICK:
                sub = pygame.event.Event(
                    e.type,
                    {"button": e.button, "pos": (e.pos[0] - rect.x, e.pos[1] - rect.y)},
                )
            elif e.type == MOUSEMOTION:
                sub = pygame.event.Event(
                    e.type,
                    {
                        "buttons": e.buttons,
                        "pos": (e.pos[0] - rect.x, e.pos[1] - rect.y),
                        "rel": e.rel,
                    },
                )
            else:
                sub = e
            r = m(sub)
            return r

        return func

    def update(self, w, m):
        def func(s):
            if w.disabled:
                return []
            r = m(surface.subsurface(s, w._rect_content))
            if type(r) == list:
                dx, dy = w._rect_content.topleft
                for rr in r:
                    rr.x, rr.y = rr.x + dx, rr.y + dy
            return r

        return func

    def open(self, w, m):
        def func(widget=None, x=None, y=None):
            if not hasattr(w, "_rect_content"):
                (
                    w.rect.w,
                    w.rect.h,
                ) = w.resize()  # HACK: so that container.open won't resize again!
            rect = w._rect_content
            ##print w.__class__.__name__, rect
            if x != None:
                x += rect.x
            if y != None:
                y += rect.y
            return m(widget, x, y)

        return func

    # def open(self,w,m):
    #    def func(widget=None):
    #        return m(widget)
    #    return func

    def decorate(self, widget, level):
        """Interface method -- decorate a widget.

        <p>The theme system is given the opportunity to decorate a widget methods at the
        end of the Widget initializer.</p>

        <pre>Theme.decorate(widget,level)</pre>

        <dl>
        <dt>widget<dd>the widget to be decorated
        <dt>level<dd>the amount of decoration to do, False for none, True for normal amount, 'app' for special treatment of App objects.
        </dl>
        """

        w = widget
        if level == False:
            return

        if type(w.style.background) != int:
            w.background = Background(w, self)

        if level == "app":
            return

        for k, v in list(w.style.__dict__.items()):
            if k in ("border", "margin", "padding"):
                for kk in ("top", "bottom", "left", "right"):
                    setattr(w.style, "%s_%s" % (k, kk), v)

        w.paint = self.paint(w, w.paint)
        w.event = self.event(w, w.event)
        w.update = self.update(w, w.update)
        w.resize = self.resize(w, w.resize)
        w.open = self.open(w, w.open)

    def render(self, s, box, r):
        """Interface method - render a special widget feature.

        <pre>Theme.render(s,box,r)</pre>

        <dl>
        <dt>s<dt>pygame.Surface
        <dt>box<dt>box data, a value returned from Theme.get, typically a pygame.Surface
        <dt>r<dt>pygame.Rect with the size that the box data should be rendered
        </dl>

        """

        if box == 0:
            return

        if type(box) == tuple:
            s.fill(box, r)
            return

        x, y, w, h = r.x, r.y, r.w, r.h
        ww, hh = box.get_width() / 3, box.get_height() / 3
        xx, yy = x + w, y + h
        src = pygame.rect.Rect(0, 0, ww, hh)
        dest = pygame.rect.Rect(0, 0, ww, hh)

        s.set_clip(pygame.Rect(x + ww, y + hh, w - ww * 2, h - hh * 2))
        src.x, src.y = ww, hh
        for dest.y in range(y + hh, yy - hh, hh):
            for dest.x in range(x + ww, xx - ww, ww):
                s.blit(box, dest, src)

        s.set_clip(pygame.Rect(x + ww, y, w - ww * 3, hh))
        src.x, src.y, dest.y = ww, 0, y
        for dest.x in range(x + ww, xx - ww * 2, ww):
            s.blit(box, dest, src)
        dest.x = xx - ww * 2
        s.set_clip(pygame.Rect(x + ww, y, w - ww * 2, hh))
        s.blit(box, dest, src)

        s.set_clip(pygame.Rect(x + ww, yy - hh, w - ww * 3, hh))
        src.x, src.y, dest.y = ww, hh * 2, y + h - hh
        for dest.x in range(x + ww, xx - ww * 2, ww):
            s.blit(box, dest, src)
        dest.x = xx - ww * 2
        s.set_clip(pygame.Rect(x + ww, yy - hh, w - ww * 2, hh))
        s.blit(box, dest, src)

        s.set_clip(pygame.Rect(x, y + hh, xx, h - hh * 3))
        src.y, src.x, dest.x = hh, 0, x
        for dest.y in range(y + hh, yy - hh * 2, hh):
            s.blit(box, dest, src)
        dest.y = yy - hh * 2
        s.set_clip(pygame.Rect(x, y + hh, xx, h - hh * 2))
        s.blit(box, dest, src)

        s.set_clip(pygame.Rect(xx - ww, y + hh, xx, h - hh * 3))
        src.y, src.x, dest.x = hh, ww * 2, x + w - ww
        for dest.y in range(y + hh, yy - hh * 2, hh):
            s.blit(box, dest, src)
        dest.y = yy - hh * 2
        s.set_clip(pygame.Rect(xx - ww, y + hh, xx, h - hh * 2))
        s.blit(box, dest, src)

        s.set_clip()
        src.x, src.y, dest.x, dest.y = 0, 0, x, y
        s.blit(box, dest, src)

        src.x, src.y, dest.x, dest.y = ww * 2, 0, x + w - ww, y
        s.blit(box, dest, src)

        src.x, src.y, dest.x, dest.y = 0, hh * 2, x, y + h - hh
        s.blit(box, dest, src)

        src.x, src.y, dest.x, dest.y = ww * 2, hh * 2, x + w - ww, y + h - hh
        s.blit(box, dest, src)


import pygame
from . import widget


class Background(widget.Widget):
    def __init__(self, value, theme, **params):
        params["decorate"] = False
        widget.Widget.__init__(self, **params)
        self.value = value
        self.theme = theme

    def paint(self, s):
        r = pygame.Rect(0, 0, s.get_width(), s.get_height())
        v = self.value.style.background
        if type(v) == tuple:
            s.fill(v)
        else:
            self.theme.render(s, v, r)
