# Copyright (C) 2012  SPARTA, Inc. a Parsons Company
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND SPARTA DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS.  IN NO EVENT SHALL SPARTA BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.

"""
Common classes for reuse in apps.
"""

__version__ = '$Id$'

from django.db import models
from django.core.exceptions import ValidationError

import rpki.resource_set
import rpki.POW


class IPAddressField(models.CharField):
    """
    Field class for rpki.POW.IPAddress, stored as zero-padded
    hexadecimal so lexicographic order is identical to numeric order.
    """

    # Django's CharField type doesn't distinguish between the length
    # of the human readable form and the length of the storage form,
    # so we have to leave room for IPv6 punctuation even though we
    # only store hexadecimal digits and thus will never use the full
    # width of the database field.  Price we pay for portability.
    #
    # Documentation on the distinction between the various conversion
    # methods is fairly opaque, to put it politely, and we have to
    # handle database engines which sometimes return buffers or other
    # classes instead of strings, so the conversions are a bit
    # finicky.  If this goes haywire, your best bet is probably to
    # litter the code with logging.debug() calls and debug by printf.

    def __init__(self, *args, **kwargs):
        kwargs["max_length"] = 40
        super(IPAddressField, self).__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super(IPAddressField, self).deconstruct()
        del kwargs["max_length"]
        return name, path, args, kwargs

    @staticmethod
    def _value_to_ipaddress(value):
        if value is None or isinstance(value, rpki.POW.IPAddress):
            return value
        value = str(value)
        if ":" in value or "." in value:
            return rpki.POW.IPAddress(value)
        else:
            return rpki.POW.IPAddress.fromBytes(value.decode("hex"))

    def from_db_value(self, value, expression, connection, context):
        # Can't use super() here, see Django documentation.
        return self._value_to_ipaddress(value)

    def to_python(self, value):
        return self._value_to_ipaddress(
            super(IPAddressField, self).to_python(value))

    @staticmethod
    def _hex_from_ipaddress(value):
        if isinstance(value, rpki.POW.IPAddress):
            return value.toBytes().encode("hex")
        else:
            return value

    def get_prep_value(self, value):
        return super(IPAddressField, self).get_prep_value(
            self._hex_from_ipaddress(value))

    def get_db_prep_value(self, value, connection, prepared = False):
        return self._hex_from_ipaddress(
            super(IPAddressField, self).get_db_prep_value(value, connection, prepared))


class Prefix(models.Model):
    """Common implementation for models with an IP address range.

    Expects that `range_cls` is set to the appropriate subclass of
    rpki.resource_set.resource_range_ip."""

    def as_resource_range(self):
        """
        Returns the prefix as a rpki.resource_set.resource_range_ip object.
        """

        return self.range_cls(self.prefix_min, self.prefix_max)

    @property
    def prefixlen(self):
        "Returns the prefix length for the prefix in this object."
        return self.as_resource_range().prefixlen()

    def get_prefix_display(self):
        "Return a string representatation of this IP prefix."
        return str(self.as_resource_range())

    def __unicode__(self):
        """This method may be overridden by subclasses.  The default
        implementation calls get_prefix_display(). """

        return self.get_prefix_display()

    class Meta:
        abstract = True

        # default sort order reflects what "sh ip bgp" outputs
        ordering = ('prefix_min',)


class PrefixV4(Prefix):
    "IPv4 Prefix."

    range_cls = rpki.resource_set.resource_range_ipv4

    prefix_min = IPAddressField(db_index=True, null=False)
    prefix_max = IPAddressField(db_index=True, null=False)

    class Meta(Prefix.Meta):
        abstract = True


class PrefixV6(Prefix):
    "IPv6 Prefix."

    range_cls = rpki.resource_set.resource_range_ipv6

    prefix_min = IPAddressField(db_index=True, null=False)
    prefix_max = IPAddressField(db_index=True, null=False)

    class Meta(Prefix.Meta):
        abstract = True


def validate_asn(value):
    if value < 0 or value > 0xFFFFFFFFL:
        raise ValidationError('%s is not valid autonomous sequence number' % value)


class ASN(models.Model):
    """Represents a range of ASNs.

    This model is abstract, and is intended to be reused by applications."""

    min = models.BigIntegerField(null=False, validators=[validate_asn])
    max = models.BigIntegerField(null=False, validators=[validate_asn])

    class Meta:
        abstract = True
        ordering = ('min', 'max')

    def as_resource_range(self):
        return rpki.resource_set.resource_range_as(self.min, self.max)

    def __unicode__(self):
        return u'AS%s' % self.as_resource_range()

# vim:sw=4 ts=8 expandtab