##############################################################################
# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://github.com/llnl/spack
# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License (as
# published by the Free Software Foundation) version 2.1, February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
"""These tests ensure that our lock works correctly.

This can be run in two ways.

First, it can be run as a node-local test, with a typical invocation like
this::

    spack test lock

You can *also* run it as an MPI program, which allows you to test locks
across nodes.  So, e.g., you can run the test like this::

    mpirun -n 7 spack test lock

And it will test locking correctness among MPI processes.  Ideally, you
want the MPI processes to span across multiple nodes, so, e.g., for SLURM
you might do this::

    srun -N 7 -n 7 -m cyclic spack test lock

You can use this to test whether your shared filesystem properly supports
POSIX reader-writer locking with byte ranges through fcntl.

If you want to test on multiple filesystems, you can modify the
``locations`` list below.  By default it looks like this::

    locations = [
        tempfile.gettempdir(),  # standard tmp directory (potentially local)
        '/nfs/tmp2/%u',         # NFS tmp mount
        '/p/lscratch*/%u'       # Lustre scratch mount
    ]

Add names and paths for your preferred filesystem mounts to test on them;
the tests are parametrized to run on all the filesystems listed in this
dict.  Note that 'tmp' will be skipped for MPI testing, as it is often a
node-local filesystem, and multi-node tests will fail if the locks aren't
actually on a shared filesystem.

"""
import os
import shutil
import tempfile
import traceback
import glob
import getpass
from contextlib import contextmanager
from multiprocessing import Process

import pytest

from llnl.util.filesystem import join_path, touch
from llnl.util.lock import *
from spack.util.multiproc import Barrier

#
# This test can be run with MPI.  MPI is "enabled" if we can import
# mpi4py and the number of total MPI processes is greater than 1.
# Otherwise it just runs as a node-local test.
#
# NOTE: MPI mode is different from node-local mode in that node-local
# mode will spawn its own test processes, while MPI mode assumes you've
# run this script as a SPMD application.  In MPI mode, no additional
# processes are spawned, and you need to ensure that you mpirun the
# script with enough processes for all the multiproc_test cases below.
#
# If you don't run with enough processes, tests that require more
# processes than you currently have will be skipped.
#
mpi = False
comm = None
try:
    from mpi4py import MPI
    comm = MPI.COMM_WORLD
    if comm.size > 1:
        mpi = True
except:
    pass

"""This is a list of filesystem locations to test locks in.  Paths are
expanded so that %u is replaced with the current username. '~' is also
legal and will be expanded to the user's home directory.

Tests are skipped for directories that don't exist, so you'll need to
update this with the locations of NFS, Lustre, and other mounts on your
system.
"""
locations = [
    tempfile.gettempdir(),
    os.path.join('/nfs/tmp2/', getpass.getuser()),
    os.path.join('/p/lscratch*/', getpass.getuser()),
]

"""This is the longest a failed multiproc test will take.
Barriers will time out and raise an exception after this interval.
In MPI mode, barriers don't time out (they hang).  See mpi_multiproc_test.
"""
barrier_timeout = 5

"""This is the lock timeout for expected failures.
This may need to be higher for some filesystems."""
lock_fail_timeout = 0.1

@contextmanager
def read_only(path):
    orginal_mode = os.stat(path).st_mode
    os.chmod(path, 0o444)
    yield
    os.chmod(path, orginal_mode)

@pytest.fixture(scope='session', params=locations)
def lock_test_directory(request):
    """This fixture causes tests to be executed for many different mounts.

    See the ``locations`` dict above for details.
    """
    return request.param

@pytest.fixture(scope='session')
def lock_dir(lock_test_directory):
    parent = next((p for p in glob.glob(lock_test_directory)
                   if os.path.exists(p) and os.access(p, os.W_OK)), None)
    if not parent:
        # Skip filesystems that don't exist or aren't writable
        pytest.skip("requires filesystem: '%s'" % lock_test_directory)
    elif mpi and parent == tempfile.gettempdir():
        # Skip local tmp test for MPI runs
        pytest.skip("skipping local tmp directory for MPI test.")

    tempdir = None
    if not mpi or comm.rank == 0:
        tempdir = tempfile.mkdtemp(dir=parent)
    if mpi:
        tempdir = comm.bcast(tempdir)

    yield tempdir

    if mpi:
        # rank 0 may get here before others, in which case it'll try to
        # remove the directory while other processes try to re-create the
        # lock.  This will give errno 39: directory not empty.  Use a
        # barrier to ensure everyone is done first.
        comm.barrier()

    if not mpi or comm.rank == 0:
        shutil.rmtree(tempdir)

@pytest.fixture
def private_lock_path(lock_dir):
    """In MPI mode, this is a private lock for each rank in a multiproc test.

    For other modes, it is the same as a shared lock.
    """
    lock_file = join_path(lock_dir, 'lockfile')
    if mpi:
        lock_file += '.%s' % comm.rank
    yield lock_file

@pytest.fixture
def lock_path(lock_dir):
    """This lock is shared among all processes in a multiproc test."""
    lock_file = join_path(lock_dir, 'lockfile')
    yield lock_file

def local_multiproc_test(*functions):
    """Order some processes using simple barrier synchronization."""
    b = Barrier(len(functions), timeout=barrier_timeout)
    procs = [Process(target=f, args=(b,)) for f in functions]

    for p in procs:
        p.start()

    for p in procs:
        p.join()
        assert p.exitcode == 0

def mpi_multiproc_test(*functions):
    """SPMD version of multiproc test.

    This needs to be run like so:

           srun spack test lock

    Each process executes its corresponding function.  This is different
    from ``multiproc_test`` above, which spawns the processes. This will
    skip tests if there are too few processes to run them.
    """
    procs = len(functions)
    if procs > comm.size:
        pytest.skip("requires at least %d MPI processes" % procs)

    comm.Barrier()  # barrier before each MPI test

    include = comm.rank < len(functions)
    subcomm = comm.Split(include)

    class subcomm_barrier(object):
        """Stand-in for multiproc barrier for MPI-parallel jobs."""
        def wait(self):
            subcomm.Barrier()

    if include:
        try:
            functions[subcomm.rank](subcomm_barrier())
        except:
            # aborting is the best we can do for MPI tests without
            # hanging, since we're using MPI barriers. This will fail
            # early and it loses the nice pytest output, but at least it
            # gets use a stacktrace on the processes that failed.
            traceback.print_exc()
            comm.Abort()
    subcomm.Free()

    comm.Barrier()  # barrier after each MPI test.

"""``multiproc_test()`` should be called by tests below.
``multiproc_test()`` will work for either MPI runs or for local runs.
"""
multiproc_test = mpi_multiproc_test if mpi else local_multiproc_test

#
# Process snippets below can be composed into tests.
#
def acquire_write(lock_path, start=0, length=0):
    def fn(barrier):
        lock = Lock(lock_path, start, length)
        lock.acquire_write()  # grab exclusive lock
        barrier.wait()
        barrier.wait()  # hold the lock until timeout in other procs.
    return fn

def acquire_read(lock_path, start=0, length=0):
    def fn(barrier):
        lock = Lock(lock_path, start, length)
        lock.acquire_read()  # grab shared lock
        barrier.wait()
        barrier.wait()  # hold the lock until timeout in other procs.
    return fn

def timeout_write(lock_path, start=0, length=0):
    def fn(barrier):
        lock = Lock(lock_path, start, length)
        barrier.wait()  # wait for lock acquire in first process
        with pytest.raises(LockError):
            lock.acquire_write(lock_fail_timeout)
        barrier.wait()
    return fn

def timeout_read(lock_path, start=0, length=0):
    def fn(barrier):
        lock = Lock(lock_path, start, length)
        barrier.wait()  # wait for lock acquire in first process
        with pytest.raises(LockError):
            lock.acquire_read(lock_fail_timeout)
        barrier.wait()
    return fn

#
# Test that exclusive locks on other processes time out when an
# exclusive lock is held.
#
def test_write_lock_timeout_on_write(lock_path):
    multiproc_test(
        acquire_write(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_on_write_2(lock_path):
    multiproc_test(
        acquire_write(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_on_write_3(lock_path):
    multiproc_test(
        acquire_write(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_on_write_ranges(lock_path):
    multiproc_test(
        acquire_write(lock_path, 0, 1),
        timeout_write(lock_path, 0, 1))

def test_write_lock_timeout_on_write_ranges_2(lock_path):
    multiproc_test(
        acquire_write(lock_path, 0, 64),
        acquire_write(lock_path, 65, 1),
        timeout_write(lock_path, 0, 1),
        timeout_write(lock_path, 63, 1))

def test_write_lock_timeout_on_write_ranges_3(lock_path):
    multiproc_test(
        acquire_write(lock_path, 0, 1),
        acquire_write(lock_path, 1, 1),
        timeout_write(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_on_write_ranges_4(lock_path):
    multiproc_test(
        acquire_write(lock_path, 0, 1),
        acquire_write(lock_path, 1, 1),
        acquire_write(lock_path, 2, 456),
        acquire_write(lock_path, 500, 64),
        timeout_write(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path))

#
# Test that shared locks on other processes time out when an
# exclusive lock is held.
#
def test_read_lock_timeout_on_write(lock_path):
    multiproc_test(
        acquire_write(lock_path),
        timeout_read(lock_path))

def test_read_lock_timeout_on_write_2(lock_path):
    multiproc_test(
        acquire_write(lock_path),
        timeout_read(lock_path),
        timeout_read(lock_path))

def test_read_lock_timeout_on_write_3(lock_path):
    multiproc_test(
        acquire_write(lock_path),
        timeout_read(lock_path),
        timeout_read(lock_path),
        timeout_read(lock_path))

def test_read_lock_timeout_on_write_ranges(lock_path):
    """small write lock, read whole file."""
    multiproc_test(
        acquire_write(lock_path, 0, 1),
        timeout_read(lock_path))

def test_read_lock_timeout_on_write_ranges_2(lock_path):
    """small write lock, small read lock"""
    multiproc_test(
        acquire_write(lock_path, 0, 1),
        timeout_read(lock_path, 0, 1))

def test_read_lock_timeout_on_write_ranges_3(lock_path):
    """two write locks, overlapping read locks"""
    multiproc_test(
        acquire_write(lock_path, 0, 1),
        acquire_write(lock_path, 64, 128),
        timeout_read(lock_path, 0, 1),
        timeout_read(lock_path, 128, 256))

#
# Test that exclusive locks time out when shared locks are held.
#
def test_write_lock_timeout_on_read(lock_path):
    multiproc_test(
        acquire_read(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_on_read_2(lock_path):
    multiproc_test(
        acquire_read(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_on_read_3(lock_path):
    multiproc_test(
        acquire_read(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_on_read_ranges(lock_path):
    multiproc_test(
        acquire_read(lock_path, 0, 1),
        timeout_write(lock_path))

def test_write_lock_timeout_on_read_ranges_2(lock_path):
    multiproc_test(
        acquire_read(lock_path, 0, 1),
        timeout_write(lock_path, 0, 1))

def test_write_lock_timeout_on_read_ranges_3(lock_path):
    multiproc_test(
        acquire_read(lock_path, 0, 1),
        acquire_read(lock_path, 10, 1),
        timeout_write(lock_path, 0, 1),
        timeout_write(lock_path, 10, 1))

def test_write_lock_timeout_on_read_ranges_4(lock_path):
    multiproc_test(
        acquire_read(lock_path, 0, 64),
        timeout_write(lock_path, 10, 1),
        timeout_write(lock_path, 32, 1))

def test_write_lock_timeout_on_read_ranges_5(lock_path):
    multiproc_test(
        acquire_read(lock_path, 64, 128),
        timeout_write(lock_path, 65, 1),
        timeout_write(lock_path, 127, 1),
        timeout_write(lock_path, 90, 10))

#
# Test that exclusive locks time while lots of shared locks are held.
#
def test_write_lock_timeout_with_multiple_readers_2_1(lock_path):
    multiproc_test(
        acquire_read(lock_path),
        acquire_read(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_with_multiple_readers_2_2(lock_path):
    multiproc_test(
        acquire_read(lock_path),
        acquire_read(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_with_multiple_readers_3_1(lock_path):
    multiproc_test(
        acquire_read(lock_path),
        acquire_read(lock_path),
        acquire_read(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_with_multiple_readers_3_2(lock_path):
    multiproc_test(
        acquire_read(lock_path),
        acquire_read(lock_path),
        acquire_read(lock_path),
        timeout_write(lock_path),
        timeout_write(lock_path))

def test_write_lock_timeout_with_multiple_readers_2_1_ranges(lock_path):
    multiproc_test(
        acquire_read(lock_path, 0, 10),
        acquire_read(lock_path, 0.5, 10),
        timeout_write(lock_path, 5, 5))

def test_write_lock_timeout_with_multiple_readers_2_3_ranges(lock_path):
    multiproc_test(
        acquire_read(lock_path, 0, 10),
        acquire_read(lock_path, 5, 15),
        timeout_write(lock_path, 0, 1),
        timeout_write(lock_path, 11, 3),
        timeout_write(lock_path, 7, 1))

def test_write_lock_timeout_with_multiple_readers_3_1_ranges(lock_path):
    multiproc_test(
        acquire_read(lock_path, 0, 5),
        acquire_read(lock_path, 5, 5),
        acquire_read(lock_path, 10, 5),
        timeout_write(lock_path, 0, 15))

def test_write_lock_timeout_with_multiple_readers_3_2_ranges(lock_path):
    multiproc_test(
        acquire_read(lock_path, 0, 5),
        acquire_read(lock_path, 5, 5),
        acquire_read(lock_path, 10, 5),
        timeout_write(lock_path, 3, 10),
        timeout_write(lock_path, 5, 1))

#
# Test that read can be upgraded to write.
#
def test_upgrade_read_to_write(private_lock_path):
    """Test that a read lock can be upgraded to a write lock.

    Note that to upgrade a read lock to a write lock, you have the be the
    only holder of a read lock.  Client code needs to coordinate that for
    shared locks.  For this test, we use a private lock just to test that an
    upgrade is possible.
    """
    # ensure lock file exists the first time, so we open it read-only
    # to begin wtih.
    touch(private_lock_path)

    lock = Lock(private_lock_path)
    assert lock._reads == 0
    assert lock._writes == 0

    lock.acquire_read()
    assert lock._reads == 1
    assert lock._writes == 0
    assert lock._file.mode == 'r+'

    lock.acquire_write()
    assert lock._reads == 1
    assert lock._writes == 1
    assert lock._file.mode == 'r+'

    lock.release_write()
    assert lock._reads == 1
    assert lock._writes == 0
    assert lock._file.mode == 'r+'

    lock.release_read()
    assert lock._reads == 0
    assert lock._writes == 0
    assert lock._file is None

#
# Test that read-only file can be read-locked but not write-locked.
#
def test_upgrade_read_to_write_fails_with_readonly_file(private_lock_path):
    # ensure lock file exists the first time, so we open it read-only
    # to begin wtih.
    touch(private_lock_path)

    with read_only(private_lock_path):
        lock = Lock(private_lock_path)
        assert lock._reads == 0
        assert lock._writes == 0

        lock.acquire_read()
        assert lock._reads == 1
        assert lock._writes == 0
        assert lock._file.mode == 'r'

        with pytest.raises(LockError):
            lock.acquire_write()

#
# Longer test case that ensures locks are reusable. Ordering is
# enforced by barriers throughout -- steps are shown with numbers.
#
def test_complex_acquire_and_release_chain(lock_path):
    def p1(barrier):
        lock = Lock(lock_path)

        lock.acquire_write()
        barrier.wait()  # ---------------------------------------- 1
        # others test timeout
        barrier.wait()  # ---------------------------------------- 2
        lock.release_write()   # release and others acquire read
        barrier.wait()  # ---------------------------------------- 3
        with pytest.raises(LockError):
            lock.acquire_write(lock_fail_timeout)
        lock.acquire_read()
        barrier.wait()  # ---------------------------------------- 4
        lock.release_read()
        barrier.wait()  # ---------------------------------------- 5

        # p2 upgrades read to write
        barrier.wait()  # ---------------------------------------- 6
        with pytest.raises(LockError):
            lock.acquire_write(lock_fail_timeout)
        with pytest.raises(LockError):
            lock.acquire_read(lock_fail_timeout)
        barrier.wait()  # ---------------------------------------- 7
        # p2 releases write and read
        barrier.wait()  # ---------------------------------------- 8

        # p3 acquires read
        barrier.wait()  # ---------------------------------------- 9
        # p3 upgrades read to write
        barrier.wait()  # ---------------------------------------- 10
        with pytest.raises(LockError):
            lock.acquire_write(lock_fail_timeout)
        with pytest.raises(LockError):
            lock.acquire_read(lock_fail_timeout)
        barrier.wait()  # ---------------------------------------- 11
        # p3 releases locks
        barrier.wait()  # ---------------------------------------- 12
        lock.acquire_read()
        barrier.wait()  # ---------------------------------------- 13
        lock.release_read()

    def p2(barrier):
        lock = Lock(lock_path)

        # p1 acquires write
        barrier.wait()  # ---------------------------------------- 1
        with pytest.raises(LockError):
            lock.acquire_write(lock_fail_timeout)
        with pytest.raises(LockError):
            lock.acquire_read(lock_fail_timeout)
        barrier.wait()  # ---------------------------------------- 2
        lock.acquire_read()
        barrier.wait()  # ---------------------------------------- 3
        # p1 tests shared read
        barrier.wait()  # ---------------------------------------- 4
        # others release reads
        barrier.wait()  # ---------------------------------------- 5

        lock.acquire_write()  # upgrade read to write
        barrier.wait()  # ---------------------------------------- 6
        # others test timeout
        barrier.wait()  # ---------------------------------------- 7
        lock.release_write()  # release read AND write (need both)
        lock.release_read()
        barrier.wait()  # ---------------------------------------- 8

        # p3 acquires read
        barrier.wait()  # ---------------------------------------- 9
        # p3 upgrades read to write
        barrier.wait()  # ---------------------------------------- 10
        with pytest.raises(LockError):
            lock.acquire_write(lock_fail_timeout)
        with pytest.raises(LockError):
            lock.acquire_read(lock_fail_timeout)
        barrier.wait()  # ---------------------------------------- 11
        # p3 releases locks
        barrier.wait()  # ---------------------------------------- 12
        lock.acquire_read()
        barrier.wait()  # ---------------------------------------- 13
        lock.release_read()

    def p3(barrier):
        lock = Lock(lock_path)

        # p1 acquires write
        barrier.wait()  # ---------------------------------------- 1
        with pytest.raises(LockError):
            lock.acquire_write(lock_fail_timeout)
        with pytest.raises(LockError):
            lock.acquire_read(lock_fail_timeout)
        barrier.wait()  # ---------------------------------------- 2
        lock.acquire_read()
        barrier.wait()  # ---------------------------------------- 3
        # p1 tests shared read
        barrier.wait()  # ---------------------------------------- 4
        lock.release_read()
        barrier.wait()  # ---------------------------------------- 5

        # p2 upgrades read to write
        barrier.wait()  # ---------------------------------------- 6
        with pytest.raises(LockError):
            lock.acquire_write(lock_fail_timeout)
        with pytest.raises(LockError):
            lock.acquire_read(lock_fail_timeout)
        barrier.wait()  # ---------------------------------------- 7
        # p2 releases write & read
        barrier.wait()  # ---------------------------------------- 8

        lock.acquire_read()
        barrier.wait()  # ---------------------------------------- 9
        lock.acquire_write()
        barrier.wait()  # ---------------------------------------- 10
        # others test timeout
        barrier.wait()  # ---------------------------------------- 11
        lock.release_read()   # release read AND write in opposite
        lock.release_write()  # order from before on p2
        barrier.wait()  # ---------------------------------------- 12
        lock.acquire_read()
        barrier.wait()  # ---------------------------------------- 13
        lock.release_read()

    multiproc_test(p1, p2, p3)

def test_transaction(lock_path):
    def enter_fn():
        vals['entered'] = True

    def exit_fn(t, v, tb):
        vals['exited'] = True
        vals['exception'] = (t or v or tb)

    lock = Lock(lock_path)
    vals = {'entered': False, 'exited': False, 'exception': False}
    with ReadTransaction(lock, enter_fn, exit_fn):
        pass

    assert vals['entered']
    assert vals['exited']
    assert not vals['exception']

    vals = {'entered': False, 'exited': False, 'exception': False}
    with WriteTransaction(lock, enter_fn, exit_fn):
        pass

    assert vals['entered']
    assert vals['exited']
    assert not vals['exception']

def test_transaction_with_exception(lock_path):
    def enter_fn():
        vals['entered'] = True

    def exit_fn(t, v, tb):
        vals['exited'] = True
        vals['exception'] = (t or v or tb)

    lock = Lock(lock_path)

    def do_read_with_exception():
        with ReadTransaction(lock, enter_fn, exit_fn):
            raise Exception()

    def do_write_with_exception():
        with WriteTransaction(lock, enter_fn, exit_fn):
            raise Exception()

    vals = {'entered': False, 'exited': False, 'exception': False}
    with pytest.raises(Exception):
        do_read_with_exception()
    assert vals['entered']
    assert vals['exited']
    assert vals['exception']

    vals = {'entered': False, 'exited': False, 'exception': False}
    with pytest.raises(Exception):
        do_write_with_exception()
    assert vals['entered']
    assert vals['exited']
    assert vals['exception']

def test_transaction_with_context_manager(lock_path):
    class TestContextManager(object):

        def __enter__(self):
            vals['entered'] = True

        def __exit__(self, t, v, tb):
            vals['exited'] = True
            vals['exception'] = (t or v or tb)

    def exit_fn(t, v, tb):
        vals['exited_fn'] = True
        vals['exception_fn'] = (t or v or tb)

    lock = Lock(lock_path)

    vals = {'entered': False, 'exited': False, 'exited_fn': False,
            'exception': False, 'exception_fn': False}
    with ReadTransaction(lock, TestContextManager, exit_fn):
        pass

    assert vals['entered']
    assert vals['exited']
    assert not vals['exception']
    assert vals['exited_fn']
    assert not vals['exception_fn']

    vals = {'entered': False, 'exited': False, 'exited_fn': False,
            'exception': False, 'exception_fn': False}


# encoding utf-8

__author__ = "Thadeus Burgess <thadeusb@thadeusb.com>"

#    we classify as "non-reserved" those key words that are explicitly known
#    to the parser but are allowed as column or table names. Some key words
#    that are otherwise non-reserved cannot be used as function or data type n
#    ames and are in the nonreserved list. (Most of these words represent
#    built-in functions or data types with special syntax. The function
#    or type is still available but it cannot be redefined by the user.)
#    Labeled "reserved" are those tokens that are not allowed as column or
#    table names. Some reserved key words are allowable as names for
#    functions or data typesself.

# Note at the bottom of the list is a dict containing references to the
# tuples, and also if you add a list don't forget to remove its default
# set of COMMON.

# Keywords that are adapter specific. Such as a list of "postgresql"
# or "mysql" keywords

# These are keywords that are common to all SQL dialects, and should
# never be used as a table or column. Even if you use one of these
# the cursor will throw an OperationalError for the SQL syntax.

COMMON = set((
    'SELECT',
    'INSERT',
    'DELETE',
    'UPDATE',
    'DROP',
    'CREATE',
    'ALTER',

    'WHERE',
    'FROM',
    'INNER',
    'JOIN',
    'AND',
    'OR',
    'LIKE',
    'ON',
    'IN',
    'SET',

    'BY',
    'GROUP',
    'ORDER',
    'LEFT',
    'OUTER',

    'IF',
    'END',
    'THEN',
    'LOOP',
    'AS',
    'ELSE',
    'FOR',

    'CASE',
    'WHEN',
    'MIN',
    'MAX',
    'DISTINCT',
))

POSTGRESQL = set((
    'FALSE',
    'TRUE',
    'ALL',
    'ANALYSE',
    'ANALYZE',
    'AND',
    'ANY',
    'ARRAY',
    'AS',
    'ASC',
    'ASYMMETRIC',
    'AUTHORIZATION',
    'BETWEEN',
    'BIGINT',
    'BINARY',
    'BIT',
    'BOOLEAN',
    'BOTH',
    'CASE',
    'CAST',
    'CHAR',
    'CHARACTER',
    'CHECK',
    'COALESCE',
    'COLLATE',
    'COLUMN',
    'CONSTRAINT',
    'CREATE',
    'CROSS',
    'CURRENT_CATALOG',
    'CURRENT_DATE',
    'CURRENT_ROLE',
    'CURRENT_SCHEMA',
    'CURRENT_TIME',
    'CURRENT_TIMESTAMP',
    'CURRENT_USER',
    'DEC',
    'DECIMAL',
    'DEFAULT',
    'DEFERRABLE',
    'DESC',
    'DISTINCT',
    'DO',
    'ELSE',
    'END',
    'EXCEPT',
    'EXISTS',
    'EXTRACT',
    'FETCH',
    'FLOAT',
    'FOR',
    'FOREIGN',
    'FREEZE',
    'FROM',
    'FULL',
    'GRANT',
    'GREATEST',
    'GROUP',
    'HAVING',
    'ILIKE',
    'IN',
    'INITIALLY',
    'INNER',
    'INOUT',
    'INT',
    'INTEGER',
    'INTERSECT',
    'INTERVAL',
    'INTO',
    'IS',
    'ISNULL',
    'JOIN',
    'LEADING',
    'LEAST',
    'LEFT',
    'LIKE',
    'LIMIT',
    'LOCALTIME',
    'LOCALTIMESTAMP',
    'NATIONAL',
    'NATURAL',
    'NCHAR',
    'NEW',
    'NONE',
    'NOT',
    'NOTNULL',
    'NULL',
    'NULLIF',
    'NUMERIC',
    'OFF',
    'OFFSET',
    'OLD',
    'ON',
    'ONLY',
    'OR',
    'ORDER',
    'OUT',
    'OUTER',
    'OVERLAPS',
    'OVERLAY',
    'PLACING',
    'POSITION',
    'PRECISION',
    'PRIMARY',
    'REAL',
    'REFERENCES',
    'RETURNING',
    'RIGHT',
    'ROW',
    'SELECT',
    'SESSION_USER',
    'SETOF',
    'SIMILAR',
    'SMALLINT',
    'SOME',
    'SUBSTRING',
    'SYMMETRIC',
    'TABLE',
    'THEN',
    'TIME',
    'TIMESTAMP',
    'TO',
    'TRAILING',
    'TREAT',
    'TRIM',
    'UNION',
    'UNIQUE',
    'USER',
    'USING',
    'VALUES',
    'VARCHAR',
    'VARIADIC',
    'VERBOSE',
    'WHEN',
    'WHERE',
    'WITH',
    'XMLATTRIBUTES',
    'XMLCONCAT',
    'XMLELEMENT',
    'XMLFOREST',
    'XMLPARSE',
    'XMLPI',
    'XMLROOT',
    'XMLSERIALIZE',
))

POSTGRESQL_NONRESERVED = set((
    'A',
    'ABORT',
    'ABS',
    'ABSENT',
    'ABSOLUTE',
    'ACCESS',
    'ACCORDING',
    'ACTION',
    'ADA',
    'ADD',
    'ADMIN',
    'AFTER',
    'AGGREGATE',
    'ALIAS',
    'ALLOCATE',
    'ALSO',
    'ALTER',
    'ALWAYS',
    'ARE',
    'ARRAY_AGG',
    'ASENSITIVE',
    'ASSERTION',
    'ASSIGNMENT',
    'AT',
    'ATOMIC',
    'ATTRIBUTE',
    'ATTRIBUTES',
    'AVG',
    'BACKWARD',
    'BASE64',
    'BEFORE',
    'BEGIN',
    'BERNOULLI',
    'BIT_LENGTH',
    'BITVAR',
    'BLOB',
    'BOM',
    'BREADTH',
    'BY',
    'C',
    'CACHE',
    'CALL',
    'CALLED',
    'CARDINALITY',
    'CASCADE',
    'CASCADED',
    'CATALOG',
    'CATALOG_NAME',
    'CEIL',
    'CEILING',
    'CHAIN',
    'CHAR_LENGTH',
    'CHARACTER_LENGTH',
    'CHARACTER_SET_CATALOG',
    'CHARACTER_SET_NAME',
    'CHARACTER_SET_SCHEMA',
    'CHARACTERISTICS',
    'CHARACTERS',
    'CHECKED',
    'CHECKPOINT',
    'CLASS',
    'CLASS_ORIGIN',
    'CLOB',
    'CLOSE',
    'CLUSTER',
    'COBOL',
    'COLLATION',
    'COLLATION_CATALOG',
    'COLLATION_NAME',
    'COLLATION_SCHEMA',
    'COLLECT',
    'COLUMN_NAME',
    'COLUMNS',
    'COMMAND_FUNCTION',
    'COMMAND_FUNCTION_CODE',
    'COMMENT',
    'COMMIT',
    'COMMITTED',
    'COMPLETION',
    'CONCURRENTLY',
    'CONDITION',
    'CONDITION_NUMBER',
    'CONFIGURATION',
    'CONNECT',
    'CONNECTION',
    'CONNECTION_NAME',
    'CONSTRAINT_CATALOG',
    'CONSTRAINT_NAME',
    'CONSTRAINT_SCHEMA',
    'CONSTRAINTS',
    'CONSTRUCTOR',
    'CONTAINS',
    'CONTENT',
    'CONTINUE',
    'CONVERSION',
    'CONVERT',
    'COPY',
    'CORR',
    'CORRESPONDING',
    'COST',
    'COUNT',
    'COVAR_POP',
    'COVAR_SAMP',
    'CREATEDB',
    'CREATEROLE',
    'CREATEUSER',
    'CSV',
    'CUBE',
    'CUME_DIST',
    'CURRENT',
    'CURRENT_DEFAULT_TRANSFORM_GROUP',
    'CURRENT_PATH',
    'CURRENT_TRANSFORM_GROUP_FOR_TYPE',
    'CURSOR',
    'CURSOR_NAME',
    'CYCLE',
    'DATA',
    'DATABASE',
    'DATE',
    'DATETIME_INTERVAL_CODE',
    'DATETIME_INTERVAL_PRECISION',
    'DAY',
    'DEALLOCATE',
    'DECLARE',
    'DEFAULTS',
    'DEFERRED',
    'DEFINED',
    'DEFINER',
    'DEGREE',
    'DELETE',
    'DELIMITER',
    'DELIMITERS',
    'DENSE_RANK',
    'DEPTH',
    'DEREF',
    'DERIVED',
    'DESCRIBE',
    'DESCRIPTOR',
    'DESTROY',
    'DESTRUCTOR',
    'DETERMINISTIC',
    'DIAGNOSTICS',
    'DICTIONARY',
    'DISABLE',
    'DISCARD',
    'DISCONNECT',
    'DISPATCH',
    'DOCUMENT',
    'DOMAIN',
    'DOUBLE',
    'DROP',
    'DYNAMIC',
    'DYNAMIC_FUNCTION',
    'DYNAMIC_FUNCTION_CODE',
    'EACH',
    'ELEMENT',
    'EMPTY',
    'ENABLE',
    'ENCODING',
    'ENCRYPTED',
    'END-EXEC',
    'ENUM',
    'EQUALS',
    'ESCAPE',
    'EVERY',
    'EXCEPTION',
    'EXCLUDE',
    'EXCLUDING',
    'EXCLUSIVE',
    'EXEC',
    'EXECUTE',
    'EXISTING',
    'EXP',
    'EXPLAIN',
    'EXTERNAL',
    'FAMILY',
    'FILTER',
    'FINAL',
    'FIRST',
    'FIRST_VALUE',
    'FLAG',
    'FLOOR',
    'FOLLOWING',
    'FORCE',
    'FORTRAN',
    'FORWARD',
    'FOUND',
    'FREE',
    'FUNCTION',
    'FUSION',
    'G',
    'GENERAL',
    'GENERATED',
    'GET',
    'GLOBAL',
    'GO',
    'GOTO',
    'GRANTED',
    'GROUPING',
    'HANDLER',
    'HEADER',
    'HEX',
    'HIERARCHY',
    'HOLD',
    'HOST',
    'HOUR',
                             #    'ID',
    'IDENTITY',
    'IF',
    'IGNORE',
    'IMMEDIATE',
    'IMMUTABLE',
    'IMPLEMENTATION',
    'IMPLICIT',
    'INCLUDING',
    'INCREMENT',
    'INDENT',
    'INDEX',
    'INDEXES',
    'INDICATOR',
    'INFIX',
    'INHERIT',
    'INHERITS',
    'INITIALIZE',
    'INPUT',
    'INSENSITIVE',
    'INSERT',
    'INSTANCE',
    'INSTANTIABLE',
    'INSTEAD',
    'INTERSECTION',
    'INVOKER',
    'ISOLATION',
    'ITERATE',
    'K',
    'KEY',
    'KEY_MEMBER',
    'KEY_TYPE',
    'LAG',
    'LANCOMPILER',
    'LANGUAGE',
    'LARGE',
    'LAST',
    'LAST_VALUE',
    'LATERAL',
    'LC_COLLATE',
    'LC_CTYPE',
    'LEAD',
    'LENGTH',
    'LESS',
    'LEVEL',
    'LIKE_REGEX',
    'LISTEN',
    'LN',
    'LOAD',
    'LOCAL',
    'LOCATION',
    'LOCATOR',
    'LOCK',
    'LOGIN',
    'LOWER',
    'M',
    'MAP',
    'MAPPING',
    'MATCH',
    'MATCHED',
    'MAX',
    'MAX_CARDINALITY',
    'MAXVALUE',
    'MEMBER',
    'MERGE',
    'MESSAGE_LENGTH',
    'MESSAGE_OCTET_LENGTH',
    'MESSAGE_TEXT',
    'METHOD',
    'MIN',
    'MINUTE',
    'MINVALUE',
    'MOD',
    'MODE',
    'MODIFIES',
    'MODIFY',
    'MODULE',
    'MONTH',
    'MORE',
    'MOVE',
    'MULTISET',
    'MUMPS',
                             #    'NAME',
    'NAMES',
    'NAMESPACE',
    'NCLOB',
    'NESTING',
    'NEXT',
    'NFC',
    'NFD',
    'NFKC',
    'NFKD',
    'NIL',
    'NO',
    'NOCREATEDB',
    'NOCREATEROLE',
    'NOCREATEUSER',
    'NOINHERIT',
    'NOLOGIN',
    'NORMALIZE',
    'NORMALIZED',
    'NOSUPERUSER',
    'NOTHING',
    'NOTIFY',
    'NOWAIT',
    'NTH_VALUE',
    'NTILE',
    'NULLABLE',
    'NULLS',
    'NUMBER',
    'OBJECT',
    'OCCURRENCES_REGEX',
    'OCTET_LENGTH',
    'OCTETS',
    'OF',
    'OIDS',
    'OPEN',
    'OPERATION',
    'OPERATOR',
    'OPTION',
    'OPTIONS',
    'ORDERING',
    'ORDINALITY',
    'OTHERS',
    'OUTPUT',
    'OVER',
    'OVERRIDING',
    'OWNED',
    'OWNER',
    'P',
    'PAD',
    'PARAMETER',
    'PARAMETER_MODE',
    'PARAMETER_NAME',
    'PARAMETER_ORDINAL_POSITION',
    'PARAMETER_SPECIFIC_CATALOG',
    'PARAMETER_SPECIFIC_NAME',
    'PARAMETER_SPECIFIC_SCHEMA',
    'PARAMETERS',
    'PARSER',
    'PARTIAL',
    'PARTITION',
    'PASCAL',
    'PASSING',
                             #    'PASSWORD',
    'PATH',
    'PERCENT_RANK',
    'PERCENTILE_CONT',
    'PERCENTILE_DISC',
    'PLANS',
    'PLI',
    'POSITION_REGEX',
    'POSTFIX',
    'POWER',
    'PRECEDING',
    'PREFIX',
    'PREORDER',
    'PREPARE',
    'PREPARED',
    'PRESERVE',
    'PRIOR',
    'PRIVILEGES',
    'PROCEDURAL',
    'PROCEDURE',
    'PUBLIC',
    'QUOTE',
    'RANGE',
    'RANK',
    'READ',
    'READS',
    'REASSIGN',
    'RECHECK',
    'RECURSIVE',
    'REF',
    'REFERENCING',
    'REGR_AVGX',
    'REGR_AVGY',
    'REGR_COUNT',
    'REGR_INTERCEPT',
    'REGR_R2',
    'REGR_SLOPE',
    'REGR_SXX',
    'REGR_SXY',
    'REGR_SYY',
    'REINDEX',
    'RELATIVE',
    'RELEASE',
    'RENAME',
    'REPEATABLE',
    'REPLACE',
    'REPLICA',
    'RESET',
    'RESPECT',
    'RESTART',
    'RESTRICT',
    'RESULT',
    'RETURN',
    'RETURNED_CARDINALITY',
    'RETURNED_LENGTH',
    'RETURNED_OCTET_LENGTH',
    'RETURNED_SQLSTATE',
    'RETURNS',
    'REVOKE',
                             #    'ROLE',
    'ROLLBACK',
    'ROLLUP',
    'ROUTINE',
    'ROUTINE_CATALOG',
    'ROUTINE_NAME',
    'ROUTINE_SCHEMA',
    'ROW_COUNT',
    'ROW_NUMBER',
    'ROWS',
    'RULE',
    'SAVEPOINT',
    'SCALE',
    'SCHEMA',
    'SCHEMA_NAME',
    'SCOPE',
    'SCOPE_CATALOG',
    'SCOPE_NAME',
    'SCOPE_SCHEMA',
    'SCROLL',
    'SEARCH',
    'SECOND',
    'SECTION',
    'SECURITY',
    'SELF',
    'SENSITIVE',
    'SEQUENCE',
    'SERIALIZABLE',
    'SERVER',
    'SERVER_NAME',
    'SESSION',
    'SET',
    'SETS',
    'SHARE',
    'SHOW',
    'SIMPLE',
    'SIZE',
    'SOURCE',
    'SPACE',
    'SPECIFIC',
    'SPECIFIC_NAME',
    'SPECIFICTYPE',
    'SQL',
    'SQLCODE',
    'SQLERROR',
    'SQLEXCEPTION',
    'SQLSTATE',
    'SQLWARNING',
    'SQRT',
    'STABLE',
    'STANDALONE',
    'START',
    'STATE',
    'STATEMENT',
    'STATIC',
    'STATISTICS',
    'STDDEV_POP',
    'STDDEV_SAMP',
    'STDIN',
    'STDOUT',
    'STORAGE',
    'STRICT',
    'STRIP',
    'STRUCTURE',
    'STYLE',
    'SUBCLASS_ORIGIN',
    'SUBLIST',
    'SUBMULTISET',
    'SUBSTRING_REGEX',
    'SUM',
    'SUPERUSER',
    'SYSID',
    'SYSTEM',
    'SYSTEM_USER',
    'T',
                             #    'TABLE_NAME',
    'TABLESAMPLE',
    'TABLESPACE',
    'TEMP',
    'TEMPLATE',
    'TEMPORARY',
    'TERMINATE',
    'TEXT',
    'THAN',
    'TIES',
    'TIMEZONE_HOUR',
    'TIMEZONE_MINUTE',
    'TOP_LEVEL_COUNT',
    'TRANSACTION',
    'TRANSACTION_ACTIVE',
    'TRANSACTIONS_COMMITTED',
    'TRANSACTIONS_ROLLED_BACK',
    'TRANSFORM',
    'TRANSFORMS',
    'TRANSLATE',
    'TRANSLATE_REGEX',
    'TRANSLATION',
    'TRIGGER',
    'TRIGGER_CATALOG',
    'TRIGGER_NAME',
    'TRIGGER_SCHEMA',
    'TRIM_ARRAY',
    'TRUNCATE',
    'TRUSTED',
    'TYPE',
    'UESCAPE',
    'UNBOUNDED',
    'UNCOMMITTED',
    'UNDER',
    'UNENCRYPTED',
    'UNKNOWN',
    'UNLISTEN',
    'UNNAMED',
    'UNNEST',
    'UNTIL',
    'UNTYPED',
    'UPDATE',
    'UPPER',
    'URI',
    'USAGE',
    'USER_DEFINED_TYPE_CATALOG',
    'USER_DEFINED_TYPE_CODE',
    'USER_DEFINED_TYPE_NAME',
    'USER_DEFINED_TYPE_SCHEMA',
    'VACUUM',
    'VALID',
    'VALIDATOR',
    'VALUE',
    'VAR_POP',
    'VAR_SAMP',
    'VARBINARY',
    'VARIABLE',
    'VARYING',
    'VERSION',
    'VIEW',
    'VOLATILE',
    'WHENEVER',
    'WHITESPACE',
    'WIDTH_BUCKET',
    'WINDOW',
    'WITHIN',
    'WITHOUT',
    'WORK',
    'WRAPPER',
    'WRITE',
    'XML',
    'XMLAGG',
    'XMLBINARY',
    'XMLCAST',
    'XMLCOMMENT',
    'XMLDECLARATION',
    'XMLDOCUMENT',
    'XMLEXISTS',
    'XMLITERATE',
    'XMLNAMESPACES',
    'XMLQUERY',
    'XMLSCHEMA',
    'XMLTABLE',
    'XMLTEXT',
    'XMLVALIDATE',
    'YEAR',
    'YES',
    'ZONE',
                             ))

#Thanks villas
FIREBIRD = set((
    'ABS',
    'ACTIVE',
    'ADMIN',
    'AFTER',
    'ASCENDING',
    'AUTO',
    'AUTODDL',
    'BASED',
    'BASENAME',
    'BASE_NAME',
    'BEFORE',
    'BIT_LENGTH',
    'BLOB',
    'BLOBEDIT',
    'BOOLEAN',
    'BOTH',
    'BUFFER',
    'CACHE',
    'CHAR_LENGTH',
    'CHARACTER_LENGTH',
    'CHECK_POINT_LEN',
    'CHECK_POINT_LENGTH',
    'CLOSE',
    'COMMITTED',
    'COMPILETIME',
    'COMPUTED',
    'CONDITIONAL',
    'CONNECT',
    'CONTAINING',
    'CROSS',
    'CSTRING',
    'CURRENT_CONNECTION',
    'CURRENT_ROLE',
    'CURRENT_TRANSACTION',
    'CURRENT_USER',
    'DATABASE',
    'DB_KEY',
    'DEBUG',
    'DESCENDING',
    'DISCONNECT',
    'DISPLAY',
    'DO',
    'ECHO',
    'EDIT',
    'ENTRY_POINT',
    'EVENT',
    'EXIT',
    'EXTERN',
    'FALSE',
    'FETCH',
    'FILE',
    'FILTER',
    'FREE_IT',
    'FUNCTION',
    'GDSCODE',
    'GENERATOR',
    'GEN_ID',
    'GLOBAL',
    'GROUP_COMMIT_WAIT',
    'GROUP_COMMIT_WAIT_TIME',
    'HELP',
    'IF',
    'INACTIVE',
    'INDEX',
    'INIT',
    'INPUT_TYPE',
    'INSENSITIVE',
    'ISQL',
    'LC_MESSAGES',
    'LC_TYPE',
    'LEADING',
    'LENGTH',
    'LEV',
    'LOGFILE',
    'LOG_BUFFER_SIZE',
    'LOG_BUF_SIZE',
    'LONG',
    'LOWER',
    'MANUAL',
    'MAXIMUM',
    'MAXIMUM_SEGMENT',
    'MAX_SEGMENT',
    'MERGE',
    'MESSAGE',
    'MINIMUM',
    'MODULE_NAME',
    'NOAUTO',
    'NUM_LOG_BUFS',
    'NUM_LOG_BUFFERS',
    'OCTET_LENGTH',
    'OPEN',
    'OUTPUT_TYPE',
    'OVERFLOW',
    'PAGE',
    'PAGELENGTH',
    'PAGES',
    'PAGE_SIZE',
    'PARAMETER',
               #    'PASSWORD',
    'PLAN',
    'POST_EVENT',
    'QUIT',
    'RAW_PARTITIONS',
    'RDB$DB_KEY',
    'RECORD_VERSION',
    'RECREATE',
    'RECURSIVE',
    'RELEASE',
    'RESERV',
    'RESERVING',
    'RETAIN',
    'RETURN',
    'RETURNING_VALUES',
    'RETURNS',
               #    'ROLE',
    'ROW_COUNT',
    'ROWS',
    'RUNTIME',
    'SAVEPOINT',
    'SECOND',
    'SENSITIVE',
    'SHADOW',
    'SHARED',
    'SHELL',
    'SHOW',
    'SINGULAR',
    'SNAPSHOT',
    'SORT',
    'STABILITY',
    'START',
    'STARTING',
    'STARTS',
    'STATEMENT',
    'STATIC',
    'STATISTICS',
    'SUB_TYPE',
    'SUSPEND',
    'TERMINATOR',
    'TRAILING',
    'TRIGGER',
    'TRIM',
    'TRUE',
    'TYPE',
    'UNCOMMITTED',
    'UNKNOWN',
    'USING',
    'VARIABLE',
    'VERSION',
    'WAIT',
    'WEEKDAY',
    'WHILE',
    'YEARDAY',
               ))
FIREBIRD_NONRESERVED = set((
    'BACKUP',
    'BLOCK',
    'COALESCE',
    'COLLATION',
    'COMMENT',
    'DELETING',
    'DIFFERENCE',
    'IIF',
    'INSERTING',
    'LAST',
    'LEAVE',
    'LOCK',
    'NEXT',
    'NULLIF',
    'NULLS',
    'RESTART',
    'RETURNING',
    'SCALAR_ARRAY',
    'SEQUENCE',
    'STATEMENT',
    'UPDATING',
    'ABS',
    'ACCENT',
    'ACOS',
    'ALWAYS',
    'ASCII_CHAR',
    'ASCII_VAL',
    'ASIN',
    'ATAN',
    'ATAN2',
    'BACKUP',
    'BIN_AND',
    'BIN_OR',
    'BIN_SHL',
    'BIN_SHR',
    'BIN_XOR',
    'BLOCK',
    'CEIL',
    'CEILING',
    'COLLATION',
    'COMMENT',
    'COS',
    'COSH',
    'COT',
    'DATEADD',
    'DATEDIFF',
    'DECODE',
    'DIFFERENCE',
    'EXP',
    'FLOOR',
    'GEN_UUID',
    'GENERATED',
    'HASH',
    'IIF',
    'LIST',
    'LN',
    'LOG',
    'LOG10',
    'LPAD',
    'MATCHED',
    'MATCHING',
    'MAXVALUE',
    'MILLISECOND',
    'MINVALUE',
    'MOD',
    'NEXT',
    'OVERLAY',
    'PAD',
    'PI',
    'PLACING',
    'POWER',
    'PRESERVE',
    'RAND',
    'REPLACE',
    'RESTART',
    'RETURNING',
    'REVERSE',
    'ROUND',
    'RPAD',
    'SCALAR_ARRAY',
    'SEQUENCE',
    'SIGN',
    'SIN',
    'SINH',
    'SPACE',
    'SQRT',
    'TAN',
    'TANH',
    'TEMPORARY',
    'TRUNC',
    'WEEK',
))

# Thanks Jonathan Lundell
MYSQL = set((
    'ACCESSIBLE',
    'ADD',
    'ALL',
    'ALTER',
    'ANALYZE',
    'AND',
    'AS',
    'ASC',
    'ASENSITIVE',
    'BEFORE',
    'BETWEEN',
    'BIGINT',
    'BINARY',
    'BLOB',
    'BOTH',
    'BY',
    'CALL',
    'CASCADE',
    'CASE',
    'CHANGE',
    'CHAR',
    'CHARACTER',
    'CHECK',
    'COLLATE',
    'COLUMN',
    'CONDITION',
    'CONSTRAINT',
    'CONTINUE',
    'CONVERT',
    'CREATE',
    'CROSS',
    'CURRENT_DATE',
    'CURRENT_TIME',
    'CURRENT_TIMESTAMP',
    'CURRENT_USER',
    'CURSOR',
    'DATABASE',
    'DATABASES',
    'DAY_HOUR',
    'DAY_MICROSECOND',
    'DAY_MINUTE',
    'DAY_SECOND',
    'DEC',
    'DECIMAL',
    'DECLARE',
    'DEFAULT',
    'DELAYED',
    'DELETE',
    'DESC',
    'DESCRIBE',
    'DETERMINISTIC',
    'DISTINCT',
    'DISTINCTROW',
    'DIV',
    'DOUBLE',
    'DROP',
    'DUAL',
    'EACH',
    'ELSE',
    'ELSEIF',
    'ENCLOSED',
    'ESCAPED',
    'EXISTS',
    'EXIT',
    'EXPLAIN',
    'FALSE',
    'FETCH',
    'FLOAT',
    'FLOAT4',
    'FLOAT8',
    'FOR',
    'FORCE',
    'FOREIGN',
    'FROM',
    'FULLTEXT',
    'GRANT',
    'GROUP',
    'HAVING',
    'HIGH_PRIORITY',
    'HOUR_MICROSECOND',
    'HOUR_MINUTE',
    'HOUR_SECOND',
    'IF',
    'IGNORE',
    'IGNORE_SERVER_IDS',
    'IGNORE_SERVER_IDS',
    'IN',
    'INDEX',
    'INFILE',
    'INNER',
    'INOUT',
    'INSENSITIVE',
    'INSERT',
    'INT',
    'INT1',
    'INT2',
    'INT3',
    'INT4',
    'INT8',
    'INTEGER',
    'INTERVAL',
    'INTO',
    'IS',
    'ITERATE',
    'JOIN',
    'KEY',
    'KEYS',
    'KILL',
    'LEADING',
    'LEAVE',
    'LEFT',
    'LIKE',
    'LIMIT',
    'LINEAR',
    'LINES',
    'LOAD',
    'LOCALTIME',
    'LOCALTIMESTAMP',
    'LOCK',
    'LONG',
    'LONGBLOB',
    'LONGTEXT',
    'LOOP',
    'LOW_PRIORITY',
    'MASTER_HEARTBEAT_PERIOD',
    'MASTER_HEARTBEAT_PERIOD',
    'MASTER_SSL_VERIFY_SERVER_CERT',
    'MATCH',
    'MAXVALUE',
    'MAXVALUE',
    'MEDIUMBLOB',
    'MEDIUMINT',
    'MEDIUMTEXT',
    'MIDDLEINT',
    'MINUTE_MICROSECOND',
    'MINUTE_SECOND',
    'MOD',
    'MODIFIES',
    'NATURAL',
    'NO_WRITE_TO_BINLOG',
    'NOT',
    'NULL',
    'NUMERIC',
    'ON',
    'OPTIMIZE',
    'OPTION',
    'OPTIONALLY',
    'OR',
    'ORDER',
    'OUT',
    'OUTER',
    'OUTFILE',
    'PRECISION',
    'PRIMARY',
    'PROCEDURE',
    'PURGE',
    'RANGE',
    'READ',
    'READ_WRITE',
    'READS',
    'REAL',
    'REFERENCES',
    'REGEXP',
    'RELEASE',
    'RENAME',
    'REPEAT',
    'REPLACE',
    'REQUIRE',
    'RESIGNAL',
    'RESIGNAL',
    'RESTRICT',
    'RETURN',
    'REVOKE',
    'RIGHT',
    'RLIKE',
    'SCHEMA',
    'SCHEMAS',
    'SECOND_MICROSECOND',
    'SELECT',
    'SENSITIVE',
    'SEPARATOR',
    'SET',
    'SHOW',
    'SIGNAL',
    'SIGNAL',
    'SMALLINT',
    'SPATIAL',
    'SPECIFIC',
    'SQL',
    'SQL_BIG_RESULT',
    'SQL_CALC_FOUND_ROWS',
    'SQL_SMALL_RESULT',
    'SQLEXCEPTION',
    'SQLSTATE',
    'SQLWARNING',
    'SSL',
    'STARTING',
    'STRAIGHT_JOIN',
    'TABLE',
    'TERMINATED',
    'THEN',
    'TINYBLOB',
    'TINYINT',
    'TINYTEXT',
    'TO',
    'TRAILING',
    'TRIGGER',
    'TRUE',
    'UNDO',
    'UNION',
    'UNIQUE',
    'UNLOCK',
    'UNSIGNED',
    'UPDATE',
    'USAGE',
    'USE',
    'USING',
    'UTC_DATE',
    'UTC_TIME',
    'UTC_TIMESTAMP',
    'VALUES',
    'VARBINARY',
    'VARCHAR',
    'VARCHARACTER',
    'VARYING',
    'WHEN',
    'WHERE',
    'WHILE',
    'WITH',
    'WRITE',
    'XOR',
    'YEAR_MONTH',
    'ZEROFILL',
))

MSSQL = set((
    'ADD',
    'ALL',
    'ALTER',
    'AND',
    'ANY',
    'AS',
    'ASC',
    'AUTHORIZATION',
    'BACKUP',
    'BEGIN',
    'BETWEEN',
    'BREAK',
    'BROWSE',
    'BULK',
    'BY',
    'CASCADE',
    'CASE',
    'CHECK',
    'CHECKPOINT',
    'CLOSE',
    'CLUSTERED',
    'COALESCE',
    'COLLATE',
    'COLUMN',
    'COMMIT',
    'COMPUTE',
    'CONSTRAINT',
    'CONTAINS',
    'CONTAINSTABLE',
    'CONTINUE',
    'CONVERT',
    'CREATE',
    'CROSS',
    'CURRENT',
    'CURRENT_DATE',
    'CURRENT_TIME',
    'CURRENT_TIMESTAMP',
    'CURRENT_USER',
    'CURSOR',
    'DATABASE',
    'DBCC',
    'DEALLOCATE',
    'DECLARE',
    'DEFAULT',
    'DELETE',
    'DENY',
    'DESC',
    'DISK',
    'DISTINCT',
    'DISTRIBUTED',
    'DOUBLE',
    'DROP',
    'DUMMY',
    'DUMP',
    'ELSE',
    'END',
    'ERRLVL',
    'ESCAPE',
    'EXCEPT',
    'EXEC',
    'EXECUTE',
    'EXISTS',
    'EXIT',
    'FETCH',
    'FILE',
    'FILLFACTOR',
    'FOR',
    'FOREIGN',
    'FREETEXT',
    'FREETEXTTABLE',
    'FROM',
    'FULL',
    'FUNCTION',
    'GOTO',
    'GRANT',
    'GROUP',
    'HAVING',
    'HOLDLOCK',
    'IDENTITY',
    'IDENTITY_INSERT',
    'IDENTITYCOL',
    'IF',
    'IN',
    'INDEX',
    'INNER',
    'INSERT',
    'INTERSECT',
    'INTO',
    'IS',
    'JOIN',
    'KEY',
    'KILL',
    'LEFT',
    'LIKE',
    'LINENO',
    'LOAD',
    'NATIONAL ',
    'NOCHECK',
    'NONCLUSTERED',
    'NOT',
    'NULL',
    'NULLIF',
    'OF',
    'OFF',
    'OFFSETS',
    'ON',
    'OPEN',
    'OPENDATASOURCE',
    'OPENQUERY',
    'OPENROWSET',
    'OPENXML',
    'OPTION',
    'OR',
    'ORDER',
    'OUTER',
    'OVER',
    'PERCENT',
    'PLAN',
    'PRECISION',
    'PRIMARY',
    'PRINT',
    'PROC',
    'PROCEDURE',
    'PUBLIC',
    'RAISERROR',
    'READ',
    'READTEXT',
    'RECONFIGURE',
    'REFERENCES',
    'REPLICATION',
    'RESTORE',
    'RESTRICT',
    'RETURN',
    'REVOKE',
    'RIGHT',
    'ROLLBACK',
    'ROWCOUNT',
    'ROWGUIDCOL',
    'RULE',
    'SAVE',
    'SCHEMA',
    'SELECT',
    'SESSION_USER',
    'SET',
    'SETUSER',
    'SHUTDOWN',
    'SOME',
    'STATISTICS',
    'SYSTEM_USER',
    'TABLE',
    'TEXTSIZE',
    'THEN',
    'TO',
    'TOP',
    'TRAN',
    'TRANSACTION',
    'TRIGGER',
    'TRUNCATE',
    'TSEQUAL',
    'UNION',
    'UNIQUE',
    'UPDATE',
    'UPDATETEXT',
    'USE',
    'USER',
    'VALUES',
    'VARYING',
    'VIEW',
    'WAITFOR',
    'WHEN',
    'WHERE',
    'WHILE',
    'WITH',
    'WRITETEXT',
))

ORACLE = set((
    'ACCESS',
    'ADD',
    'ALL',
    'ALTER',
    'AND',
    'ANY',
    'AS',
    'ASC',
    'AUDIT',
    'BETWEEN',
    'BY',
    'CHAR',
    'CHECK',
    'CLUSTER',
    'COLUMN',
    'COMMENT',
    'COMPRESS',
    'CONNECT',
    'CREATE',
    'CURRENT',
    'DATE',
    'DECIMAL',
    'DEFAULT',
    'DELETE',
    'DESC',
    'DISTINCT',
    'DROP',
    'ELSE',
    'EXCLUSIVE',
    'EXISTS',
    'FILE',
    'FLOAT',
    'FOR',
    'FROM',
    'GRANT',
    'GROUP',
    'HAVING',
    'IDENTIFIED',
    'IMMEDIATE',
    'IN',
    'INCREMENT',
    'INDEX',
    'INITIAL',
    'INSERT',
    'INTEGER',
    'INTERSECT',
    'INTO',
    'IS',
    'LEVEL',
    'LIKE',
    'LOCK',
    'LONG',
    'MAXEXTENTS',
    'MINUS',
    'MLSLABEL',
    'MODE',
    'MODIFY',
    'NOAUDIT',
    'NOCOMPRESS',
    'NOT',
    'NOWAIT',
    'NULL',
    'NUMBER',
    'OF',
    'OFFLINE',
    'ON',
    'ONLINE',
    'OPTION',
    'OR',
    'ORDER',
    'PCTFREE',
    'PRIOR',
    'PRIVILEGES',
    'PUBLIC',
    'RAW',
    'RENAME',
    'RESOURCE',
    'REVOKE',
    'ROW',
    'ROWID',
    'ROWNUM',
    'ROWS',
    'SELECT',
    'SESSION',
    'SET',
    'SHARE',
    'SIZE',
    'SMALLINT',
    'START',
    'SUCCESSFUL',
    'SYNONYM',
    'SYSDATE',
    'TABLE',
    'THEN',
    'TO',
    'TRIGGER',
    'UID',
    'UNION',
    'UNIQUE',
    'UPDATE',
    'USER',
    'VALIDATE',
    'VALUES',
    'VARCHAR',
    'VARCHAR2',
    'VIEW',
    'WHENEVER',
    'WHERE',
    'WITH',
))

SQLITE = set((
    'ABORT',
    'ACTION',
    'ADD',
    'AFTER',
    'ALL',
    'ALTER',
    'ANALYZE',
    'AND',
    'AS',
    'ASC',
    'ATTACH',
    'AUTOINCREMENT',
    'BEFORE',
    'BEGIN',
    'BETWEEN',
    'BY',
    'CASCADE',
    'CASE',
    'CAST',
    'CHECK',
    'COLLATE',
    'COLUMN',
    'COMMIT',
    'CONFLICT',
    'CONSTRAINT',
    'CREATE',
    'CROSS',
    'CURRENT_DATE',
    'CURRENT_TIME',
    'CURRENT_TIMESTAMP',
    'DATABASE',
    'DEFAULT',
    'DEFERRABLE',
    'DEFERRED',
    'DELETE',
    'DESC',
    'DETACH',
    'DISTINCT',
    'DROP',
    'EACH',
    'ELSE',
    'END',
    'ESCAPE',
    'EXCEPT',
    'EXCLUSIVE',
    'EXISTS',
    'EXPLAIN',
    'FAIL',
    'FOR',
    'FOREIGN',
    'FROM',
    'FULL',
    'GLOB',
    'GROUP',
    'HAVING',
    'IF',
    'IGNORE',
    'IMMEDIATE',
    'IN',
    'INDEX',
    'INDEXED',
    'INITIALLY',
    'INNER',
    'INSERT',
    'INSTEAD',
    'INTERSECT',
    'INTO',
    'IS',
    'ISNULL',
    'JOIN',
    'KEY',
    'LEFT',
    'LIKE',
    'LIMIT',
    'MATCH',
    'NATURAL',
    'NO',
    'NOT',
    'NOTNULL',
    'NULL',
    'OF',
    'OFFSET',
    'ON',
    'OR',
    'ORDER',
    'OUTER',
    'PLAN',
    'PRAGMA',
    'PRIMARY',
    'QUERY',
    'RAISE',
    'REFERENCES',
    'REGEXP',
    'REINDEX',
    'RELEASE',
    'RENAME',
    'REPLACE',
    'RESTRICT',
    'RIGHT',
    'ROLLBACK',
    'ROW',
    'SAVEPOINT',
    'SELECT',
    'SET',
    'TABLE',
    'TEMP',
    'TEMPORARY',
    'THEN',
    'TO',
    'TRANSACTION',
    'TRIGGER',
    'UNION',
    'UNIQUE',
    'UPDATE',
    'USING',
    'VACUUM',
    'VALUES',
    'VIEW',
    'VIRTUAL',
    'WHEN',
    'WHERE',
))

MONGODB_NONRESERVED = set(('SAFE',))

# remove from here when you add a list.
JDBCSQLITE = SQLITE
DB2 = INFORMIX = INGRES = JDBCPOSTGRESQL = COMMON

ADAPTERS = {
    'sqlite': SQLITE,
    'mysql': MYSQL,
    'postgres': POSTGRESQL,
    'postgres_nonreserved': POSTGRESQL_NONRESERVED,
    'oracle': ORACLE,
    'mssql': MSSQL,
    'mssql2': MSSQL,
    'db2': DB2,
    'informix': INFORMIX,
    'firebird': FIREBIRD,
    'firebird_embedded': FIREBIRD,
    'firebird_nonreserved': FIREBIRD_NONRESERVED,
    'ingres': INGRES,
    'ingresu': INGRES,
    'jdbc:sqlite': JDBCSQLITE,
    'jdbc:postgres': JDBCPOSTGRESQL,
    'common': COMMON,
    'mongodb_nonreserved': MONGODB_NONRESERVED
}


#!/usr/bin/env python3

import logging
from typing import List, Optional, Callable
from datetime import datetime

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.executors.pool import ThreadPoolExecutor

from homecon.core.event import Event
from homecon.core.states.state import State, IStateManager
from homecon.core.plugins.plugin import BasePlugin

from homecon.util.weather import incidentirradiance, clearskyirrradiance, sunposition, cloudyskyirrradiance

logger = logging.getLogger(__name__)

class IShading:
    """
    position: 0 means fully open, maximum solar gains, 1 means fully closed, minimum solar gains
    """

    @property
    def position(self) -> float:
        raise NotImplementedError

    def set_position(self, value) -> None:
        raise NotImplementedError

    @property
    def minimum_position(self) -> float:
        raise NotImplementedError

    @property
    def maximum_position(self) -> float:
        raise NotImplementedError

    def get_heat_gain(self, position: float, date: datetime) -> float:
        raise NotImplementedError

class StateBasedShading(IShading):
    """
    position: 0 means fully open, 1 means fully closed
    """

    def __init__(self, position_state: Optional[State] = None,
                 minimum_position_state: Optional[State] = None,
                 maximum_position_state: Optional[State] = None,
                 area: float = 1., transparency: float = 0., azimuth: float = 180., tilt: float = 90.,
                 longitude: float = 0, latitude: float = 0, elevation: float = 80.):
        self.position_state = position_state
        self.minimum_position_state = minimum_position_state
        self.maximum_position_state = maximum_position_state
        self._area = area
        self.transparency = transparency
        self._azimuth = azimuth
        self._tilt = tilt
        self._longitude = longitude
        self._latitude = latitude
        self._elevation = elevation

    def get_shading_factor(self, position):
        return position * self.transparency + (1 - position) * 1

    def get_heat_gain(self, position: float, date: datetime) -> float:
        return self.get_shading_factor(position) * self.get_maximum_heat_gain(date)

    def get_maximum_heat_gain(self, date: datetime) -> float:
        solar_azimuth, solar_altitude = sunposition(self._latitude, self._longitude, self._elevation,
                                                    timestamp=int(date.timestamp()))
        irradiance_direct_clearsky, irradiance_diffuse_clearsky = clearskyirrradiance(solar_azimuth, solar_altitude)
        cloud_cover = 0.0
        irradiance_direct, irradiance_diffuse = cloudyskyirrradiance(
            irradiance_direct_clearsky, irradiance_diffuse_clearsky, cloud_cover, solar_azimuth, solar_altitude,
            timestamp=int(date.timestamp()))
        irradiance_total_surface, irradiance_direct_surface, irradiance_diffuse_surface, irradiance_ground_surface = \
            incidentirradiance(
                irradiance_direct, irradiance_diffuse, solar_azimuth, solar_altitude, self._azimuth, self._tilt)

        return irradiance_total_surface * self._area

    @property
    def minimum_position(self) -> float:
        if self.minimum_position_state is not None and self.minimum_position_state.value is not None:
            return self.minimum_position_state.value
        return 0.

    @property
    def maximum_position(self) -> float:
        if self.maximum_position_state is not None and self.maximum_position_state.value is not None:
            return self.maximum_position_state.value
        return 1.

    @property
    def position(self) -> float:
        if self.position_state is not None and self.position_state.value is not None:
            return self.position_state.value
        return 0.

    def set_position(self, value) -> None:
        if self.position_state is not None:
            self.position_state.set_value(value)
        else:
            logger.error('cannot set position, no state')

class IShadingPositionCalculator:
    def get_positions(self, shadings: List[IShading], wanted_heat_gain: float):
        raise NotImplementedError

class EqualShadingPositionCalculator(IShadingPositionCalculator):
    def __init__(self, heat_gain_threshold: float = 50, position_step: float = 0.05,
                 now: Callable[[], datetime] = datetime.now):
        self.position_step = position_step
        self.heat_gain_threshold = heat_gain_threshold
        self._now = now

    def get_positions(self, shadings: List[IShading], wanted_heat_gain):
        date = self._now()
        maximum_heat_gains = [s.get_heat_gain(s.minimum_position, date) for s in shadings]
        minimum_heat_gains = [s.get_heat_gain(s.maximum_position, date) for s in shadings]

        if wanted_heat_gain > sum(maximum_heat_gains):
            return [s.minimum_position for s in shadings]

        elif wanted_heat_gain < sum(minimum_heat_gains):
            return [s.maximum_position if g > self.heat_gain_threshold else s.minimum_position
                    for s, g in zip(shadings, maximum_heat_gains)]

        else:
            def _get_positions(pos: float):
                return [min(max(pos, s.minimum_position), s.maximum_position)
                        if g > self.heat_gain_threshold else s.minimum_position
                        for s, g in zip(shadings, maximum_heat_gains)]

            p = 1
            while p > 0:
                positions = _get_positions(p)
                heat_gain = sum([s.get_heat_gain(p, date) for s, p in zip(shadings, positions)])
                if heat_gain >= wanted_heat_gain:
                    return positions

                p -= self.position_step
                p = round(p / self.position_step) * self.position_step
            return _get_positions(0)

class IWantedHeatGainCalculator:
    def calculate_wanted_heat_gain(self) -> float:
        raise NotImplementedError

class StateBasedHeatingCurveWantedHeatGainCalculator(IWantedHeatGainCalculator):
    def __init__(self, ambient_temperature_state: State, indoor_temperature_state: Optional[State] = None,
                 setpoint_temperature_state: Optional[State] = None,
                 ambient_temperature_min_state: Optional[State] = None,
                 ambient_temperature_max_state: Optional[State] = None,
                 heat_demand_max_state: Optional[State] = None,
                 indoor_temperature_correction_factor_state: Optional[State] = None):
        self._ambient_temperature_state = ambient_temperature_state
        self._indoor_temperature_state = indoor_temperature_state
        self._setpoint_temperature_state = setpoint_temperature_state
        self._ambient_temperature_min_state = ambient_temperature_min_state
        self._ambient_temperature_max_state = ambient_temperature_max_state
        self._heat_demand_max_state = heat_demand_max_state
        self._indoor_temperature_correction_factor_state = indoor_temperature_correction_factor_state

    def _get_ambient_temperature(self) -> float:
        return self._ambient_temperature_state.value

    def _get_indoor_temperature(self) -> float:
        if self._indoor_temperature_state is not None:
            return self._indoor_temperature_state.value
        return 20.

    def _get_setpoint_temperature(self) -> float:
        if self._setpoint_temperature_state is not None:
            return self._setpoint_temperature_state.value
        return 20.

    def _get_ambient_temperature_min(self) -> float:
        if self._ambient_temperature_min_state is not None:
            return self._ambient_temperature_min_state.value
        return -10.

    def _get_ambient_temperature_max(self) -> float:
        if self._ambient_temperature_max_state is not None:
            return self._ambient_temperature_max_state.value
        return 18.

    def _get_heat_demand_max(self) -> float:
        if self._heat_demand_max_state is not None:
            return self._heat_demand_max_state.value
        return 8000.

    def _get_indoor_temperature_correction_factor(self) -> float:
        if self._indoor_temperature_correction_factor_state is not None:
            return self._indoor_temperature_correction_factor_state.value
        return 0.2

    @staticmethod
    def _calculate_heat_demand(ambient_temperature, indoor_temperature, setpoint_temperature,
                               ambient_temperature_min, ambient_temperature_max, heat_demand_max,
                               indoor_temperature_correction_scale):

        indoor_temperature_factor = -indoor_temperature_correction_scale * (
                indoor_temperature - setpoint_temperature)
        outdoor_temperature_factor = (ambient_temperature_max - ambient_temperature) / (
            ambient_temperature_max - ambient_temperature_min)

        return (indoor_temperature_factor + outdoor_temperature_factor) * heat_demand_max

    def calculate_wanted_heat_gain(self) -> float:
        ambient_temperature = self._get_ambient_temperature()
        indoor_temperature = self._get_indoor_temperature()
        setpoint_temperature = self._get_setpoint_temperature()
        ambient_temperature_min = self._get_ambient_temperature_min()
        ambient_temperature_max = self._get_ambient_temperature_max()
        heat_demand_max = self._get_heat_demand_max()
        indoor_temperature_correction_scale = self._get_indoor_temperature_correction_factor()

        return self._calculate_heat_demand(ambient_temperature, indoor_temperature, setpoint_temperature,
                                           ambient_temperature_min, ambient_temperature_max, heat_demand_max,
                                           indoor_temperature_correction_scale)

class ShadingController:

    SHADING_STATE_TYPE = 'shading'

    def __init__(self,
                 state_manager: IStateManager,
                 wanted_heat_gain_calculator: IWantedHeatGainCalculator,
                 position_calculator: IShadingPositionCalculator,
                 longitude_state: State, latitude_state: State, elevation_state: State,
                 interval: int = 1800):
        self._state_manager = state_manager
        self._wanted_heat_gain_calculator = wanted_heat_gain_calculator
        self._position_calculator = position_calculator

        self._longitude_state = longitude_state
        self._latitude_state = latitude_state
        self._elevation_state = elevation_state

        executors = {
            'default': ThreadPoolExecutor(5),
        }
        job_defaults = {
            'coalesce': True,
            'max_instances': 3,
            'misfire_grace_time': 3600
        }
        self.scheduler = BackgroundScheduler(executors=executors, job_defaults=job_defaults)
        self.scheduler.add_job(self.run, trigger='interval', seconds=interval)

    def start(self):
        self.scheduler.start()

    def stop(self):
        self.scheduler.shutdown(wait=False)

    def _get_shading_from_state(self, state: State) -> StateBasedShading:
        position_state = None
        minimum_position_state = None
        maximum_position_state = None

        for child in state.children:
            if child.name == 'position':
                position_state = child
            if child.name == 'minimum':
                minimum_position_state = child
            if child.name == 'maximum':
                maximum_position_state = child

        return StateBasedShading(position_state=position_state,
                                 minimum_position_state=minimum_position_state,
                                 maximum_position_state=maximum_position_state,
                                 area=state.config.get('area', 1.0),
                                 transparency=state.config.get('transparency', 0.0),
                                 azimuth=state.config.get('azimuth', 0.0),
                                 tilt=state.config.get('tilt', 90.0),
                                 longitude=self._longitude_state.value or 0.0,
                                 latitude=self._latitude_state.value or 0.0,
                                 elevation=self._elevation_state.value or 0.0)

    def run(self):
        shadings = []
        for state in self._state_manager.all():
            if state.type == self.SHADING_STATE_TYPE:
                shadings.append(self._get_shading_from_state(state))

        wanted_heat_gain = self._wanted_heat_gain_calculator.calculate_wanted_heat_gain()
        positions = self._position_calculator.get_positions(shadings, wanted_heat_gain)

        for shading, position in zip(shadings, positions):
            shading.set_position(position)

    def listen_state_value_changed(self, event: Event):
        state = event.data['state']
        if state.parent is not None and state.parent.type == self.SHADING_STATE_TYPE:
            self.run()

class Shading(BasePlugin):
    """
    Class to control the HomeCon scheduler

    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self._state_manager.add('settings', type=None)
        self._state_manager.add('shading', type=None, parent_path='/settings')
        self._state_manager.add('heat_demand', type=None, parent_path='/settings/shading')
        ambient_temperature_state = self._state_manager.add(
            'ambient_temperature', parent_path='/settings/shading/heat_demand',
            type='float', quantity='Temperature', unit='degC',
            label='', description='Ambient temperature', value=15)
        indoor_temperature_state = self._state_manager.add(
            'indoor_temperature', parent_path='/settings/shading/heat_demand',
            type='float', quantity='Temperature', unit='degC',
            label='', description='Ambient temperature', value=20)
        setpoint_temperature_state = self._state_manager.add(
            'setpoint_temperature', parent_path='/settings/shading/heat_demand',
            type='float', quantity='Temperature', unit='degC',
            label='', description='Ambient temperature', value=15)

        self._state_manager.add('location', type=None, parent_path='/settings')
        longitude_state = self._state_manager.add(
            'longitude', parent_path='/settings/location',
            type='float', quantity='Angle', unit='deg',
            label='', description='Longitude', value=5)
        latitude_state = self._state_manager.add(
            'latitude', parent_path='/settings/location',
            type='float', quantity='Angle', unit='deg',