from siege import game
from siege.io import DataStream
from siege.network import Message, NetworkId
from siege.util import Rect, TileRect, Vector, Random, Sizes
from siege.world.realm import Layer, TilePosition

SERVICE_LAYERS = [Layer.Back, Layer.Item, Layer.Active]


def tryMany(times, callback, maxAttempts=0):
    attempts = 0
    successes = 0
    items = []
    while successes < times and (maxAttempts == 0 or attempts < maxAttempts):
        item = callback(items)
        if item:
            attempts = 0
            successes += 1
            items.append(item)
        else:
            attempts += 1
            continue
    return items


def pixelRect(rect):
    return Rect(
        rect.x * Sizes.TILE,
        rect.y * Sizes.TILE,
        rect.width * Sizes.TILE,
        rect.height * Sizes.TILE)


def pixelVector(vector):
    return Vector(vector.x * Sizes.TILE, vector.y * Sizes.TILE)


def tileRect(rect):
    return TileRect(
        int(rect.x / Sizes.TILE),
        int(rect.y / Sizes.TILE),
        int(rect.width / Sizes.TILE),
        int(rect.height / Sizes.TILE))


def tileVector(vector):
    return Vector(
        int(vector.x / Sizes.TILE),
        int(vector.y / Sizes.TILE))


class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self


def callEvent(name, player, *args, **kwargs):
    results = AttrDict(**kwargs)
    if player:
        arguments = [player, results]
        arguments.extend(args)
        eventComponent = player.entity.event
        if eventComponent.has(name):
            eventComponent[name].invokeExpand(arguments, kwargs)
        game.events[name].invokeExpand(arguments, kwargs)
    else:
        arguments = [results]
        arguments.extend(args)
        game.events[name].invokeExpand(arguments, kwargs)
    return results


def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    reverse = dict((value, key) for key, value in enums.iteritems())
    enums['getString'] = reverse
    return type('Enum', (), enums)


def shuffle(data):
    '''Randomly shuffles the provided list in place.'''
    #Used from Python random library with modification for Crea
    for i in reversed(xrange(1, len(data))):
        # pick an element in data[:i+1] with which to exchange data[i]
        j = int(Random.getFloat() * (i + 1))
        data[i], data[j] = data[j], data[i]


def select(data):
    '''Randomly selects an element from data. Selected element is removed from data.'''
    length = len(data)
    if length == 1:
        value = data[0]
        del data[0]
        return value
    elif length >= 2:
        index = Random.get(len(data) - 1)
        value = data[index]
        data[index] = data[-1]
        del data[-1]
        return value
    else:
        assert False, "Cannot select on empty container."


def partial(func, *args, **keywords):
#Copied from Python functools.partial to avoid that dependency
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc


def getRandomPath(path):
    if '[' not in path:
        return path
    start, end = path.split('[')
    lower, end = end.split('-')
    upper, end = end.split(']')
    return "{}{}{}".format(start, Random.get(int(lower), int(upper)), end)


def sendMessage(category, sender, message, recipients=None, **kwargs):
    if isinstance(recipients, NetworkId):
        recipients = [recipients]
    packet = DataStream(Message.CHAT_SYSTEM)
    packet.writeString(category)
    packet.writeString(sender)
    packet.writeString(message)
    packet.writeUint8(len(kwargs))
    for k, v in kwargs.iteritems():
        packet.writeString(str(k))
        packet.writeString(str(v))
    if recipients:
        for recipient in recipients:
            game.network.getServer().send(recipient, packet)
    else:
        #If no recipients are specified then broadcast to everyone
        game.network.getServer().broadcast(packet)


def getPositionOnGroundForEntity(realm, area, entity):
    ground = realm.layers[Layer.Ground]
    body = entity.physics.getWorldBody()
    position = entity.getPosition()
    difference = Vector(position.x - body.x, position.y - body.y)
    for attempt in xrange(10):
        x = Random.get(area.x, area.x + area.width)
        tilePos = TilePosition(x, area.y, realm.size)
        body.x = x * Sizes.TILE
        for y in xrange(area.y, area.y + area.height):
            if ground.isEmpty(tilePos):
                body.y = tilePos.getWorld().y * Sizes.TILE
                while y >= area.y:
                    body.y -= Sizes.TILE
                    if len(ground.overlaps(body, True, False)) == 0:
                        position.x = body.x + difference.x
                        position.y = body.y + difference.y
                        return position
                    y -= 1
            tilePos.move(0, 1)
    return None


def getRelativePosition(entity, body, position):
    finalPosition = Vector(body.x, position.y + body.y)
    if entity.getRender().getFlipX():
        finalPosition.x -= position.x
    else:
        finalPosition.x += position.x
    return finalPosition


def getRelativeArea(entity, body, area):
    # Area offset is always relative to the front of the monster
    finalArea = Rect(body.x, area.y + body.y, area.width, area.height)
    if entity.getRender().getFlipX():
        finalArea.x -= area.x + finalArea.width
    else:
        finalArea.x += area.x + body.width
    return finalArea
