""" Properties are objects that can be assigned as class level
attributes on Bokeh models, to provide automatic serialization
and validation.

For example, the following defines a model that has integer,
string, and list[float] properties::

    class Model(HasProps):
        foo = Int
        bar = String
        baz = List(Float)

The properties of this class can be initialized by specifying
keyword arguments to the initializer::

    m = Model(foo=10, bar="a str", baz=[1,2,3,4])

But also by setting the attributes on an instance::

    m.foo = 20

Attempts to set a property to a value of the wrong type will
result in a ``ValueError`` exception::

    >>> m.foo = 2.3
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/bryan/work/bokeh/bokeh/properties.py", line 585, in __setattr__
        super(HasProps, self).__setattr__(name, value)
      File "/Users/bryan/work/bokeh/bokeh/properties.py", line 159, in __set__
        raise e
      File "/Users/bryan/work/bokeh/bokeh/properties.py", line 152, in __set__
        self.validate(value)
      File "/Users/bryan/work/bokeh/bokeh/properties.py", line 707, in validate
        (nice_join([ cls.__name__ for cls in self._underlying_type ]), value, type(value).__name__))
    ValueError: expected a value of type int8, int16, int32, int64 or int, got 2.3 of type float

Additionally, properties know how to serialize themselves,
to be understood by BokehJS.

"""
from __future__ import absolute_import, print_function

import re
import types
import difflib
import datetime
import dateutil.parser
import collections
from importlib import import_module
from copy import copy
from warnings import warn
import inspect
import logging
logger = logging.getLogger(__name__)

from six import integer_types, string_types, add_metaclass, iteritems
import numpy as np

from . import enums
from .util.string import nice_join

def field(name):
    ''' Convenience function do explicitly mark a field specification for
    a Bokeh model property.

    Args:
        name (str) : name of a data source field to reference for a property.

    Returns:
        dict : `{"field": name}`

    Note:
        This function is included for completeness. String values for
        property specifications are by default interpreted as field names.

    '''
    return dict(field=name)

def value(val):
    ''' Convenience function do explicitly mark a value specification for
    a Bokeh model property.

    Args:
        val (any) : a fixed value to specify for a property.

    Returns:
        dict : `{"value": name}`

    Note:
        String values for property specifications are by default interpreted
        as field names. This function is especially useful when you want to
        specify a fixed value with text properties.

    Example:

    .. code-block:: python

        # The following will take text values to render from a data source
        # column "text_column", but use a fixed value "12pt" for font size
        p.text("x", "y", text="text_column",
               text_font_size=value("12pt"), source=source)

    '''
    return dict(value=val)

bokeh_integer_types = (np.int8, np.int16, np.int32, np.int64) + integer_types

# used to indicate properties that are not set (vs null, None, etc)
class _NotSet(object):
    pass

class DeserializationError(Exception):
    pass

class Property(object):
    """ Base class for all type properties. """

    def __init__(self, default=None, help=None):
        """ This is how the descriptor is created in the class declaration """
        if isinstance(default, types.FunctionType): # aka. lazy value
            self.validate(default())
        else:
            self.validate(default)

        self._default = default
        self.__doc__ = help
        self.alternatives = []

        # This gets set by the class decorator at class creation time
        self.name = "unnamed"

    def __str__(self):
        return self.__class__.__name__

    @property
    def _name(self):
        return "_" + self.name

    @property
    def default(self):
        if not isinstance(self._default, types.FunctionType):
            return copy(self._default)
        else:
            value = self._default()
            self.validate(value)
            return value

    @classmethod
    def autocreate(cls, name=None):
        """ Called by the metaclass to create a
        new instance of this descriptor
        if the user just assigned it to a property without trailing
        parentheses.
        """
        return cls()

    def matches(self, new, old):
        # XXX: originally this code warned about not being able to compare values, but that
        # doesn't make sense, because most comparisons involving numpy arrays will fail with
        # ValueError exception, thus warning about inevitable.
        try:
            if new is None or old is None:
                return new is old           # XXX: silence FutureWarning from NumPy
            else:
                return new == old
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception as e:
            logger.debug("could not compare %s and %s for property %s (Reason: %s)", new, old, self.name, e)
        return False

    def from_json(self, json, models=None):
        return json

    def transform(self, value):
        return value

    def validate(self, value):
        pass

    def is_valid(self, value):
        try:
            self.validate(value)
        except ValueError:
            return False
        else:
            return True

    def _get(self, obj):
        if not hasattr(obj, self._name):
            setattr(obj, self._name, self.default)
        return getattr(obj, self._name)

    def __get__(self, obj, owner=None):
        if obj is not None:
            return self._get(obj)
        elif owner is not None:
            return self
        else:
            raise ValueError("both 'obj' and 'owner' are None, don't know what to do")

    def __set__(self, obj, value):
        try:
            self.validate(value)
        except ValueError as e:
            for tp, converter in self.alternatives:
                if tp.is_valid(value):
                    value = converter(value)
                    break
            else:
                raise e
        else:
            value = self.transform(value)

        old = self.__get__(obj)
        obj._changed_vars.add(self.name)
        if self._name in obj.__dict__ and self.matches(value, old):
            return
        setattr(obj, self._name, value)
        obj._dirty = True
        if hasattr(obj, '_trigger'):
            if hasattr(obj, '_block_callbacks') and obj._block_callbacks:
                obj._callback_queue.append((self.name, old, value))
            else:
                obj._trigger(self.name, old, value)

    def __delete__(self, obj):
        if hasattr(obj, self._name):
            delattr(obj, self._name)

    @property
    def has_ref(self):
        return False

    def accepts(self, tp, converter):
        tp = ParameterizedProperty._validate_type_param(tp)
        self.alternatives.append((tp, converter))
        return self

    def __or__(self, other):
        return Either(self, other)

class Include(object):
    """ Include other properties from mixin Models, with a given prefix. """

    def __init__(self, delegate, help="", use_prefix=True):
        if not (isinstance(delegate, type) and issubclass(delegate, HasProps)):
            raise ValueError("expected a subclass of HasProps, got %r" % delegate)

        self.delegate = delegate
        self.help = help
        self.use_prefix = use_prefix

class MetaHasProps(type):
    def __new__(cls, class_name, bases, class_dict):
        names = set()
        names_with_refs = set()
        container_names = set()

        # First pre-process to handle all the Includes
        includes = {}
        removes = set()
        for name, prop in class_dict.items():
            if not isinstance(prop, Include):
                continue

            delegate = prop.delegate
            if prop.use_prefix:
                prefix = re.sub("_props$", "", name) + "_"
            else:
                prefix = ""

            for subpropname in delegate.class_properties(withbases=False):
                fullpropname = prefix + subpropname
                subprop = delegate.lookup(subpropname)
                if isinstance(subprop, Property):
                    # If it's an actual instance, then we need to make a copy
                    # so two properties don't write to the same hidden variable
                    # inside the instance.
                    subprop = copy(subprop)
                if "%s" in prop.help:
                    doc = prop.help % subpropname.replace('_', ' ')
                else:
                    doc = prop.help
                try:
                    includes[fullpropname] = subprop(help=doc)
                except TypeError:
                    includes[fullpropname] = subprop
                    subprop.__doc__ = doc
            # Remove the name of the Include attribute itself
            removes.add(name)

        # Update the class dictionary, taking care not to overwrite values
        # from the delegates that the subclass may have explicitly defined
        for key, val in includes.items():
            if key not in class_dict:
                class_dict[key] = val
        for tmp in removes:
            del class_dict[tmp]

        dataspecs = {}
        units_to_add = {}
        for name, prop in class_dict.items():
            if isinstance(prop, Property):
                prop.name = name
                if prop.has_ref:
                    names_with_refs.add(name)
                elif isinstance(prop, ContainerProperty):
                    container_names.add(name)
                names.add(name)
                if isinstance(prop, DataSpec):
                    dataspecs[name] = prop
                    if hasattr(prop, '_units_type'):
                        units_to_add[name+"_units"] = prop._units_type

            elif isinstance(prop, type) and issubclass(prop, Property):
                # Support the user adding a property without using parens,
                # i.e. using just the Property subclass instead of an
                # instance of the subclass
                newprop = prop.autocreate(name=name)
                class_dict[name] = newprop
                newprop.name = name
                names.add(name)

                # Process dataspecs
                if issubclass(prop, DataSpec):
                    dataspecs[name] = newprop

        for name, prop in units_to_add.items():
            prop.name = name
            names.add(name)
            class_dict[name] = prop

        class_dict["__properties__"] = names
        class_dict["__properties_with_refs__"] = names_with_refs
        class_dict["__container_props__"] = container_names
        if dataspecs:
            class_dict["_dataspecs"] = dataspecs
        return type.__new__(cls, class_name, bases, class_dict)

def accumulate_from_subclasses(cls, propname):
    s = set()
    for c in inspect.getmro(cls):
        if issubclass(c, HasProps):
            s.update(getattr(c, propname))
    return s

@add_metaclass(MetaHasProps)
class HasProps(object):

    def __init__(self, **properties):
        super(HasProps, self).__init__()
        self._changed_vars = set()

        for name, value in properties.items():
            setattr(self, name, value)

    def __setattr__(self, name, value):
        props = sorted(self.properties())

        if name.startswith("_") or name in props:
            super(HasProps, self).__setattr__(name, value)
        else:
            matches, text = difflib.get_close_matches(name.lower(), props), "similar"

            if not matches:
                matches, text = props, "possible"

            raise AttributeError("unexpected attribute '%s' to %s, %s attributes are %s" %
                (name, self.__class__.__name__, text, nice_join(matches)))

    def clone(self):
        """ Returns a duplicate of this object with all its properties
        set appropriately.  Values which are containers are shallow-copied.
        """
        return self.__class__(**self.changed_properties_with_values())

    @classmethod
    def lookup(cls, name):
        return getattr(cls, name)

    @classmethod
    def properties_with_refs(cls):
        """ Returns a set of the names of this object's properties that
        have references. We traverse the class hierarchy and
        pull together the full list of properties.
        """
        if not hasattr(cls, "__cached_allprops_with_refs"):
            s = accumulate_from_subclasses(cls, "__properties_with_refs__")
            cls.__cached_allprops_with_refs = s
        return cls.__cached_allprops_with_refs

    @classmethod
    def properties_containers(cls):
        """ Returns a list of properties that are containers
        """
        if not hasattr(cls, "__cached_allprops_containers"):
            s = accumulate_from_subclasses(cls, "__container_props__")
            cls.__cached_allprops_containers = s
        return cls.__cached_allprops_containers

    @classmethod
    def properties(cls):
        """ Returns a set of the names of this object's properties. We
        traverse the class hierarchy and pull together the full
        list of properties.
        """
        if not hasattr(cls, "__cached_allprops"):
            s = cls.class_properties()
            cls.__cached_allprops = s
        return cls.__cached_allprops

    @classmethod
    def dataspecs(cls):
        """ Returns a set of the names of this object's dataspecs (and
        dataspec subclasses).  Traverses the class hierarchy.
        """
        if not hasattr(cls, "__cached_dataspecs"):
            dataspecs = set()
            for c in reversed(inspect.getmro(cls)):
                if hasattr(c, "_dataspecs"):
                    dataspecs.update(c._dataspecs.keys())
            cls.__cached_dataspecs = dataspecs
        return cls.__cached_dataspecs

    @classmethod
    def dataspecs_with_refs(cls):
        dataspecs = {}
        for c in reversed(inspect.getmro(cls)):
            if hasattr(c, "_dataspecs"):
                dataspecs.update(c._dataspecs)
        return dataspecs

    def changed_vars(self):
        """ Returns which variables changed since the creation of the object,
        or the last called to reset_changed_vars().
        """
        return set.union(self._changed_vars, self.properties_with_refs(),
                         self.properties_containers())

    def reset_changed_vars(self):
        self._changed_vars = set()

    def properties_with_values(self):
        return dict([ (attr, getattr(self, attr)) for attr in self.properties() ])

    def changed_properties(self):
        return self.changed_vars()

    def changed_properties_with_values(self):
        return dict([ (attr, getattr(self, attr)) for attr in self.changed_properties() ])

    @classmethod
    def class_properties(cls, withbases=True):
        if withbases:
            return accumulate_from_subclasses(cls, "__properties__")
        else:
            return set(cls.__properties__)

    def set(self, **kwargs):
        """ Sets a number of properties at once """
        for kw in kwargs:
            setattr(self, kw, kwargs[kw])

    def pprint_props(self, indent=0):
        """ Prints the properties of this object, nicely formatted """
        for key, value in self.properties_with_values().items():
            print("%s%s: %r" % ("  "*indent, key, value))

class PrimitiveProperty(Property):
    """ A base class for simple property types. Subclasses should
    define a class attribute ``_underlying_type`` that is a tuple
    of acceptable type values for the property.

    """

    _underlying_type = None

    def validate(self, value):
        super(PrimitiveProperty, self).validate(value)

        if not (value is None or isinstance(value, self._underlying_type)):
            raise ValueError("expected a value of type %s, got %s of type %s" %
                (nice_join([ cls.__name__ for cls in self._underlying_type ]), value, type(value).__name__))

    def from_json(self, json, models=None):
        if json is None or isinstance(json, self._underlying_type):
            return json
        else:
            expected = nice_join([ cls.__name__ for cls in self._underlying_type ])
            raise DeserializationError("%s expected %s, got %s" % (self, expected, json))

class Bool(PrimitiveProperty):
    """ Boolean type property. """
    _underlying_type = (bool,)

class Int(PrimitiveProperty):
    """ Signed integer type property. """
    _underlying_type = bokeh_integer_types

class Float(PrimitiveProperty):
    """ Floating point type property. """
    _underlying_type = (float, ) + bokeh_integer_types

class Complex(PrimitiveProperty):
    """ Complex floating point type property. """
    _underlying_type = (complex, float) + bokeh_integer_types

class String(PrimitiveProperty):
    """ String type property. """
    _underlying_type = string_types

class Regex(String):
    """ Regex type property validates that text values match the
    given regular expression.
    """
    def __init__(self, regex, default=None, help=None):
        self.regex = re.compile(regex)
        super(Regex, self).__init__(default=default, help=help)

    def validate(self, value):
        super(Regex, self).validate(value)

        if not (value is None or self.regex.match(value) is not None):
            raise ValueError("expected a string matching %r pattern, got %r" % (self.regex.pattern, value))

    def __str__(self):
        return "%s(%r)" % (self.__class__.__name__, self.regex.pattern)

class JSON(String):
    """ JSON type property validates that text values are valid JSON.

    ..  note::
        The string is transmitted and received by BokehJS as a *string*
        containing JSON content. i.e., you must use ``JSON.parse`` to unpack
        the value into a JavaScript hash.

    """
    def validate(self, value):
        super(JSON, self).validate(value)

        if value is None: return

        try:
            import json
            json.loads(value)
        except ValueError:
            raise ValueError("expected JSON text, got %r" % value)

class ParameterizedProperty(Property):
    """ Base class for Properties that have type parameters, e.g.
    ``List(String)``.

    """

    @staticmethod
    def _validate_type_param(type_param):
        if isinstance(type_param, type):
            if issubclass(type_param, Property):
                return type_param()
            else:
                type_param = type_param.__name__
        elif isinstance(type_param, Property):
            return type_param

        raise ValueError("expected a property as type parameter, got %s" % type_param)

    @property
    def type_params(self):
        raise NotImplementedError("abstract method")

    @property
    def has_ref(self):
        return any(type_param.has_ref for type_param in self.type_params)

class ContainerProperty(ParameterizedProperty):
    """ Base class for Container-like type properties. """
    pass

class Seq(ContainerProperty):
    """ Sequence (list, tuple) type property.

    """

    def _is_seq(self, value):
        return isinstance(value, collections.Container) and not isinstance(value, collections.Mapping)

    def _new_instance(self, value):
        return value

    def __init__(self, item_type, default=None, help=None):
        self.item_type = self._validate_type_param(item_type)
        super(Seq, self).__init__(default=default, help=help)

    @property
    def type_params(self):
        return [self.item_type]

    def validate(self, value):
        super(Seq, self).validate(value)

        if value is not None:
            if not (self._is_seq(value) and all(self.item_type.is_valid(item) for item in value)):
                raise ValueError("expected an element of %s, got %r" % (self, value))

    def __str__(self):
        return "%s(%s)" % (self.__class__.__name__, self.item_type)

    def from_json(self, json, models=None):
        if json is None:
            return None
        elif isinstance(json, list):
            return self._new_instance([ self.item_type.from_json(item, models) for item in json ])
        else:
            raise DeserializationError("%s expected a list or None, got %s" % (self, json))

class List(Seq):
    """ Python list type property.

    """

    def __init__(self, item_type, default=[], help=None):
        # todo: refactor to not use mutable objects as default values.
        # Left in place for now because we want to allow None to express
        # opional values. Also in Dict.
        super(List, self).__init__(item_type, default=default, help=help)

    def _is_seq(self, value):
        return isinstance(value, list)

class Array(Seq):
    """ NumPy array type property.

    """

    def _is_seq(self, value):
        import numpy as np
        return isinstance(value, np.ndarray)

    def _new_instance(self, value):
        return np.array(value)

class Dict(ContainerProperty):
    """ Python dict type property.

    If a default value is passed in, then a shallow copy of it will be
    used for each new use of this property.

    """

    def __init__(self, keys_type, values_type, default={}, help=None):
        self.keys_type = self._validate_type_param(keys_type)
        self.values_type = self._validate_type_param(values_type)
        super(Dict, self).__init__(default=default, help=help)

    @property
    def type_params(self):
        return [self.keys_type, self.values_type]

    def validate(self, value):
        super(Dict, self).validate(value)

        if value is not None:
            if not (isinstance(value, dict) and \
                    all(self.keys_type.is_valid(key) and self.values_type.is_valid(val) for key, val in iteritems(value))):
                raise ValueError("expected an element of %s, got %r" % (self, value))

    def __str__(self):
        return "%s(%s, %s)" % (self.__class__.__name__, self.keys_type, self.values_type)

    def from_json(self, json, models=None):
        if json is None:
            return None
        elif isinstance(json, dict):
            return { self.keys_type.from_json(key, models): self.values_type.from_json(value, models) for key, value in iteritems(json) }
        else:
            raise DeserializationError("%s expected a dict or None, got %s" % (self, json))

class Tuple(ContainerProperty):
    """ Tuple type property. """
    def __init__(self, tp1, tp2, *type_params, **kwargs):
        self._type_params = list(map(self._validate_type_param, (tp1, tp2) + type_params))
        super(Tuple, self).__init__(default=kwargs.get("default"), help=kwargs.get("help"))

    @property
    def type_params(self):
        return self._type_params

    def validate(self, value):
        super(Tuple, self).validate(value)

        if value is not None:
            if not (isinstance(value, (tuple, list)) and len(self.type_params) == len(value) and \
                    all(type_param.is_valid(item) for type_param, item in zip(self.type_params, value))):
                raise ValueError("expected an element of %s, got %r" % (self, value))

    def __str__(self):
        return "%s(%s)" % (self.__class__.__name__, ", ".join(map(str, self.type_params)))

    def from_json(self, json, models=None):
        if json is None:
            return None
        elif isinstance(json, list):
            return tuple(type_param.from_json(item, models) for type_param, item in zip(self.type_params, json))
        else:
            raise DeserializationError("%s expected a list or None, got %s" % (self, json))

class Instance(Property):
    """ Instance type property, for references to other Models in the object
    graph.

    """
    def __init__(self, instance_type, default=None, help=None):
        if not isinstance(instance_type, (type,) + string_types):
            raise ValueError("expected a type or string, got %s" % instance_type)

        if isinstance(instance_type, type) and not issubclass(instance_type, HasProps):
            raise ValueError("expected a subclass of HasProps, got %s" % instance_type)

        self._instance_type = instance_type

        super(Instance, self).__init__(default=default, help=help)

    @property
    def instance_type(self):
        if isinstance(self._instance_type, str):
            module, name = self._instance_type.rsplit(".", 1)
            self._instance_type = getattr(import_module(module, "bokeh"), name)

        return self._instance_type

    @property
    def has_ref(self):
        return True

    def validate(self, value):
        super(Instance, self).validate(value)

        if value is not None:
            if not isinstance(value, self.instance_type):
                raise ValueError("expected an instance of type %s, got %s of type %s" %
                    (self.instance_type.__name__, value, type(value).__name__))

    def __str__(self):
        return "%s(%s)" % (self.__class__.__name__, self.instance_type.__name__)

    def from_json(self, json, models=None):
        if json is None:
            return None
        elif isinstance(json, dict):
            from .plot_object import PlotObject
            if issubclass(self.instance_type, PlotObject):
                if models is None:
                    raise DeserializationError("%s can't deserialize without models" % self)
                else:
                    model = models.get(json["id"])

                    if model is not None:
                        return model
                    else:
                        raise DeserializationError("%s failed to deserilize reference to %s" % (self, json))
            else:
                attrs = {}

                for name, value in iteritems(json):
                    prop = self.instance_type.lookup(name)
                    attrs[name] = prop.from_json(value, models)

                # XXX: this doesn't work when Instance(Superclass) := Subclass()
                # Serialization dict must carry type information to resolve this.
                return self.instance_type(**attrs)
        else:
            raise DeserializationError("%s expected a dict or None, got %s" % (self, json))

class This(Property):
    """ A reference to an instance of the class being defined. """
    pass

# Fake types, ABCs
class Any(Property):
    """ Any type property accepts any values. """
    pass

class Function(Property):
    """ Function type property. """
    pass

class Event(Property):
    """ Event type property. """
    pass

class Interval(ParameterizedProperty):
    ''' Range type property ensures values are contained inside a given interval. '''
    def __init__(self, interval_type, start, end, default=None, help=None):
        self.interval_type = self._validate_type_param(interval_type)
        self.interval_type.validate(start)
        self.interval_type.validate(end)
        self.start = start
        self.end = end
        super(Interval, self).__init__(default=default, help=help)

    @property
    def type_params(self):
        return [self.interval_type]

    def validate(self, value):
        super(Interval, self).validate(value)

        if not (value is None or self.interval_type.is_valid(value) and value >= self.start and value <= self.end):
            raise ValueError("expected a value of type %s in range [%s, %s], got %r" % (self.interval_type, self.start, self.end, value))

    def __str__(self):
        return "%s(%s, %r, %r)" % (self.__class__.__name__, self.interval_type, self.start, self.end)

class Byte(Interval):
    ''' Byte type property. '''
    def __init__(self, default=0, help=None):
        super(Byte, self).__init__(Int, 0, 255, default=default, help=help)

class Either(ParameterizedProperty):
    """ Takes a list of valid properties and validates against them in succession. """

    def __init__(self, tp1, tp2, *type_params, **kwargs):
        self._type_params = list(map(self._validate_type_param, (tp1, tp2) + type_params))
        default = kwargs.get("default", self._type_params[0].default)
        help = kwargs.get("help")
        super(Either, self).__init__(default=default, help=help)

    @property
    def type_params(self):
        return self._type_params

    def validate(self, value):
        super(Either, self).validate(value)

        if not (value is None or any(param.is_valid(value) for param in self.type_params)):
            raise ValueError("expected an element of either %s, got %r" % (nice_join(self.type_params), value))

    def transform(self, value):
        for param in self.type_params:
            try:
                return param.transform(value)
            except ValueError:
                pass

        raise ValueError("Could not transform %r" % value)

    def from_json(self, json, models=None):
        for tp in self.type_params:
            try:
                return tp.from_json(json, models)
            except DeserializationError:
                pass
        else:
            raise DeserializationError("%s couldn't deserialize %s" % (self, json))

    def __str__(self):
        return "%s(%s)" % (self.__class__.__name__, ", ".join(map(str, self.type_params)))

    def __or__(self, other):
        return self.__class__(*(self.type_params + [other]), default=self._default, help=self.help)

class Enum(Property):
    """ An Enum with a list of allowed values. The first value in the list is
    the default value, unless a default is provided with the "default" keyword
    argument.
    """
    def __init__(self, enum, *values, **kwargs):
        if not (not values and isinstance(enum, enums.Enumeration)):
            enum = enums.enumeration(enum, *values)

        self.allowed_values = enum._values

        default = kwargs.get("default", enum._default)
        help = kwargs.get("help")
        super(Enum, self).__init__(default=default, help=help)

    def validate(self, value):
        super(Enum, self).validate(value)

        if not (value is None or value in self.allowed_values):
            raise ValueError("invalid value for %s: %r; allowed values are %s" % (self.name, value, nice_join(self.allowed_values)))

    def __str__(self):
        return "%s(%s)" % (self.__class__.__name__, ", ".join(map(repr, self.allowed_values)))

class Auto(Enum):

    def __init__(self):
        super(Auto, self).__init__("auto")

    def __str__(self):
        return self.__class__.__name__

# Properties useful for defining visual attributes
class Color(Either):
    """ Accepts color definition in a variety of ways, and produces an
    appropriate serialization of its value for whatever backend.

    For colors, because we support named colors and hex values prefaced
    with a "#", when we are handed a string value, there is a little
    interpretation: if the value is one of the 147 SVG named colors or
    it starts with a "#", then it is interpreted as a value.

    If a 3-tuple is provided, then it is treated as an RGB (0..255).
    If a 4-tuple is provided, then it is treated as an RGBa (0..255), with
    alpha as a float between 0 and 1.  (This follows the HTML5 Canvas API.)
    """

    def __init__(self, default=None, help=None):
        types = (Enum(enums.NamedColor),
                 Regex("^#[0-9a-fA-F]{6}$"),
                 Tuple(Byte, Byte, Byte),
                 Tuple(Byte, Byte, Byte, Percent))
        super(Color, self).__init__(*types, default=default, help=help)

    def __str__(self):
        return self.__class__.__name__


class Align(Property):
    pass

class DashPattern(Either):
    """ Dash type property.

    Express patterns that describe line dashes.  ``DashPattern`` values
    can be specified in a variety of ways:

    * An enum: "solid", "dashed", "dotted", "dotdash", "dashdot"
    * a tuple or list of integers in the `HTML5 Canvas dash specification style`_.
      Note that if the list of integers has an odd number of elements, then
      it is duplicated, and that duplicated list becomes the new dash list.

    To indicate that dashing is turned off (solid lines), specify the empty
    list [].

    .. _HTML5 Canvas dash specification style: http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas/#dash-list

    """

    _dash_patterns = {
        "solid": [],
        "dashed": [6],
        "dotted": [2,4],
        "dotdash": [2,4,6,4],
        "dashdot": [6,4,2,4],
    }

    def __init__(self, default=[], help=None):
        types = Enum(enums.DashPattern), Regex(r"^(\d+(\s+\d+)*)?$"), Seq(Int)
        super(DashPattern, self).__init__(*types, default=default, help=help)

    def transform(self, value):
        value = super(DashPattern, self).transform(value)

        if isinstance(value, string_types):
            try:
                return self._dash_patterns[value]
            except KeyError:
                return [int(x) for x in  value.split()]
        else:
            return value

    def __str__(self):
        return self.__class__.__name__

class Size(Float):
    """ Size type property.

    .. note::
        ``Size`` is equivalent to an unsigned int.

    """
    def validate(self, value):
        super(Size, self).validate(value)

        if not (value is None or 0.0 <= value):
            raise ValueError("expected a non-negative number, got %r" % value)

class Percent(Float):
    """ Percentage type property.

    Percents are useful for specifying alphas and coverage and extents; more
    semantically meaningful than Float(0..1).

    """
    def validate(self, value):
        super(Percent, self).validate(value)

        if not (value is None or 0.0 <= value <= 1.0):
            raise ValueError("expected a value in range [0, 1], got %r" % value)

class Angle(Float):
    """ Angle type property. """
    pass

class Date(Property):
    """ Date (not datetime) type property.

    """
    def __init__(self, default=datetime.date.today(), help=None):
        super(Date, self).__init__(default=default, help=help)

    def validate(self, value):
        super(Date, self).validate(value)

        if not (value is None or isinstance(value, (datetime.date,) + string_types + (float,) + bokeh_integer_types)):
            raise ValueError("expected a date, string or timestamp, got %r" % value)

    def transform(self, value):
        value = super(Date, self).transform(value)

        if isinstance(value, (float,) + bokeh_integer_types):
            try:
                value = datetime.date.fromtimestamp(value)
            except ValueError:
                value = datetime.date.fromtimestamp(value/1000)
        elif isinstance(value, string_types):
            value = dateutil.parser.parse(value).date()

        return value

class Datetime(Property):
    """ Datetime type property.

    """

    def __init__(self, default=datetime.date.today(), help=None):
        super(Datetime, self).__init__(default=default, help=help)

    def validate(self, value):
        super(Datetime, self).validate(value)

        if (isinstance(value, (datetime.datetime, datetime.date, np.datetime64))):
            return
        try:
            import pandas
            if isinstance(value, (pandas.Timestamp)):
                return
        except ImportError:
            pass

        raise ValueError("Expected a datetime instance, got %r" % value)

    def transform(self, value):
        value = super(Datetime, self).transform(value)
        return value
        # Handled by serialization in protocol.py for now


class RelativeDelta(Dict):
    """ RelativeDelta type property for time deltas.

    """

    def __init__(self, default={}, help=None):
        keys = Enum("years", "months", "days", "hours", "minutes", "seconds", "microseconds")
        values = Int
        super(RelativeDelta, self).__init__(keys, values, default=default, help=help)

    def __str__(self):
        return self.__class__.__name__

class DataSpec(Either):
    def __init__(self, typ, default, help=None):
        super(DataSpec, self).__init__(String, Dict(String, Either(String, typ)), typ, default=default, help=help)
        self._type = self._validate_type_param(typ)

    def to_dict(self, obj):
        val = getattr(obj, self._name, self.default)

        # Check for None value
        if val is None:
            return dict(value=None)

        # Check for spec type value
        try:
            self._type.validate(val)
            return dict(value=val)
        except ValueError:
            pass

        # Check for data source field name
        if isinstance(val, string_types):
            return dict(field=val)

        # Must be dict, return as-is
        return val

    def __str__(self):
        val = getattr(self, self._name, self.default)
        return "%s(%r)" % (self.__class__.__name__, val)

class NumberSpec(DataSpec):
    def __init__(self, default, help=None):
        super(NumberSpec, self).__init__(Float, default=default, help=help)

class StringSpec(DataSpec):
    def __init__(self, default, help=None):
        super(StringSpec, self).__init__(List(String), default=default, help=help)

    def __set__(self, obj, value):
        if isinstance(value, list):
            if len(value) != 1:
                raise TypeError("StringSpec convenience list values must have length 1")
            value = dict(value=value[0])
        super(StringSpec, self).__set__(obj, value)

class FontSizeSpec(DataSpec):
    def __init__(self, default, help=None):
        super(FontSizeSpec, self).__init__(List(String), default=default, help=help)

    def __set__(self, obj, value):
        if isinstance(value, string_types):
            warn('Setting a fixed font size value as a string %r is deprecated, '
                 'set with value(%r) or [%r] instead' % (value, value, value),
                 DeprecationWarning, stacklevel=2)
            if len(value) > 0 and value[0].isdigit():
                value = dict(value=value)
        super(FontSizeSpec, self).__set__(obj, value)

class UnitsSpec(NumberSpec):
    def __init__(self, default, units_type, units_default, help=None):
        super(UnitsSpec, self).__init__(default=default, help=help)
        self._units_type = self._validate_type_param(units_type)
        self._units_type.validate(units_default)
        self._units_type._default = units_default

    def to_dict(self, obj):
        d = super(UnitsSpec, self).to_dict(obj)
        d["units"] = getattr(obj, self.name+"_units")
        return d

    def __set__(self, obj, value):
        if isinstance(value, dict):
            units = value.pop("units", None)
            if units: setattr(obj, self.name+"_units", units)
        super(UnitsSpec, self).__set__(obj, value)

    def __str__(self):
        val = getattr(self, self._name, self.default)
        return "%s(%r, units_default=%r)" % (self.__class__.__name__, val, self._units_type._default)

class AngleSpec(UnitsSpec):
    def __init__(self, default, units_default="rad", help=None):
        super(AngleSpec, self).__init__(default=default, units_type=Enum(enums.AngleUnits), units_default=units_default, help=help)

class DistanceSpec(UnitsSpec):
    def __init__(self, default, units_default="data", help=None):
        super(DistanceSpec, self).__init__(default=default, units_type=Enum(enums.SpatialUnits), units_default=units_default, help=help)

    def __set__(self, obj, value):
        try:
            if value < 0:
                raise ValueError("Distances must be non-negative")
        except TypeError:
            pass
        super(DistanceSpec, self).__set__(obj, value)

class ScreenDistanceSpec(NumberSpec):
    def to_dict(self, obj):
        d = super(ScreenDistanceSpec, self).to_dict(obj)
        d["units"] = "screen"
        return d

    def __set__(self, obj, value):
        try:
            if value < 0:
                raise ValueError("Distances must be non-negative")
        except TypeError:
            pass
        super(ScreenDistanceSpec, self).__set__(obj, value)

class DataDistanceSpec(NumberSpec):
    def to_dict(self, obj):
        d = super(ScreenDistanceSpec, self).to_dict(obj)
        d["units"] = "data"
        return d

    def __set__(self, obj, value):
        try:
            if value < 0:
                raise ValueError("Distances must be non-negative")
        except TypeError:
            pass
        super(DataDistanceSpec, self).__set__(obj, value)

class ColorSpec(DataSpec):
    def __init__(self, default, help=None):
        super(ColorSpec, self).__init__(Color, default=default, help=help)

    @classmethod
    def isconst(cls, arg):
        """ Returns True if the argument is a literal color.  Check for a
        well-formed hexadecimal color value.
        """
        return isinstance(arg, string_types) and \
               ((len(arg) == 7 and arg[0] == "#") or arg in enums.NamedColor._values)

    @classmethod
    def is_color_tuple(cls, val):
        return isinstance(val, tuple) and len(val) in (3, 4)

    @classmethod
    def format_tuple(cls, colortuple):
        if len(colortuple) == 3:
            return "rgb%r" % (colortuple,)
        else:
            return "rgba%r" % (colortuple,)

    def to_dict(self, obj):
        val = getattr(obj, self._name, self.default)

        if val is None:
            return dict(value=None)

        # Check for hexadecimal or named color
        if self.isconst(val):
            return dict(value=val)

        # Check for RGB or RGBa tuple
        if isinstance(val, tuple):
            return dict(value=self.format_tuple(val))

        # Check for data source field name
        if isinstance(val, string_types):
            return dict(field=val)

        # Must be dict, return as-is
        return val

    def validate(self, value):
        try:
            return super(ColorSpec, self).validate(value)
        except ValueError as e:
            # Check for tuple input if not yet a valid input type
            if self.is_color_tuple(value):
                return True
            else:
                raise e

    def transform(self, value):

        # Make sure that any tuple has either three integers, or three integers and one float
        if isinstance(value, tuple):
            value = tuple(int(v) if i < 3 else v for i, v in enumerate(value))

        return value
