from siege import game
from siege.component import Component, ComponentDefinition
from siege.network import NetworkManager
from siege.util import Uint32Set, GameEvent


class Research(ComponentDefinition):
    def __init__(self):
        ComponentDefinition.__init__(self)
        self.freeze()

    def getType(self):
        return ResearchComponent.TYPE


class ResearchComponent(Component):
    TYPE = "research"
    CID = 0
    VERSION = 1

    def getType(self):
        return ResearchComponent.TYPE

    def __init__(self, definition):
        Component.__init__(self)
        # Mapping between recipe (contentId) and list of materials researched (contentId)
        self.recipes = {}
        # Mapping between material (contentId) and list of recipes been researched for it
        self.materials = {}
        # Mapping between unique materials (contentId) and list of recipes it is available for
        self.uniqueMaterials = {}
        self.onRecipeChange = GameEvent()
        # Pair list (recipeContentId, materialContentId) of changed recipes
        self.changed = []
        self.freeze()

    def setup(self):
        # If on host then setup handler to listen for inventory changes to discover new unique items
        if NetworkManager.isHost():
            entity = self.getParent()
            if entity.has('inventory'):
                entity.inventory.onChange.listen(self.handleItemChanged)

    def researchRecipe(self, recipe, material):
        self.dirty()
        self._associate(recipe, material)
        self.changed.append((recipe, material, False))

    def learnRecipe(self, recipe):
        recipeContent = game.content.get(recipe)
        entity = recipeContent.entity
        if entity.has('craft'):
            self.dirty()
            for material in entity.craft.materials:
                self._associate(recipeContent.id, material.content.id)
                self.changed.append((recipeContent.id, material.content.id, material.content.entity.item.isUnique))

    def handleItemChanged(self, inventory, bagIndex, index, previous, item):
        if item and self.isNewlyDiscoveredUnique(item.content):
            self.discoverUnique(item.content)

    def isNewlyDiscoveredUnique(self, content):
        return content.entity.item.isUnique and content.id not in self.uniqueMaterials

    def discoverUnique(self, content):
        material = content.id
        materials = game.research.materials
        if material not in materials:
            return
        for recipe, level in materials[material]:
            self._associate(recipe, material, isUnique=True)
            self.changed.append((recipe, material, True))

    def hasRecipe(self, recipe):
        if recipe not in self.recipes:
            return False
        return len(self.recipes[recipe]) >= len(game.content.get(recipe).entity.craft.materials)

    def _associate(self, recipe, material, isUnique=False):
        if recipe not in self.recipes:
            self.recipes[recipe] = Uint32Set()
        self.recipes[recipe].add(material)
        materials = self.uniqueMaterials if isUnique else self.materials
        if material not in materials:
            materials[material] = Uint32Set()
        materials[material].add(recipe)
        self.onRecipeChange.invoke(recipe, material)

    def fullDirty(self):
        self.changed = []
        for recipe, materials in self.recipes.iteritems():
            for material in materials:
                content = game.content.get(material)
                self.changed.append((recipe, material, content.entity.item.isUnique))

    def pack(self, stream):
        stream.writeUint16(len(self.changed))
        for recipe, material, isUnique in self.changed:
            stream.writeUint32(recipe)
            stream.writeUint32(material)
            stream.writeBool(isUnique)
        self.changed = []

    def unpack(self, stream):
        count = stream.readUint16()
        for i in xrange(count):
            recipe = stream.readUint32()
            material = stream.readUint32()
            isUnique = stream.readBool()
            self._associate(recipe, material, isUnique)

    def write(self, stream):
        stream.writeUint16(len(self.recipes))
        for recipe, materials in self.recipes.iteritems():
            stream.writeUint32(recipe)
            stream.writeUint8(len(materials))
            for material in materials:
                stream.writeUint32(material)

    def read(self, stream, version):
        recipeCount = stream.readUint16()
        for i in xrange(recipeCount):
            recipe = stream.readUint32()
            materialCount = stream.readUint8()
            for j in xrange(materialCount):
                material = stream.readUint32()
                content = game.content.get(material)
                self._associate(recipe, material, content.entity.item.isUnique)

    @staticmethod
    def register():
        ResearchComponent.CID = game.entity.requestCid(ResearchComponent.TYPE, Research)
