from __future__ import absolute_import
from __future__ import division

import base64
import uuid

import six

from vdsm.common.password import ProtectedPassword


class Property(object):

    def __init__(self, required=False, default=None, doc=None):
        self.name = None
        self.required = required
        self.default = default
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
        return obj.__dict__.get(self.name, self.default)

    def __set__(self, obj, value):
        if self.required and value is None:
            raise ValueError("Property %s is required" % self.name)
        if value is not None:
            value = self.validate(value)
        obj.__dict__[self.name] = value

    def check(self, obj):
        if self.required and obj.__dict__.get(self.name) is None:
            raise ValueError("Property %s is required" % self.name)

    def validate(self, value):
        return value


class Enum(Property):

    def __init__(self, required=False, default=None, doc=None, values=()):
        if not required and default not in values:
            raise ValueError("Default value %s not in allowed values %s" %
                             (default, values))
        super(Enum, self).__init__(required=required, default=default, doc=doc)
        self.values = values

    def validate(self, value):
        if value not in self.values:
            raise ValueError("Invalid value %r for property %s" %
                             (value, self.name))
        return value


class String(Property):

    def validate(self, value):
        if not isinstance(value, six.string_types):
            raise ValueError("Invalid value %r for string property %s" %
                             (value, self.name))
        return value


class _Number(Property):

    def __init__(self, required=False, default=None, doc=None, minval=None,
                 maxval=None):
        if minval is not None and default is not None and default < minval:
            raise ValueError("Invalid default %s < %s" % (default, minval))
        if maxval is not None and default is not None and default > maxval:
            raise ValueError("Invalid default %s > %s" % (default, maxval))
        super(_Number, self).__init__(required=required, default=default,
                                      doc=doc)
        self.minval = minval
        self.maxval = maxval

    def validate(self, value):
        if self.minval is not None and value < self.minval:
            raise ValueError("Invalid value %s < %s for property %s" %
                             (value, self.minval, self.name))
        if self.maxval is not None and value > self.maxval:
            raise ValueError("Invalid value %s > %s for property %s" %
                             (value, self.maxval, self.name))
        return value


class Integer(_Number):

    def validate(self, value):
        if not isinstance(value, int):
            raise ValueError("Invalid value %r for integer property %s" %
                             (value, self.name))
        return super(Integer, self).validate(value)


class Float(_Number):

    def validate(self, value):
        if not isinstance(value, float):
            raise ValueError("Invalid value %r for float property %s" %
                             (value, self.name))
        return super(Float, self).validate(value)


class Boolean(Property):

    def validate(self, value):
        if value not in (True, False):
            raise ValueError("Invalid value %r for boolean property %s" %
                             (value, self.name))
        return value


class UUID(Property):

    def validate(self, value):
        return str(uuid.UUID(value))


class Password(Property):

    def __init__(self, required=False, default=None, doc=None, decode=None):
        super(Password, self).__init__(required=required, default=default,
                                       doc=doc)
        self.decode = decode

    def validate(self, password):
        if not isinstance(password, ProtectedPassword):
            raise ValueError("Not a ProtectedPassword %s" % type(password))
        if self.decode:
            password.value = self.decode(password.value)
        return password


def decode_base64(value):
    try:
        return base64.b64decode(value)
    except TypeError as e:
        raise ValueError("Unable to decode base64 value %s" % e)


class OwnerType(type):

    def __new__(cls, name, bases, dct):
        for name, obj in dct.items():
            if isinstance(obj, Property):
                obj.name = name
        return type.__new__(cls, name, bases, dct)

    def __call__(self, *args, **kw):
        instance = super(OwnerType, self).__call__(*args, **kw)
        for name, obj in instance.__class__.__dict__.items():
            if isinstance(obj, Property):
                obj.check(instance)
        return instance


@six.add_metaclass(OwnerType)
class Owner(object):
    """
    Base class for classes using properties
    """
