def __init__(self, name, color, position):
     super(SpellBase, self).__init__(color, position, 20)
     self.__name = name
     self.name_label = SpellLabel(self, name, (0, -self.radius*1.3))
     self.efficiency_label = SpellLabel(self, '{}%'.format(int(self.efficiency*100)))
     self.is_template = False
     self.links_in = list()
     self.links_out = list()
class SpellLumen(SpellBase):

    def __init__(self, position):
        self.color = (255, 255, 255)
        super(SpellLumen, self).__init__('Lumen', self.color, position)
        self.__lumen_label = SpellLabel(self, '0', (0, self.radius * 1.3))
        self.__lumen = 0.0

        # set properties
        self.lumen = 3000

    def create_instance(self):
        return SpellLumen(self.position)

    def calculate_local_power_requirement(self):
        # 1 cd = 1 lumen/sr
        # 1 cd = 683 W/sr
        # 1 lumen/sr = 1/683 W/sr
        # 1 lumen = 1/683 W
        return self.__lumen / 683.0

    def draw(self, surface):
        super(SpellLumen, self).draw(surface)
        if self.is_activated:
            self.__lumen_label.draw(surface)

    @property
    def lumen(self):
        return self.__lumen

    @lumen.setter
    def lumen(self, lumen):
        self.__lumen = lumen
        self.__lumen_label.text = '{} lm'.format(int(self.__lumen))

    @property
    def total_links_in(self):
        return 1

    @property
    def total_links_out(self):
        return 0

    @property
    def efficiency(self):
        # Based on the chemical reaction of luciferase with Adenosine triphosphate (ATP) (what fireflies use),
        # the most efficient energy-to-ligth conversion lies somewhere between 80% and 90%.
        # https://en.wikipedia.org/wiki/Luciferase
        return 0.85

    @property
    def heat_dissipation(self):
        # luciferase reactions create almost no heat whatsoever.
        return 0.01
class Horn(SpellBase):

    def __init__(self, position):
        self.color = (255, 180, 255)
        super(Horn, self).__init__("Horn", self.color, position)
        self.is_draggable = False

        self.__num_supported_links = 2
        self.__power_label = SpellLabel(self, '0 W', (-20, 0))
        self.update_power()

    def draw(self, surface):
        super(Horn, self).draw(surface)
        pygame.draw.circle(surface, self.color, (self.position[0] - 32, self.position[1]), 30, 1)
        pygame.draw.circle(surface, self.color, (self.position[0] - 70, self.position[1]), 40, 1)
        pygame.draw.circle(surface, (0, 0, 0), (self.position[0] - 32, self.position[1]), 29)
        pygame.draw.circle(surface, (0, 0, 0), (self.position[0] - 70, self.position[1]), 39)
        pygame.draw.circle(surface, (0, 0, 0), self.position, 19)
        self.__power_label.draw(surface)

    def create_instance(self):
        raise RuntimeError("Can't create instances of horns!")

    def calculate_local_power_requirement(self):
        return 0.0

    def calculate_total_power_requirement(self):
        power = super(Horn, self).calculate_total_power_requirement()
        return power / self.efficiency

    def update_power(self):
        power = self.calculate_total_power_requirement()
        self.__power_label.text = Utils.metric_scale(power, 'W', decimal_places=1)

    @property
    def total_links_in(self):
        return 0

    @property
    def total_links_out(self):
        return self.__num_supported_links

    @property
    def efficiency(self):
        return 0.90

    @property
    def heat_dissipation(self):
        return 0.05
    def __init__(self, position):
        self.color = (255, 180, 255)
        super(Horn, self).__init__("Horn", self.color, position)
        self.is_draggable = False

        self.__num_supported_links = 2
        self.__power_label = SpellLabel(self, '0 W', (-20, 0))
        self.update_power()
    def __init__(self, position):
        self.color = (255, 255, 255)
        super(SpellLumen, self).__init__('Lumen', self.color, position)
        self.__lumen_label = SpellLabel(self, '0', (0, self.radius * 1.3))
        self.__lumen = 0.0

        # set properties
        self.lumen = 3000
class SpellBase(DraggableCircle):

    def __init__(self, name, color, position):
        super(SpellBase, self).__init__(color, position, 20)
        self.__name = name
        self.name_label = SpellLabel(self, name, (0, -self.radius*1.3))
        self.efficiency_label = SpellLabel(self, '{}%'.format(int(self.efficiency*100)))
        self.is_template = False
        self.links_in = list()
        self.links_out = list()

    def calculate_total_power_requirement(self):
        power = self.calculate_local_power_requirement()
        for link in self.links_out:
            power += link.calculate_total_power_requirement()
        return power / self.efficiency

    def distance_to_squared(self, other_spell):
        dx = self.position[0] - other_spell.position[0]
        dy = self.position[1] - other_spell.position[1]
        return dx**2 + dy**2

    def process_event(self, event):
        super(SpellBase, self).process_event(event)

        if event.type == Event.DRAGGABLECIRCLECLICKED and event.draggable_circle is self:
            self.__notify_spell_clicked()
        if event.type == Event.DRAGGABLECIRCLERELEASED and event.draggable_circle is self:
            self.__notify_spell_released()

    def update(self, time_step):
        super(SpellBase, self).update(time_step)
        self.__keep_spell_on_screen()

    def draw(self, surface):
        super(SpellBase, self).draw(surface)
        self.__draw_spell_links(surface)
        self.name_label.draw(surface)
        self.efficiency_label.draw(surface)

    def __draw_spell_links(self, surface):
        for spell in self.links_out:
            pygame.draw.line(surface, self.color, self.position, spell.position)

    def link_input_to(self, other_spell):
        if not self.is_linkable or not other_spell.is_linkable:
            raise RuntimeError("Spells can't link!")
        if self.free_link_slots_in == 0 or other_spell.free_link_slots_out == 0:
            raise RuntimeError("Spells don't have any free link slots")
        self.links_in.append(other_spell)
        other_spell.links_out.append(self)

    def link_output_to(self, other_spell):
        if not self.is_linkable or not other_spell.is_linkable:
            raise RuntimeError("Spells can't link!")
        if self.free_link_slots_out == 0 or other_spell.free_link_slots_in == 0:
            raise RuntimeError("Spells don't have any free link slots")
        self.links_out.append(other_spell)
        other_spell.links_in.append(self)

    def unlink_local(self):
        for linked in self.links_out:
            linked.links_in.remove(self)

        for linked in self.links_in:
            linked.links_out.remove(self)

        self.links_out = list()
        self.links_in = list()

    def unlink_chain(self):
        self.unlink_chain_out()
        self.unlink_chain_in()

    def unlink_chain_out(self):
        for linked in self.links_out:
            linked.unlink_chain_out()
        self.links_out = list()

    def unlink_chain_in(self):
        for linked in self.links_in:
            linked.unlink_chain()
        self.links_in = list()

    def is_linked_to(self, other_spell):
        return other_spell in self.outward_spells or other_spell in self.inward_spells

    @property
    def is_linkable(self):
        return not self.is_template

    @property
    def outward_spells(self):
        spells = list()
        self.get_outward_spells(spells)
        return spells

    @property
    def inward_spells(self):
        spells = list()
        self.get_inward_spells(spells)
        return spells

    def get_outward_spells(self, spells):
        spells.append(self)
        for linked in self.links_out:
            linked.get_outward_spells(spells)

    def get_inward_spells(self, spells):
        spells.append(self)
        for linked in self.links_in:
            linked.get_inward_spells(spells)

    @property
    def free_link_slots_in(self):
        return self.total_links_in - len(self.links_in)

    @property
    def free_link_slots_out(self):
        return self.total_links_out - len(self.links_out)

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, name):
        self.__name = name
        self.name_label = SpellLabel(self, name)

    @property
    def is_activated(self):
        return not self.is_template and not self.is_dragging

    def __keep_spell_on_screen(self):
        info = pygame.display.Info()
        width, height = info.current_w, info.current_h
        self.position = (min(max(self.position[0], self.radius), width - self.radius),
                         min(max(self.position[1], self.radius), height - self.radius))

    def __notify_spell_clicked(self):
        evt = pygame.event.Event(Event.SPELLCLICKED, spell=self)
        pygame.event.post(evt)

    def __notify_spell_released(self):
        evt = pygame.event.Event(Event.SPELLRELEASED, spell=self)
        pygame.event.post(evt)

    ####################################################################################################################
    # Interface for derived spells
    ####################################################################################################################

    def create_instance(self):
        raise NotImplementedError('Spells must be able to create instances of themselves')
        # return a new instance of this class. The new instance shouldn't be an exact copy of all members, it just needs
        # to have bare initialisation (e.g. set the position and name).
        #
        # something like:
        # return MySpell(self.position)

    def calculate_local_power_requirement(self):
        raise NotImplementedError('Spells must provide their power requirement in Watts')

    @property
    def total_links_in(self):
        raise NotImplementedError('Spells must provide information on how many links they can handle as input')

    @property
    def total_links_out(self):
        raise NotImplementedError('Spells must provide information on how many links they can handle as output')

    @property
    def efficiency(self):
        raise NotImplementedError('Spells must provide an efficiency factor [0..1]')

    @property
    def heat_dissipation(self):
        raise NotImplementedError('Spells must provide a heat dissipation factor [0..1]')
 def name(self, name):
     self.__name = name
     self.name_label = SpellLabel(self, name)