"""
PostgreSQL JDBC database backend for Django.
"""
import sys

from django.db import utils
from djangojy.db.backends import *
from djangojy.db.backends.signals import connection_created
from djangojy.db.backends.postgresql.operations import DatabaseOperations
from djangojy.db.backends.postgresql.client import DatabaseClient
from djangojy.db.backends.postgresql.creation import DatabaseCreation
from djangojy.db.backends.postgresql.version import get_version
from djangojy.db.backends.postgresql.introspection import DatabaseIntrospection
from djangojy.db.backends.postgresql.operations import DatabaseOperations as PostgresqlDatabaseOperations
from django.utils.log import getLogger
from django.utils.safestring import SafeUnicode, SafeString
from django.utils.timezone import utc

try:
    import com.ziclix.python.sql.zxJDBC as Database
    from com.ziclix.python.sql import PyConnectionEx
except ImportError, exc:
    from django.core.exceptions import ImproperlyConfigured
    raise ImproperlyConfigured("Error loading djangojy.db.backends.sqlite3 backend: %s" % exc)

DatabaseError = Database.DatabaseError
IntegrityError = Database.IntegrityError

#PyConnectionEx.register_type(PyConnectionEx.UNICODE)
#PyConnectionEx.register_adapter(SafeString, PyConnectionEx.QuotedString)
#PyConnectionEx.register_adapter(SafeUnicode, PyConnectionEx.QuotedString)

logger = getLogger('djangojy.db.backends')

def utc_tzinfo_factory(offset):
    if offset != 0:
        raise AssertionError("database connection isn't set to UTC")
    return utc

class CursorWrapper(BaseCursorWrapper):
    def __init__(self, cursor):
        super(CursorWrapper, self).__init__(cursor)
        self.tzinfo_factory = None
        self.query = None

    def execute(self, query, args=None):
        query, args = self.transform_params(query, args)
        try:
            return self.cursor.execute(query, args)
        except Database.IntegrityError, e:
            raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
        except Database.DatabaseError, e:
            raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
        finally:
            self.query = query

    def executemany(self, query, args):
        query, args = self.transform_params(query, args)
        try:
            return self.cursor.executemany(query, args)
        except Database.IntegrityError, e:
            raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
        except Database.DatabaseError, e:
            raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]
        finally:
            self.query = query

    def __getattr__(self, attr):
        if attr in self.__dict__:
            return self.__dict__[attr]
        else:
            return getattr(self.cursor, attr)

    def __iter__(self):
        return iter(self.cursor)

class DatabaseFeatures(zxJDBCFeaturesMixin, BaseDatabaseFeatures):
    needs_datetime_string_cast = False
    can_return_id_from_insert = True
    requires_rollback_on_dirty_transaction = True
    has_real_datatype = True
    can_defer_constraint_checks = True
    has_select_for_update = True
    has_select_for_update_nowait = True
    has_bulk_insert = True
    supports_tablespaces = True
    can_distinct_on_fields = True

class DatabaseOperations(zxJDBCOperationsMixin, PostgresqlDatabaseOperations):
    pass # The mixin contains all what is needed

class DatabaseWrapper(BaseDatabaseWrapper):
    vendor = 'postgresql'
    driver_class_name = 'org.postgresql.Driver'
    jdbc_url_pattern = "jdbc:postgresql://%(HOST)s:%(PORT)s/%(NAME)s"
    default_host = "localhost"
    default_port = 5432
    operators = {
        'exact': '= %s',
        'iexact': '= UPPER(%s)',
        'contains': 'LIKE %s',
        'icontains': 'LIKE UPPER(%s)',
        'regex': '~ %s',
        'iregex': '~* %s',
        'gt': '> %s',
        'gte': '>= %s',
        'lt': '< %s',
        'lte': '<= %s',
        'startswith': 'LIKE %s',
        'endswith': 'LIKE %s',
        'istartswith': 'LIKE UPPER(%s)',
        'iendswith': 'LIKE UPPER(%s)',
    }

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

        self.features = DatabaseFeatures(self)
        autocommit = self.settings_dict["OPTIONS"].get('autocommit', False)
        self.features.uses_autocommit = autocommit
        if autocommit:
            #level = PyConnectionEx.ISOLATION_LEVEL_AUTOCOMMIT
            self._set_auto_commit()
            self.features.uses_savepoints = False
        else:
            #level = PyConnectionEx.ISOLATION_LEVEL_READ_COMMITTED
            self._set_isolation_level(PyConnectionEx.TRANSACTION_READ_COMMITTED)
            self.features.uses_savepoints = False
        self.ops = DatabaseOperations(self)
        self.client = DatabaseClient(self)
        self.creation = DatabaseCreation(self)
        self.introspection = DatabaseIntrospection(self)
        self.validation = BaseDatabaseValidation(self)
        self._pg_version = None

    def check_constraints(self, table_names=None):
        """
        To check constraints, we set constraints to immediate. Then, when, we're done we must ensure they
        are returned to deferred.
        """
        self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
        self.cursor().execute('SET CONSTRAINTS ALL DEFERRED')

    def close(self):
        self.validate_thread_sharing()
        if self.connection is None:
            return

        try:
            self.connection.close()
            self.connection = None
        except Database.Error:
            # In some cases (database restart, network connection lost etc...)
            # the connection to the database is lost without giving Django a
            # notification. If we don't set self.connection to None, the error
            # will occur a every request.
            self.connection = None
            logger.warning('postgresql error while closing the connection.',
                exc_info=sys.exc_info()
            )
            raise

    def _get_pg_version(self):
        if self._pg_version is None:
            self._pg_version = get_version(self.connection)
        return self._pg_version
    pg_version = property(_get_pg_version)

    def _cursor(self):
        settings_dict = self.settings_dict
        if self.connection is None:
            if settings_dict['NAME'] == '':
                from django.core.exceptions import ImproperlyConfigured
                raise ImproperlyConfigured("You need to specify NAME in your Django settings file.")
            conn_params = {
                'database': settings_dict['NAME'],
            }
            conn_params.update(settings_dict['OPTIONS'])
            if 'autocommit' in conn_params:
                del conn_params['autocommit']
            if settings_dict['USER']:
                conn_params['user'] = settings_dict['USER']
            if settings_dict['PASSWORD']:
                conn_params['password'] = settings_dict['PASSWORD']
            if settings_dict['HOST']:
                conn_params['host'] = settings_dict['HOST']
            if settings_dict['PORT']:
                conn_params['port'] = settings_dict['PORT']
            self.connection = self.new_connection()
            self.connection.set_client_encoding('UTF8')
            tz = 'UTC' if settings.USE_TZ else settings_dict.get('TIME_ZONE')
            if tz:
                try:
                    get_parameter_status = self.connection.get_parameter_status
                except AttributeError:
                    # psycopg2 < 2.0.12 doesn't have get_parameter_status
                    conn_tz = None
                else:
                    conn_tz = get_parameter_status('TimeZone')

                if conn_tz != tz:
                    # Set the time zone in autocommit mode (see #17062)
                    # self.connection.set_isolation_level(PyConnectionEx.ISOLATION_LEVEL_AUTOCOMMIT)
                    self.connection.set_auto_commit(True)
                    self.connection.cursor().execute(self.ops.set_time_zone_sql() % (tz,))
            self.connection.set_isolation_level(self.isolation_level)
            self._get_pg_version()
            connection_created.send(sender=self.__class__, connection=self)
        cursor = CursorWrapper(self.connection.cursor())
        cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
        return cursor

    def _enter_transaction_management(self, managed):
        """
        Switch the isolation level when needing transaction support, so that
        the same transaction is visible across all the queries.
        """
        if self.features.uses_autocommit and managed and not self.isolation_level:
            #self._set_isolation_level(PyConnectionEx.ISOLATION_LEVEL_READ_COMMITTED)
            self._set_isolation_level(PyConnectionEx.TRANSACTION_READ_COMMITTED)

    def _leave_transaction_management(self, managed):
        """
        If the normal operating mode is "autocommit", switch back to that when
        leaving transaction management.
        """
        if self.features.uses_autocommit and not managed and self.isolation_level:
            #self._set_isolation_level(PyConnectionEx.ISOLATION_LEVEL_AUTOCOMMIT)
            self._set_auto_commit()

    def _set_isolation_level(self, level):
        """
        Do all the related feature configurations for changing isolation
        levels. This doesn't touch the uses_autocommit feature, since that
        controls the movement *between* isolation levels.
        """
        assert level in range(5)
        try:
            if self.connection is not None:
                self.connection.set_isolation_level(level)
        finally:
            self.isolation_level = level
            self.features.uses_savepoints = bool(level)

    def _set_auto_commit(self):
        self.connection.set_auto_commit(True)

    def _commit(self):
        if self.connection is not None:
            try:
                return self.connection.commit()
            except Database.IntegrityError, e:
                raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
