# coding=utf-8
# Copyright 2018 The Gin-Config Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Provides a dict-like object that handles Gin "selectors"."""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import re

import six

# Key used to represent terminal nodes (nodes that correspond to a complete
# selector that was added to the map) in the selector tree.
_TERMINAL_KEY = '$'

# A regular expression that matches valid selectors.
SELECTOR_RE = re.compile(r'^([a-zA-Z_]\w*\.)*[a-zA-Z_]\w*$')

class SelectorMap(object):
  """A dict-like object that supports partial matches of "selectors".

  A "selector" is an identifier consisting of multiple components separated by
  periods, for instance "outer.inner.name". The `SelectorMap` object acts like a
  dictionary, allowing these selectors to be stored and retrieved (but currently
  not deleted). Additionally, it supports partial matching of selectors through
  the `matching_selectors`, `get_match`, and `get_all_matches` methods. Partial
  matching is done from the *innermost* components of selectors. For instance
  "name" and "inner.name" both match "outer.inner.name", but "outer" and
  "outer.inner" do not.
  """

  def __init__(self):
    """Constructs an empty `SelectorMap`."""
    # Stores a suffix-tree representation of selectors as a dictionary of
    # dictionaries (of dictionaries ...), where the top-level dictionary's keys
    # are *innermost* selector components. The values in the leaves of the tree
    # are the complete selectors corresponding to the path to the terminal.
    self._selector_tree = {}
    # Stores a mapping from complete selectors to values.
    self._selector_map = {}

  def clear(self):
    self._selector_tree.clear()
    self._selector_map.clear()

  def copy(self):
    # pylint: disable=protected-access
    sm = SelectorMap()
    sm._selector_tree = self._selector_tree.copy()
    sm._selector_map = self._selector_map.copy()
    return sm

  def __copy__(self):
    return self.copy()

  def iteritems(self):
    return six.iteritems(self._selector_map)

  def items(self):
    if six.PY2:
      return self._selector_map.items()
    return self.iteritems()

  def __setitem__(self, complete_selector, value):
    """Associates a value with `complete_selector`.

    This function also performs some additional bookkeeping to facilitate
    partial matching of selectors.

    Args:
      complete_selector: The (complete) selector to associate a value with.
      value: The value to associate.

    Raises:
      ValueError: If `complete_selector` isn't a string consisting of valid
        Python identifiers separated by periods.
    """
    if not SELECTOR_RE.match(complete_selector):
      raise ValueError("Invalid selector '{}'.".format(complete_selector))

    selector_components = complete_selector.split('.')
    node = self._selector_tree

    # Iterate backwards over the components of the selector.
    for component in selector_components[::-1]:
      node = node.setdefault(component, {})
    node[_TERMINAL_KEY] = complete_selector
    self._selector_map[complete_selector] = value

  def __getitem__(self, complete_selector):
    """Look up the value of `complete_selector` (no partial matching)."""
    return self._selector_map[complete_selector]

  def __contains__(self, complete_selector):
    """Check if `complete_selector` is present."""
    return complete_selector in self._selector_map

  def get(self, complete_selector, default=None):
    """Look up the value of `complete_selector` if present, or `default`."""
    return self._selector_map.get(complete_selector, default)

  def matching_selectors(self, partial_selector):
    """Retrieves all selectors matching `partial_selector`.

    For instance, if "one.a.b" and "two.a.b" are stored in a `SelectorMap`, both
    `matching_selectors('b')` and `matching_selectors('a.b')` will return them.

    In the event that `partial_selector` exactly matches an existing complete
    selector, only that complete selector is returned. For instance, if
    "a.b.c.d" and "c.d" are stored, `matching_selectors('c.d')` will return only
    `['c.d']`, while `matching_selectors('d')` will return both.

    Args:
      partial_selector: The partial selector to find matches for.

    Returns:
      A list of selectors matching `partial_selector`.
    """
    if partial_selector in self._selector_map:
      return [partial_selector]

    selector_components = partial_selector.split('.')
    node = self._selector_tree

    for component in reversed(selector_components):
      if component not in node:
        return []
      node = node[component]

    selectors = []
    dfs_stack = [node]
    while dfs_stack:
      node = dfs_stack.pop().copy()
      selector = node.pop(_TERMINAL_KEY, None)
      dfs_stack.extend(node.values())
      if selector:
        selectors.append(selector)

    return selectors

  def get_match(self, partial_selector, default=None):
    """Gets a (single) value matching `partial_selector`.

    If the partial_selector exactly matches a complete selector, the value
    associated with the complete selector is returned.

    Args:
      partial_selector: The partial selector to find values for.
      default: A default value to return if nothing matches `partial_selector`.

    Returns:
      The value associated with `partial_selector` if it exists, else `default`.

    Raises:
      KeyError: If `partial_selector` matches more than one selector in the map.
    """
    matching_selectors = self.matching_selectors(partial_selector)
    if not matching_selectors:
      return default
    if len(matching_selectors) > 1:
      err_str = "Ambiguous selector '{}', matches {}."
      raise KeyError(err_str.format(partial_selector, matching_selectors))
    return self._selector_map[matching_selectors[0]]

  def get_all_matches(self, partial_selector):
    """Returns all values matching `partial_selector` as a list."""
    matching_selectors = self.matching_selectors(partial_selector)
    return [self._selector_map[selector] for selector in matching_selectors]

  def minimal_selector(self, complete_selector):
    """Returns the minimal selector that uniquely matches `complete_selector`.

    Args:
      complete_selector: A complete selector stored in the map.

    Returns:
      A partial selector that unambiguously matches `complete_selector`.

    Raises:
      KeyError: If `complete_selector` is not in the map.
    """
    if complete_selector not in self._selector_map:
      raise KeyError("No value with selector '{}'.".format(complete_selector))

    selector_components = complete_selector.split('.')
    node = self._selector_tree

    start = None
    for i, component in enumerate(reversed(selector_components)):
      if len(node) == 1:
        if start is None:
          start = -i  # Negative index, since we're iterating in reverse.
      else:
        start = None
      node = node[component]

    if len(node) > 1:  # The selector is a substring of another selector.
      return complete_selector
    return '.'.join(selector_components[start:])


from __future__ import unicode_literals

import collections
import logging

from mopidy import models

import spotify

logger = logging.getLogger(__name__)

class memoized(object):
    def __init__(self, func):
        self.func = func
        self.cache = {}

    def __call__(self, *args, **kwargs):
        # NOTE Only args, not kwargs, are part of the memoization key.
        if not isinstance(args, collections.Hashable):
            return self.func(*args, **kwargs)
        if args in self.cache:
            return self.cache[args]
        else:
            value = self.func(*args, **kwargs)
            if value is not None:
                self.cache[args] = value
            return value

@memoized
def to_artist(sp_artist):
    if not sp_artist.is_loaded:
        return

    return models.Artist(uri=sp_artist.link.uri, name=sp_artist.name)

@memoized
def to_artist_ref(sp_artist):
    if not sp_artist.is_loaded:
        return

    return models.Ref.artist(uri=sp_artist.link.uri, name=sp_artist.name)

def to_artist_refs(sp_artists, timeout=None):
    for sp_artist in sp_artists:
        sp_artist.load(timeout)
        ref = to_artist_ref(sp_artist)
        if ref is not None:
            yield ref

@memoized
def to_album(sp_album):
    if not sp_album.is_loaded:
        return

    if sp_album.artist is not None and sp_album.artist.is_loaded:
        artists = [to_artist(sp_album.artist)]
    else:
        artists = []

    if sp_album.year is not None and sp_album.year != 0:
        date = '%d' % sp_album.year
    else:
        date = None

    return models.Album(
        uri=sp_album.link.uri,
        name=sp_album.name,
        artists=artists,
        date=date)

@memoized
def to_album_ref(sp_album):
    if not sp_album.is_loaded:
        return

    if sp_album.artist is None or not sp_album.artist.is_loaded:
        name = sp_album.name
    else:
        name = '%s - %s' % (sp_album.artist.name, sp_album.name)

    return models.Ref.album(uri=sp_album.link.uri, name=name)

def to_album_refs(sp_albums, timeout=None):
    for sp_album in sp_albums:
        sp_album.load(timeout)
        ref = to_album_ref(sp_album)
        if ref is not None:
            yield ref

@memoized
def to_track(sp_track, bitrate=None):
    if not sp_track.is_loaded:
        return

    if sp_track.error != spotify.ErrorType.OK:
        logger.debug(
            'Error loading %s: %r', sp_track.link.uri, sp_track.error)
        return

    if sp_track.availability != spotify.TrackAvailability.AVAILABLE:
        return

    artists = [to_artist(sp_artist) for sp_artist in sp_track.artists]
    artists = filter(None, artists)

    album = to_album(sp_track.album)

    return models.Track(
        uri=sp_track.link.uri,
        name=sp_track.name,
        artists=artists,
        album=album,
        date=album.date,
        length=sp_track.duration,
        disc_no=sp_track.disc,
        track_no=sp_track.index,
        bitrate=bitrate)

@memoized
def to_track_ref(sp_track):
    if not sp_track.is_loaded:
        return

    if sp_track.error != spotify.ErrorType.OK:
        logger.debug(
            'Error loading %s: %r', sp_track.link.uri, sp_track.error)
        return

    if sp_track.availability != spotify.TrackAvailability.AVAILABLE:
        return

    return models.Ref.track(uri=sp_track.link.uri, name=sp_track.name)

def to_track_refs(sp_tracks, timeout=None):
    for sp_track in sp_tracks:
        sp_track.load(timeout)
        ref = to_track_ref(sp_track)
        if ref is not None:
            yield ref

def to_playlist(
        sp_playlist, folders=None, username=None, bitrate=None,
        as_ref=False, as_items=False):
    if not isinstance(sp_playlist, spotify.Playlist):
        return

    if not sp_playlist.is_loaded:
        return

    if as_items:
        return list(to_track_refs(sp_playlist.tracks))

    name = sp_playlist.name

    if not as_ref:
        tracks = [
            to_track(sp_track, bitrate=bitrate)
            for sp_track in sp_playlist.tracks]
        tracks = filter(None, tracks)
        if name is None:
            # Use same starred order as the Spotify client
            tracks = list(reversed(tracks))

    if name is None:
        name = 'Starred'
    if folders is not None:
        name = '/'.join(folders + [name])
    if username is not None and sp_playlist.owner.canonical_name != username:
        name = '%s (by %s)' % (name, sp_playlist.owner.canonical_name)

    if as_ref:
        return models.Ref.playlist(uri=sp_playlist.link.uri, name=name)
    else:
        return models.Playlist(
            uri=sp_playlist.link.uri, name=name, tracks=tracks)

def to_playlist_ref(sp_playlist, folders=None, username=None):
    return to_playlist(
        sp_playlist, folders=folders, username=username, as_ref=True)

# Maps from Mopidy search query field to Spotify search query field.
# `None` if there is no matching concept.
SEARCH_FIELD_MAP = {
    'albumartist': 'artist',
    'date': 'year',
    'track_name': 'track',
    'track_number': None,
}

def sp_search_query(query):
    """Translate a Mopidy search query to a Spotify search query"""

    result = []

    for (field, values) in query.items():
        field = SEARCH_FIELD_MAP.get(field, field)
        if field is None:
            continue

        for value in values:
            if field == 'year':
                value = _transform_year(value)
                if value is not None:
                    result.append('%s:%d' % (field, value))
            elif field == 'any':
                result.append('"%s"' % value)
            else:
                result.append('%s:"%s"' % (field, value))

    return ' '.join(result)

def _transform_year(date):
    try:
        return int(date.split('-')[0])
    except ValueError:
        logger.debug(
            'Excluded year from search query: '
            'Cannot parse date "%s"', date)

def web_to_artist(web_artist):
    return models.Artist(uri=web_artist['uri'], name=web_artist['name'])

def web_to_album(web_album):
    artists = [
        web_to_artist(web_artist) for web_artist in web_album['artists']]

    return models.Album(
        uri=web_album['uri'],
        name=web_album['name'],
        artists=artists)

def web_to_track(web_track):
    artists = [
        web_to_artist(web_artist) for web_artist in web_track['artists']]
    album = web_to_album(web_track['album'])

    return models.Track(
        uri=web_track['uri'],
        name=web_track['name'],
        artists=artists,
        album=album,
        length=web_track['duration_ms'],
        disc_no=web_track['disc_number'],
        track_no=web_track['track_number'])


from __future__ import unicode_literals

import email
import logging
import os
import re
import time
import urllib
import urlparse

import requests

from mopidy_spotify import utils

logger = logging.getLogger(__name__)

class OAuthTokenRefreshError(Exception):
    def __init__(self, reason):
        message = 'OAuth token refresh failed: %s' % reason
        super(OAuthTokenRefreshError, self).__init__(message)

class OAuthClientError(Exception):
    pass

class OAuthClient(object):

    def __init__(self, base_url, refresh_url, client_id=None,
                 client_secret=None, proxy_config=None, expiry_margin=60,
                 timeout=10, retries=3, retry_statuses=(500, 502, 503, 429)):

        if client_id and client_secret:
            self._auth = (client_id, client_secret)
        else:
            self._auth = None

        self._base_url = base_url
        self._refresh_url = refresh_url

        self._margin = expiry_margin
        self._expires = 0
        self._authorization_failed = False

        self._timeout = timeout
        self._number_of_retries = retries
        self._retry_statuses = retry_statuses
        self._backoff_factor = 0.5

        self._headers = {'Content-Type': 'application/json'}
        self._session = utils.get_requests_session(proxy_config or {})

    def get(self, path, *args, **kwargs):
        if self._authorization_failed:
            logger.debug('Blocking request as previous authorization failed.')
            return {}

        # TODO: Factor this out once we add more methods.
        # TODO: Don't silently error out.
        try:
            if self._should_refresh_token():
                self._refresh_token()
        except OAuthTokenRefreshError as e:
            logger.error(e)
            return {}

        # Make sure our headers always override user supplied ones.
        kwargs.setdefault('headers', {}).update(self._headers)
        result = self._request_with_retries('GET', path, *args, **kwargs)

        if result is None or 'error' in result:
            return {}
        return result

    def _should_refresh_token(self):
        # TODO: Add jitter to margin?
        return not self._auth or time.time() > self._expires - self._margin

    def _refresh_token(self):
        logger.debug('Fetching OAuth token from %s', self._refresh_url)

        data = {'grant_type': 'client_credentials'}
        result = self._request_with_retries(
            'POST', self._refresh_url, auth=self._auth, data=data)

        if result is None:
            raise OAuthTokenRefreshError('Unknown error.')
        elif result.get('error'):
            raise OAuthTokenRefreshError('%s %s' % (
                result['error'], result.get('error_description', '')))
        elif not result.get('access_token'):
            raise OAuthTokenRefreshError('missing access_token')
        elif result.get('token_type') != 'Bearer':
            raise OAuthTokenRefreshError('wrong token_type: %s' %
                                         result.get('token_type'))

        self._headers['Authorization'] = 'Bearer %s' % result['access_token']
        self._expires = time.time() + result.get('expires_in', float('Inf'))

        if result.get('expires_in'):
            logger.debug('Token expires in %s seconds.', result['expires_in'])
        if result.get('scope'):
            logger.debug('Token scopes: %s', result['scope'])

    def _request_with_retries(self, method, url, *args, **kwargs):
        prepared_request = self._session.prepare_request(
            requests.Request(method, self._prepare_url(url, *args), **kwargs))

        try_until = time.time() + self._timeout

        result = None
        backoff_time = None

        for i in range(self._number_of_retries):
            remaining_timeout = max(try_until - time.time(), 1)

            # Give up if we don't have any timeout left after sleeping.
            if backoff_time > remaining_timeout:
                break
            elif backoff_time > 0:
                time.sleep(backoff_time)

            try:
                response = self._session.send(
                    prepared_request, timeout=remaining_timeout)
            except requests.RequestException as e:
                logger.debug('Fetching %s failed: %s', prepared_request.url, e)
                status_code = None
                backoff_time = None
                result = None
            else:
                status_code = response.status_code
                backoff_time = self._parse_retry_after(response)
                result = self._decode(response)

            if status_code >= 400 and status_code < 600:
                logger.debug('Fetching %s failed: %s',
                             prepared_request.url, status_code)

            # Filter out cases where we should not retry.
            if status_code and status_code not in self._retry_statuses:
                break

            # TODO: Provider might return invalid JSON for "OK" responses.
            # This should really not happen, so ignoring for the purpose of
            # retries. It would be easier if they correctly used 204, but
            # instead some endpoints return 200 with no content, or true/false.

            # Decide how long to sleep in the next iteration.
            backoff_time = backoff_time or (2**i * self._backoff_factor)
            logger.debug('Retrying %s in %.3f seconds.',
                         prepared_request.url, backoff_time)

        if status_code == 401:
            self._authorization_failed = True
            logger.error('Authorization failed, not attempting Spotify API '
                         'request. Please get new credentials from '
                         'https://www.mopidy.com/authenticate and/or restart '
                         'Mopidy to resolve this problem.')
        return result

    def _prepare_url(self, url, *args, **kwargs):
        # TODO: Move this out as a helper and unit-test it directly?
        b = urlparse.urlsplit(self._base_url)
        u = urlparse.urlsplit(url.format(*args))

        if u.scheme or u.netloc:
            scheme, netloc, path = u.scheme, u.netloc, u.path
            query = urlparse.parse_qsl(u.query, keep_blank_values=True)
        else:
            scheme, netloc = b.scheme, b.netloc
            path = os.path.normpath(os.path.join(b.path, u.path))
            query = urlparse.parse_qsl(b.query, keep_blank_values=True)
            query.extend(urlparse.parse_qsl(u.query, keep_blank_values=True))

        for key, value in kwargs.items():
            if isinstance(value, unicode):
                value = value.encode('utf-8')
            query.append((key, value))

        encoded_query = urllib.urlencode(dict(query))
        return urlparse.urlunsplit((scheme, netloc, path, encoded_query, ''))

    def _decode(self, response):
        # Deal with 204 and other responses with empty body.
        if not response.content:
            return None
        try:
            return response.json()
        except ValueError as e:
            url = response.request.url
            logger.error('JSON decoding %s failed: %s', url, e)
            return None

    def _parse_retry_after(self, response):
        """Parse Retry-After header from response if it is set."""
        value = response.headers.get('Retry-After')

        if not value:
            seconds = 0
        elif re.match(r'^\s*[0-9]+\s*$', value):
            seconds = int(value)
        else:
            date_tuple = email.utils.parsedate(value)
            if date_tuple is None:
                seconds = 0
            else:
                seconds = time.mktime(date_tuple) - time.time()
        return max(0, seconds)


"""High-level Property base class implementation."""

from copy import deepcopy
from jsonschema import validate
from jsonschema.exceptions import ValidationError

from .errors import PropertyError

class Property:
    """A Property represents an individual state value of a thing."""

    def __init__(self, thing, name, value, metadata=None):
        """
        Initialize the object.

        thing -- the Thing this property belongs to
        name -- name of the property
        value -- Value object to hold the property value
        metadata -- property metadata, i.e. type, description, unit, etc.,
                    as a dict
        """
        self.thing = thing
        self.name = name
        self.value = value
        self.href_prefix = ''
        self.href = '/properties/{}'.format(self.name)
        self.metadata = metadata if metadata is not None else {}

        # Add the property change observer to notify the Thing about a property
        # change.
        self.value.on('update', lambda _: self.thing.property_notify(self))

    def validate_value(self, value):
        """
        Validate new property value before setting it.

        value -- New value
        """
        if 'readOnly' in self.metadata and self.metadata['readOnly']:
            raise PropertyError('Read-only property')

        try:
            validate(value, self.metadata)
        except ValidationError:
            raise PropertyError('Invalid property value')

    def as_property_description(self):
        """
        Get the property description.

        Returns a dictionary describing the property.
        """
        description = deepcopy(self.metadata)

        if 'links' not in description:
            description['links'] = []

        description['links'].append(
            {
                'rel': 'property',
                'href': self.href_prefix + self.href,
            }
        )
        return description

    def set_href_prefix(self, prefix):
        """
        Set the prefix of any hrefs associated with this property.

        prefix -- the prefix
        """
        self.href_prefix = prefix

    def get_href(self):
        """
        Get the href of this property.

        Returns the href.
        """
        return self.href_prefix + self.href

    def get_value(self):
        """
        Get the current property value.

        Returns the value.
        """
        return self.value.get()

    def set_value(self, value):
        """
        Set the current value of the property.

        value -- the value to set
        """
        self.validate_value(value)
        self.value.set(value)

    def get_name(self):
        """
        Get the name of this property.

        Returns the name.
        """
        return self.name

    def get_thing(self):
        """Get the thing associated with this property."""
        return self.thing

    def get_metadata(self):
        """Get the metadata associated with this property."""
        return self.metadata


"""Python Web Thing server implementation."""

from zeroconf import ServiceInfo, Zeroconf
import json
import socket
import tornado.concurrent
import tornado.gen
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.websocket

from .errors import PropertyError
from .utils import get_addresses, get_ip

@tornado.gen.coroutine
def perform_action(action):
    """Perform an Action in a coroutine."""
    action.start()

class SingleThing:
    """A container for a single thing."""

    def __init__(self, thing):
        """
        Initialize the container.

        thing -- the thing to store
        """
        self.thing = thing

    def get_thing(self, _=None):
        """Get the thing at the given index."""
        return self.thing

    def get_things(self):
        """Get the list of things."""
        return [self.thing]

    def get_name(self):
        """Get the mDNS server name."""
        return self.thing.name

class MultipleThings:
    """A container for multiple things."""

    def __init__(self, things, name):
        """
        Initialize the container.

        things -- the things to store
        name -- the mDNS server name
        """
        self.things = things
        self.name = name

    def get_thing(self, idx):
        """
        Get the thing at the given index.

        idx -- the index
        """
        try:
            idx = int(idx)
        except ValueError:
            return None

        if idx < 0 or idx >= len(self.things):
            return None

        return self.things[idx]

    def get_things(self):
        """Get the list of things."""
        return self.things

    def get_name(self):
        """Get the mDNS server name."""
        return self.name

class BaseHandler(tornado.web.RequestHandler):
    """Base handler that is initialized with a thing."""

    def initialize(self, things, hosts):
        """
        Initialize the handler.

        things -- list of Things managed by this server
        hosts -- list of allowed hostnames
        """
        self.things = things
        self.hosts = hosts

    def prepare(self):
        """Validate Host header."""
        host = self.request.headers.get('Host', None)
        if host is not None and host.lower() in self.hosts:
            return

        raise tornado.web.HTTPError(403)

    def get_thing(self, thing_id):
        """
        Get the thing this request is for.

        thing_id -- ID of the thing to get, in string form

        Returns the thing, or None if not found.
        """
        return self.things.get_thing(thing_id)

    def set_default_headers(self, *args, **kwargs):
        """Set the default headers for all requests."""
        self.set_header('Access-Control-Allow-Origin', '*')
        self.set_header('Access-Control-Allow-Headers',
                        'Origin, X-Requested-With, Content-Type, Accept')
        self.set_header('Access-Control-Allow-Methods',
                        'GET, HEAD, PUT, POST, DELETE')

class ThingsHandler(BaseHandler):
    """Handle a request to / when the server manages multiple things."""

    def get(self):
        """
        Handle a GET request.

        property_name -- the name of the property from the URL path
        """
        self.set_header('Content-Type', 'application/json')
        ws_href = '{}://{}'.format(
            'wss' if self.request.protocol == 'https' else 'ws',
            self.request.headers.get('Host', '')
        )

        descriptions = []
        for thing in self.things.get_things():
            description = thing.as_thing_description()
            description['links'].append({
                'rel': 'alternate',
                'href': '{}{}'.format(ws_href, thing.get_href()),
            })
            descriptions.append(description)

        self.write(json.dumps(descriptions))

class ThingHandler(tornado.websocket.WebSocketHandler):
    """Handle a request to /."""

    def initialize(self, things, hosts):
        """
        Initialize the handler.

        things -- list of Things managed by this server
        hosts -- list of allowed hostnames
        """
        self.things = things
        self.hosts = hosts

    def prepare(self):
        """Validate Host header."""
        host = self.request.headers.get('Host', None)
        if host is not None and host in self.hosts:
            return

        raise tornado.web.HTTPError(403)

    def set_default_headers(self, *args, **kwargs):
        """Set the default headers for all requests."""
        self.set_header('Access-Control-Allow-Origin', '*')
        self.set_header('Access-Control-Allow-Headers',
                        'Origin, X-Requested-With, Content-Type, Accept')
        self.set_header('Access-Control-Allow-Methods',
                        'GET, HEAD, PUT, POST, DELETE')

    def get_thing(self, thing_id):
        """
        Get the thing this request is for.

        thing_id -- ID of the thing to get, in string form

        Returns the thing, or None if not found.
        """
        return self.things.get_thing(thing_id)

    @tornado.gen.coroutine
    def get(self, thing_id='0'):
        """
        Handle a GET request, including websocket requests.

        thing_id -- ID of the thing this request is for
        """
        self.thing = self.get_thing(thing_id)
        if self.thing is None:
            self.set_status(404)
            self.finish()
            return

        if self.request.headers.get('Upgrade', '').lower() == 'websocket':
            yield tornado.websocket.WebSocketHandler.get(self)
            return

        self.set_header('Content-Type', 'application/json')
        ws_href = '{}://{}'.format(
            'wss' if self.request.protocol == 'https' else 'ws',
            self.request.headers.get('Host', '')
        )

        description = self.thing.as_thing_description()
        description['links'].append({
            'rel': 'alternate',
            'href': '{}{}'.format(ws_href, self.thing.get_href()),
        })

        self.write(json.dumps(description))
        self.finish()

    def open(self):
        """Handle a new connection."""
        self.thing.add_subscriber(self)

    def on_message(self, message):
        """
        Handle an incoming message.

        message -- message to handle
        """
        try:
            message = json.loads(message)
        except ValueError:
            try:
                self.write_message(json.dumps({
                    'messageType': 'error',
                    'data': {
                        'status': '400 Bad Request',
                        'message': 'Parsing request failed',
                    },
                }))
            except tornado.websocket.WebSocketClosedError:
                pass

            return

        if 'messageType' not in message or 'data' not in message:
            try:
                self.write_message(json.dumps({
                    'messageType': 'error',
                    'data': {
                        'status': '400 Bad Request',
                        'message': 'Invalid message',
                    },
                }))
            except tornado.websocket.WebSocketClosedError:
                pass

            return

        msg_type = message['messageType']
        if msg_type == 'setProperty':
            for property_name, property_value in message['data'].items():
                try:
                    self.thing.set_property(property_name, property_value)
                except PropertyError as e:
                    self.write_message(json.dumps({
                        'messageType': 'error',
                        'data': {
                            'status': '400 Bad Request',
                            'message': str(e),
                        },
                    }))
        elif msg_type == 'requestAction':
            for action_name, action_params in message['data'].items():
                input_ = None
                if 'input' in action_params:
                    input_ = action_params['input']

                action = self.thing.perform_action(action_name, input_)
                if action:
                    tornado.ioloop.IOLoop.current().spawn_callback(
                        perform_action,
                        action,
                    )
                else:
                    self.write_message(json.dumps({
                        'messageType': 'error',
                        'data': {
                            'status': '400 Bad Request',
                            'message': 'Invalid action request',
                            'request': message,
                        },
                    }))
        elif msg_type == 'addEventSubscription':
            for event_name in message['data'].keys():
                self.thing.add_event_subscriber(event_name, self)
        else:
            try:
                self.write_message(json.dumps({
                    'messageType': 'error',
                    'data': {
                        'status': '400 Bad Request',
                        'message': 'Unknown messageType: ' + msg_type,
                        'request': message,
                    },
                }))
            except tornado.websocket.WebSocketClosedError:
                pass

    def on_close(self):
        """Handle a close event on the socket."""
        self.thing.remove_subscriber(self)

    def check_origin(self, origin):
        """Allow connections from all origins."""
        return True

class PropertiesHandler(BaseHandler):
    """Handle a request to /properties."""

    def get(self, thing_id='0'):
        """
        Handle a GET request.

        thing_id -- ID of the thing this request is for
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        self.set_header('Content-Type', 'application/json')
        self.write(json.dumps(thing.get_properties()))

class PropertyHandler(BaseHandler):
    """Handle a request to /properties/<property>."""

    def get(self, thing_id='0', property_name=None):
        """
        Handle a GET request.

        thing_id -- ID of the thing this request is for
        property_name -- the name of the property from the URL path
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        if thing.has_property(property_name):
            self.set_header('Content-Type', 'application/json')
            self.write(json.dumps({
                property_name: thing.get_property(property_name),
            }))
        else:
            self.set_status(404)

    def put(self, thing_id='0', property_name=None):
        """
        Handle a PUT request.

        thing_id -- ID of the thing this request is for
        property_name -- the name of the property from the URL path
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        try:
            args = json.loads(self.request.body.decode())
        except ValueError:
            self.set_status(400)
            return

        if property_name not in args:
            self.set_status(400)
            return

        if thing.has_property(property_name):
            try:
                thing.set_property(property_name, args[property_name])
            except PropertyError:
                self.set_status(400)
                return

            self.set_header('Content-Type', 'application/json')
            self.write(json.dumps({
                property_name: thing.get_property(property_name),
            }))
        else:
            self.set_status(404)

class ActionsHandler(BaseHandler):
    """Handle a request to /actions."""

    def get(self, thing_id='0'):
        """
        Handle a GET request.

        thing_id -- ID of the thing this request is for
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        self.set_header('Content-Type', 'application/json')
        self.write(json.dumps(thing.get_action_descriptions()))

    def post(self, thing_id='0'):
        """
        Handle a POST request.

        thing_id -- ID of the thing this request is for
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        try:
            message = json.loads(self.request.body.decode())
        except ValueError:
            self.set_status(400)
            return

        response = {}
        for action_name, action_params in message.items():
            input_ = None
            if 'input' in action_params:
                input_ = action_params['input']

            action = thing.perform_action(action_name, input_)
            if action:
                response.update(action.as_action_description())

                # Start the action
                tornado.ioloop.IOLoop.current().spawn_callback(
                    perform_action,
                    action,
                )

        self.set_status(201)
        self.write(json.dumps(response))

class ActionHandler(BaseHandler):
    """Handle a request to /actions/<action_name>."""

    def get(self, thing_id='0', action_name=None):
        """
        Handle a GET request.

        thing_id -- ID of the thing this request is for
        action_name -- name of the action from the URL path
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        self.set_header('Content-Type', 'application/json')
        self.write(json.dumps(thing.get_action_descriptions(
            action_name=action_name)))

    def post(self, thing_id='0', action_name=None):
        """
        Handle a POST request.

        thing_id -- ID of the thing this request is for
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        try:
            message = json.loads(self.request.body.decode())
        except ValueError:
            self.set_status(400)
            return

        response = {}
        for name, action_params in message.items():
            if name != action_name:
                continue

            input_ = None
            if 'input' in action_params:
                input_ = action_params['input']

            action = thing.perform_action(name, input_)
            if action:
                response.update(action.as_action_description())

                # Start the action
                tornado.ioloop.IOLoop.current().spawn_callback(
                    perform_action,
                    action,
                )

        self.set_status(201)
        self.write(json.dumps(response))

class ActionIDHandler(BaseHandler):
    """Handle a request to /actions/<action_name>/<action_id>."""

    def get(self, thing_id='0', action_name=None, action_id=None):
        """
        Handle a GET request.

        thing_id -- ID of the thing this request is for
        action_name -- name of the action from the URL path
        action_id -- the action ID from the URL path
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        action = thing.get_action(action_name, action_id)
        if action is None:
            self.set_status(404)
            return

        self.set_header('Content-Type', 'application/json')
        self.write(json.dumps(action.as_action_description()))

    def put(self, thing_id='0', action_name=None, action_id=None):
        """
        Handle a PUT request.

        TODO: this is not yet defined in the spec

        thing_id -- ID of the thing this request is for
        action_name -- name of the action from the URL path
        action_id -- the action ID from the URL path
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        self.set_status(200)

    def delete(self, thing_id='0', action_name=None, action_id=None):
        """
        Handle a DELETE request.

        thing_id -- ID of the thing this request is for
        action_name -- name of the action from the URL path
        action_id -- the action ID from the URL path
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        if thing.remove_action(action_name, action_id):
            self.set_status(204)
        else:
            self.set_status(404)

class EventsHandler(BaseHandler):
    """Handle a request to /events."""

    def get(self, thing_id='0'):
        """
        Handle a GET request.

        thing_id -- ID of the thing this request is for
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        self.set_header('Content-Type', 'application/json')
        self.write(json.dumps(thing.get_event_descriptions()))

class EventHandler(BaseHandler):
    """Handle a request to /events/<event_name>."""

    def get(self, thing_id='0', event_name=None):
        """
        Handle a GET request.

        thing_id -- ID of the thing this request is for
        event_name -- name of the event from the URL path
        """
        thing = self.get_thing(thing_id)
        if thing is None:
            self.set_status(404)
            return

        self.set_header('Content-Type', 'application/json')
        self.write(json.dumps(thing.get_event_descriptions(
            event_name=event_name)))

class WebThingServer:
    """Server to represent a Web Thing over HTTP."""

    def __init__(self, things, port=80, hostname=None, ssl_options=None,
                 additional_routes=None, base_path=''):
        """
        Initialize the WebThingServer.

        For documentation on the additional route format, see:
        https://www.tornadoweb.org/en/stable/web.html#tornado.web.Application

        things -- things managed by this server -- should be of type
                  SingleThing or MultipleThings
        port -- port to listen on (defaults to 80)
        hostname -- Optional host name, i.e. mything.com
        ssl_options -- dict of SSL options to pass to the tornado server
        additional_routes -- list of additional routes to add to the server
        base_path -- base URL path to use, rather than '/'
        """
        self.things = things
        self.name = things.get_name()
        self.port = port
        self.hostname = hostname
        self.base_path = base_path.rstrip('/')

        system_hostname = socket.gethostname().lower()
        self.hosts = [
            'localhost',
            'localhost:{}'.format(self.port),
            '{}.local'.format(system_hostname),
            '{}.local:{}'.format(system_hostname, self.port),
        ]

        for address in get_addresses():
            self.hosts.extend([
                address,
                '{}:{}'.format(address, self.port),
            ])

        if self.hostname is not None:
            self.hostname = self.hostname.lower()
            self.hosts.extend([
                self.hostname,
                '{}:{}'.format(self.hostname, self.port),
            ])

        if isinstance(self.things, MultipleThings):
            for idx, thing in enumerate(self.things.get_things()):
                thing.set_href_prefix('{}/{}'.format(self.base_path, idx))

            handlers = [
                [
                    r'/?',
                    ThingsHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/(?P<thing_id>\d+)/?',
                    ThingHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/(?P<thing_id>\d+)/properties/?',
                    PropertiesHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/(?P<thing_id>\d+)/properties/' +
                    r'(?P<property_name>[^/]+)/?',
                    PropertyHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/(?P<thing_id>\d+)/actions/?',
                    ActionsHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/(?P<thing_id>\d+)/actions/(?P<action_name>[^/]+)/?',
                    ActionHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/(?P<thing_id>\d+)/actions/' +
                    r'(?P<action_name>[^/]+)/(?P<action_id>[^/]+)/?',
                    ActionIDHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/(?P<thing_id>\d+)/events/?',
                    EventsHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/(?P<thing_id>\d+)/events/(?P<event_name>[^/]+)/?',
                    EventHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
            ]
        else:
            self.things.get_thing().set_href_prefix(self.base_path)
            handlers = [
                [
                    r'/?',
                    ThingHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/properties/?',
                    PropertiesHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/properties/(?P<property_name>[^/]+)/?',
                    PropertyHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/actions/?',
                    ActionsHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/actions/(?P<action_name>[^/]+)/?',
                    ActionHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/actions/(?P<action_name>[^/]+)/(?P<action_id>[^/]+)/?',
                    ActionIDHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/events/?',
                    EventsHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
                [
                    r'/events/(?P<event_name>[^/]+)/?',
                    EventHandler,
                    dict(things=self.things, hosts=self.hosts),
                ],
            ]

        if isinstance(additional_routes, list):
            handlers = additional_routes + handlers

        if self.base_path:
            for h in handlers:
                h[0] = self.base_path + h[0]

        self.app = tornado.web.Application(handlers)
        self.app.is_tls = ssl_options is not None
        self.server = tornado.httpserver.HTTPServer(self.app,
                                                    ssl_options=ssl_options)

    def start(self):
        """Start listening for incoming connections."""
        self.service_info = ServiceInfo(
            '_webthing._tcp.local.',
            '{}._webthing._tcp.local.'.format(self.name),
            address=socket.inet_aton(get_ip()),
            port=self.port,
            properties={
                'path': '/',
            },
            server='{}.local.'.format(socket.gethostname()))
        self.zeroconf = Zeroconf()
        self.zeroconf.register_service(self.service_info)

        self.server.listen(self.port)
        tornado.ioloop.IOLoop.current().start()

    def stop(self):
        """Stop listening."""
        self.zeroconf.unregister_service(self.service_info)
        self.zeroconf.close()
        self.server.stop()


"""High-level Action base class implementation."""

from .utils import timestamp

class Action:
    """An Action represents an individual action on a thing."""

    def __init__(self, id_, thing, name, input_):
        """
        Initialize the object.

        id_ ID of this action
        thing -- the Thing this action belongs to
        name -- name of the action
        input_ -- any action inputs
        """
        self.id = id_
        self.thing = thing
        self.name = name
        self.input = input_
        self.href_prefix = ''
        self.href = '/actions/{}/{}'.format(self.name, self.id)
        self.status = 'created'
        self.time_requested = timestamp()
        self.time_completed = None

    def as_action_description(self):
        """
        Get the action description.

        Returns a dictionary describing the action.
        """
        description = {
            self.name: {
                'href': self.href_prefix + self.href,
                'timeRequested': self.time_requested,
                'status': self.status,
            },
        }

        if self.input is not None:
            description[self.name]['input'] = self.input

        if self.time_completed is not None:
            description[self.name]['timeCompleted'] = self.time_completed

        return description

    def set_href_prefix(self, prefix):
        """
        Set the prefix of any hrefs associated with this action.

        prefix -- the prefix
        """
        self.href_prefix = prefix

    def get_id(self):
        """Get this action's ID."""
        return self.id

    def get_name(self):
        """Get this action's name."""
        return self.name

    def get_href(self):
        """Get this action's href."""
        return self.href_prefix + self.href

    def get_status(self):
        """Get this action's status."""
        return self.status

    def get_thing(self):
        """Get the thing associated with this action."""
        return self.thing

    def get_time_requested(self):
        """Get the time the action was requested."""
        return self.time_requested

    def get_time_completed(self):
        """Get the time the action was completed."""
        return self.time_completed

    def get_input(self):
        """Get the inputs for this action."""
        return self.input

    def start(self):
        """Start performing the action."""
        self.status = 'pending'
        self.thing.action_notify(self)
        self.perform_action()
        self.finish()

    def perform_action(self):
        """Override this with the code necessary to perform the action."""
        pass

    def cancel(self):
        """Override this with the code necessary to cancel the action."""
        pass

    def finish(self):
        """Finish performing the action."""
        self.status = 'completed'
        self.time_completed = timestamp()
        self.thing.action_notify(self)


"""High-level Event base class implementation."""

from .utils import timestamp

class Event:
    """An Event represents an individual event from a thing."""

    def __init__(self, thing, name, data=None):
        """
        Initialize the object.

        thing -- Thing this event belongs to
        name -- name of the event
        data -- data associated with the event
        """
        self.thing = thing
        self.name = name
        self.data = data
        self.time = timestamp()

    def as_event_description(self):
        """
        Get the event description.

        Returns a dictionary describing the event.
        """
        description = {
            self.name: {
                'timestamp': self.time,
            },
        }

        if self.data is not None:
            description[self.name]['data'] = self.data

        return description

    def get_thing(self):
        """Get the thing associated with this event."""
        return self.thing

    def get_name(self):
        """Get the event's name."""
        return self.name

    def get_data(self):
        """Get the event's data."""
        return self.data

    def get_time(self):
        """Get the event's timestamp."""
        return self.time


"""Utility functions."""

import datetime
import ifaddr
import socket

def timestamp():
    """
    Get the current time.

    Returns the current time in the form YYYY-mm-ddTHH:MM:SS+00:00
    """
    return datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S+00:00')

def get_ip():
    """
    Get the default local IP address.

    From: https://stackoverflow.com/a/28950776
    """
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        s.connect(('10.255.255.255', 1))
        ip = s.getsockname()[0]
    except (socket.error, IndexError):
        ip = '127.0.0.1'
    finally:
        s.close()

    return ip

def get_addresses():
    """
    Get all IP addresses.

    Returns list of addresses.
    """
    addresses = set()

    for iface in ifaddr.get_adapters():
        for addr in iface.ips:
            # Filter out link-local addresses.
            if addr.is_IPv4:
                ip = addr.ip

                if not ip.startswith('169.254.'):
                    addresses.add(ip)
            elif addr.is_IPv6:
                # Sometimes, IPv6 addresses will have the interface name
                # appended, e.g. %eth0. Handle that.
                ip = addr.ip[0].split('%')[0].lower()

                if not ip.startswith('fe80:'):
                    addresses.add('[{}]'.format(ip))

    return sorted(list(addresses))


"""An observable, settable value interface."""

import sys

if sys.version_info.major == 3:
    from pyee import AsyncIOEventEmitter as EventEmitter
else:
    from pyee import EventEmitter

class Value(EventEmitter):
    """
    A property value.

    This is used for communicating between the Thing representation and the
    actual physical thing implementation.

    Notifies all observers when the underlying value changes through an
    external update (command to turn the light off) or if the underlying sensor
    reports a new value.
    """

    def __init__(self, initial_value, value_forwarder=None):
        """
        Initialize the object.

        initial_value -- the initial value
        value_forwarder -- the method that updates the actual value on the
                           thing
        """
        EventEmitter.__init__(self)
        self.last_value = initial_value
        self.value_forwarder = value_forwarder

    def set(self, value):
        """
        Set a new value for this thing.

        value -- value to set
        """
        if self.value_forwarder is not None:
            self.value_forwarder(value)

        self.notify_of_external_update(value)

    def get(self):
        """Return the last known value from the underlying thing."""
        return self.last_value

    def notify_of_external_update(self, value):
        """
        Notify observers of a new value.

        value -- new value
        """
        if value is not None and value != self.last_value:
            self.last_value = value
            self.emit('update', value)


"""High-level Thing base class implementation."""

from jsonschema import validate
from jsonschema.exceptions import ValidationError
import json
import tornado.websocket

class Thing:
    """A Web Thing."""

    def __init__(self, name, type_=[], description=''):
        """
        Initialize the object.

        name -- the thing's name
        type_ -- the thing's type(s)
        description -- description of the thing
        """
        if not isinstance(type_, list):
            type_ = [type_]

        self.context = 'https://iot.mozilla.org/schemas'
        self.type = type_
        self.name = name
        self.description = description
        self.properties = {}
        self.available_actions = {}
        self.available_events = {}
        self.actions = {}
        self.events = []
        self.subscribers = set()
        self.href_prefix = ''
        self.ui_href = None

    def as_thing_description(self):
        """
        Return the thing state as a Thing Description.

        Returns the state as a dictionary.
        """
        thing = {
            'name': self.name,
            'href': self.href_prefix if self.href_prefix else '/',
            '@context': self.context,
            '@type': self.type,
            'properties': self.get_property_descriptions(),
            'actions': {},
            'events': {},
            'links': [
                {
                    'rel': 'properties',
                    'href': '{}/properties'.format(self.href_prefix),
                },
                {
                    'rel': 'actions',
                    'href': '{}/actions'.format(self.href_prefix),
                },
                {
                    'rel': 'events',
                    'href': '{}/events'.format(self.href_prefix),
                },
            ],
        }

        for name, action in self.available_actions.items():
            thing['actions'][name] = action['metadata']
            thing['actions'][name]['links'] = [
                {
                    'rel': 'action',
                    'href': '{}/actions/{}'.format(self.href_prefix, name),
                },
            ]

        for name, event in self.available_events.items():
            thing['events'][name] = event['metadata']
            thing['events'][name]['links'] = [
                {
                    'rel': 'event',
                    'href': '{}/events/{}'.format(self.href_prefix, name),
                },
            ]

        if self.ui_href is not None:
            thing['links'].append({
                'rel': 'alternate',
                'mediaType': 'text/html',
                'href': self.ui_href,
            })

        if self.description:
            thing['description'] = self.description

        return thing

    def get_href(self):
        """Get this thing's href."""
        if self.href_prefix:
            return self.href_prefix

        return '/'

    def get_ui_href(self):
        """Get the UI href."""
        return self.ui_href

    def set_href_prefix(self, prefix):
        """
        Set the prefix of any hrefs associated with this thing.

        prefix -- the prefix
        """
        self.href_prefix = prefix

        for property_ in self.properties.values():
            property_.set_href_prefix(prefix)

        for action_name in self.actions.keys():
            for action in self.actions[action_name]:
                action.set_href_prefix(prefix)

    def set_ui_href(self, href):
        """
        Set the href of this thing's custom UI.

        href -- the href
        """
        self.ui_href = href

    def get_name(self):
        """
        Get the name of the thing.

        Returns the name as a string.
        """
        return self.name

    def get_context(self):
        """
        Get the type context of the thing.

        Returns the context as a string.
        """
        return self.context

    def get_type(self):
        """
        Get the type(s) of the thing.

        Returns the list of types.
        """
        return self.type

    def get_description(self):
        """
        Get the description of the thing.

        Returns the description as a string.
        """
        return self.description

    def get_property_descriptions(self):
        """
        Get the thing's properties as a dictionary.

        Returns the properties as a dictionary, i.e. name -> description.
        """
        return {k: v.as_property_description()
                for k, v in self.properties.items()}

    def get_action_descriptions(self, action_name=None):
        """
        Get the thing's actions as an array.

        action_name -- Optional action name to get descriptions for

        Returns the action descriptions.
        """
        descriptions = []

        if action_name is None:
            for name in self.actions:
                for action in self.actions[name]:
                    descriptions.append(action.as_action_description())
        elif action_name in self.actions:
            for action in self.actions[action_name]:
                descriptions.append(action.as_action_description())

        return descriptions

    def get_event_descriptions(self, event_name=None):
        """
        Get the thing's events as an array.

        event_name -- Optional event name to get descriptions for

        Returns the event descriptions.
        """
        if event_name is None:
            return [e.as_event_description() for e in self.events]
        else:
            return [e.as_event_description()
                    for e in self.events if e.get_name() == event_name]

    def add_property(self, property_):
        """
        Add a property to this thing.

        property_ -- property to add
        """
        property_.set_href_prefix(self.href_prefix)
        self.properties[property_.name] = property_

    def remove_property(self, property_):
        """
        Remove a property from this thing.

        property_ -- property to remove
        """
        if property_.name in self.properties:
            del self.properties[property_.name]

    def find_property(self, property_name):
        """
        Find a property by name.

        property_name -- the property to find

        Returns a Property object, if found, else None.
        """
        return self.properties.get(property_name, None)

    def get_property(self, property_name):
        """
        Get a property's value.

        property_name -- the property to get the value of

        Returns the properties value, if found, else None.
        """
        prop = self.find_property(property_name)
        if prop:
            return prop.get_value()

        return None

    def get_properties(self):
        """
        Get a mapping of all properties and their values.

        Returns a dictionary of property_name -> value.
        """
        return {prop.get_name(): prop.get_value()
                for prop in self.properties.values()}

    def has_property(self, property_name):
        """
        Determine whether or not this thing has a given property.

        property_name -- the property to look for

        Returns a boolean, indicating whether or not the thing has the
        property.
        """
        return property_name in self.properties

    def set_property(self, property_name, value):
        """
        Set a property value.

        property_name -- name of the property to set
        value -- value to set
        """
        prop = self.find_property(property_name)
        if not prop:
            return

        prop.set_value(value)

    def get_action(self, action_name, action_id):
        """
        Get an action.

        action_name -- name of the action
        action_id -- ID of the action

        Returns the requested action if found, else None.
        """
        if action_name not in self.actions:
            return None

        for action in self.actions[action_name]:
            if action.id == action_id:
                return action

        return None

    def add_event(self, event):
        """
        Add a new event and notify subscribers.

        event -- the event that occurred
        """
        self.events.append(event)
        self.event_notify(event)

    def add_available_event(self, name, metadata):
        """
        Add an available event.

        name -- name of the event
        metadata -- event metadata, i.e. type, description, etc., as a dict
        """
        if metadata is None:
            metadata = {}

        self.available_events[name] = {
            'metadata': metadata,
            'subscribers': set(),
        }

    def perform_action(self, action_name, input_=None):
        """
        Perform an action on the thing.

        action_name -- name of the action
        input_ -- any action inputs

        Returns the action that was created.
        """
        if action_name not in self.available_actions:
            return None

        action_type = self.available_actions[action_name]

        if 'input' in action_type['metadata']:
            try:
                validate(input_, action_type['metadata']['input'])
            except ValidationError:
                return None

        action = action_type['class'](self, input_=input_)
        action.set_href_prefix(self.href_prefix)
        self.action_notify(action)
        self.actions[action_name].append(action)
        return action

    def remove_action(self, action_name, action_id):
        """
        Remove an existing action.

        action_name -- name of the action
        action_id -- ID of the action

        Returns a boolean indicating the presence of the action.
        """
        action = self.get_action(action_name, action_id)
        if action is None:
            return False

        action.cancel()
        self.actions[action_name].remove(action)
        return True

    def add_available_action(self, name, metadata, cls):
        """
        Add an available action.

        name -- name of the action
        metadata -- action metadata, i.e. type, description, etc., as a dict
        cls -- class to instantiate for this action
        """
        if metadata is None:
            metadata = {}

        self.available_actions[name] = {
            'metadata': metadata,
            'class': cls,
        }
        self.actions[name] = []

    def add_subscriber(self, ws):
        """
        Add a new websocket subscriber.

        ws -- the websocket
        """
        self.subscribers.add(ws)

    def remove_subscriber(self, ws):
        """
        Remove a websocket subscriber.

        ws -- the websocket
        """
        if ws in self.subscribers:
            self.subscribers.remove(ws)

        for name in self.available_events:
            self.remove_event_subscriber(name, ws)

    def add_event_subscriber(self, name, ws):
        """
        Add a new websocket subscriber to an event.

        name -- name of the event
        ws -- the websocket
        """
        if name in self.available_events:
            self.available_events[name]['subscribers'].add(ws)

    def remove_event_subscriber(self, name, ws):
        """
        Remove a websocket subscriber from an event.

        name -- name of the event
        ws -- the websocket
        """
        if name in self.available_events and \
                ws in self.available_events[name]['subscribers']:
            self.available_events[name]['subscribers'].remove(ws)

    def property_notify(self, property_):
        """
        Notify all subscribers of a property change.

        property_ -- the property that changed
        """
        message = json.dumps({
            'messageType': 'propertyStatus',
            'data': {
                property_.name: property_.get_value(),
            }
        })

        for subscriber in list(self.subscribers):
            try:
                subscriber.write_message(message)
            except tornado.websocket.WebSocketClosedError:
                pass

    def action_notify(self, action):
        """
        Notify all subscribers of an action status change.

        action -- the action whose status changed
        """
        message = json.dumps({
            'messageType': 'actionStatus',
            'data': action.as_action_description(),
        })

        for subscriber in list(self.subscribers):
            try:
                subscriber.write_message(message)
            except tornado.websocket.WebSocketClosedError:
                pass

    def event_notify(self, event):
        """
        Notify all subscribers of an event.

        event -- the event that occurred
        """
        if event.name not in self.available_events:
            return

        message = json.dumps({
            'messageType': 'event',
            'data': event.as_event_description(),
        })

        for subscriber in self.available_events[event.name]['subscribers']:
            try:
                subscriber.write_message(message)
            except tornado.websocket.WebSocketClosedError:
                pass


from typing import Dict, List, Union, Tuple

import django
from django.conf import settings
from django.db import models, transaction
from django.db.models.sql import UpdateQuery
from django.db.models.sql.constants import CURSOR
from django.db.models.fields import NOT_PROVIDED
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation

from psqlextra import signals
from psqlextra.compiler import (PostgresReturningUpdateCompiler,
                                PostgresInsertCompiler)
from psqlextra.query import PostgresQuery, PostgresInsertQuery, ConflictAction

class PostgresQuerySet(models.QuerySet):
    """Adds support for PostgreSQL specifics."""

    def __init__(self, model=None, query=None, using=None, hints=None):
        """Initializes a new instance of :see:PostgresQuerySet."""

        super().__init__(model, query, using, hints)

        self.query = query or PostgresQuery(self.model)

        self.conflict_target = None
        self.conflict_action = None
        self.index_predicate = None

    def annotate(self, **annotations):
        """Custom version of the standard annotate function
        that allows using field names as annotated fields.

        Normally, the annotate function doesn't allow you
        to use the name of an existing field on the model
        as the alias name. This version of the function does
        allow that.
        """

        fields = {
            field.name: field
            for field in self.model._meta.get_fields()
        }

        # temporarily rename the fields that have the same
        # name as a field name, we'll rename them back after
        # the function in the base class ran
        new_annotations = {}
        renames = {}
        for name, value in annotations.items():
            if name in fields:
                new_name = '%s_new' % name
                new_annotations[new_name] = value
                renames[new_name] = name
            else:
                new_annotations[name] = value

        # run the base class's annotate function
        result = super().annotate(**new_annotations)

        # rename the annotations back to as specified
        result.rename_annotations(**renames)
        return result

    def rename_annotations(self, **annotations):
        """Renames the aliases for the specified annotations:

            .annotate(myfield=F('somestuf__myfield'))
            .rename_annotations(myfield='field')

        Arguments:
            annotations:
                The annotations to rename. Mapping the
                old name to the new name.
        """

        self.query.rename_annotations(annotations)
        return self

    def join(self, **conditions):
        """Adds extra conditions to existing joins.

        WARNING: This is an extremely experimental feature.
                 DO NOT USE unless you know what you're doing.
        """

        self.query.add_join_conditions(conditions)
        return self

    def update(self, **fields):
        """Updates all rows that match the filter."""

        # build up the query to execute
        self._for_write = True
        if django.VERSION >= (2, 0):
            query = self.query.chain(UpdateQuery)
        else:
            query = self.query.clone(UpdateQuery)
        query._annotations = None
        query.add_update_values(fields)

        # build the compiler for for the query
        connection = django.db.connections[self.db]
        compiler = PostgresReturningUpdateCompiler(query, connection, self.db)

        # execute the query
        with transaction.atomic(using=self.db, savepoint=False):
            rows = compiler.execute_sql(CURSOR)
        self._result_cache = None

        # send out a signal for each row
        for row in rows:
            signals.update.send(self.model, pk=row[0])

        # the original update(..) returns the amount of rows
        # affected, let's do the same
        return len(rows)

    def on_conflict(self, fields: List[Union[str, Tuple[str]]], action, index_predicate: str=None):
        """Sets the action to take when conflicts arise when attempting
        to insert/create a new row.

        Arguments:
            fields:
                The fields the conflicts can occur in.

            action:
                The action to take when the conflict occurs.

            index_predicate:
                The index predicate to satisfy an arbiter partial index (i.e. what partial index to use for checking
                conflicts)
        """

        self.conflict_target = fields
        self.conflict_action = action
        self.index_predicate = index_predicate

        return self

    def bulk_insert(self, rows, return_model=False):
        """Creates multiple new records in the database.

        This allows specifying custom conflict behavior using .on_conflict().
        If no special behavior was specified, this uses the normal Django create(..)

        Arguments:
            rows:
                An array of dictionaries, where each dictionary
                describes the fields to insert.

            return_model (default: False):
                If model instances should be returned rather than
                just dicts.

        Returns:
            A list of either the dicts of the rows inserted, including the pk or
            the models of the rows inserted with defaults for any fields not specified
        """

        if self.conflict_target or self.conflict_action:
            compiler = self._build_insert_compiler(rows)
            objs = compiler.execute_sql(return_id=True)
            if return_model:
                return [self.model(**dict(r, **k)) for r, k in zip(rows, objs)]
            else:
                return [dict(r, **k) for r, k in zip(rows, objs)]

        # no special action required, use the standard Django bulk_create(..)
        return super().bulk_create([self.model(**fields) for fields in rows])

    def insert(self, **fields):
        """Creates a new record in the database.

        This allows specifying custom conflict behavior using .on_conflict().
        If no special behavior was specified, this uses the normal Django create(..)

        Arguments:
            fields:
                The fields of the row to create.

        Returns:
            The primary key of the record that was created.
        """

        if self.conflict_target or self.conflict_action:
            compiler = self._build_insert_compiler([fields])
            rows = compiler.execute_sql(return_id=True)

            pk_field_name = self.model._meta.pk.name
            return rows[0][pk_field_name]

        # no special action required, use the standard Django create(..)
        return super().create(**fields).pk

    def insert_and_get(self, **fields):
        """Creates a new record in the database and then gets
        the entire row.

        This allows specifying custom conflict behavior using .on_conflict().
        If no special behavior was specified, this uses the normal Django create(..)

        Arguments:
            fields:
                The fields of the row to create.

        Returns:
            The model instance representing the row that was created.
        """

        if not self.conflict_target and not self.conflict_action:
            # no special action required, use the standard Django create(..)
            return super().create(**fields)

        compiler = self._build_insert_compiler([fields])
        rows = compiler.execute_sql(return_id=False)

        columns = rows[0]

        # get a list of columns that are officially part of the model and preserve the fact that the attribute name
        # might be different than the database column name
        model_columns = {}
        for field in self.model._meta.local_concrete_fields:
            model_columns[field.column] = field.attname

        # strip out any columns/fields returned by the db that
        # are not present in the model
        model_init_fields = {}
        for column_name, column_value in columns.items():
            try:
                model_init_fields[model_columns[column_name]] = column_value
            except KeyError:
                pass

        return self.model(**model_init_fields)

    def upsert(self, conflict_target: List, fields: Dict, index_predicate: str=None) -> int:
        """Creates a new record or updates the existing one
        with the specified data.

        Arguments:
            conflict_target:
                Fields to pass into the ON CONFLICT clause.

            fields:
                Fields to insert/update.

            index_predicate:
                The index predicate to satisfy an arbiter partial index (i.e. what partial index to use for checking
                conflicts)

        Returns:
            The primary key of the row that was created/updated.
        """

        self.on_conflict(conflict_target, ConflictAction.UPDATE, index_predicate)
        return self.insert(**fields)

    def upsert_and_get(self, conflict_target: List, fields: Dict, index_predicate: str=None):
        """Creates a new record or updates the existing one
        with the specified data and then gets the row.

        Arguments:
            conflict_target:
                Fields to pass into the ON CONFLICT clause.

            fields:
                Fields to insert/update.

            index_predicate:
                The index predicate to satisfy an arbiter partial index (i.e. what partial index to use for checking
                conflicts)

        Returns:
            The model instance representing the row
            that was created/updated.
        """

        self.on_conflict(conflict_target, ConflictAction.UPDATE, index_predicate)
        return self.insert_and_get(**fields)

    def bulk_upsert(self, conflict_target: List, rows: List[Dict], index_predicate: str=None):
        """Creates a set of new records or updates the existing
        ones with the specified data.

        Arguments:
            conflict_target:
                Fields to pass into the ON CONFLICT clause.

            rows:
                Rows to upsert.

            index_predicate:
                The index predicate to satisfy an arbiter partial index (i.e. what partial index to use for checking
                conflicts)
        """

        if not rows or len(rows) <= 0:
            return

        self.on_conflict(conflict_target, ConflictAction.UPDATE, index_predicate)
        return self.bulk_insert(rows)

    def _build_insert_compiler(self, rows: List[Dict]):
        """Builds the SQL compiler for a insert query.

        Arguments:
            rows:
                A list of dictionaries, where each entry
                describes a record to insert.

        Returns:
            The SQL compiler for the insert.
        """

        # create model objects, we also have to detect cases
        # such as:
        #   [dict(first_name='swen'), dict(fist_name='swen', last_name='kooij')]
        # we need to be certain that each row specifies the exact same
        # amount of fields/columns
        objs = []
        field_count = len(rows[0])
        for index, row in enumerate(rows):
            if field_count != len(row):
                raise SuspiciousOperation((
                    'In bulk upserts, you cannot have rows with different field '
                    'configurations. Row {0} has a different field config than '
                    'the first row.'
                ).format(index))

            objs.append(self.model(**row))

        # indicate this query is going to perform write
        self._for_write = True

        # get the fields to be used during update/insert
        insert_fields, update_fields = self._get_upsert_fields(rows[0])

        # build a normal insert query
        query = PostgresInsertQuery(self.model)
        query.conflict_action = self.conflict_action
        query.conflict_target = self.conflict_target
        query.index_predicate = self.index_predicate
        query.values(objs, insert_fields, update_fields)

        # use the postgresql insert query compiler to transform the insert
        # into an special postgresql insert
        connection = django.db.connections[self.db]
        compiler = PostgresInsertCompiler(query, connection, self.db)

        return compiler

    def _is_magical_field(self, model_instance, field, is_insert: bool):
        """Verifies whether this field is gonna modify something
        on its own.

        "Magical" means that a field modifies the field value
        during the pre_save.

        Arguments:
            model_instance:
                The model instance the field is defined on.

            field:
                The field to get of whether the field is
                magical.

            is_insert:
                Pretend whether this is an insert?

        Returns:
            True when this field modifies something.
        """

        # does this field modify someting upon insert?
        old_value = getattr(model_instance, field.name, None)
        field.pre_save(model_instance, is_insert)
        new_value = getattr(model_instance, field.name, None)

        return old_value != new_value

    def _get_upsert_fields(self, kwargs):
        """Gets the fields to use in an upsert.

        This some nice magic. We'll split the fields into
        a group of "insert fields" and "update fields":

        INSERT INTO bla ("val1", "val2") ON CONFLICT DO UPDATE SET val1 = EXCLUDED.val1

                         ^^^^^^^^^^^^^^                            ^^^^^^^^^^^^^^^^^^^^
                         insert_fields                                 update_fields

        Often, fields appear in both lists. But, for example,
        a :see:DateTime field with `auto_now_add=True` set, will
        only appear in "insert_fields", since it won't be set
        on existing rows.

        Other than that, the user specificies a list of fields
        in the upsert() call. That migt not be all fields. The
        user could decide to leave out optional fields. If we
        end up doing an update, we don't want to overwrite
        those non-specified fields.

        We cannot just take the list of fields the user
        specifies, because as mentioned, some fields
        make modifications to the model on their own.

        We'll have to detect which fields make modifications
        and include them in the list of insert/update fields.
        """

        model_instance = self.model(**kwargs)
        insert_fields = []
        update_fields = []

        for field in model_instance._meta.local_concrete_fields:
            has_default = field.default != NOT_PROVIDED
            if (field.name in kwargs or field.column in kwargs):
                insert_fields.append(field)
                update_fields.append(field)
                continue
            elif has_default:
                insert_fields.append(field)
                continue

            # special handling for 'pk' which always refers to
            # the primary key, so if we the user specifies `pk`
            # instead of a concrete field, we have to handle that
            if field.primary_key is True and 'pk' in kwargs:
                insert_fields.append(field)
                update_fields.append(field)
                continue

            if self._is_magical_field(model_instance, field, is_insert=True):
                insert_fields.append(field)

            if self._is_magical_field(model_instance, field, is_insert=False):
                update_fields.append(field)

        return insert_fields, update_fields

class PostgresManager(models.Manager):
    """Adds support for PostgreSQL specifics."""

    use_in_migrations = True

    def __init__(self, *args, **kwargs):
        """Initializes a new instance of :see:PostgresManager."""

        super(PostgresManager, self).__init__(*args, **kwargs)

        # make sure our back-end is set and refuse to proceed
        # if it's not set
        db_backend = settings.DATABASES['default']['ENGINE']
        if 'psqlextra' not in db_backend:
            raise ImproperlyConfigured((
                '\'%s\' is not the \'psqlextra.backend\'. '
                'django-postgres-extra cannot function without '
                'the \'psqlextra.backend\'. Set DATABASES.ENGINE.'
            ) % db_backend)

        # hook into django signals to then trigger our own

        django.db.models.signals.post_save.connect(
            self._on_model_save, sender=self.model, weak=False)

        django.db.models.signals.pre_delete.connect(
            self._on_model_delete, sender=self.model, weak=False)

        self._signals_connected = True

    def __del__(self):
        """Disconnects signals."""

        if self._signals_connected is False:
            return

        # django.db.models.signals.post_save.disconnect(
        #     self._on_model_save, sender=self.model)

        # django.db.models.signals.pre_delete.disconnect(
        #     self._on_model_delete, sender=self.model)

    def get_queryset(self):
        """Gets the query set to be used on this manager."""

        return PostgresQuerySet(self.model, using=self._db)

    def on_conflict(self, fields: List[Union[str, Tuple[str]]], action, index_predicate: str=None):
        """Sets the action to take when conflicts arise when attempting
        to insert/create a new row.

        Arguments:
            fields:
                The fields the conflicts can occur in.

            action:
                The action to take when the conflict occurs.

            index_predicate:
                The index predicate to satisfy an arbiter partial index.
        """
        return self.get_queryset().on_conflict(fields, action, index_predicate)

    def upsert(self, conflict_target: List, fields: Dict, index_predicate: str=None) -> int:
        """Creates a new record or updates the existing one
        with the specified data.

        Arguments:
            conflict_target:
                Fields to pass into the ON CONFLICT clause.

            fields:
                Fields to insert/update.

            index_predicate:
                The index predicate to satisfy an arbiter partial index.

        Returns:
            The primary key of the row that was created/updated.
        """

        return self.get_queryset().upsert(conflict_target, fields, index_predicate)

    def upsert_and_get(self, conflict_target: List, fields: Dict, index_predicate: str=None):
        """Creates a new record or updates the existing one
        with the specified data and then gets the row.

        Arguments:
            conflict_target:
                Fields to pass into the ON CONFLICT clause.

            fields:
                Fields to insert/update.

            index_predicate:
                The index predicate to satisfy an arbiter partial index.

        Returns:
            The model instance representing the row
            that was created/updated.
        """

        return self.get_queryset().upsert_and_get(conflict_target, fields, index_predicate)

    def bulk_upsert(self, conflict_target: List, rows: List[Dict], index_predicate: str=None):
        """Creates a set of new records or updates the existing
        ones with the specified data.

        Arguments:
            conflict_target:
                Fields to pass into the ON CONFLICT clause.

            index_predicate:
                The index predicate to satisfy an arbiter partial index.

            rows:
                Rows to upsert.
        """

        return self.get_queryset().bulk_upsert(conflict_target, rows, index_predicate)

    @staticmethod
    def _on_model_save(sender, **kwargs):
        """When a model gets created or updated."""

        created, instance = kwargs['created'], kwargs['instance']

        if created:
            signals.create.send(sender, pk=instance.pk)
        else:
            signals.update.send(sender, pk=instance.pk)

    @staticmethod
    def _on_model_delete(sender, **kwargs):
        """When a model gets deleted."""

        instance = kwargs['instance']
        signals.delete.send(sender, pk=instance.pk)


from django.db.models import expressions, CharField

class HStoreValue(expressions.Expression):
    """Represents a HStore value.

    The base PostgreSQL implementation Django provides,
    always represents HStore values as dictionaries,
    but this doesn't work if you want to use expressions
    inside hstore values."""

    def __init__(self, value):
        """Initializes a new instance."""

        self.value = value

    def resolve_expression(self, *args, **kwargs):
        """Resolves expressions inside the dictionary."""

        result = dict()
        for key, value in self.value.items():
            if hasattr(value, 'resolve_expression'):
                result[key] = value.resolve_expression(
                    *args, **kwargs)
            else:
                result[key] = value

        return HStoreValue(result)

    def as_sql(self, compiler, connection):
        """Compiles the HStore value into SQL.

        Compiles expressions contained in the values
        of HStore entries as well.

        Given a dictionary like:

            dict(key1='val1', key2='val2')

        The resulting SQL will be:

            hstore(hstore('key1', 'val1'), hstore('key2', 'val2'))
        """

        result = []
        for key, value in self.value.items():
            if hasattr(value, 'as_sql'):
                sql, params = value.as_sql(compiler, connection)
                result.append('hstore(\'%s\', %s)' % (
                    key, sql % params))
            elif value is not None:
                result.append('hstore(\'%s\', \'%s\')' % ((
                    key, value)))
            else:
                result.append('hstore(\'%s\', NULL)' % key)

        return '%s' % ' || '.join(result), []

class HStoreColumn(expressions.Col):
    """HStoreColumn expression.

    Generates expressions like:

    [db table].[column]->'[hstore key]'
    """

    contains_column_references = True

    def __init__(self, alias, target, hstore_key):
        """Initializes a new instance of :see:HStoreColumn.

        Arguments:
            alias:
                The table name.

            target:
                The field instance.

            hstore_key
                The name of the hstore key to include
                in the epxression.
        """

        super().__init__(alias, target, output_field=target)
        self.alias, self.target, self.hstore_key = alias, target, hstore_key

    def __repr__(self):
        """Gets a textual representation of this expresion."""

        return "{}({}, {}->'{}')".format(
            self.__class__.__name__,
            self.alias,
            self.target,
            self.hstore_key
        )

    def as_sql(self, compiler, connection):
        """Compiles this expression into SQL."""

        qn = compiler.quote_name_unless_alias
        return "%s.%s->'%s'" % (qn(self.alias), qn(self.target.column), self.hstore_key), []

    def relabeled_clone(self, relabels):
        """Gets a re-labeled clone of this expression."""

        return self.__class__(
            relabels.get(self.alias, self.alias),
            self.target,
            self.hstore_key,
            self.output_field
        )

class HStoreRef(expressions.F):
    """Inline reference to a HStore key.

    Allows selecting individual keys in annotations.
    """

    def __init__(self, name: str, key: str):
        """Initializes a new instance of :see:HStoreRef.

        Arguments:
            name:
                The name of the column/field to resolve.

            key:
                The name of the HStore key to select.
        """

        super().__init__(name)
        self.key = key

    def resolve_expression(self, *args, **kwargs) -> HStoreColumn:
        """Resolves the expression into a :see:HStoreColumn expression."""

        original_expression = super().resolve_expression(*args, **kwargs)
        expression = HStoreColumn(
            original_expression.alias,
            original_expression.target,
            self.key
        )
        return expression

class NonGroupableFunc(expressions.Func):
    """A version of Django's :see:Func expression that
    is _never_ included in the GROUP BY clause."""

    def get_group_by_cols(self):
        """Gets the columns to be included in the GROUP BY clause.

        We have to override this because Django's default behavior
        is to include function calls in GROUP by clauses."""
        return []

class Min(NonGroupableFunc):
    """Exposes PostgreSQL's MIN(..) func."""

    def __init__(self, expression):
        super().__init__(expression, function='MIN')

class Max(NonGroupableFunc):
    """Exposes PostgreSQL's Max(..) func."""

    def __init__(self, expression):
        super().__init__(expression, function='Max')

class DateTimeEpochColumn(expressions.Col):
    """Gets the date/time column as a UNIX epoch timestamp."""

    contains_column_references = True

    def as_sql(self, compiler, connection):
        """Compiles this expression into SQL."""

        sql, params = super().as_sql(compiler, connection)
        return 'EXTRACT(epoch FROM {})'.format(sql), params

    def get_group_by_cols(self):
        return []

class DateTimeEpoch(expressions.F):
    """Gets the date/time column as a UNIX epoch timestamp."""

    contains_aggregate = False

    def resolve_expression(self, *args, **kwargs):
        original_expression = super().resolve_expression(*args, **kwargs)
        expression = DateTimeEpochColumn(
            original_expression.alias,
            original_expression.target,
        )
        return expression

def IsNotNone(*fields, default=None):
    """Selects whichever field is not None, in the specified order.

    Arguments:
        fields:
            The fields to attempt to get a value from,
            in order.

        default:
            The value to return in case all values are None.

    Returns:
        A Case-When expression that tries each field and
        returns the specified default value when all of
        them are None.
    """

    when_clauses = [
        expressions.When(
            ~expressions.Q(**{field: None}),
            then=expressions.F(field)
        )
        for field in reversed(fields)
    ]

    return expressions.Case(
        *when_clauses,
        default=expressions.Value(default),
        output_field=CharField()
    )