from dbparti.backends import BasePartition, transaction
from dbparti.backends.utilities import DateTimeUtil
from dbparti.backends.exceptions import PartitionRangeSubtypeError, PartitionFunctionError


"""
MySQL partition backend.

MySQL supports partitioning natively via PARTITION BY clause. Thing to keep in
mind is that python MySQLdb doesn't support compound MySQL statements, so each
separate statement should be executed inside each self.cursor.execute().
"""
class Partition(BasePartition):
    """Common methods for all partition types"""
    def prepare(self):
        """Converts original table to partitioned one"""
        self.cursor.execute("""
            -- We need to rebuild primary key for our partitioning to work
            ALTER table {parent_table} DROP PRIMARY KEY, add PRIMARY KEY ({pk}, {partition_column});
        """.format(
            parent_table=self.table,
            partition_column=self.partition_column,
        ))

        transaction.commit_unless_managed()

    def exists(self):
        """Checks if partition exists"""
        self.cursor.execute("""
            SELECT EXISTS(
                SELECT 1 FROM information_schema.partitions
                WHERE table_name='{parent_table}' AND partition_name='{partition_name}');
        """.format(
            pk = self.partition_pk.column,
            parent_table=self.table,
            partition_name=self._get_name(),
        ))

        return self.cursor.fetchone()[0]


class RangePartition(Partition):
    """Range partition type implementation"""
    def __init__(self, *args, **kwargs):
        super(RangePartition, self).__init__(*args, **kwargs)
        self.partition_range = kwargs['partition_range']
        self.partition_subtype = kwargs['partition_subtype']
        self.datetime = DateTimeUtil(self.column_value, self.partition_range, model=self.model)

    def prepare(self):
        """Converts original table to partitioned one"""
        super(RangePartition, self).prepare()
        self.datetime.now = None

        self.cursor.execute("""
            -- We need to create zero partition to speed up things due to the partitioning
            -- implementation in the early versions of MySQL database (see bug #49754)
            ALTER TABLE {parent_table} PARTITION BY RANGE ({function}({partition_column}))(
                PARTITION {partition_pattern} VALUES LESS THAN (0)
            );
        """.format(
            parent_table=self.table,
            partition_column=self.partition_column,
            partition_pattern=self._get_name(),
            function=self._get_partition_function(),
        ))

        transaction.commit_unless_managed()

    def create(self):
        """Creates new partition"""
        self.cursor.execute("""
            ALTER TABLE {parent_table} ADD PARTITION (
                PARTITION {child_table} VALUES LESS THAN ({function}('{period_end}') + {addition})
            );
        """.format(
            child_table=self._get_name(),
            parent_table=self.table,
            function=self._get_partition_function(),
            period_end=self.datetime.get_period()[1],
            addition='86400' if self._get_column_type() == 'timestamp' else '1',
        ))

        transaction.commit_unless_managed()

    def _get_name(self):
        """Dynamically defines new partition name depending on the partition subtype"""
        try:
            return getattr(self, '_get_{0}_name'.format(self.partition_subtype))()
        except AttributeError:
            import re
            raise PartitionRangeSubtypeError(
                model=self.model,
                current_value=self.partition_subtype,
                allowed_values=[re.match('_get_(\w+)_name', c).group(1) for c in dir(
                    self) if re.match('_get_\w+_name', c) is not None]
            )

    def _get_date_name(self):
        """Defines name for a new partition for date partition subtype"""
        return '{0}_{1}'.format(self.table, self.datetime.get_name())

    def _get_partition_function(self):
        """Returns correct partition function depending on the MySQL column type"""
        functions = {
            'date': 'TO_DAYS',
            'datetime': 'TO_DAYS',
            'timestamp': 'UNIX_TIMESTAMP',
        }

        column_type = self._get_column_type()

        try:
            return functions[column_type]
        except KeyError:
            raise PartitionFunctionError(current_value=column_type, allowed_values=functions.keys())

    def _get_column_type(self):
        """
        We can't rely on self.column_type in MySQL, because Django uses only date
        and datetime types internally, but MySQL has an additional timestamp type
        and we need to know that, otherwise we can apply incorrect partition function
        """
        self.cursor.execute("""
            SELECT data_type
            FROM information_schema.columns
            WHERE table_name = '{parent_table}' AND column_name = '{partition_column}';
        """.format(
            parent_table=self.table,
            partition_column=self.partition_column,
        ))

        return self.cursor.fetchone()[0]
