from pygame import Rect
from itertools import tee, islice, izip, product
from collections import defaultdict
from constants import *



"""
Various code that doesn't really fit anywhere else
"""


def read_points(text):
    return [ tuple(map(lambda x: int(x), i.split(',')))
         for i in text.split() ]



def parse_properties(node):
    """
    parse a node and return a dict that represents a tiled "property"
    """

    # the "properties" from tiled's tmx have an annoying quality that "name"
    # and "value" is included. here we mangle it to get that junk out.

    d = {}

    for child in node.findall('properties'):
        for subnode in child.findall('property'):
            d[subnode.get('name')] = subnode.get('value')

    return d


def decode_gid(raw_gid):
    # gid's are encoded with extra information
    # as of 0.7.0 it determines if the tile should be flipped when rendered
    # as of 0.8.0 bit 30 determines if GID is rotated

    flags = 0
    if raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX: flags += TRANS_FLIPX
    if raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY: flags += TRANS_FLIPY
    if raw_gid & GID_TRANS_ROT == GID_TRANS_ROT: flags += TRANS_ROT
    gid = raw_gid & ~(GID_TRANS_FLIPX | GID_TRANS_FLIPY | GID_TRANS_ROT)

    return gid, flags


def handle_bool(text):
    # properly convert strings to a bool
    try:
        return bool(int(text))
    except:
        pass

    try:
        text = str(text).lower()
        if text == "true":   return True
        if text == "yes":    return True
        if text == "false":  return False
        if text == "no":     return False
    except:
        pass
        
    raise ValueError


# used to change the unicode string returned from xml to proper python 
# variable types.
types = defaultdict(lambda: str)
types.update({
    "version": float,
    "orientation": str,
    "width": int,
    "height": int,
    "tilewidth": int,
    "tileheight": int,
    "firstgid": int,
    "source": str,
    "name": str,
    "spacing": int,
    "margin": int,
    "source": str,
    "trans": str,
    "id": int,
    "opacity": float,
    "visible": handle_bool,
    "encoding": str,
    "compression": str,
    "gid": int,
    "type": str,
    "x": int,
    "y": int,
    "value": str,
})


def pairwise(iterable):
    # return a list as a sequence of pairs
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)


def group(l, n):
    # return a list as a sequence of n tuples
    return izip(*(islice(l, i, None, n) for i in xrange(n)))


def buildDistributionRects(tmxmap, layer, tileset=None, real_gid=None):
    """
    generate a set of non-overlapping rects that represents the distribution
    of the specfied gid.

    useful for generating rects for use in collision detection
    """
    
    if isinstance(tileset, int):
        try:
            tileset = tmxmap.tilesets[tileset]
        except IndexError:
            msg = "Tileset #{} not found in map {}."
            raise IndexError, msg.format(tileset, tmxmap)

    elif isinstance(tileset, str):
        try:
            tileset = [ t for t in tmxmap.tilesets if t.name == tileset ].pop()
        except IndexError:
            msg = "Tileset \"{}\" not found in map {}."
            raise ValueError, msg.format(tileset, tmxmap)

    elif tileset:
        msg = "Tileset must be either a int or string. got: {}"
        raise ValueError, msg.format(type(tileset))

    gid = None
    if real_gid:
        try:
            gid, flags = tmxmap.mapGID(real_gid)[0]
        except IndexError:
            msg = "GID #{} not found"
            raise ValueError, msg.format(real_gid)


    if isinstance(layer, int):
        layer_data = tmxmap.getLayerData(layer).data
    elif isinstance(layer, str):
        try:
            layer = [ l for l in tmxmap.tilelayers if l.name == layer ].pop()
            layer_data = layer.data
        except IndexError:
            msg = "Layer \"{}\" not found in map {}."
            raise ValueError, msg.format(layer, tmxmap)

    p = product(xrange(tmxmap.width), xrange(tmxmap.height))
    if gid:
        points = [ (x,y) for (x,y) in p if layer_data[y][x] == gid ]
    else:
        points = [ (x,y) for (x,y) in p if layer_data[y][x] ]

    rects = simplify(points, tmxmap.tilewidth, tmxmap.tileheight)
    return rects


def simplify(all_points, tilewidth, tileheight):
    """
    klugde:

    "A kludge (or kluge) is a workaround, a quick-and-dirty solution,
    a clumsy or inelegant, yet effective, solution to a problem, typically
    using parts that are cobbled together."

    -- wikipedia
   
    turn a list of points into a rects 
    adjacent rects will be combined.

    plain english:
        the input list must be a a list of tuples that represent
        the areas to be combined into rects
        the rects will be blended together over solid groups

        so if data is something like:

        0 1 1 1 0 0 0 
        0 1 1 0 0 0 0 
        0 0 0 0 0 4 0 
        0 0 0 0 0 4 0
        0 0 0 0 0 0 0 
        0 0 1 1 1 1 1

        you'll have the 4 rects that mask the area like this:

        ..######......
        ..####........
        ..........##..
        ..........##..
        ..............
        ....##########

        pretty cool, right?

    there may be cases where the number of rectangles is not as low as possible,
    but i haven't found that it is excessively bad.  certainly much better than
    making a list of rects, one for each tile on the map!

    """

    def pick_rect(points, rects):
        ox, oy = sorted([ (sum(p), p) for p in points ])[0][1]
        x = ox
        y = oy
        ex = None            

        while 1:
            x += 1
            if not (x, y) in points:
                if ex == None:
                    ex = x - 1

                if ((ox, y+1) in points):
                    if x == ex + 1 :
                        y += 1
                        x = ox

                    else:
                        y -= 1
                        break
                else:
                    if x <= ex: y-= 1
                    break

        c_rect = Rect(ox*tilewidth,oy*tileheight,\
                     (ex-ox+1)*tilewidth,(y-oy+1)*tileheight)

        rects.append(c_rect)

        rect = Rect(ox,oy,ex-ox+1,y-oy+1)
        kill = [ p for p in points if rect.collidepoint(p) ]
        [ points.remove(i) for i in kill ]

        if points:
            pick_rect(points, rects)

    rect_list = []
    while all_points:
        pick_rect(all_points, rect_list)

    return rect_list

