# Path: django_sharding_library/constants.py
# class Backends(object):
#     MYSQL = ('django.db.backends.mysql', 'django.contrib.gis.db.backends.mysql')
#     POSTGRES = ('django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql', 'django.contrib.gis.db.backends.postgis')
#     SQLITE = ('django.db.backends.sqlite3', 'django.contrib.gis.db.backends.spacialite')
# 
# Path: django_sharding_library/utils.py
# def create_postgres_global_sequence(sequence_name, db_alias, reset_sequence=False):
#     cursor = connections[db_alias].cursor()
#     sid = transaction.savepoint(db_alias)
#     create_sequence_if_not_exists_sql = """DO
# $$
# BEGIN
#         CREATE SEQUENCE %s;
# EXCEPTION WHEN duplicate_table THEN
#         -- do nothing, it's already there
# END
# $$ LANGUAGE plpgsql;"""
#     try:
#         cursor.execute(create_sequence_if_not_exists_sql % sequence_name)
#     except DatabaseError:
#         transaction.savepoint_rollback(sid, using=db_alias)
#     else:
#         transaction.savepoint_commit(sid, using=db_alias)
#     if reset_sequence:
#         cursor.execute("SELECT setval('%s', 1, false)" % (sequence_name,))
#     cursor.close()
# 
# def create_postgres_shard_id_function(sequence_name, db_alias, shard_id):
#     cursor = connections[db_alias].cursor()
#     cursor.execute(postgres_shard_id_function_sql % {'shard_epoch': settings.SHARD_EPOCH,
#                                                      'shard_id': shard_id,
#                                                      'sequence_name': sequence_name})
#     cursor.close()
# 
# def get_next_sharded_id(shard):
#     cursor = connections[shard].cursor()
#     cursor.execute("SELECT next_sharded_id();")
#     generated_id = cursor.fetchone()
#     cursor.close()
# 
#     return generated_id[0]


from django.apps import apps
from django.conf import settings
from django.db.models import AutoField, CharField, ForeignKey, BigIntegerField, OneToOneField
from django_sharding_library.constants import Backends
from django_sharding_library.utils import create_postgres_global_sequence, create_postgres_shard_id_function, get_next_sharded_id
    from django.db.models import BigAutoField
    from django.utils.translation import ugettext_lazy as _
        from django_sharding_library.id_generation_strategies import TableStrategy
        from django_sharding_library.id_generation_strategies import UUIDStrategy
            from django.db.backends.postgresql.base import DatabaseWrapper as PostgresDatabaseWrapper
            from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper as PostgresDatabaseWrapper
    """
    A field that uses a Postgres stored procedure to return an ID generated on the database.

    Generates them prior to save with a seperate call to the DB.
    """

    def get_shard_from_id(self, instance_id):
        group = getattr(self, 'django_sharding__shard_group', None)
        shard_id_to_find = int(bin(instance_id)[-23:-10], 2)  # We know where the shard id is stored in the PK's bits.

        # We can check the shard id from the PK against the shard ID in the databases config
        for alias, db_settings in settings.DATABASES.items():
            if db_settings["SHARD_GROUP"] == group and db_settings["SHARD_ID"] == shard_id_to_find:
                return alias

        return None  # Return None if we could not determine the shard so we can fall through to the next shard grab attempt

    def get_pk_value_on_save(self, instance):
        return self.generate_id(instance)

    def pre_save(self, model_instance, add):
        if getattr(model_instance, self.attname, None) is not None:
            return super(PostgresShardGeneratedIDField, self).pre_save(model_instance, add)
        value = self.generate_id(model_instance)
        setattr(model_instance, self.attname, value)
        return value

    @staticmethod
    def generate_id(instance):
        shard = instance._state.db or instance.get_shard()

# Path: django_sharding_library/constants.py
# class Backends(object):
#     MYSQL = ('django.db.backends.mysql', 'django.contrib.gis.db.backends.mysql')
#     POSTGRES = ('django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql', 'django.contrib.gis.db.backends.postgis')
#     SQLITE = ('django.db.backends.sqlite3', 'django.contrib.gis.db.backends.spacialite')
# 
# Path: django_sharding_library/utils.py
# def create_postgres_global_sequence(sequence_name, db_alias, reset_sequence=False):
#     cursor = connections[db_alias].cursor()
#     sid = transaction.savepoint(db_alias)
#     create_sequence_if_not_exists_sql = """DO
# $$
# BEGIN
#         CREATE SEQUENCE %s;
# EXCEPTION WHEN duplicate_table THEN
#         -- do nothing, it's already there
# END
# $$ LANGUAGE plpgsql;"""
#     try:
#         cursor.execute(create_sequence_if_not_exists_sql % sequence_name)
#     except DatabaseError:
#         transaction.savepoint_rollback(sid, using=db_alias)
#     else:
#         transaction.savepoint_commit(sid, using=db_alias)
#     if reset_sequence:
#         cursor.execute("SELECT setval('%s', 1, false)" % (sequence_name,))
#     cursor.close()
# 
# def create_postgres_shard_id_function(sequence_name, db_alias, shard_id):
#     cursor = connections[db_alias].cursor()
#     cursor.execute(postgres_shard_id_function_sql % {'shard_epoch': settings.SHARD_EPOCH,
#                                                      'shard_id': shard_id,
#                                                      'sequence_name': sequence_name})
#     cursor.close()
# 
# def get_next_sharded_id(shard):
#     cursor = connections[shard].cursor()
#     cursor.execute("SELECT next_sharded_id();")
#     generated_id = cursor.fetchone()
#     cursor.close()
# 
#     return generated_id[0]


# Path: django_sharding_library/fields.py
from django.apps import apps
from django.conf import settings
from django.db.models import AutoField, CharField, ForeignKey, BigIntegerField, OneToOneField
from django_sharding_library.constants import Backends
from django_sharding_library.utils import create_postgres_global_sequence, create_postgres_shard_id_function, get_next_sharded_id
    from django.db.models import BigAutoField
    from django.utils.translation import ugettext_lazy as _
        from django_sharding_library.id_generation_strategies import TableStrategy
        from django_sharding_library.id_generation_strategies import UUIDStrategy
            from django.db.backends.postgresql.base import DatabaseWrapper as PostgresDatabaseWrapper
            from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper as PostgresDatabaseWrapper

    """
    A field that uses a Postgres stored procedure to return an ID generated on the database.

    Generates them prior to save with a seperate call to the DB.
    """

    def get_shard_from_id(self, instance_id):
        group = getattr(self, 'django_sharding__shard_group', None)
        shard_id_to_find = int(bin(instance_id)[-23:-10], 2)  # We know where the shard id is stored in the PK's bits.

        # We can check the shard id from the PK against the shard ID in the databases config
        for alias, db_settings in settings.DATABASES.items():
            if db_settings["SHARD_GROUP"] == group and db_settings["SHARD_ID"] == shard_id_to_find:
                return alias

        return None  # Return None if we could not determine the shard so we can fall through to the next shard grab attempt

    def get_pk_value_on_save(self, instance):
        return self.generate_id(instance)

    def pre_save(self, model_instance, add):
        if getattr(model_instance, self.attname, None) is not None:
            return super(PostgresShardGeneratedIDField, self).pre_save(model_instance, add)
        value = self.generate_id(model_instance)
        setattr(model_instance, self.attname, value)
        return value

    @staticmethod
    def generate_id(instance):
        shard = instance._state.db or instance.get_shard()

        return get_next_sharded_id(shard)