# -*- coding: utf-8 -*-

"""
TileMap loader for python for Tiled, a generic tile map editor
from http://mapeditor.org/ .
It loads the \*.tmx files produced by Tiled.


"""

# Versioning scheme based on: http://en.wikipedia.org/wiki/Versioning#Designating_development_stage
#
#   +-- api change, probably incompatible with older versions
#   |     +-- enhancements but no api change
#   |     |
# major.minor[.build[.revision]]
#                |
#                +-|* 0 for alpha (status)
#                  |* 1 for beta (status)
#                  |* 2 for release candidate
#                  |* 3 for (public) release
#
# For instance:
#     * 1.2.0.1 instead of 1.2-a
#     * 1.2.1.2 instead of 1.2-b2 (beta with some bug fixes)
#     * 1.2.2.3 instead of 1.2-rc (release candidate)
#     * 1.2.3.0 instead of 1.2-r (commercial distribution)
#     * 1.2.3.5 instead of 1.2-r5 (commercial distribution with many bug fixes)

__revision__ = "$Rev: 115 $"
__version__ = "3.1.0." + __revision__[6:-2]
__author__ = 'DR0ID @ 2009-2011'

# import logging
# #the following few lines are needed to use logging if this module used without
# # a previous call to logging.basicConfig()
# if 0 == len(logging.root.handlers):
    # logging.basicConfig(level=logging.DEBUG)

# _LOGGER = logging.getLogger('tiledtmxloader')
# if __debug__:
    # _LOGGER.debug('%s loading ...' % (__name__))
#  -----------------------------------------------------------------------------


import sys
from xml.dom import minidom, Node
try:
    import StringIO
    from StringIO import StringIO
except:
    from io import StringIO
import os.path
import struct
import array

#  -----------------------------------------------------------------------------
class TileMap(object):
    """

    The TileMap holds all the map data.

    :Ivariables:
        orientation : string
            orthogonal or isometric or hexagonal or shifted
        tilewidth : int
            width of the tiles (for all layers)
        tileheight : int
            height of the tiles (for all layers)
        width : int
            width of the map (number of tiles)
        height : int
            height of the map (number of tiles)
        version : string
            version of the map format
        tile_sets : list
            list of TileSet
        properties : dict
            the propertis set in the editor, name-value pairs, strings
        pixel_width : int
            width of the map in pixels
        pixel_height : int
            height of the map in pixels
        layers : list
            list of TileLayer
        map_file_name : dict
            file name of the map
        named_layers : dict of string:TledLayer
            dict containing {name : TileLayer}
        named_tile_sets : dict
            dict containing {name : TileSet}

    """


    def __init__(self):
#        This is the top container for all data. The gid is the global id 
#       (for a image).
#        Before calling convert most of the values are strings. Some additional
#        values are also calculated, see convert() for details. After calling
#        convert, most values are integers or floats where appropriat.
        """
        The TileMap holds all the map data.
        """
        # set through parser
        self.orientation = None
        self.tileheight = 0
        self.tilewidth = 0
        self.width = 0
        self.height = 0
        self.version = 0
        self.tile_sets = [] # TileSet
        # ISSUE 9: object groups should be in the same order as layers
        self.layers = [] # WorldTileLayer <- what order? back to front (guessed)
        # self.object_groups = []
        self.properties = {} # {name: value}
        # additional info
        self.pixel_width = 0
        self.pixel_height = 0
        self.named_layers = {} # {name: layer}
        self.named_tile_sets = {} # {name: tile_set}
        self.map_file_name = ""

    def convert(self):
        """
        Converts numerical values from strings to numerical values.
        It also calculates or set additional data:
        pixel_width
        pixel_height
        named_layers
        named_tile_sets
        """
        self.tilewidth = int(self.tilewidth)
        self.tileheight = int(self.tileheight)
        self.width = int(self.width)
        self.height = int(self.height)
        self.pixel_width = self.width * self.tilewidth
        self.pixel_height = self.height * self.tileheight

        for layer in self.layers:
            # ISSUE 9
            if not layer.is_object_group:
                layer.tilewidth = self.tilewidth
                layer.tileheight = self.tileheight
                self.named_layers[layer.name] = layer
            layer.convert()

        for tile_set in self.tile_sets:
            self.named_tile_sets[tile_set.name] = tile_set
            tile_set.spacing = int(tile_set.spacing)
            tile_set.margin = int(tile_set.margin)
            for img in tile_set.images:
                if img.trans:
                    img.trans = (int(img.trans[:2], 16), \
                                 int(img.trans[2:4], 16), \
                                 int(img.trans[4:], 16))

    def decode(self):
        """
        Decodes the TileLayer encoded_content and saves it in decoded_content.
        """
        for layer in self.layers:
            if not layer.is_object_group:
                layer.decode()
#  -----------------------------------------------------------------------------


class TileSet(object):
    """
    A tileset holds the tiles and its images.

    :Ivariables:
        firstgid : int
            the first gid of this tileset
        name : string
            the name of this TileSet
        images : list
            list of TileImages
        tiles : list
            list of Tiles
        indexed_images : dict
            after calling load() it is dict containing id: image
        spacing : int
            the spacing between tiles
        marging : int
            the marging of the tiles
        properties : dict
            the propertis set in the editor, name-value pairs
        tilewidth : int
            the actual width of the tile, can be different from the tilewidth
            of the map
        tilehight : int
            the actual hight of th etile, can be different from the tilehight
            of the  map

    """

    def __init__(self):
        self.firstgid = 0
        self.name = None
        self.images = [] # TileImage
        self.tiles = [] # Tile
        self.indexed_images = {} # {id:image}
        self.spacing = 0
        self.margin = 0
        self.properties = {}
        self.tileheight = 0
        self.tilewidth = 0

#  -----------------------------------------------------------------------------

class TileImage(object):
    """
    An image of a tile or just an image.

    :Ivariables:
        id : int
            id of this image (has nothing to do with gid)
        format : string
            the format as string, only 'png' at the moment
        source : string
            filename of the image. either this is set or the content
        encoding : string
            encoding of the content
        trans : tuple of (r,g,b)
            the colorkey color, raw as hex, after calling convert just a 
            (r,g,b) tuple
        properties : dict
            the propertis set in the editor, name-value pairs
        image : TileImage
            after calling load the pygame surface
    """

    def __init__(self):
        self.id = 0
        self.format = None
        self.source = None
        self.encoding = None # from <data>...</data>
        self.content = None # from <data>...</data>
        self.image = None
        self.trans = None
        self.properties = {} # {name: value}

#  -----------------------------------------------------------------------------

class Tile(object):
    """
    A single tile.

    :Ivariables:
        id : int
            id of the tile gid = TileSet.firstgid + Tile.id
        images : list of :class:TileImage
            list of TileImage, either its 'id' or 'image data' will be set
        properties : dict of name:value
            the propertis set in the editor, name-value pairs
    """

# [20:22]	DR0ID_: to sum up: there are two use cases,
# if the tile element has a child element 'image' then tile is
# standalone with its own id and
# the other case where a tileset is present then it
# referes to the image with that id in the tileset

    def __init__(self):
        self.id = 0
        self.images = [] # uses TileImage but either only id will be set or image data
        self.properties = {} # {name: value}

#  -----------------------------------------------------------------------------

class TileLayer(object):
    """
    A layer of the world.

    :Ivariables:
        x : int
            position of layer in the world in number of tiles (not pixels)
        y : int
            position of layer in the world in number of tiles (not pixels)
        width : int
            number of tiles in x direction
        height : int
            number of tiles in y direction
        pixel_width : int
            width of layer in pixels
        pixel_height : int
            height of layer in pixels
        name : string
            name of this layer
        opacity : float
            float from 0 (full transparent) to 1.0 (opaque)
        decoded_content : list
            list of graphics id going through the map::

                e.g [1, 1, 1, ]
                where decoded_content[0]   is (0,0)
                      decoded_content[1]   is (1,0)
                      ...
                      decoded_content[w]   is (width,0)
                      decoded_content[w+1] is (0,1)
                      ...
                      decoded_content[w * h]  is (width,height)

                usage: graphics id = decoded_content[tile_x + tile_y * width]
        content2D : list
            list of list, usage: graphics id = content2D[x][y]

    """

    def __init__(self):
        self.width = 0
        self.height = 0
        self.x = 0
        self.y = 0
        self.pixel_width = 0
        self.pixel_height = 0
        self.name = None
        self.opacity = -1
        self.encoding = None
        self.compression = None
        self.encoded_content = None
        self.decoded_content = []
        self.visible = True
        self.properties = {} # {name: value}
        self.content2D = None
        self.is_object_group = False    # ISSUE 9


    def decode(self):
        """
        Converts the contents in a list of integers which are the gid of the 
        used tiles. If necessairy it decodes and uncompresses the contents.
        """
        self.decoded_content = []
        if self.encoded_content:
            content = self.encoded_content
            if self.encoding:
                if self.encoding.lower() == 'base64':
                    content = decode_base64(content)
                elif self.encoding.lower() == 'csv':
                    list_of_lines = content.split()
                    for line in list_of_lines:
                        self.decoded_content.extend(line.split(','))
                    self.decoded_content = list(map(int, \
                                [val for val in self.decoded_content if val]))
                    content = ""
                else:
                    raise Exception('unknown data encoding %s' % \
                                                                (self.encoding))
            else:
                # in the case of xml the encoded_content already contains a 
                # list of integers
                self.decoded_content = list(map(int, self.encoded_content))
                content = ""
            if self.compression:
                if self.compression == 'gzip':
                    content = decompress_gzip(content)
                elif self.compression == 'zlib':
                    content = decompress_zlib(content)
                else:
                    raise Exception('unknown data compression %s' % \
                                                            (self.compression))
        else:
            raise Exception('no encoded content to decode')

        struc = struct.Struct("<" + "I" * self.width)
        struc_unpack_from = struc.unpack_from
        self_decoded_content_extend = self.decoded_content.extend
        for idx in range(0, len(content), 4 * self.width):
            val = struc_unpack_from(content, idx)
            self_decoded_content_extend(val)

        arr = array.array('I')
        arr.fromlist(self.decoded_content)
        self.decoded_content = arr

        # TODO: generate property grid here??

        self._gen_2D()

    def _gen_2D(self):
        self.content2D = []

        # generate the needed lists and fill them
        for xpos in range(self.width):
            self.content2D.append(array.array('I'))
            for ypos in range(self.height):
                self.content2D[xpos].append( \
                                self.decoded_content[xpos + ypos * self.width])

    def pretty_print(self):
        num = 0
        for y in range(int(self.height)):
            output = ""
            for x in range(int(self.width)):
                output += str(self.decoded_content[num])
                num += 1
            print(output)

    def convert(self):
        self.opacity = float(self.opacity)
        self.x = int(self.x)
        self.y = int(self.y)
        self.width = int(self.width)
        self.height = int(self.height)
        self.pixel_width = self.width * self.tilewidth
        self.pixel_height = self.height * self.tileheight
        self.visible = bool(int(self.visible))

    # def get_visible_tile_range(self, xmin, ymin, xmax, ymax):
        # tile_w = self.pixel_width / self.width
        # tile_h = self.pixel_height / self.height
        # left = int(round(float(xmin) / tile_w)) - 1
        # right = int(round(float(xmax) / tile_w)) + 2
        # top = int(round(float(ymin) / tile_h)) - 1
        # bottom = int(round(float(ymax) / tile_h)) + 2
        # return (left, top, left - right, top - bottom)

    # def get_tiles(self, xmin, ymin, xmax, ymax):
        # tiles = []
        # if self.visible:
            # for ypos in range(ymin, ymax):
                # for xpos in range(xmin, xmax):
                    # try:
                        # img_idx = self.content2D[xpos][ypos]
                        # if img_idx:
                            # tiles.append((xpos, ypos, img_idx))
                    # except IndexError:
                        # pass
        # return tiles

#  -----------------------------------------------------------------------------


class MapObjectGroupLayer(object):
    """
    Group of objects on the map.

    :Ivariables:
        x : int
            the x position
        y : int
            the y position
        width : int
            width of the bounding box (usually 0, so no use)
        height : int
            height of the bounding box (usually 0, so no use)
        name : string
            name of the group
        objects : list
            list of the map objects

    """

    def __init__(self):
        self.width = 0
        self.height = 0
        self.name = None
        self.objects = []
        self.x = 0
        self.y = 0
        self.visible = True
        self.properties = {} # {name: value}
        self.is_object_group = True # ISSUE 9

    def convert(self):
        self.x = int(self.x)
        self.y = int(self.y)
        self.width = int(self.width)
        self.height = int(self.height)
        for map_obj in self.objects:
            map_obj.x = int(map_obj.x)
            map_obj.y = int(map_obj.y)
            map_obj.width = int(map_obj.width)
            map_obj.height = int(map_obj.height)

#  -----------------------------------------------------------------------------

class MapObject(object):
    """
    A single object on the map.

    :Ivariables:
        x : int
            x position relative to group x position
        y : int
            y position relative to group y position
        width : int
            width of this object
        height : int
            height of this object
        type : string
            the type of this object
        image_source : string
            source path of the image for this object
        image : :class:TileImage
            after loading this is the pygame surface containing the image
    """
    def __init__(self):
        self.name = None
        self.x = 0
        self.y = 0
        self.width = 0
        self.height = 0
        self.type = None
        self.image_source = None
        self.image = None
        self.properties = {} # {name: value}

#  -----------------------------------------------------------------------------
def decode_base64(in_str):
    """
    Decodes a base64 string and returns it.

    :Parameters:
        in_str : string
            base64 encoded string

    :returns: decoded string
    """
    import base64
    return base64.decodestring(in_str.encode('latin-1'))

#  -----------------------------------------------------------------------------
def decompress_gzip(in_str):
    """
    Uncompresses a gzip string and returns it.

    :Parameters:
        in_str : string
            gzip compressed string

    :returns: uncompressed string
    """
    import gzip
    
    if sys.version_info > (2, ):
        from io import BytesIO
        copmressed_stream = BytesIO(in_str)
    else:
        # gzip can only handle file object therefore using StringIO
        copmressed_stream = StringIO(in_str.decode("latin-1"))
    gzipper = gzip.GzipFile(fileobj=copmressed_stream)
    content = gzipper.read()
    gzipper.close()
    return content

#  -----------------------------------------------------------------------------
def decompress_zlib(in_str):
    """
    Uncompresses a zlib string and returns it.

    :Parameters:
        in_str : string
            zlib compressed string

    :returns: uncompressed string
    """
    import zlib
    content = zlib.decompress(in_str)
    return content
#  -----------------------------------------------------------------------------
def printer(obj, ident=''):
    """
    Helper function, prints a hirarchy of objects.
    """
    import inspect
    print(ident + obj.__class__.__name__.upper())
    ident += '    '
    lists = []
    for name in dir(obj):
        elem = getattr(obj, name)
        if isinstance(elem, list) and name != 'decoded_content':
            lists.append(elem)
        elif not inspect.ismethod(elem):
            if not name.startswith('__'):
                if name == 'data' and elem:
                    print(ident + 'data = ')
                    printer(elem, ident + '    ')
                else:
                    print(ident + '%s\t= %s' % (name, getattr(obj, name)))
    for objt_list in lists:
        for _obj in objt_list:
            printer(_obj, ident + '    ')

#  -----------------------------------------------------------------------------

class VersionError(Exception): pass

#  -----------------------------------------------------------------------------
class TileMapParser(object):
    """
    Allows to parse and decode map files for 'Tiled', a open source map editor
    written in java. It can be found here: http://mapeditor.org/
    """

    def _build_tile_set(self, tile_set_node, world_map):
        tile_set = TileSet()
        self._set_attributes(tile_set_node, tile_set)
        if hasattr(tile_set, "source"):
            tile_set = self._parse_tsx(tile_set.source, tile_set, world_map)
        else:
            tile_set = self._get_tile_set(tile_set_node, tile_set, \
                                                            self.map_file_name)
        world_map.tile_sets.append(tile_set)

    def _parse_tsx(self, file_name, tile_set, world_map):
        # ISSUE 5: the *.tsx file is probably relative to the *.tmx file
        if not os.path.isabs(file_name):
            # print "map file name", self.map_file_name
            file_name = self._get_abs_path(self.map_file_name, file_name)
        # print "tsx filename: ", file_name
        # would be more elegant to use  "with open(file_name, "rb") as file:" 
        # but that is python 2.6
        file = None
        try:
            file = open(file_name, "rb")
            dom = minidom.parseString(file.read())
        finally:
            if file:
                file.close()
        for node in self._get_nodes(dom.childNodes, 'tileset'):
            tile_set = self._get_tile_set(node, tile_set, file_name)
            break
        return tile_set

    def _get_tile_set(self, tile_set_node, tile_set, base_path):
        for node in self._get_nodes(tile_set_node.childNodes, 'image'):
            self._build_tile_set_image(node, tile_set, base_path)
        for node in self._get_nodes(tile_set_node.childNodes, 'tile'):
            self._build_tile_set_tile(node, tile_set)
        self._set_attributes(tile_set_node, tile_set)
        return tile_set

    def _build_tile_set_image(self, image_node, tile_set, base_path):
        image = TileImage()
        self._set_attributes(image_node, image)
        # id of TileImage has to be set! -> Tile.TileImage will only have id set
        for node in self._get_nodes(image_node.childNodes, 'data'):
            self._set_attributes(node, image)
            image.content = node.childNodes[0].nodeValue
        image.source = self._get_abs_path(base_path, image.source) # ISSUE 5
        tile_set.images.append(image)

    def _get_abs_path(self, base, relative):
        if os.path.isabs(relative):
            return relative
        if os.path.isfile(base):
            base = os.path.dirname(base)
        return os.path.abspath(os.path.join(base, relative))

    def _build_tile_set_tile(self, tile_set_node, tile_set):
        tile = Tile()
        self._set_attributes(tile_set_node, tile)
        for node in self._get_nodes(tile_set_node.childNodes, 'image'):
            self._build_tile_set_tile_image(node, tile)
        tile_set.tiles.append(tile)

    def _build_tile_set_tile_image(self, tile_node, tile):
        tile_image = TileImage()
        self._set_attributes(tile_node, tile_image)
        for node in self._get_nodes(tile_node.childNodes, 'data'):
            self._set_attributes(node, tile_image)
            tile_image.content = node.childNodes[0].nodeValue
        tile.images.append(tile_image)

    def _build_layer(self, layer_node, world_map):
        layer = TileLayer()
        self._set_attributes(layer_node, layer)
        for node in self._get_nodes(layer_node.childNodes, 'data'):
            self._set_attributes(node, layer)
            if layer.encoding:
                layer.encoded_content = node.lastChild.nodeValue
            else:
                #print 'has childnodes', node.hasChildNodes()
                layer.encoded_content = []
                for child in node.childNodes:
                    if child.nodeType == Node.ELEMENT_NODE and \
                                                    child.nodeName == "tile":
                        val = child.attributes["gid"].nodeValue
                        #print child, val
                        layer.encoded_content.append(val)
        world_map.layers.append(layer)

    def _build_world_map(self, world_node):
        world_map = TileMap()
        self._set_attributes(world_node, world_map)
        if world_map.version != "1.0":
            raise VersionError('this parser was made for maps of version 1.0, found version %s' % world_map.version)
        for node in self._get_nodes(world_node.childNodes, 'tileset'):
            self._build_tile_set(node, world_map)
        for node in self._get_nodes(world_node.childNodes, 'layer'):
            self._build_layer(node, world_map)
        for node in self._get_nodes(world_node.childNodes, 'objectgroup'):
            self._build_object_groups(node, world_map)
        return world_map

    def _build_object_groups(self, object_group_node, world_map):
        object_group = MapObjectGroupLayer()
        self._set_attributes(object_group_node,  object_group)
        for node in self._get_nodes(object_group_node.childNodes, 'object'):
            tiled_object = MapObject()
            self._set_attributes(node, tiled_object)
            for img_node in self._get_nodes(node.childNodes, 'image'):
                tiled_object.image_source = \
                                        img_node.attributes['source'].nodeValue
            object_group.objects.append(tiled_object)
        # ISSUE 9
        world_map.layers.append(object_group)

    # -- helpers -- #
    def _get_nodes(self, nodes, name):
        for node in nodes:
            if node.nodeType == Node.ELEMENT_NODE and node.nodeName == name:
                yield node

    def _set_attributes(self, node, obj):
        attrs = node.attributes
        for attr_name in list(attrs.keys()):
            setattr(obj, attr_name, attrs.get(attr_name).nodeValue)
        self._get_properties(node, obj)

    def _get_properties(self, node, obj):
        props = {}
        for properties_node in self._get_nodes(node.childNodes, 'properties'):
            for property_node in self._get_nodes(properties_node.childNodes, 'property'):
                try:
                    props[property_node.attributes['name'].nodeValue] = \
                                    property_node.attributes['value'].nodeValue
                except KeyError:
                    props[property_node.attributes['name'].nodeValue] = \
                                            property_node.lastChild.nodeValue
        obj.properties.update(props)


    # -- parsers -- #
    def parse(self, file_name):
        """
        Parses the given map. Does no decoding nor loading of the data.
        :return: instance of TileMap
        """
        # would be more elegant to use  
        # "with open(file_name, "rb") as tmx_file:" but that is python 2.6
        self.map_file_name = os.path.abspath(file_name)
        tmx_file = None
        try:
            tmx_file = open(self.map_file_name, "rb")
            dom = minidom.parseString(tmx_file.read())
        finally:
            if tmx_file:
                tmx_file.close()
        for node in self._get_nodes(dom.childNodes, 'map'):
            world_map = self._build_world_map(node)
            break
        world_map.map_file_name = self.map_file_name
        world_map.convert()
        return world_map

    def parse_decode(self, file_name):
        """
        Parses the map but additionally decodes the data.
        :return: instance of TileMap
        """
        world_map = self.parse(file_name)
        world_map.decode()
        return world_map


#  -----------------------------------------------------------------------------

class AbstractResourceLoader(object):
    """
    Abstract base class for the resource loader.

    """

    FLIP_X = 1 << 31
    FLIP_Y = 1 << 30

    def __init__(self):
        self.indexed_tiles = {} # {gid: (offsetx, offsety, image}
        self.world_map = None
        self._img_cache = {}

    def _load_image(self, filename, colorkey=None): # -> image
        """
        Load a single image.

        :Parameters:
            filename : string
                Path to the file to be loaded.
            colorkey : tuple
                The (r, g, b) color that should be used as colorkey 
                (or magic color).
                Default: None

        :rtype: image

        """
        raise NotImplementedError('This should be implemented in a inherited class')

    def _load_image_file_like(self, file_like_obj, colorkey=None): # -> image
        """
        Load a image from a file like object.

        :Parameters:
            file_like_obj : file
                This is the file like object to load the image from.
            colorkey : tuple
                The (r, g, b) color that should be used as colorkey 
                (or magic color).
                Default: None

        :rtype: image
        """
        raise NotImplementedError('This should be implemented in a inherited class')

    def _load_image_parts(self, filename, margin, spacing, tilewidth, tileheight, colorkey=None): #-> [images]
        """
        Load different tile images from one source image.

        :Parameters:
            filename : string
                Path to image to be loaded.
            margin : int
                The margin around the image.
            spacing : int
                The space between the tile images.
            tilewidth : int
                The width of a single tile.
            tileheight : int
                The height of a single tile.
            colorkey : tuple
                The (r, g, b) color that should be used as colorkey 
                (or magic color).
                Default: None

        Luckily that iteration is so easy in python::

            ...
            w, h = image_size
            for y in xrange(margin, h, tileheight + spacing):
                for x in xrange(margin, w, tilewidth + spacing):
                    ...

        :rtype: a list of images
        """
        raise NotImplementedError('This should be implemented in a inherited class')

    def load(self, tile_map):
        """
        """
        self.world_map = tile_map
        for tile_set in tile_map.tile_sets:
            # do images first, because tiles could reference it
            for img in tile_set.images:
                if img.source:
                    self._load_image_from_source(tile_map, tile_set, img)
                else:
                    tile_set.indexed_images[img.id] = self._load_tile_image(img)
            # tiles
            for tile in tile_set.tiles:
                for img in tile.images:
                    if not img.content and not img.source:
                        # only image id set
                        indexed_img = tile_set.indexed_images[img.id]
                        self.indexed_tiles[int(tile_set.firstgid) + int(tile.id)] = (0, 0, indexed_img)
                    else:
                        if img.source:
                            self._load_image_from_source(tile_map, tile_set, img)
                        else:
                            indexed_img = self._load_tile_image(img)
                            self.indexed_tiles[int(tile_set.firstgid) + int(tile.id)] = (0, 0, indexed_img)

    def _load_image_from_source(self, tile_map, tile_set, a_tile_image):
        # relative path to file
        img_path = os.path.join(os.path.dirname(tile_map.map_file_name), \
                                                            a_tile_image.source)
        tile_width = int(tile_map.tilewidth)
        tile_height = int(tile_map.tileheight)
        if tile_set.tileheight:
            tile_width = int(tile_set.tilewidth)
        if tile_set.tilewidth:
            tile_height = int(tile_set.tileheight)
        offsetx = 0
        offsety = 0
        # the offset is used for pygame because the origin is topleft in pygame
        if tile_height > tile_map.tileheight:
            offsety = tile_height - tile_map.tileheight
        idx = 0
        for image in self._load_image_parts(img_path, \
                    tile_set.margin, tile_set.spacing, \
                    tile_width, tile_height, a_tile_image.trans):
            self.indexed_tiles[int(tile_set.firstgid) + idx] = \
                                                    (offsetx, -offsety, image)
            idx += 1

    def _load_tile_image(self, a_tile_image):
        img_str = a_tile_image.content
        if a_tile_image.encoding:
            if a_tile_image.encoding == 'base64':
                img_str = decode_base64(a_tile_image.content)
            else:
                raise Exception('unknown image encoding %s' % a_tile_image.encoding)
        sio = StringIO(img_str)
        new_image = self._load_image_file_like(sio, a_tile_image.trans)
        return new_image

#  -----------------------------------------------------------------------------

