# -*- coding: utf-8 -*-
#
# Copyright (C)2005-2010 Edgewall Software
# Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Christopher Lenz <cmlenz@gmx.de>

import errno
import os
import re
import weakref

from genshi.builder import tag

from trac.config import ConfigurationError, ListOption
from trac.core import Component, TracError, implements
from trac.db.api import ConnectionBase, IDatabaseConnector
from trac.db.util import ConnectionWrapper, IterableCursor
from trac.env import ISystemInfoProvider
from trac.util import get_pkginfo, getuser, lazy
from trac.util.translation import _, tag_

_like_escape_re = re.compile(r'([/_%])')

_glob_escape_re = re.compile(r'[*?\[]')

try:
    import pysqlite2.dbapi2 as sqlite
except ImportError:
    import sqlite3 as sqlite

sqlite_version = sqlite.sqlite_version_info
sqlite_version_string = sqlite.sqlite_version
pysqlite_version = sqlite.version_info
pysqlite_version_string = get_pkginfo(sqlite).get('version',
                                                  '%d.%d.%s'
                                                  % pysqlite_version)
min_sqlite_version = (3, 0, 0)
min_pysqlite_version = (2, 4, 1)  # version provided by Python 2.6


class PyFormatCursor(sqlite.Cursor):
    def _rollback_on_error(self, function, *args, **kwargs):
        try:
            return function(self, *args, **kwargs)
        except sqlite.DatabaseError:
            self.cnx.rollback()
            raise

    def execute(self, sql, args=None):
        if args:
            sql = sql % (('?',) * len(args))
        return self._rollback_on_error(sqlite.Cursor.execute, sql,
                                       args or [])

    def executemany(self, sql, args):
        if not args:
            return
        sql = sql % (('?',) * len(args[0]))
        return self._rollback_on_error(sqlite.Cursor.executemany, sql,
                                       args)


# EagerCursor taken from the example in pysqlite's repository:
#
#   http://code.google.com/p/pysqlite/source/browse/misc/eager.py
#
# Only change is to subclass it from PyFormatCursor instead of
# sqlite.Cursor.

class EagerCursor(PyFormatCursor):
    def __init__(self, con):
        PyFormatCursor.__init__(self, con)
        self.rows = []
        self.pos = 0

    def execute(self, *args):
        result = PyFormatCursor.execute(self, *args)
        self.rows = PyFormatCursor.fetchall(self)
        self.pos = 0
        return result

    def fetchone(self):
        try:
            row = self.rows[self.pos]
        except IndexError:
            return None
        else:
            self.pos += 1
            return row

    def fetchmany(self, num=None):
        if num is None:
            num = self.arraysize

        result = self.rows[self.pos:self.pos + num]
        self.pos += num
        return result

    def fetchall(self):
        result = self.rows[self.pos:]
        self.pos = len(self.rows)
        return result


# Mapping from "abstract" SQL types to DB-specific types
_type_map = {
    'int': 'integer',
    'int64': 'integer',
}


def _to_sql(table):
    sql = ["CREATE TABLE %s (" % table.name]
    coldefs = []
    for column in table.columns:
        ctype = column.type.lower()
        ctype = _type_map.get(ctype, ctype)
        if column.auto_increment:
            ctype = "integer PRIMARY KEY"
        elif len(table.key) == 1 and column.name in table.key:
            ctype += " PRIMARY KEY"
        coldefs.append("    %s %s" % (column.name, ctype))
    if len(table.key) > 1:
        coldefs.append("    UNIQUE (%s)" % ','.join(table.key))
    sql.append(',\n'.join(coldefs) + '\n);')
    yield '\n'.join(sql)
    for index in table.indices:
        unique = 'UNIQUE' if index.unique else ''
        yield "CREATE %s INDEX %s_%s_idx ON %s (%s);" % (unique, table.name,
              '_'.join(index.columns), table.name, ','.join(index.columns))


class SQLiteConnector(Component):
    """Database connector for SQLite.

    Database URLs should be of the form:
    {{{
    sqlite:path/to/trac.db
    }}}
    """
    implements(IDatabaseConnector, ISystemInfoProvider)

    required = False

    extensions = ListOption('sqlite', 'extensions',
        doc="""Paths to [https://sqlite.org/loadext.html sqlite extensions].
        The paths may be absolute or relative to the Trac environment.
        (''since 0.12'')
        """)

    memory_cnx = None

    def __init__(self):
        self.error = None

    # ISystemInfoProvider methods

    def get_system_info(self):
        if self.required:
            yield 'SQLite', sqlite_version_string
            yield 'pysqlite', pysqlite_version_string

    # IDatabaseConnector methods

    def get_supported_schemes(self):
        if sqlite_version < min_sqlite_version:
            self.error = _("SQLite version is %(version)s. Minimum required "
                           "version is %(min_version)s.",
                           version=sqlite_version_string,
                           min_version='%d.%d.%d' % min_sqlite_version)
        elif pysqlite_version < min_pysqlite_version:
            self.error = _("Need at least PySqlite %(version)s or higher",
                           version='%d.%d.%d' % min_pysqlite_version)
        elif (2, 5, 2) <= pysqlite_version < (2, 5, 5):
            self.error = _("PySqlite 2.5.2 - 2.5.4 break Trac, please use "
                           "2.5.5 or higher")
        yield 'sqlite', -1 if self.error else 1

    def get_connection(self, path, log=None, params={}):
        self.required = True
        params['extensions'] = self._extensions
        if path == ':memory:':
            if not self.memory_cnx:
                self.memory_cnx = SQLiteConnection(path, log, params)
            return self.memory_cnx
        else:
            return SQLiteConnection(path, log, params)