import random
from collections import defaultdict

from siege.component import AxisType
from siege.log import Log
from siege.world.realm import Layer, TilePosition, RealmArea
from siege.worldgen import Terraform
from siege.util import Vector, TileVector, TileRect, Rect, Sizes, Random

from core.helper import pixelRect, pixelVector, tryMany
from core.terraform.stamps import loadBoolStamps, shouldFlipX


stampCollection = defaultdict(list)
stampCollection['tile_small'] = loadBoolStamps('tiles/small')
stampCollection['tile_medium'] = loadBoolStamps('tiles/medium')
stampCollection['holes'] = loadBoolStamps('holes')


def determineAmount(volume, frequency):
    times = int(volume * frequency)
    #World sizes boil down to 8, 24 and 64. Frequency is multiplied by this.
    if times >= 8:
        quarterTimes = times / 4
        times = Random.get(times - quarterTimes, times + quarterTimes)
    return times


def placeTiles(layer, tile, frequency, widthRange, heightRange, depthRange, fillMode, stamps, area, ratio, game, world, realm, data):
    def place(finalArea, results):
        placeTilesWithStamps(layer, tile, finalArea, stamps, fillMode, game, world, realm)
        return True
    times = determineAmount(area.width * area.height, frequency)
    getPosition = lambda: getRandomPosition(area, depthRange)
    getArea = lambda: getRandomSizeRect(getPosition(), widthRange, heightRange)
    data['results'] = tryMany(times, lambda results: place(getArea(), results))


def placeUndergroundTiles(layer, tile, frequency, widthRange, heightRange, depthRange, fillMode, stamps, game, world, realm, data):
    def place(finalArea, results):
        if Terraform.overlapsSurface(finalArea.x, finalArea.y, finalArea.width, realm.biomeTracker.undergroundThreshold, realm.size):
            return False
        else:
            placeTilesWithStamps(layer, tile, finalArea, stamps, fillMode, game, world, realm)
            return True
    times = determineAmount(realm.biomeTracker.undergroundVolume, frequency)
    getPosition = lambda: realm.biomeTracker.getRandomUndergroundPosition(depthRange[0], depthRange[1])
    getArea = lambda: getRandomSizeRect(getPosition(), widthRange, heightRange)
    data['results'] = tryMany(times, lambda results: place(getArea(), results))


def placeUndergroundStamps(name, stamps, rules, frequency, depthRange, distance, unavailableAreas, game, world, realm, data):
    existing = data.get(name, [])

    def place(position, results):
        stamp = random.choice(stamps)
        # Keep a minimum distance stamps
        if distance > 0:
            for resultStamp, resultPosition in existing + results:
                if position.getDistance(resultPosition) < distance:
                    return False
        # Do not allow this stamp to overlap any taken areas
        for unavailableArea in unavailableAreas:
            area = TileRect(position, stamp.getSize())
            if unavailableArea.intersects(area, realm.size.tileWidth):
                return False
        if Terraform.overlapsSurface(position.x, position.y, stamp.getSize().x, realm.biomeTracker.undergroundThreshold, realm.size):
            return False
        else:
            placeColorStamp(stamp, position, rules, realm)
            return (stamp, position)
    times = determineAmount(realm.biomeTracker.undergroundVolume, frequency)
    getPosition = lambda: realm.biomeTracker.getRandomUndergroundPosition(depthRange[0], depthRange[1])
    results = tryMany(times, lambda results: place(getPosition(), results), maxAttempts=20)
    existing.extend(results)
    data[name] = existing
    return results


def placeTilesWithStamps(layer, tile, finalArea, stamps, fillMode, game, world, realm):
    stamp = random.choice(stampCollection[stamps])
    tile = game.content.get(tile).id if tile else 0
    if layer == Layer.WallAndGround:
        Terraform.placeTiles(tile, finalArea, realm.layers[Layer.Ground], fillMode, stamp)
        Terraform.placeTiles(tile, finalArea, realm.layers[Layer.Wall], fillMode, stamp)
    else:
        Terraform.placeTiles(tile, finalArea, realm.layers[layer], fillMode, stamp)
    return True


def placeColorStamp(stamp, position, rules, realm):
    Terraform.applySimpleStamp(stamp, position, rules, shouldFlipX(stamp), realm.layers[Layer.Ground], realm.layers[Layer.Wall], realm.automata)
    return True


def getRandomSizeRect(position, widthRange, heightRange):
    return TileRect(
        position.x,
        position.y,
        Random.get(widthRange[0], widthRange[1]),
        Random.get(heightRange[0], heightRange[1]))


def getRandomPosition(area, depthRange):
    startingDepth = int(area.height * depthRange[0])
    endingDepth = int(area.height * depthRange[1])

    return TileVector(
        Random.get(area.x, area.x + area.width - 1),
        Random.get(area.y + startingDepth, area.y + endingDepth - 1))


def plantOrganicAction(organic, layer, amount, onSurface, isUnderground, area, ratio, game, world, realm, data):
    finalAmount = int(round(Random.get(amount[0], amount[1]) * ratio))
    content = game.content.get(organic)
    finalArea = Rect(area.x * Sizes.TILE, area.y * Sizes.TILE, area.width * Sizes.TILE, area.height * Sizes.TILE)
    entities = game.organic.plantMany(content, realm, RealmArea(finalArea, onSurface, isUnderground), layer, finalAmount)
    randomGrowth(entities, 5)


def placeOnSurfaceAction(content, amount, area, ratio, game, world, realm, data):
    content = game.content.get(content)
    finalAmount = round(Random.get(amount[0], amount[1]) * ratio)
    for _ in range(0, int(finalAmount)):
        placeContentInArea(realm, content.entity, pixelRect(area), True)


def placeItem(game, realm, content, stamp, stampPosition, color):
    """ Place item(s) on stamp indicators. """

    if not stamp.has(color):
        return False
    content = game.content.get(content)

    def arrangeAndPlace(coords):
        target = pixelVector(coords + stampPosition)
        target.x += Sizes.TILE / 2
        return placeContentAtPosition(realm, content.entity, target)

    return [arrangeAndPlace(coords) for coords in stamp.getColor(color)]


def placeItemAtop(game, realm, contentName, target):
    """ Place an item atop target item. """
    # use getRect() so we get the top of the target, regardless of origin
    targetRect = target.render.getRect()
    x = targetRect.x + (targetRect.width / 2)
    y = targetRect.y - 1
    content = game.content.get(contentName)
    return placeContentAtPosition(realm, content.entity, Vector(x, y))


def placeContentAtPosition(realm, contentEntity, position):
    foundPosition = realm.placement.calculatePosition(position, contentEntity)
    if position.x == 0 and position.y == 1:
        raise RuntimeError("Couldn't find a valid position for {0.name}.".format(contentEntity))
    return realm.placement.createPlacement(foundPosition, contentEntity, False)


def placeContentInArea(realm, contentEntity, area, onSurface=False, isUnderground=False, validation=None):
    position = realm.placement.findSpace(contentEntity.content, RealmArea(area, onSurface, isUnderground), AxisType.AXIS_FLOOR)
    if position.x == 0 and position.y == 0:
        Log.warn("Failed to find a place for {0}".format(contentEntity.name))
        return None
    elif validation is None or validation(realm, contentEntity, position):
        position.loop(realm.size.width)
        entity = realm.placement.createPlacement(position, contentEntity, False)
        return entity


def chunkDistribute(realmTileWidth, totalArea, chunkSize, callback):
    """ One placement in every square chunk of a certain size. """

    def incrementArea(chunkArea):
        chunkArea.x += chunkSize
        if chunkArea.x + chunkArea.width > realmTileWidth:
            chunkArea.x = totalArea.x
            chunkArea.y += chunkSize

    totalTiles = totalArea.width * totalArea.height
    distTiles = chunkSize * chunkSize
    quantity = totalTiles / distTiles
    chunkArea = TileRect(totalArea.x, totalArea.y, chunkSize, chunkSize)

    for index in range(0, quantity):
        callback(pixelRect(chunkArea))
        incrementArea(chunkArea)


def randomGrowth(organics, maxGrowth):
    for entity in organics:
        plant = entity.organic.organic
        if hasattr(plant, "canGrow"):
            for x in range(0, Random.get(maxGrowth)):
                if plant.canGrow():
                    plant.grow()


def placeTilesInArea(ground, tileId, tilePos, yAmount, direction, tileArea, amount, moveX=0):
    if amount[0] == 0:
        return
    if moveX != 0:
        rand = Random.get(100)
        tilePos.move(moveX if rand < 70 else (moveX * 2 if rand < 92 else moveX * 3), 0)
    for _ in xrange(yAmount):
        if not tileArea.contains(tilePos.getWorld()):
            break
        if ground.getTile(tilePos) != tileId and not ground.isSolid(tilePos) and ground.isSolid(tilePos.getNeighbor(0, 1)):
            # Place pots here
            ground.setTile(tilePos, tileId)
            amount[0] -= 1
            if moveX == 0:
                moveX = -1 if Random.getBool() else 1
                (
                    placeTilesInArea(ground, tileId, TilePosition(tilePos), 3, direction, tileArea, amount, moveX) or
                    placeTilesInArea(ground, tileId, TilePosition(tilePos), 3, -direction, tileArea, amount, moveX) or
                    placeTilesInArea(ground, tileId, TilePosition(tilePos), 3, direction, tileArea, amount, -moveX) or
                    placeTilesInArea(ground, tileId, TilePosition(tilePos), 3, -direction, tileArea, amount, -moveX)
                )
            else:
                (
                    placeTilesInArea(ground, tileId, TilePosition(tilePos), 3, direction, tileArea, amount, moveX) or
                    placeTilesInArea(ground, tileId, TilePosition(tilePos), 3, -direction, tileArea, amount, moveX)
                )
            return
        tilePos.move(0, direction)
    return
