# -*- coding: utf-8 -*-
"""
orm sql schema mixin module.
"""

from sqlalchemy import BigInteger, Integer, Sequence

import pyrin.utils.misc as misc_utils

from pyrin.database.orm.sql.schema.exceptions import SequenceColumnTypeIsInvalidError


class SequenceColumnMixin:
    """
    sequence column mixin class.
    """

    DEFAULT_CACHE = 100
    DEFAULT_TYPE = BigInteger

    def __init__(self, *args, **kwargs):
        """
        initializes an instance of SequenceColumnMixin.

        :param str name: the name of this column as represented in the database.
                         this argument may be the first positional argument, or
                         specified via keyword.

        :param TypeEngine type_: the column's type, indicated using an instance which
                                 if no arguments are required for the type, the class of
                                 the type can be sent as well.
                                 this argument may be the second positional argument, or
                                 specified via keyword.

        :param object *args: additional positional arguments include various
                             `SchemaItem` derived constructs which will be applied
                             as options to the column.

        :keyword str sequence: sequence name to be generated for this column.
                               this value is required, but has to be set as
                               keyword to prevent errors.

        :keyword str doc: optional string that can be used by the ORM or similar
                          to document attributes on the python side.

        :keyword str key: an optional string identifier which will identify this
                          `Column` object on the `Table`.

        :keyword bool index: when `True`, indicates that the column is indexed.

        :keyword dict info: optional data dictionary which will be populated into the
                            `SchemaItem.info` attribute of this object.

        :keyword bool nullable: when set to `False`, will cause the `Not NULL`
                                phrase to be added when generating ddl for the column.
                                defaults to False if not provided.

        :keyword bool primary_key: if `True`, marks this column as a primary key
                                   column. multiple columns can have this flag set to
                                   specify composite primary keys.

        :keyword bool quote: force quoting of this column's name on or off,
                             corresponding to `True` or `False`. when left at its default
                             of `None`, the column identifier will be quoted according to
                             whether the name is case sensitive (identifiers with at least one
                             upper case character are treated as case sensitive), or if it's a
                             reserved word.

        :keyword bool unique: when `True`, indicates that this column contains a
                              unique constraint, or if `index` is `True` as well, indicates
                              that the `index` should be created with the unique flag.

        :keyword bool system: when `True`, indicates this is a system column,
                              that is a column which is automatically made available by the
                              database, and should not be included in the columns list for a
                              `create table` statement.

        :keyword str comment: optional string that will render an sql comment
                              on table creation.

        :keyword bool allow_read: specifies that the column should be
                                  included in entity to dict conversion.
                                  defaults to True if not provided.

        :keyword bool allow_write: specifies that the column should be
                                   populated on conversion from dict.
                                   defaults to False if not provided.

        :keyword bool validated: specifies that an automatic validator for this column
                                 must be registered, that is usable through validator
                                 services. defaults to False if not provided.

        :keyword int cache: cache size for sequence.
                            defaults to `DEFAULT_CACHE`, if not provided.
                            to disable cache, you can pass it as None or `0`.
                            note that cache is per session, so if you stop the
                            connection to database and start it again, the new
                            session will get its own cache. but it's good for
                            performance to have cache on sequences, gaps are not
                            bad at all.

        :raises SequenceColumnTypeIsInvalidError: sequence column type is invalid error.
        """

        args = list(args)
        name, type_ = self._extract_name_and_type(args, kwargs)
        if type_ is None:
            type_ = self.DEFAULT_TYPE

        if not misc_utils.is_subclass_or_instance(type_, Integer):
            raise SequenceColumnTypeIsInvalidError('The sequence column type must be an '
                                                   'instance or subclass of [{integer}].'
                                                   .format(integer=Integer))

        cache = kwargs.pop('cache', self.DEFAULT_CACHE)
        sequence = kwargs.pop('sequence', None)
        sequence_kwargs = dict()
        if cache is not None and cache > 0:
            sequence_kwargs.update(cache=cache)

        sequence_instance = Sequence(sequence, **sequence_kwargs)
        # this is to prevent sqlalchemy errors.
        if sequence is not None:
            args.append(sequence_instance)

        kwargs.setdefault('allow_write', False)
        kwargs.setdefault('nullable', False)

        kwargs.update(name=name, type_=type_,
                      autoincrement=False, min_value=1,
                      default=sequence_instance,
                      server_default=sequence_instance.next_value())

        kwargs.pop('onupdate', None)
        kwargs.pop('server_onupdate', None)

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