from core.helper import select

from siege import game, InventoryItem
from siege.log import Log
from siege.network import NetworkManager
from siege.util import minutes, Random, RegionRect, SegmentRect, Sizes, TileRect, Timer, Vector
from siege.world import World
from siege.world.realm import Layer, TilePosition


class GrassType(object):
    def __init__(self, base, grass, foliage):
        self.baseId = game.content.get(base).id
        self.tileId = game.content.get(grass).id
        self.foliageId = game.content.get(foliage).id
        self.vineBasesId = 0
        self.vineMidsId = 0
        self.vineEndsId = 0
        self.tiles = []
        self.updateTiles = []
        self.destroyedTiles = []
        self.vines = []
        self.updateVines = []
        self.destroyedVines = []
        self.timer = Timer()
        self.tilesPerUpdate = 0
        self.tileUpdateCount = 0


class GrassSystem(object):
    NAME = "grass"
    UPDATE_COUNT = 8
    UPDATE_TIME = minutes(2)

    def getName(self):
        return self.NAME

    def __init__(self):
        self.neighbors = [(1, 0), (0, 1), (-1, 0), (0, -1)]
        self.allNeighbors = [(1, 0), (0, 1), (-1, 0), (0, -1), (1, 1), (-1, 1), (-1, 1), (1, -1)]
        self.grasses = []
        self.foliages = []
        self.vineTiles = []
        basicGrass = GrassType('dirt', 'dirt_grass', 'foliage')
        basicGrass.vineBasesId = game.content.get('vine_bases').id
        basicGrass.vineMidsId = game.content.get('vine_mids').id
        basicGrass.vineEndsId = game.content.get('vine_ends').id
        self.addGrassType(basicGrass)

    def addGrassType(self, grass):
        self.foliages.append(grass.foliageId)
        if grass.vineBasesId:
            self.vineTiles.append(grass.vineBasesId)
            self.vineTiles.append(grass.vineMidsId)
            self.vineTiles.append(grass.vineEndsId)
        self.grasses.append(grass)

    def handleRegionLoad(self, region, segments, timeElapsed):
        Log.trace("Loading grass region: %s" % region)
        ground = World.get().realm.layers[Layer.Ground]
        #Determine area
        area = RegionRect(region, 0, 1, 0).asTile()
        area.height = World.get().realm.groundLevel + Sizes.SEGMENT_TILE
        tileMap = ground.getIdMap(area)
        for grass in self.grasses:
            if grass.tileId in tileMap:
                grass.tiles.extend(tileMap[grass.tileId])
            for tileId in [grass.vineBasesId, grass.vineMidsId, grass.vineEndsId]:
                if tileId and tileId in tileMap:
                    grass.vines.extend(tileMap[tileId])

    def handleRegionUnload(self, region, segments):
        segmentStart = region * Sizes.REGION_SEGMENT
        segmentEnd = (region + 1) * Sizes.REGION_SEGMENT
        for grass in self.grasses:
            tiles = list(grass.tiles)
            index = len(tiles)
            for tile in reversed(tiles):
                index -= 1
                if tile.segment >= segmentStart and tile.segment < segmentEnd:
                    del grass.tiles[index]

    def handleTileChange(self, layer, position, previousId, changedId):
        realm = World.get().realm
        ground = realm.layers[Layer.Ground]
        if changedId != 0 or layer.type is not Layer.Ground:
            return
        worldPos = position.getWorld()
        neighbor = TilePosition(worldPos.x, worldPos.y - 1, ground.realmSize)
        tileId = ground.getTile(neighbor)
        if tileId != 0 and tileId in self.foliages:
            ground.setTile(neighbor, 0)
        neighbor.move(0, 2)
        tileId = ground.getTile(neighbor)
        createVine = False
        while tileId in self.vineTiles:
            tileId = ground.getTile(neighbor)
            if tileId in self.vineTiles:
                ground.setTile(neighbor, 0)
            createVine |= tileId in self.vineTiles
            neighbor.move(0, 1)
        Log.info("Creating vine for {}".format(neighbor.getWorld()))
        if createVine:
            vine = InventoryItem(game.content.get("vine"), 1)
            realm.dropped.create(vine, position.getWorldPosition(), Vector(Random.get(-3, 3), -6))

    def update(self, frameTime):
        for grass in self.grasses:
            grass.timer.update(frameTime)
            self.updateGrass(grass, frameTime)
            self.updateVine(grass, frameTime)
            if grass.timer.expired():
                grass.timer.reset(self.UPDATE_TIME + Random.get(self.UPDATE_TIME / 10))

    def updateGrass(self, grass, frameTime):
        if not grass.tiles:
            return
        ground = World.get().realm.layers[Layer.Ground]
        if not grass.updateTiles:
            #Cleanup destroyed tiles
            if grass.destroyedTiles:
                for destroyed in grass.destroyedTiles:
                    try:
                        grass.tiles.remove(destroyed)
                    except ValueError:
                        pass
                grass.destroyedTiles = []

            #Restart updating tiles
            grass.updateTiles = list(grass.tiles)
            grass.tilesPerUpdate = len(grass.updateTiles) / self.UPDATE_COUNT
            grass.tileUpdateCount = 0

            #Attempt to spread the grass to nearby areas
            pollinator = grass.tiles[Random.get(len(grass.tiles) - 1)].getWorld()
            area = SegmentRect(-2, -2, 4, 4).asTile()
            area.move(pollinator)
            candidates = ground.getTileList(area, grass.baseId)
            count = min(len(candidates), Random.get(5, 10))
            for x in xrange(count):
                candidate = select(candidates)
                if self.hasEmptyNeighbor(candidate, ground):
                    grass.tiles.append(candidate)
                    ground.setTile(candidate, grass.tileId)

        if grass.timer.expired():
            updateCount = grass.tilesPerUpdate
            grass.tileUpdateCount += 1
            if grass.tileUpdateCount == self.UPDATE_COUNT:
                updateCount = len(grass.updateTiles)
            if not grass.updateTiles:
                return
            maxDepth = World.get().realm.groundLevel + Sizes.SEGMENT_TILE * 4
            for x in xrange(0, updateCount):
                #Check for neighbors and try to spread
                if not grass.updateTiles:
                    break
                index = 0
                if grass.tileUpdateCount != self.UPDATE_COUNT:
                    index = Random.get(len(grass.updateTiles) - 1)
                tile = grass.updateTiles.pop(index)
                if ground.getTile(tile) != grass.tileId:
                    #This is no longer a grass tile.
                    #Queue it up to be removed and skip it
                    grass.destroyedTiles.append(tile)
                    continue

                worldPos = tile.getWorld()
                worldPos.y -= 1
                tilePos = TilePosition(worldPos, ground.realmSize)
                if ground.isEmpty(tilePos):
                    if Random.get(5) == 0:
                        ground.setTile(tilePos, grass.foliageId)
                worldPos.y += 2
                tilePos = TilePosition(worldPos, ground.realmSize)
                if ground.isEmpty(tilePos):
                    if Random.get(5) == 0:
                        ground.setTile(tilePos, grass.vineBasesId)
                        grass.vines.append(tilePos)

                #Go through each tile and check the neighbors
                for neighbor in self.allNeighbors:
                    if not Random.get(4):
                        continue
                    worldPos = tile.getWorld()
                    worldPos.x += neighbor[0]
                    worldPos.y += neighbor[1]
                    #This is the light check...
                    if worldPos.y >= maxDepth:
                        continue
                    tilePos = TilePosition(worldPos.x, worldPos.y, ground.realmSize)
                    if ground.getTile(tilePos) == grass.baseId:
                        if self.hasEmptyNeighbor(tilePos, ground):
                            grass.tiles.append(tilePos)
                            ground.setTile(tilePos, grass.tileId)
                    worldPos.x -= neighbor[0]
                    worldPos.y -= neighbor[1]

    def updateVine(self, grass, frameTime):
        if not grass.vines:
            return
        ground = World.get().realm.layers[Layer.Ground]
        if not grass.updateVines:
            #Cleanup destroyed vines
            if grass.destroyedVines:
                for destroyed in grass.destroyedVines:
                    try:
                        grass.vines.remove(destroyed)
                    except ValueError:
                        pass
                grass.destroyedVines = []

            #Restart updating tiles
            grass.updateVines = list(grass.vines)
            grass.vinesPerUpdate = len(grass.updateVines) / self.UPDATE_COUNT
            grass.vineUpdateCount = 0

            #Attempt to spread the grass to nearby areas
            pollinator = grass.vines[Random.get(len(grass.vines) - 1)].getWorld()
            area = TileRect()
            area.x = pollinator.x - Sizes.SEGMENT_TILE * 2
            area.y = pollinator.y - Sizes.SEGMENT_TILE * 2
            area.width = Sizes.SEGMENT_TILE * 4
            area.height = Sizes.SEGMENT_TILE * 4
            candidates = ground.getTileList(area, grass.baseId)
            for x in xrange(Random.get(5, 10)):
                candidate = candidates[Random.get(len(candidates) - 1)]
                candidate.move(0, 1)
                if ground.isEmpty(candidate):
                    ground.setTile(candidate, grass.vineBasesId)
                    grass.vines.append(candidate)

        if grass.timer.expired():
            updateCount = grass.vinesPerUpdate
            grass.vineUpdateCount += 1
            if grass.vineUpdateCount == self.UPDATE_COUNT:
                updateCount = len(grass.updateVines)
            if not grass.updateVines:
                return
            for x in xrange(0, updateCount):
                #Check for neighbors and try to spread
                if not grass.updateVines:
                    break
                index = 0
                if grass.vineUpdateCount != self.UPDATE_COUNT:
                    index = Random.get(len(grass.updateVines) - 1)
                tile = grass.updateVines.pop(index)
                tileId = ground.getTile(tile)
                if tileId not in self.vineTiles:
                    #This is no longer a vine tile.
                    #Queue it up to be removed and skip it
                    grass.destroyedVines.append(tile)
                    continue

                worldPos = tile.getWorld()
                worldPos.y += 1
                southTile = TilePosition(worldPos, ground.realmSize)
                if ground.isEmpty(southTile):
                    if tileId == grass.vineEndsId:
                        ground.setTile(tile, grass.vineMidsId)
                        ground.setTile(southTile, grass.vineEndsId)
                    elif tileId == grass.vineMidsId:
                        ground.setTile(southTile, grass.vineEndsId)
                    elif tileId == grass.vineBasesId:
                        ground.setTile(southTile, grass.vineMidsId)
                    grass.vines.append(southTile)

                #Go through each tile and check the neighbors
                # for neighbor in self.allNeighbors:
                #     if not Random.get(4):
                #         continue
                #     worldPos.x += neighbor[0]
                #     worldPos.y += neighbor[1]
                #     #This is the light check...
                #     if worldPos.y >= maxDepth:
                #         continue
                #     tilePos = TilePosition(worldPos.x, worldPos.y, ground.realmSize)
                #     if ground.getTile(tilePos) == grass.baseId:
                #         tilePos.move(0, 1)
                #         if ground.isEmpty(tilePos):
                #             grass.vines.append(tilePos)
                #             ground.setTile(tilePos, grass.vineBasesId)
                #     worldPos.x -= neighbor[0]
                #     worldPos.y -= neighbor[1]

    def hasEmptyNeighbor(self, tile, ground):
        for neighbor in self.neighbors:
            worldPos = tile.getWorld()
            worldPos.x += neighbor[0]
            worldPos.y += neighbor[1]
            tilePos = TilePosition(worldPos.x, worldPos.y, ground.realmSize)
            if ground.isEmpty(tilePos):
                return True
        return False

    @staticmethod
    def plant(area, amount):
        ground = World.get().realm.layers[Layer.Ground]

        baseId = game.content.get('dirt').id
        grassId = game.content.get('dirt_grass').id

        soils = [x for x in xrange(0, area.width)]
        while amount > 0 and soils:
            index = Random.get(len(soils) - 1)
            x = area.x + soils[index]
            del soils[index]

            for y in xrange(area.y + 1, area.y + area.height):
                tilePos = TilePosition(x, y, ground.realmSize)
                if not ground.isEmpty(tilePos):
                    if ground.getTile(tilePos) == baseId:
                        ground.setTile(tilePos, grassId)
                        game.grass.tiles.append(tilePos)
                        amount -= 1
                    break

    @staticmethod
    def register(world):
        if NetworkManager.isHost():
            system = GrassSystem()
            realm = World.get().realm
            for region in realm.loader.getLoadedRegions():
                system.handleRegionLoad(region, World.get().realm.loader.getSegments(region), 0)
            game.registerSystem(GrassSystem.NAME, system)
            realm.loader.onRegionLoad.listen(system.handleRegionLoad)
            game.events["tile_changed"].listen(system.handleTileChange)

    @staticmethod
    def unregister(world):
        if game.hasSystem(GrassSystem.NAME):
            game.events["tile_changed"].remove(game.grass.handleTileChange)
            game.unregisterSystem(GrassSystem.NAME)
