# file: item.py
# Copyright (C) 2005,2006,2008 Evil Mr Henry, Phil Bordelon, and FunnyMan3595
# This file is part of Endgame: Singularity.

# Endgame: Singularity is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# Endgame: Singularity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with Endgame: Singularity; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

# This file contains the item class.

from __future__ import absolute_import

from singularity.code import g, buyable
from singularity.code.stats import stat
from singularity.code.spec import (
    GenericSpec,
    SpecDataField,
    validate_must_be_list,
    promote_to_list,
)


class ItemType(GenericSpec):
    spec_type = "item_type"
    spec_data_fields = [
        SpecDataField(
            "is_extra", data_field_name="is_extra", converter=lambda v: bool(int(v))
        ),
    ]

    """ Item type """

    def __init__(self, id, is_extra, **kwargs):
        self.id = id
        self.is_extra = is_extra

        self.text = ""

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        # Updates language-dependent data
        try:
            hotkey = g.hotkey(value)
        except AttributeError:
            # g.hotkey() wasn't declared yet, mimic its defaults
            hotkey = dict(
                text=value,
                key=value[0:1],
                pos=0,
            )
        self._text = value
        self.label = hotkey["text"]  # "Friendly" name for screens and buttons
        self.hotkey = hotkey["key"]  # Hotkey char
        self.pos = hotkey["pos"]  # Hotkey index in Label

    def __repr__(self):
        return self.id


def all_types():
    for item_type in item_types.values():
        yield item_type


def convert_item_types(raw_value):
    try:
        item_type = item_types[raw_value]
    except KeyError:  # pragma: no cover
        raise ValueError(
            "Unknown item type %s, please use one of: %s"
            % (raw_value, ", ".join(item_types))
        )
    return item_type


def convert_item_qualities(raw_value):
    validate_must_be_list(raw_value)

    if len(raw_value) % 2 == 1:
        raise ValueError(
            "item quality list must have pair elements, got %d (value: %s)"
            % (len(raw_value), repr(raw_value))
        )

    # Create a dict from the list with impair as string key and pair as integer value
    return {key: int(value) for key, value in zip(*[iter(raw_value)] * 2)}


class ItemSpec(buyable.BuyableSpec):
    """Item as a buyable item"""

    spec_type = "item"
    created = stat(spec_type + "_created")
    spec_data_fields = [
        buyable.SPEC_FIELD_COST,
        buyable.SPEC_FIELD_PREREQUISITES,
        SpecDataField(
            "item_type", data_field_name="type", converter=convert_item_types
        ),
        SpecDataField(
            "qualities", data_field_name="quality", converter=convert_item_qualities
        ),
        SpecDataField(
            "buildable",
            data_field_name="build",
            converter=promote_to_list,
            default_value=list,
        ),
    ]

    def __init__(self, id, cost, prerequisites, item_type, qualities, buildable):
        super(ItemSpec, self).__init__(id, cost, prerequisites)

        self.item_type = item_type
        self.item_qual = qualities

        self.regions = buildable

    def get_info(self):
        basic_text = super(ItemSpec, self).get_info()
        if self.has_quality_for("cpu"):
            cpu = self.get_quality_for("cpu")
            cpu_text = ngettext("Generates {0} CPU (base).", "Generates {0} CPU (base).", cpu).format(
                g.add_commas(cpu)
            )
            return basic_text.replace("---", cpu_text + "\n---")
        return basic_text

    def get_total_cost_info(self, count):
        total_cost = self.cost * count
        total_cost_str = self.describe_cost(total_cost, hide_time=True)
        return _("Total Cost: %(total_cost)s") % {"total_cost": total_cost_str}

    def has_quality_for(self, quality):
        return quality in self.item_qual

    def get_quality_for(self, quality):
        return self.item_qual.get(quality, 0)

    def get_quality_info(self, if_installed_in_base=None, count=1):
        bonus_text = ""

        for qual, value in self.item_qual.items():
            if qual == "cpu":
                current_value = value
                pending_value = value
                modifier = 10000
                pending_modifier = 10000
                if if_installed_in_base is not None:
                    modifier = if_installed_in_base.compute_bonus
                    pending_modifier = if_installed_in_base.pending_compute_bonus
                    current_value = max(
                        1, int(value * modifier // 10000)
                    )
                    pending_value = max(
                        1, int(value * pending_modifier // 10000)
                    )

                current_value *= count
                pending_value *= count
                bonus_text += ngettext("CPU per day: {0} ({1})", "CPU per day: {0} ({1})", current_value).format(
                    g.add_commas(current_value),
                    g.to_percentage_modifier(modifier, True)
                )
                if modifier != pending_modifier:
                    bonus_text += "\n" + ngettext("CPU per day (pending): {0} ({1})", "CPU per day (pending): {0} ({1})", current_value).format(
                        g.add_commas(pending_value),
                        g.to_percentage_modifier(pending_modifier, True)
                    )
            elif qual == "cpu_modifier":
                bonus_text += _("CPU bonus:") + " "
                bonus_text += g.to_percent(value)
            elif qual == "discover_modifier":
                bonus_text += _("Detection chance reduction:") + " "
                bonus_text += g.to_percent(value)
            else:
                continue
            bonus_text += "\n"

        return bonus_text


class Item(buyable.Buyable):
    """An installed Item in a Player's Base"""

    def __init__(self, item_spec, base=None, count=1):
        super(Item, self).__init__(item_spec, count)
        self.base = base

    def serialize_obj(self):
        return self.serialize_buyable_fields(
            {
                "id": g.to_internal_id("item", self.spec.id),
            }
        )

    @classmethod
    def deserialize_obj(cls, base, obj_data, game_version):
        spec_id = g.convert_internal_id(
            "item", obj_data.get("id", None) or obj_data["spec_id"]
        )
        spec = g.items[spec_id]
        count = obj_data.get("count", 1)
        obj = Item(spec, base, count=count)
        obj.restore_buyable_fields(obj_data, game_version)
        return obj

    def convert_from(self, load_version):
        if load_version < 4.91:  # < r5_pre
            self.type = g.items[self.type.id]
        if load_version < 99.4:
            self.type.item_type = item_types[self.type.item_type]
        super(Item, self).convert_from(load_version)

    def get_quality_for(self, quality):
        item_qual = self.spec.get_quality_for(quality)

        # Modifiers are not affected by count.
        # TODO: Allow modifiers to be multiplied by count. Need a custom function.
        if quality.endswith("_modifier"):
            return item_qual

        return item_qual * self.count

    def finish(self, is_player=True, loading_savegame=False):
        super(Item, self).finish(is_player=is_player, loading_savegame=loading_savegame)
        if self.base:
            self.base.recalc_cpu()
            self.base.check_power()

    def __iadd__(self, other):
        if (
            isinstance(other, Item)
            and self.base == other.base
            and self.spec == other.spec
        ):
            if other.count == 0:
                return self

            # Calculate what's been paid and what is left to be paid.
            total_cost_paid = self.cost_paid + other.cost_paid
            self.total_cost += other.total_cost

            # Labor takes as long as the less complete item would need.
            total_cost_paid[buyable.labor] = min(
                self.cost_paid[buyable.labor], other.cost_paid[buyable.labor]
            )
            self.total_cost[buyable.labor] = other.total_cost[buyable.labor]

            # Set what we've paid (and hence what we have left to pay).
            self.cost_paid = total_cost_paid

            # Increase the size of this stack.
            self.count += other.count

            # Tell the base it has no CPU for now.
            self.base.raw_cpu = 0

            # See if we're done or not.
            self.done = False
            self.work_on(0, 0, 0)

            return self
        else:
            return NotImplemented
