from functools import partial

from siege.world.realm import Layer, TilePosition, RealmArea
from siege.worldgen import FillMode
from siege.util import TileRect, Rect, RangeInt, RangeFloat, Random, Sizes

from core.terraform.helper import determineAmount, randomGrowth
from core.terraform.helper import placeItem, placeItemAtop
from core.terraform.helper import placeTilesInArea, placeUndergroundTiles, placeUndergroundStamps
from core.terraform.stamps import loadStamps, createStampColor
from core.helper import pixelRect


MAGIC_UNDERGROUND_RATIO = 262144.0

EMPTY_COLOR = createStampColor(0, 0, 0)
WALL_COLOR = createStampColor(154, 126, 98)
DIRT_COLOR = createStampColor(96, 57, 19)
WATER_COLOR = createStampColor(0, 0, 255)
LAVA_COLOR = createStampColor(255, 0, 0)
TORCH_COLOR = createStampColor(180, 50, 30)
ALTAR_COLOR = createStampColor(0, 255, 255)
BRICK_COLOR = createStampColor(200, 170, 50)
SHELF_COLOR = createStampColor(96, 57, 19)

waterStamps = loadStamps('underground/water')
lavaStamps = loadStamps('underground/lava')
treasureStamps = loadStamps('underground/treasure')
forestStamps = loadStamps('underground/forest')

f = lambda frequency: frequency / MAGIC_UNDERGROUND_RATIO
undergroundRatio = lambda tracker: tracker.undergroundVolume / MAGIC_UNDERGROUND_RATIO


def PlaceTreasureRooms(game, world, realm, data):
    return [
        partial(_placeTreasureRooms, f(0.5), (0.05, 0.3)),
        partial(_placeTreasureRooms, f(1.5), (0.1, 0.6)),
        partial(_placeTreasureRooms, f(2.5), (0.5, 0.9)),
    ]


def PlantMushroomTrees(game, world, realm, data):
    return [
        partial(_plantUndergroundTrees, 'mushroom_tree', Layer.Back, RangeInt(1, 2), False, RangeFloat(0.0, 0.2)),
        partial(_plantUndergroundTrees, 'mushroom_tree', Layer.Back, RangeInt(2, 3), False, RangeFloat(0.2, 0.4)),
        partial(_plantUndergroundTrees, 'mushroom_tree', Layer.Back, RangeInt(3, 4), False, RangeFloat(0.4, 0.6)),
        partial(_plantUndergroundTrees, 'mushroom_tree', Layer.Back, RangeInt(2, 3), False, RangeFloat(0.6, 0.8)),
    ]


def PlaceMushroomForests(game, world, realm, data):
    return [
        partial(_placeMushroomForests, f(0.4), (0.1, 0.2)),
        partial(_placeMushroomForests, f(4), (0.3, 0.8)),
        partial(_placeMushroomForests, f(1), (0.8, 0.9)),
    ]


def PlaceSediments(game, world, realm, data):
    groundLevel = float(realm.groundLevel) / float(realm.size.tileHeight)
    stoneLevel = data['stoneStartY'] / float(realm.size.segmentHeight)
    stoneStartY = (stoneLevel - groundLevel) / (1.0 - groundLevel)
    stoneDepthRange = (stoneStartY - 0.03, stoneStartY + 0.05)
    return [
        partial(placeUndergroundTiles, Layer.Ground, '', f(25), (30, 45), (30, 45), (0.0, 0.2), FillMode.SOLID_ONLY, "holes"),
        partial(placeUndergroundTiles, Layer.Ground, '', f(25), (30, 45), (30, 45), (0.0, 0.4), FillMode.SOLID_ONLY, "holes"),
        partial(placeUndergroundTiles, Layer.Ground, '', f(35), (30, 45), (30, 45), (0.4, 0.7), FillMode.SOLID_ONLY, "holes"),
        partial(placeUndergroundTiles, Layer.Ground, '', f(35), (25, 40), (25, 40), (0.0, 0.5), FillMode.SOLID_ONLY, "holes"),
        partial(placeUndergroundTiles, Layer.Ground, '', f(35), (25, 40), (25, 40), (0.5, 1.0), FillMode.SOLID_ONLY, "holes"),
        partial(placeUndergroundTiles, Layer.Ground, '', f(30), (30, 45), (30, 45), (0.6, 1.0), FillMode.SOLID_ONLY, "holes"),

        partial(placeUndergroundTiles, Layer.WallAndGround, 'dirt', f(20), (5, 10), (5, 10), (0.45, 1.0), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'dirt', f(35), (10, 20), (10, 20), (0.35, 1.0), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'dirt', f(50), (10, 30), (10, 30), (0.2, 0.85), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'dirt', f(30), (5, 10), (5, 10), (0.35, 1.0), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'dirt', f(35), (10, 20), (10, 20), (0.2, 1.0), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'dirt', f(50), (10, 30), (10, 30), (0.2, 0.85), FillMode.SOLID_ONLY, "tile_medium"),

        partial(placeUndergroundTiles, Layer.Ground, 'clay', f(20), (4, 8), (4, 8), (0.0, 0.2), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'clay', f(15), (4, 8), (4, 8), (0.2, 0.35), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'clay', f(15), (4, 8), (4, 8), (0.2, 0.35), FillMode.SOLID_ONLY, "tile_medium"),

        partial(placeUndergroundTiles, Layer.Ground, 'sand', f(10), (8, 18), (8, 16), (0.2, 0.35), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'sand', f(5), (4, 10), (7, 15), (0.0, 0.35), FillMode.SOLID_ONLY, "tile_medium"),

        partial(placeUndergroundTiles, Layer.WallAndGround, 'stone', f(120), (8, 14), (8, 14), (0.0, 0.35), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'stone', f(110), (12, 20), (12, 20), (0.2, 0.45), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'stone', f(120), (8, 14), (8, 14), (0.0, 0.35), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'stone', f(110), (12, 20), (12, 20), (0.2, 0.45), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'stone', f(70), (18, 30), (18, 30), stoneDepthRange, FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.WallAndGround, 'stone', f(70), (18, 30), (18, 30), stoneDepthRange, FillMode.SOLID_ONLY, "tile_medium"),
    ]


def PlaceMetalOres(game, world, realm, data):
    actions = [
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(12), (4, 7), (4, 7), (0.0, 0.2), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(14), (4, 7), (4, 7), (0.0, 0.35), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(16), (5, 8), (5, 8), (0.0, 0.45), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(26), (4, 7), (4, 7), (0.45, 1), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(20), (5, 10), (5, 10), (0.0, 1), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(12), (4, 7), (4, 7), (0.0, 0.2), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(15), (4, 7), (4, 7), (0.0, 0.35), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(16), (5, 8), (5, 8), (0.0, 0.45), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(25), (4, 7), (4, 7), (0.45, 1), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'copper_ore', f(20), (5, 10), (5, 10), (0.0, 1), FillMode.SOLID_ONLY, "tile_medium"),

        partial(placeUndergroundTiles, Layer.Ground, 'iron_ore', f(28), (4, 7), (4, 7), (0.0, 0.35), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'iron_ore', f(30), (5, 8), (5, 8), (0.2, 0.55), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'iron_ore', f(28), (4, 7), (4, 7), (0.45, 1), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'iron_ore', f(28), (4, 7), (4, 7), (0.0, 0.35), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'iron_ore', f(20), (5, 8), (5, 8), (0.2, 0.55), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'iron_ore', f(28), (4, 7), (4, 7), (0.45, 1), FillMode.SOLID_ONLY, "tile_medium"),

        partial(placeUndergroundTiles, Layer.Ground, 'silver_ore', f(18), (4, 7), (4, 7), (0.2, 0.45), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'silver_ore', f(23), (4, 7), (4, 7), (0.35, 0.7), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'silver_ore', f(25), (3, 8), (3, 8), (0.55, 1), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'silver_ore', f(18), (4, 7), (4, 7), (0.2, 0.45), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'silver_ore', f(23), (4, 7), (4, 7), (0.35, 0.7), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'silver_ore', f(25), (3, 8), (3, 8), (0.55, 1), FillMode.SOLID_ONLY, "tile_medium"),

        partial(placeUndergroundTiles, Layer.Ground, 'gold', f(16), (4, 7), (4, 7), (0.35, 0.55), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'gold', f(19), (4, 7), (4, 7), (0.45, 0.7), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'gold', f(20), (3, 7), (4, 7), (0.55, 1), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, 'gold', f(16), (4, 7), (4, 7), (0.35, 0.55), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'gold', f(19), (4, 5), (4, 5), (0.45, 0.7), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, 'gold', f(20), (3, 7), (4, 7), (0.55, 1), FillMode.SOLID_ONLY, "tile_medium"),

        # partial(placeUndergroundTiles, Layer.Ground, 'osmium', f(10), (4, 5), (4, 5), (0.45, 0.55), FillMode.SOLID_ONLY, "tile_small"),
        # partial(placeUndergroundTiles, Layer.Ground, 'osmium', f(15), (4, 5), (4, 5), (0.45, 0.7), FillMode.SOLID_ONLY, "tile_small"),
        # partial(placeUndergroundTiles, Layer.Ground, 'osmium', f(15), (3, 7), (3, 7), (0.55, 1), FillMode.SOLID_ONLY, "tile_small"),
        # partial(placeUndergroundTiles, Layer.Ground, 'osmium', f(10), (4, 5), (4, 5), (0.45, 0.55), FillMode.SOLID_ONLY, "tile_medium"),
        # partial(placeUndergroundTiles, Layer.Ground, 'osmium', f(15), (4, 5), (4, 5), (0.45, 0.7), FillMode.SOLID_ONLY, "tile_medium"),
        # partial(placeUndergroundTiles, Layer.Ground, 'osmium', f(15), (3, 7), (3, 7), (0.55, 1), FillMode.SOLID_ONLY, "tile_medium"),
    ]

    actions.extend(_placeGems(game, world, realm, data, 'diamond_block'))
    actions.extend(_placeGems(game, world, realm, data, 'ruby_block'))
    actions.extend(_placeGems(game, world, realm, data, 'sapphire_block'))
    return actions


def _placeGems(game, world, realm, data, gem):
    return [
        partial(placeUndergroundTiles, Layer.Ground, gem, f(4), (2, 5), (2, 5), (0.15, 0.25), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, gem, f(12), (3, 6), (3, 6), (0.25, 0.6), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, gem, f(18), (4, 7), (4, 7), (0.6, 1.0), FillMode.SOLID_ONLY, "tile_small"),
        partial(placeUndergroundTiles, Layer.Ground, gem, f(4), (2, 5), (2, 5), (0.15, 0.25), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, gem, f(12), (3, 6), (3, 6), (0.25, 0.6), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, gem, f(18), (4, 7), (4, 7), (0.6, 1.0), FillMode.SOLID_ONLY, "tile_medium"),
        partial(placeUndergroundTiles, Layer.Ground, gem, f(6), (5, 9), (5, 9), (0.25, 1.0), FillMode.SOLID_ONLY, "tile_medium"),
    ]


def PlaceUndergroundWater(game, world, realm, data):
    return [
        partial(_placeUndergroundWater, f(7), (0.0, 0.3)),
        partial(_placeUndergroundWater, f(4), (0.3, 0.5)),
        partial(_placeUndergroundWater, f(1), (0.5, 0.7)),
    ]


def _placeUndergroundWater(frequency, depthRange, game, world, realm, data):
    dirtId = game.content.get('dirt').id
    tileRules = {
        # set empty & item colors so the backwall isn't empty
        WATER_COLOR: (0, dirtId),
        WALL_COLOR: (0, dirtId),
    }
    placeUndergroundStamps('water', waterStamps, tileRules, frequency, depthRange, 30, [], game, world, realm, data)


def PlaceUndergroundLava(game, world, realm, data):
    return [
        partial(_placeUndergroundLava, f(2), (0.7, 0.8)),
        partial(_placeUndergroundLava, f(5), (0.8, 0.9)),
        partial(_placeUndergroundLava, f(8), (0.9, 0.95)),
        partial(_placeUndergroundLava, f(14), (0.95, 1.0)),
    ]


def _placeUndergroundLava(frequency, depthRange, game, world, realm, data):
    dirtId = game.content.get('dirt').id
    tileRules = {
        # set empty & item colors so the backwall isn't empty
        LAVA_COLOR: (0, dirtId),
        WALL_COLOR: (0, dirtId),
    }
    placeUndergroundStamps('lava', lavaStamps, tileRules, frequency, depthRange, 30, [], game, world, realm, data)


def PlaceTreasureChests(game, world, realm, data):
    return [
        partial(_placeTreasureChests, "basic", 4, RangeInt(5, 10), RangeFloat(0.0, 0.3)),
        partial(_placeTreasureChests, "basic", 5, RangeInt(10, 15), RangeFloat(0.3, 0.6)),
        partial(_placeTreasureChests, "basic", 6, RangeInt(10, 15), RangeFloat(0.6, 1.0)),
    ]


def _placeTreasureChests(group, frequency, qualityRange, depthRange, game, world, realm, data):
    quantity = determineAmount(realm.biomeTracker.undergroundVolume, f(frequency))

    offset = float(realm.groundLevel) / float(realm.size.tileHeight)
    area = Rect(0, 0, 1.0, 0)
    area.y = offset + depthRange.min * (1.0 - offset)
    area.height = offset + depthRange.max * (1.0 - offset)

    finalArea = Rect()
    finalArea.x = (area.x * data['undergroundArea'].x)
    finalArea.y = (area.y * (data['undergroundArea'].height + data['undergroundArea'].y))
    finalArea.width = (area.width * data['undergroundArea'].width)
    finalArea.height = ((area.height - area.y) * data['undergroundArea'].height)

    for _ in range(quantity):
        quality = qualityRange.getRandom()
        game.treasure.generate(group, quality, realm, pixelRect(finalArea), isUnderground=True)


def _placeTreasureRooms(frequency, depthRange, game, world, realm, data):
    stoneId = game.content.get('stone').id
    tileRules = {
        # set empty & item colors so the backwall isn't empty
        EMPTY_COLOR: (0, stoneId),
        TORCH_COLOR: (0, stoneId),
        ALTAR_COLOR: (0, stoneId),
        SHELF_COLOR: (0, stoneId),
        BRICK_COLOR: (game.content.get('gold_brick').id, stoneId),
    }

    results = placeUndergroundStamps('treasure_room', treasureStamps, tileRules, frequency, depthRange, 160, data['minibiomes'], game, world, realm, data)
    for stamp, stampPosition in results:
        data['minibiomes'].append(TileRect(stampPosition, stamp.getSize()))
        placeItem(game, realm, 'torch', stamp, stampPosition, TORCH_COLOR)
        placeItem(game, realm, 'bookshelf', stamp, stampPosition, SHELF_COLOR)
        altar = placeItem(game, realm, 'altar', stamp, stampPosition, ALTAR_COLOR).pop()
        chest = placeItemAtop(game, realm, 'large_gold_chest', altar)
        items = game.treasure.getItemsForQuality("gear", 20)
        game.treasure.fillChestWithItems(chest, items)


def _placeMushroomForests(frequency, depthRange, game, world, realm, data):
    stoneId = game.content.get('stone').id
    tileRules = {
        EMPTY_COLOR: (0, stoneId),
        DIRT_COLOR: (game.content.get('dirt').id, stoneId),
    }
    results = placeUndergroundStamps('mushroom_forest', forestStamps, tileRules, frequency, depthRange, 100, data['minibiomes'], game, world, realm, data)
    content = game.content.get('mushroom_tree')
    for stamp, stampPosition in results:
        area = TileRect(stampPosition, stamp.getSize())
        data['minibiomes'].append(area)
        trees = game.organic.plantMany(content, realm, RealmArea(pixelRect(area)), Layer.Back, Random.get(5, 8))
        randomGrowth(trees, 3)


def _plantUndergroundTrees(organic, layer, amount, onSurface, depthRange, game, world, realm, data):
    offset = float(realm.groundLevel) / float(realm.size.tileHeight)
    area = Rect(data['undergroundArea'].x * Sizes.TILE, 0, data['undergroundArea'].width * Sizes.TILE, 0)
    area.y = offset + depthRange.min * (1.0 - offset)
    area.height = offset + depthRange.max * (1.0 - offset)
    area.height = ((area.height - area.y) * data['undergroundArea'].height) * Sizes.TILE
    area.y = (area.y * (data['undergroundArea'].height + data['undergroundArea'].y)) * Sizes.TILE
    finalAmount = int(round(amount.getRandom() * undergroundRatio(realm.biomeTracker)))
    content = game.content.get(organic)
    entities = game.organic.plantMany(content, realm, RealmArea(area, onSurface, True), layer, finalAmount)
    randomGrowth(entities, 3)


def PlacePots(game, world, realm, data):
    return [
        partial(_placePots, f(4), RangeInt(4, 8), RangeFloat(0, 0.1)),
        partial(_placePots, f(3), RangeInt(3, 6), RangeFloat(0.1, 0.2)),
        partial(_placePots, f(2), RangeInt(3, 5), RangeFloat(0.2, 0.3)),
        partial(_placePots, f(1.5), RangeInt(3, 5), RangeFloat(0.3, 0.4)),
        partial(_placePots, f(1), RangeInt(3, 5), RangeFloat(0.4, 0.5)),
        partial(_placePots, f(1), RangeInt(3, 5), RangeFloat(0.5, 0.6))
    ]


def _placePots(frequency, amountRange, depthRange, game, world, realm, data):
    ground = realm.layers[Layer.Ground]
    potsTileId = game.content.get('pots').id
    tileWidth = realm.size.tileWidth
    remaining = determineAmount(tileWidth * data['undergroundArea'].height, frequency)
    while remaining > 0:
        x = Random.get(tileWidth)
        threshold = realm.biomeTracker.undergroundThreshold[x]
        top = int(threshold + (realm.size.tileHeight - threshold) * depthRange.min)
        bottom = int(threshold + (realm.size.tileHeight - threshold) * depthRange.max)
        startY = Random.get(top, bottom)
        if Random.getBool():
            direction = 1
            yAmount = bottom - startY
        else:
            direction = -1
            yAmount = startY - top
        tilePos = TilePosition(x, startY, realm.size)
        amount = [amountRange.getRandom()] * 2
        placeTilesInArea(ground, potsTileId, tilePos, yAmount, direction, TileRect(0, top, tileWidth, bottom), amount)
        if amount[0] != amount[1]:
            remaining -= 1
