class MilitaryStance(models.Model):
    class Meta:
        unique_together = (("from_organization", "to_organization",
                            "region"), )

    DEFAULT = 'default'
    AVOID_BATTLE = 'avoid battle'
    DEFENSIVE = 'defensive'
    AGGRESSIVE = 'aggressive'

    STANCE_CHOICES = (
        (DEFAULT, "automatic by diplomatic relationship"),
        (AVOID_BATTLE, AVOID_BATTLE),
        (DEFENSIVE, DEFENSIVE),
        (AGGRESSIVE, AGGRESSIVE),
    )

    from_organization = models.ForeignKey(Organization,
                                          related_name='mil_stances_stemming')
    to_organization = models.ForeignKey(Organization,
                                        related_name='mil_stances_receiving')
    region = models.ForeignKey('world.Tile',
                               related_name='+',
                               null=True,
                               blank=True)
    stance_type = models.CharField(max_length=20,
                                   choices=STANCE_CHOICES,
                                   default=DEFAULT)

    def get_stance(self):
        if self.stance_type != MilitaryStance.DEFAULT:
            return self.stance_type
        if self.region:
            return self.from_organization.get_default_stance_to(
                self.to_organization).get_stance()
        else:
            return self.from_organization.get_relationship_to(
                self.to_organization).default_military_stance()

    def get_html_stance(self):
        stance = self.get_stance()
        if stance == MilitaryStance.DEFENSIVE:
            bootstrap_type = 'primary'
        elif stance == MilitaryStance.AGGRESSIVE:
            bootstrap_type = 'danger'
        elif stance == MilitaryStance.AVOID_BATTLE:
            bootstrap_type = 'info'
        else:
            bootstrap_type = 'default'

        return '<span class="label label-{bootstrap_type}">{stance}</span>'.format(
            bootstrap_type=bootstrap_type, stance=stance)
class CapabilityVote(models.Model):
    YEA = 'yea'
    NAY = 'nay'
    INVALID = 'invalid'
    VOTE_CHOICES = (
        (YEA, YEA),
        (NAY, NAY),
        (INVALID, INVALID),
    )

    proposal = models.ForeignKey(CapabilityProposal)
    voter = models.ForeignKey('world.Character')
    vote = models.CharField(max_length=10, choices=VOTE_CHOICES)
class OrganizationRelationship(models.Model):
    class Meta:
        unique_together = (("from_organization", "to_organization"), )

    PEACE = 'peace'
    WAR = 'war'
    BANNED = 'banned'
    FRIENDSHIP = 'friendship'
    DEFENSIVE_ALLIANCE = 'defensive alliance'
    ALLIANCE = 'alliance'
    RELATIONSHIP_CHOICES = (
        (PEACE, PEACE),
        (WAR, WAR),
        (BANNED, BANNED),
        (FRIENDSHIP, FRIENDSHIP),
        (DEFENSIVE_ALLIANCE, DEFENSIVE_ALLIANCE),
        (ALLIANCE, ALLIANCE),
    )

    RELATIONSHIP_LEVEL = {
        WAR: 0,
        BANNED: 0,
        PEACE: 1,
        FRIENDSHIP: 2,
        DEFENSIVE_ALLIANCE: 3,
        ALLIANCE: 5
    }

    from_organization = models.ForeignKey(
        Organization, related_name='relationships_stemming')
    to_organization = models.ForeignKey(Organization,
                                        related_name='relationships_receiving')
    relationship = models.CharField(max_length=20,
                                    choices=RELATIONSHIP_CHOICES,
                                    default=PEACE)
    desired_relationship = models.CharField(max_length=20,
                                            choices=RELATIONSHIP_CHOICES,
                                            blank=True,
                                            null=True)

    def reverse_relation(self):
        return self.to_organization.get_relationship_to(self.from_organization)

    @staticmethod
    def _get_badge_type(relationship):
        if relationship in (OrganizationRelationship.WAR,
                            OrganizationRelationship.BANNED):
            return 'danger'
        elif relationship == OrganizationRelationship.FRIENDSHIP:
            return 'success'
        elif relationship in (OrganizationRelationship.DEFENSIVE_ALLIANCE,
                              OrganizationRelationship.ALLIANCE):
            return 'info'
        else:
            return 'default'

    @staticmethod
    def _format_relationship(relationship, relationship_name):
        template = '<span class="label label-{badge_type}">{name}</span>'
        return template.format(
            name=relationship_name.capitalize(),
            badge_type=OrganizationRelationship._get_badge_type(relationship))

    def get_relationship_html(self):
        return OrganizationRelationship._format_relationship(
            self.relationship, self.get_relationship_display())

    def get_desired_relationship_html(self):
        if self.desired_relationship is None:
            return OrganizationRelationship._format_relationship(
                'default', 'None')
        else:
            return OrganizationRelationship._format_relationship(
                self.desired_relationship,
                self.get_desired_relationship_display())

    def is_proposal(self):
        return self.desired_relationship and self.desired_relationship != self.relationship

    @transaction.atomic
    def desire(self, target_relationship):
        if target_relationship == self.WAR:
            self.to_organization.world.broadcast("{} declared war on {}!",
                                                 "diplomacy")

        if self.RELATIONSHIP_LEVEL[
                target_relationship] < self.RELATIONSHIP_LEVEL[
                    self.relationship]:
            self.set_relationship(target_relationship)
        else:
            self.desired_relationship = target_relationship
            self.save()

    @transaction.atomic
    def set_relationship(self, target_relationship):
        self.relationship = target_relationship
        self.desired_relationship = None
        self.save()
        reverse_relation = self.reverse_relation()
        reverse_relation.relationship = target_relationship
        reverse_relation.desired_relationship = None
        reverse_relation.save()

        if target_relationship != self.WAR:
            self.to_organization.world.broadcast(
                "{} and {} now have the following diplomatic relationship: {}"
                "".format(self.from_organization, self.to_organization,
                          self.relationship), "diplomacy")

    def default_military_stance(self):
        if self.relationship in (OrganizationRelationship.PEACE, ):
            return MilitaryStance.DEFENSIVE
        if self.relationship in (OrganizationRelationship.WAR,
                                 OrganizationRelationship.BANNED):
            return MilitaryStance.AGGRESSIVE
        return MilitaryStance.AVOID_BATTLE

    def __str__(self):
        return "Relationship {} to {}".format(self.from_organization,
                                              self.to_organization)
class Capability(models.Model):
    BAN = 'ban'
    POLICY_DOCUMENT = 'policy'
    CONSCRIPT = 'conscript'
    DIPLOMACY = 'diplomacy'
    MANAGE_SUBORGANIZATIONS = 'suborganizations'
    MEMBERSHIPS = 'memberships'
    HEIR = 'heir'
    ELECT = 'elect'
    CANDIDACY = 'candidacy'
    CONVOKE_ELECTIONS = 'convoke elections'
    MILITARY_STANCE = 'military stance'
    BATTLE_FORMATION = 'battle formation'
    CONQUEST = 'occupy region'
    TAKE_GRAIN = 'take grain'
    GUILDS = 'manage guilds'
    TAXES = 'manage taxation'

    TYPE_CHOICES = (
        (BAN, 'ban'),
        (POLICY_DOCUMENT, 'write policy and law'),
        (DIPLOMACY, 'conduct diplomacy'),
        (CONSCRIPT, 'conscript troops'),
        (MANAGE_SUBORGANIZATIONS, 'manage subordinate organizations'),
        (MEMBERSHIPS, 'manage memberships'),
        (HEIR, 'set heir'),
        (ELECT, 'elect'),
        (CANDIDACY, 'present candidacy'),
        (CONVOKE_ELECTIONS, 'convoke elections'),
        (MILITARY_STANCE, 'military orders'),
        (BATTLE_FORMATION, 'battle formation'),
        (CONQUEST, 'occupy region'),
        (TAKE_GRAIN, 'take grain'),
        (GUILDS, 'manage guilds'),
        (TAXES, 'manage taxation'),
    )

    organization = models.ForeignKey(Organization)
    type = models.CharField(max_length=20, choices=TYPE_CHOICES)
    applying_to = models.ForeignKey(Organization,
                                    related_name='capabilities_to_this')
    stemming_from = models.ForeignKey('Capability',
                                      null=True,
                                      blank=True,
                                      related_name='transfers')

    def get_absolute_url(self):
        return reverse('organization:capability',
                       kwargs={'capability_id': self.id})

    def create_proposal(self, character, proposal_dict):
        voted_proposal = self.organization.is_position or self.organization.decision_taking == Organization.DISTRIBUTED
        proposal = CapabilityProposal.objects.create(
            proposing_character=character,
            capability=self,
            proposal_json=json.dumps(proposal_dict),
            vote_end_turn=self.organization.world.current_turn + 2,
            democratic=voted_proposal)
        if voted_proposal:
            proposal.execute()
        else:
            proposal.announce_proposal()
            proposal.issue_vote(character, CapabilityVote.YEA)

    def is_passive(self):
        return self.type in (
            self.CONSCRIPT,
            self.TAKE_GRAIN,
        )

    def is_individual_action(self):
        return self.is_passive() or self.type in (self.ELECT, self.CANDIDACY)

    def __str__(self):
        return "{} can {} in {}{}".format(
            self.organization, self.get_type_display(), self.applying_to,
            "" if self.stemming_from is None else " (delegated by {})".format(
                self.stemming_from))
class Organization(models.Model):
    DEMOCRATIC = 'democratic'  # decisions are voted among members
    DISTRIBUTED = 'distributed'  # decisions can be taken by each member
    DECISION_TAKING_CHOICES = (
        (DEMOCRATIC, DEMOCRATIC),
        (DISTRIBUTED, DISTRIBUTED),
    )

    CHARACTER = 'character'
    ORGANIZATION = 'organization'
    MEMBERSHIP_TYPE_CHOICES = (
        (CHARACTER, CHARACTER),
        (ORGANIZATION, ORGANIZATION),
    )

    INHERITED = 'inherited'
    ELECTED = 'elected'
    POSITION_TYPE_CHOICES = (
        (INHERITED, INHERITED),
        (ELECTED, ELECTED),
    )

    world = models.ForeignKey('world.World')
    name = models.CharField(max_length=100)
    color = models.CharField(max_length=6,
                             default="FFFFFF",
                             help_text="Format: RRGGBB (hex)")
    barbaric = models.BooleanField(default=False)
    description = models.TextField()
    is_position = models.BooleanField()
    position_type = models.CharField(max_length=15,
                                     choices=POSITION_TYPE_CHOICES,
                                     blank=True,
                                     default='')
    owner = models.ForeignKey('Organization',
                              null=True,
                              blank=True,
                              related_name='owned_organizations')
    leader = models.ForeignKey('Organization',
                               null=True,
                               blank=True,
                               related_name='leaded_organizations')
    owner_and_leader_locked = models.BooleanField(
        help_text="If set, this organization will have always the same "
        "leader as it's owner.")
    violence_monopoly = models.BooleanField(default=False)
    decision_taking = models.CharField(max_length=15,
                                       choices=DECISION_TAKING_CHOICES)
    membership_type = models.CharField(max_length=15,
                                       choices=MEMBERSHIP_TYPE_CHOICES)
    character_members = models.ManyToManyField('world.Character', blank=True)
    organization_members = models.ManyToManyField('Organization', blank=True)
    election_period_months = models.IntegerField(default=0)
    current_election = models.ForeignKey('PositionElection',
                                         blank=True,
                                         null=True,
                                         related_name='+')
    last_election = models.ForeignKey('PositionElection',
                                      blank=True,
                                      null=True,
                                      related_name='+')
    heir_first = models.ForeignKey('world.Character',
                                   blank=True,
                                   null=True,
                                   related_name='first_heir_to')
    heir_second = models.ForeignKey('world.Character',
                                    blank=True,
                                    null=True,
                                    related_name='second_heir_to')
    tax_countdown = models.SmallIntegerField(default=0)

    def remove_member(self, member):
        if member not in self.character_members.all():
            raise Exception("{} is not a member of {}".format(member, self))
        self.character_members.remove(member)

        message_content = "{} left {}.".format(member, self)

        if self.leader and member in \
                self.leader.character_members.all():
            self.leader.remove_member(member)

        if member.get_violence_monopoly() is None:
            member.world.get_barbaric_state().character_members.add(member)

        if self.is_position:
            if (self.heir_first
                    and self.heir_first in self.get_heir_candidates()):
                self.character_members.add(self.heir_first)
                self.heir_first = self.heir_second = None
                self.save()
                message_content += " {} is the new {}.".format(
                    self.get_position_occupier(), self)
            elif (self.heir_second
                  and self.heir_second in self.get_heir_candidates()):
                self.character_members.add(self.heir_second)
                self.heir_first = self.heir_second = None
                self.save()
                message_content += " {} is the new {}.".format(
                    self.get_position_occupier(), self)
            elif self.position_type == self.ELECTED:
                self.convoke_elections()

        if self.leader and self.leader.character_is_member(member):
            self.leader.remove_member(member)

        message = shortcuts.create_message(message_content,
                                           self.world,
                                           'leaving',
                                           link=self.get_absolute_url())
        shortcuts.add_organization_recipient(message, self)
        for org in self.leaded_organizations.all():
            shortcuts.add_organization_recipient(message, org)

    def get_descendants_list(self, including_self=False):
        descendants = list()
        if including_self:
            descendants.append(self)
        for child in self.owned_organizations.all():
            descendants += child.get_descendants_list(True)
        return descendants

    def get_membership_including_descendants(self):
        members = set(self.character_members.all())
        for child in self.owned_organizations.all():
            members |= child.get_membership_including_descendants()
        return members

    def character_can_use_capabilities(self, character):
        if character in self.character_members.all():
            return True

    def organizations_character_can_apply_capabilities_to_this_with(
            self, character, capability_type):
        result = []
        capabilities = Capability.objects.filter(applying_to=self,
                                                 type=capability_type)
        for capability in capabilities:
            if capability.organization.character_can_use_capabilities(
                    character):
                result.append(capability.organization)
        return result

    def character_is_member(self, character):
        return character in self.character_members.all()

    def get_violence_monopoly(self):
        if self.violence_monopoly:
            return self
        try:
            return self.leaded_organizations.get(violence_monopoly=True)
        except Organization.DoesNotExist:
            pass
        if self.owner:
            return self.owner.get_violence_monopoly()
        return None

    def conquestable_tiles(self):
        if not self.violence_monopoly:
            return None
        candidate_tiles = world.models.Tile.objects \
            .filter(world=self.world) \
            .exclude(controlled_by=self) \
            .exclude(type__in=(world.models.Tile.SHORE,
                               world.models.Tile.DEEPSEA))
        result = []
        for tile in candidate_tiles:
            conquest_tile_event = tile.tileevent_set.filter(
                organization=self,
                type=world.models.TileEvent.CONQUEST,
                end_turn__isnull=True)
            conquering_units = tile.get_units()\
                .filter(owner_character__in=self.character_members.all())\
                .exclude(status=world.models.WorldUnit.NOT_MOBILIZED)
            if (conquering_units.exists()
                    and not conquest_tile_event.exists()):
                result.append(tile)
        return result

    def get_open_proposals(self):
        return CapabilityProposal.objects.filter(capability__organization=self,
                                                 closed=False)

    def get_all_controlled_tiles(self):
        return world.models.Tile.objects.filter(
            controlled_by__in=self.get_descendants_list(including_self=True))

    def external_capabilities_to_this(self):
        return self.capabilities_to_this.exclude(organization=self)

    def get_position_occupier(self):
        if not self.is_position or not self.character_members.exists():
            return None
        return list(self.character_members.all())[0]

    def get_relationship_to(self, organization):
        return OrganizationRelationship.objects.get_or_create(
            defaults={
                'relationship':
                (OrganizationRelationship.WAR if organization.barbaric
                 or self.barbaric else OrganizationRelationship.PEACE)
            },
            from_organization=self,
            to_organization=organization)[0]

    def get_relationship_from(self, organization):
        return organization.get_relationship_to(self)

    def get_default_stance_to(self, state):
        return MilitaryStance.objects.get_or_create(from_organization=self,
                                                    to_organization=state,
                                                    region=None)[0]

    def get_region_stances_to(self, state):
        return MilitaryStance.objects.filter(
            from_organization=self,
            to_organization=state,
        ).exclude(region=None)

    def get_region_stance_to(self, state, region):
        return MilitaryStance.objects.get_or_create(from_organization=self,
                                                    to_organization=state,
                                                    region=region)[0]

    def get_default_formation_settings(self):
        try:
            return BattleFormation.objects.get(organization=self, battle=None)
        except BattleFormation.DoesNotExist:
            return BattleFormation.objects.create(
                organization=self,
                battle=None,
                formation=BattleFormation.LINE,
                element_size=2,
                spacing=2,
            )

    @transaction.atomic
    def convoke_elections(self, months_to_election=6):
        if not self.is_position:
            raise Exception("Elections only work for positions")
        election = PositionElection.objects.create(
            position=self, turn=self.world.current_turn + months_to_election)
        self.current_election = election
        self.save()
        if self.get_position_occupier() is not None:
            PositionCandidacy.objects.create(
                election=election,
                candidate=self.get_position_occupier(),
                description="Auto-generated candidacy for incumbent character."
            )

        message = shortcuts.create_message(
            "Elections have been convoked for the position {}. "
            "They will take place in {} months.".format(
                self, months_to_election),
            self.world,
            'elections',
            link=election.get_absolute_url())
        shortcuts.add_organization_recipient(message, self)
        for org in self.leaded_organizations.all():
            shortcuts.add_organization_recipient(message, org)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('organization:view',
                       kwargs={'organization_id': self.id})

    def get_html_name(self):
        template = '{name}{icon}{suffix}'
        icon = self.get_bootstrap_icon()

        occupier = self.get_position_occupier()
        if occupier:
            suffix = '<small>{}</small>'.format(occupier.name)
        else:
            suffix = ''

        return template.format(name=escape(self.name),
                               icon=icon,
                               suffix=suffix)

    def get_bootstrap_icon(self):
        template = '<span style="color: #{color}" ' \
                   'class="glyphicon glyphicon-{icon}" ' \
                   'aria-hidden="true"></span>'
        if self.violence_monopoly and not self.barbaric:
            icon = "tower"
        elif self.violence_monopoly and self.barbaric:
            icon = "fire"
        elif self.leaded_organizations.filter(violence_monopoly=True).exists():
            icon = "king"
        elif self.get_violence_monopoly():
            icon = "knight"
        elif self.leaded_organizations.exists():
            icon = "menu-up"
        elif not self.owner:
            icon = "triangle-top"
        else:
            icon = "option-vertical"
        return template.format(
            icon=icon,
            color=escape(self.color),
        )

    def get_html_link(self):
        return '<a href="{}">{}</a>'.format(self.get_absolute_url(),
                                            self.get_html_name())

    def current_elections_can_vote_in(self):
        result = []
        elect_capabilities = Capability.objects.filter(type=Capability.ELECT,
                                                       organization=self)
        for capability in elect_capabilities:
            if capability.applying_to.current_election is not None:
                result.append(capability)
        return result

    def get_heir_candidates(self):
        # TODO this works only for violence monopolies
        return self.get_violence_monopoly().\
            get_membership_including_descendants()