# -*- coding: utf-8 -*-
from django.db import models
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator
from netfields import CidrAddressField, NetManager
from netaddr import IPNetwork, IPSet

class IPPool(models.Model):
    """Pool of IP addresses (either v4 or v6)."""
    name = models.CharField(max_length=255, blank=False, null=False,
                            verbose_name='Name of the IP pool')
    default_subnetsize = models.PositiveSmallIntegerField(blank=False,
                                                          verbose_name='Default subnet size to allocate to subscribers in this pool',
                                                          validators=[MaxValueValidator(64)])
    inet = CidrAddressField()
    objects = NetManager()

    def clean(self):
        if self.inet is not None and self.inet.version == 4:
            if not self.inet.prefixlen <= self.default_subnetsize <= 32:
                raise ValidationError('Invalid default subnet size')
        elif self.inet is not None and self.inet.version == 6:
            if not self.inet.prefixlen <= self.default_subnetsize <= 64:
                raise ValidationError('Invalid default subnet size')

    def __unicode__(self):
        return self.name


class IPSubnet(models.Model):
    inet = CidrAddressField(blank=True, verbose_name="Leave empty for automatic allocation")
    objects = NetManager()
    ip_pool = models.ForeignKey(IPPool)
    offer_subscription = models.ForeignKey('offers.OfferSubscription',
                                           related_name='ip_subnet')

    def clean(self):
        if not self.inet:
            # Automatically allocate a free subnet
            pool = IPSet([self.ip_pool.inet])
            used = IPSet((s.inet for s in self.ip_pool.ipsubnet_set.all()))
            free = pool.difference(used)
            # Generator for efficiency (we don't build the whole list)
            available = (p for p in free.iter_cidrs() if p.prefixlen <= self.ip_pool.default_subnetsize)
            # TODO: for IPv4, get rid of the network and broadcast
            # addresses? Not really needed nowadays, and we usually don't
            # have a real subnet in practice (i.e. Ethernet segment), but
            # many /32.
            try:
                first_free = available.next()
            except StopIteration:
                raise ValidationError('Unable to allocate an IP subnet in the specified pool: not enough space left.')
            self.inet = first_free.subnet(SUBNET_SIZE, 1).next()
        else:
            # TODO:
            # Check that we are included in the IP pool.
            # Check that we don't conflict with existing subnets
            pass

    def __unicode__(self):
        return str(self.inet)
