import random
from functools import partial

from siege.world import World
from siege.world.realm import Layer
from siege.worldgen import Terraform
from siege.util import TileRect, RangeFloat, Random

from core.helper import select
from core.terraform.helper import determineAmount
from core.terraform.stamps import loadStamps, createStampColor, PlacedStamp, ConnectorSide
from core.terraform.stamps import CAVE_ENTRANCE_COLOR
from core.terraform.underground import MAGIC_UNDERGROUND_RATIO, f, undergroundRatio

caveStampRules = {
    createStampColor(255, 0, 255): (0, 1),
    createStampColor(154, 126, 98): (0, 1)
}

def PlaceCaves(game, world, realm, data):
    area = TileRect(0, 0, realm.size.tileWidth, realm.size.tileHeight)

    entrances = loadStamps('caves/entrances')
    caveStamps = loadStamps('caves')

    actions = []

    if data['caveEntrances']:
        # Determine how many caves to create based off of world size
        # We want roughly 5 for every 2000 tiles wide
        caveCount = min(determineAmount(area.width * area.height, f(0.5)), len(data['caveEntrances']))

        # Now go through and make the surface caves!
        for x in xrange(caveCount):
            # Determine which entrance and entrance stamp to use
            worldEntrance = select(data['caveEntrances'])
            starterStamp = PlacedStamp(random.choice(entrances))
            entranceConnectors = starterStamp.stamp.getColor(CAVE_ENTRANCE_COLOR)
            assert entranceConnectors != 1, "There is not exactly one cave entrance for stamp (%s) found %d." % (starterStamp.stamp.image, len(entranceConnectors))
            stampEntrance = entranceConnectors[0]
            starterStamp.position.x = worldEntrance.x - stampEntrance.x
            starterStamp.position.y = worldEntrance.y - stampEntrance.y
            builder = CaveBuilder(realm.size, realm.biomeTracker, starterStamp, caveStamps, Random.get(30, 35), (13, 18), (0, 3))
            builder.execute()
            for branch in builder.branches:
                actions.append(partial(applyStamps, branch.stamps, caveStampRules))
    else:
        Log.info("No cave entrances are available!")

    actions.extend(_createUndergroundCaves(realm.size, realm.biomeTracker, area, caveStamps, f(.8), RangeFloat(0.1, 0.4), Random.get(1, 2)))
    actions.extend(_createUndergroundCaves(realm.size, realm.biomeTracker, area, caveStamps, f(1.5), RangeFloat(0.4, 0.95), Random.get(1, 2)))
    actions.extend(_createUndergroundCaves(realm.size, realm.biomeTracker, area, caveStamps, f(.35), RangeFloat(0.4, 0.7), Random.get(3, 8)))
    actions.extend(_createUndergroundCaves(realm.size, realm.biomeTracker, area, caveStamps, f(.5), RangeFloat(0.7, 0.95), Random.get(3, 8)))
    # Now make some underground caves
    return actions


def _createUndergroundCaves(realmSize, biomeTracker, area, caveStamps, frequency, heightRange, caveLength):
    actions = []
    times = determineAmount(area.width * area.height, frequency)
    for x in xrange(times):
        starterStamp = PlacedStamp(random.choice(caveStamps))
        starterStamp.position.x = Random.get(area.width)
        starterStamp.position.y = Random.get(int(area.height * heightRange.start), int(area.height * heightRange.end))
        builder = CaveBuilder(realmSize, biomeTracker, starterStamp, caveStamps, caveLength, (2, 6), (0, 1))
        builder.execute()
        for branch in builder.branches:
            actions.append(partial(applyStamps, branch.stamps, caveStampRules))
    return actions


class CaveBuilder(object):
    def __init__(self, realmSize, biomeTracker, starter, stamps, length, branchLengthRange, branchRange):
        self.realmSize = realmSize
        self.biomeTracker = biomeTracker
        self.length = 0
        self.maxLength = length
        self.branchLengthRange = branchLengthRange
        # Number of branches remaining (Do not have to use them all up)
        self.branchesRemaining = Random.get(branchRange[0], branchRange[1])
        self.stamps = []
        # Split the stamps up into different lists based on number of connectors
        self.buckets = {}
        for stamp in stamps:
            placed = PlacedStamp(stamp)
            count = len(placed.connectors)
            if count not in self.buckets:
                self.buckets[count] = []
            self.stamps.append(placed)
            self.buckets[count].append(placed)

        # List of current running branches
        self.branches = []
        # Make a branch from all of the starter connectors
        for connector in starter.connectors:
            self.branches.append(CaveBranch(self.realmSize, self.biomeTracker, starter, connector, Random.get(self.branchLengthRange[0], self.branchLengthRange[1])))

    def execute(self):
        """ Fill out the cave tree until all branches have reached their end. """
        remaining = list(self.branches)
        while remaining:
            branch = select(remaining)
            for x in xrange(branch.length):
                if self.length == self.maxLength:
                    # Reached cave limit so make this the last stamp in the branch
                    x = branch.length - 1
                # Determine which type of stamp to use next
                if x == branch.length - 1:
                    # Last stamp in branch
                    stamps = list(self.buckets[1])
                elif self.branchesRemaining > 0 and not Random.get(8):
                    # Provide stamps that can branch
                    stamps = []
                    for key, value in self.buckets.iteritems():
                        if key >= 3:
                            stamps.extend(value)
                else:
                    # Build onto branch with stamps that only have 2 connectors
                    stamps = list(self.buckets[2])
                # Keep trying to add a stamp to the branch until we get one that works
                for i in xrange(len(stamps)):
                    stamp = select(stamps)
                    if branch.add(stamp):
                        stamp = branch.stamps[-1]
                        if len(stamp.connectors) > 2:
                            # Additional branches!
                            for connector in stamp.connectors:
                                if connector != branch.connector and connector != branch.lastConnector:
                                    self.branches.append(CaveBranch(self.realmSize, self.biomeTracker, stamp, connector, Random.get(self.branchLengthRange[0], self.branchLengthRange[1])))
                                    self.branchesRemaining -= 1
                                    remaining.append(self.branches[-1])
                        self.length += 1
                        break


class CaveBranch(object):
    """ Responsible for tracking a section of a cave. """

    def __init__(self, realmSize, biomeTracker, placedStamp, connector, length):
        self.realmSize = realmSize
        self.biomeTracker = biomeTracker
        # The starting stamp
        self.stamps = [placedStamp]
        # The connector added stamps will need to match up with
        self.connector = connector
        self.lastConnector = None
        self.previousSide = ConnectorSide.get(connector, placedStamp.size)
        self.length = length

    def add(self, stamp):
        '''Attempts to add the provided stamp to this branch. Returns True if successful and false otherwise'''
        # Create a copy of the placed stamp
        placed = PlacedStamp(stamp.stamp)

        # Check all connectors on this stamp and see if this has any matching points
        connectors = list(placed.connectors)
        connector = self._canPlace(placed, connectors)

        if connector:
            self._placeAtConnector(placed, connector)
            return True
        else:
            return False

    def _canPlace(self, placed, connectors):
        for x in xrange(len(placed.connectors)):
            connector = select(connectors)
            if self._canPlaceAtConnector(placed, connector):
                return connector

        return None

    def _canPlaceAtConnector(self, placed, connector):
        side = ConnectorSide.get(connector, placed.size)

        # Give preference to the new stamp moving downwards (if this is not the cap to the branch)
        if len(placed.connectors) != 1:
            hasHigher = False
            for other in placed.connectors:
                # Higher is less than (closer to 0 is higher)
                if connector != other and other.y <= connector.y:
                    hasHigher = True
                    break
            # If it has a higher connector then skip this one (sometimes randomly let it use this one)
            if hasHigher and Random.get(4):
                return False

        # Connector is compatible but how?
        needsFlip = (self.previousSide & side) != 0
        if needsFlip and not placed.stamp.canFlipX():
            return False

        placed.flipX = needsFlip or placed.stamp.forceFlipX()

        self._alignWithPrevious(placed, connector)

        if placed.position.y >= self.realmSize.tileHeight:
            return False

        if overlapsSurface(placed, self.realmSize, self.biomeTracker):
            return False

        return True

    def _alignWithPrevious(self, placed, connector):
        previous = self.stamps[-1]

        # Move stamp to match either its right side being on the left of
        # the previous or left side being on the right of the previous
        if self.previousSide & ConnectorSide.RIGHT:
            placed.position.x = previous.position.x + previous.size.x
        else:
            placed.position.x = previous.position.x - placed.size.x
        placed.position.y = previous.position.y + self.connector.y - connector.y

    def _placeAtConnector(self, placed, connector):
        self.lastConnector = connector
        if len(placed.connectors) != 1:
            for other in placed.connectors:
                if connector != other:
                    self.connector = other
                    break

        self.stamps.append(placed)
        self.previousSide = ConnectorSide.get(self.connector, placed.size)

        if placed.flipX:
            self.previousSide = ConnectorSide.RIGHT if self.previousSide & ConnectorSide.LEFT else ConnectorSide.LEFT


def applyStamps(stamps, rules, game, world, realm, data):
    for placed in stamps:
        Terraform.applySimpleStamp(placed.stamp, placed.position, rules, placed.flipX, realm.layers[Layer.Ground], realm.layers[Layer.Wall], realm.automata)


def overlapsSurface(placed, realmSize, biomeTracker):
    x = placed.position.x
    if x < 0:
        x += realmSize.tileWidth
    elif x >= realmSize.tileWidth:
        x -= realmSize.tileWidth
    return Terraform.overlapsSurface(x, placed.position.y, placed.size.x, biomeTracker.undergroundThreshold, realmSize)
