from django.contrib.auth.models import User, Group
from rest_framework import serializers
from api.models import Zone, Subnet, Block, IP
from api.validators import CIDRValidator, IPValidator
from api.allocators import N2Allocator, AllocationError
from ipaddress import IPv4Network
import logging

logger = logging.getLogger(__name__)

class CIDRField(serializers.Field):
    def to_representation(self, obj):
        return str(obj)

    def to_internal_value(self, data):
        if data == '':
            return data
        try:
            ret = IPv4Network(data)
            return ret
        except ValueError as e:
            raise serializers.ValidationError(e)

def getNetmasks(minimum=1):
    r = list()
    for i in range(minimum, 33):
        r.append((i, '/%d' % i))
    return r

class CIDRSerializer(serializers.HyperlinkedModelSerializer):
    cidr = CIDRField(required=False, label='CIDR',
            help_text='leave empty for auto-allocation')
    netmask = serializers.ChoiceField(required=False, write_only=True,
            label='Netmask', help_text='set one for auto-allocation',
            allow_blank=True, choices=getNetmasks())

    validator = CIDRValidator

    # complex validation method for creation
    def validate(self, data):

        if 'cidr' not in data.keys():
            data['cidr'] = ''
        if 'netmask' not in data.keys():
            data['netmask'] = ''

        # error if both cidr & netmask are None
        if data['netmask'] == '' and data['cidr'] == '':
            raise serializers.ValidationError('either cidr or netmask must be defined')
        # the contrary
        if data['netmask'] != '' and data['cidr'] != '':
            raise serializers.ValidationError('cidr and netmask must no be defined both at the same time')

        brothers = self.getBrothers()
        father = self.getFather(data=data)
        gfather = self.getGrandFather(data=data)

        if data['netmask'] != '':
            try:
                allocator = N2Allocator(father=father, gfather=gfather,
                        brothers=brothers, netmask=data['netmask'])
                data['cidr'] = allocator.allocate()
            except AllocationError as e:
                raise serializers.ValidationError('cant allocate a CIDR with this netmask')
            except ValueError as ve:
                raise serializers.ValidationError(ve)

        value=data['cidr']
        cval = self.validator(value=value, brothers=brothers, parent=father)
        data['cidr'] = cval.normalize_value()

        if self.instance != None:
            if str(data['cidr']) == str(self.instance.cidr):
                # update and normalized cidr has not changed, so validate
                return data

        # else validate the cidr
        if cval.validate():
            return data

class CIDRUpdateSerializer(serializers.HyperlinkedModelSerializer):
    cidr = serializers.ReadOnlyField()
    owner = serializers.ReadOnlyField(source='owner.username')

    # simple validation method for update
    def validate(self, data):
        return data

    # abstract methods used for validate() complicated
    def getBrothers(self):
        raise NotImplementedError('please implement getBrothers()')

    def getFather(self, data):
        raise NotImplementedError('please implement getFather(data)')

    def getGrandFather(self, data):
        return None

class ZoneUpdateSerializer(CIDRUpdateSerializer):
    subnets = serializers.HyperlinkedRelatedField(many=True, read_only=True,
            view_name='subnet-detail')

    def getBrothers(self):
        brothers = list()
        for b in Zone.objects.all():
            brothers.append(b.cidr)
        return brothers

    def getFather(self, data):
        return '0.0.0.0/0'

    class Meta:
        model = Zone
        fields = ('id', 'created', 'modified', 'url', 'name', 'description', 'cidr',
                'owner', 'subnets')

class ZoneSerializer(ZoneUpdateSerializer, CIDRSerializer):
    def validate(self, data):
        return super(CIDRUpdateSerializer, self).validate(data)

    class Meta:
        model = Zone
        fields = ('id', 'created', 'modified', 'url', 'name', 'description', 'cidr',
                'owner', 'subnets', 'netmask')

class SubnetUpdateSerializer(CIDRUpdateSerializer):
    blocks = serializers.HyperlinkedRelatedField(many=True, read_only=True,
            view_name='block-detail')

    def getBrothers(self):
        brothers = list()
        for b in Subnet.objects.select_related('zone'):
            brothers.append(b.cidr)
        return brothers

    def getFather(self, data):
        return data['zone'].cidr

    class Meta:
        model = Subnet
        fields = ('id', 'created', 'modified', 'url', 'name', 'description', 'cidr',
                'owner', 'zone', 'blocks')

class SubnetSerializer(SubnetUpdateSerializer, CIDRSerializer):
    def validate(self, data):
        return super(CIDRUpdateSerializer, self).validate(data)

    class Meta:
        model = Subnet
        fields = ('id', 'created', 'modified', 'url', 'name', 'description', 'cidr',
                'owner', 'zone', 'blocks', 'netmask')

class BlockUpdateSerializer(CIDRUpdateSerializer):
    ips = serializers.HyperlinkedRelatedField(many=True, read_only=True,
            view_name='ip-detail')

    def getBrothers(self):
        brothers = list()
        for b in Block.objects.select_related('subnet'):
            brothers.append(b.cidr)
        return brothers

    def getFather(self, data):
        return data['subnet'].cidr

    class Meta:
        model = Block
        fields = ('id', 'created', 'modified', 'url', 'name', 'description', 'cidr',
                'owner', 'subnet', 'ips')

class BlockSerializer(BlockUpdateSerializer, CIDRSerializer):
    def validate(self, data):
        return super(CIDRUpdateSerializer, self).validate(data)

    class Meta:
        model = Block
        fields = ('id', 'created', 'modified', 'url', 'name', 'description', 'cidr',
                'owner', 'subnet', 'ips', 'netmask')

class IPUpdateSerializer(CIDRUpdateSerializer):
    def getBrothers(self):
        brothers = list()
        for b in IP.objects.select_related('block'):
            brothers.append(b.cidr)
        return brothers

    def getFather(self, data):
        return data['block'].cidr

    def getGrandFather(self, data):
        return data['block'].subnet.cidr

    class Meta:
        model = IP
        fields = ('id', 'created', 'modified', 'url', 'name', 'description', 'cidr',
            'owner', 'block')

class IPSerializer(IPUpdateSerializer, CIDRSerializer):
    validator = IPValidator

    netmask = serializers.ChoiceField(required=False, write_only=True,
            label='Netmask', help_text='set /32 for auto-allocation',
            allow_blank=True, choices=getNetmasks(minimum=32))

    def validate(self, data):
        return super(CIDRUpdateSerializer, self).validate(data)

    class Meta:
        model = IP
        fields = ('id', 'created', 'modified', 'url', 'name', 'description', 'cidr',
            'owner', 'block', 'netmask')
