"Simple Fts backend"
import re
import os
import datetime

from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db import connection, transaction
from django.db.models import Q, Max
from django.core.cache import cache

from fts.backends.base import BaseClass, BaseModel, BaseManager
from fts.models import Word, Index, Namespace

import unicodedata
from fts.words.stop import FTS_STOPWORDS
try:
    from fts.words.snowball import Stemmer
except ImportError:
    from fts.words.porter import Stemmer

# http://code.google.com/p/django-fts/issues/detail?id=7
# http://djangosnippets.org/snippets/1725/
try:
    from functools import wraps
except ImportError:
    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
def commit_on_success_unless_managed(func):
    """
    If the decorated function runs successfully, a commit is made, unless the
    transactions are being managed; if the function produces an exception,
    a rollback is made, again unless transactions are being managed somewhere
    else.
    """
    def _commit_on_success_unless_managed(*args, **kw):
        try:
            if transaction.is_managed():
                forced_managed = False
            else:
                transaction.enter_transaction_management()
                forced_managed = True
            
            try:
                res = func(*args, **kw)
            except:
                # All exceptions must be handled here (even string ones).
                if transaction.is_dirty():
                    if forced_managed:
                        transaction.rollback()
                    else:
                        transaction.rollback_unless_managed()
                raise
            else:
                if transaction.is_dirty():
                    if forced_managed:
                        transaction.commit()
                    else:
                        transaction.commit_unless_managed()
            return res
        finally:
            if forced_managed:
                transaction.leave_transaction_management()
    return wraps(func)(_commit_on_success_unless_managed)

qn = connection.ops.quote_name

WEIGHTS = {
    'A' : 10,
    'B' : 4,
    'C' : 2,
    'D' : 1
}
SEP = re.compile(r'[\s,.()\[\]|]')

_NAMESPACES_CACHE = {}
_NAMESPACES_CACHE_SYNC = {}

class SearchClass(BaseClass):
    def __init__(self, server, params):
        self.backend = 'simple'

class SearchManager(BaseManager):
    def __init__(self, **kwargs):
        super(SearchManager, self).__init__(**kwargs)
        # For autocomplete, generally you'd want:
        #   full_index=True and stem_words=False (full_index implies exact_search)
        # For regular Fulltext search, you'd want:
        #   full_index=False, steam_words=True and exact_search=True
        self.full_index = kwargs.get('full_index', False)
        self.stem_words = kwargs.get('stem_words', True)
        self.exact_search = kwargs.get('exact_search', True)
        self.namespace = kwargs.get('namespace', None)

    def _get_namespace_id(self, namespace):
        _k_ = namespace
        try:
            now = datetime.datetime.now()
            sync_time = _NAMESPACES_CACHE_SYNC.get(_k_)
            expired = not sync_time and True or False
            keys = (
                'fts-namespaces-last-updated',
            )
            for key in keys:
                if not key: continue
                last_updated = cache.get(key)
                if not last_updated:
                    last_updated = now
                    cache.set(key, last_updated)
                if sync_time and last_updated > sync_time:
                    expired = True
            if expired:
                raise KeyError
            namespace_id = _NAMESPACES_CACHE[_k_]
        except KeyError:
            for n in Namespace.objects.all():
                _NAMESPACES_CACHE[n.slug] = n.id

            namespace_id = _NAMESPACES_CACHE.get(namespace)

            # save sync time for cache:
            _NAMESPACES_CACHE[_k_] = namespace_id
            _NAMESPACES_CACHE_SYNC[_k_] = datetime.datetime.now()

        return namespace_id

    def _get_idx_words(self, line, minlen=0):
        words = self._get_words(line, minlen)
        if self.full_index:
            # Find all the substrings of the word (all digit words treated differently):
            words = set( word[i:j] for word in words for i in not word.isdigit() and range(len(word)) or (0,) for j in range(i+1, len(word)+1) if j-i > minlen )
        return words
    
    def _get_words(self, line, minlen=0):
        # Remove accents
        line = ''.join((c for c in unicodedata.normalize('NFD', unicode(line)) if unicodedata.category(c) != 'Mn'))
        # Lowercase and split in a set of words
        words = set(SEP.split(line.lower()))
        # Stemmer function
        if self.stem_words:
            stem = Stemmer(self.language_code)
        else:
            stem = lambda w: w
        # Get stemmed set of words not in the list of stop words and with a minimum of a minlen length
        return set( stem(word) for word in words if word and word not in FTS_STOPWORDS[self.language_code] and len(word) > minlen )
        
    @commit_on_success_unless_managed
    def _update_index(self, pk, dumping=None):
        """
            Index Update (Live or Dumping)
            For Dumping update (recommended method):
                dumping = {}  # use to pass and keep context for multiple calls
                Entity.autocomplete._update_index(None, dumping)
                GeonameAlternateName.autocomplete._update_index(None, dumping)
                TagLabel.autocomplete._update_index(None, dumping)
                then in Sqlite3:
                    .separator "\t"
                    .import fts_word.txt fts_word
                    .import fts_index.txt fts_index
                ...or in PostgreSQL:
                    COPY fts_word FROM 'fts_word.txt';
                    COPY fts_index FROM 'fts_index.txt';
            For Live update (very slow compared to dumping update):
                TagLabel.autocomplete.update_index()
            Usage:
                TagLabel.autocomplete.search('label')
        """
        if self.model._meta.abstract:
            return # skip abstract class updates
        namespace_id = self._get_namespace_id(self.namespace)
        if not namespace_id and self.namespace:
            ns = Namespace.objects.create(slug=self.namespace)
            namespace_id = ns.id
        ctype = ContentType.objects.get_for_model(self.model)
        filter = { 'content_type__pk': ctype.pk }
        if namespace_id: filter['namespace'] = namespace_id
        if pk is not None:
            if isinstance(pk, (set,list,tuple)):
                filter['object_id__in'] = pk
                items = self.filter(pk__in=pk)
            else:
                filter['object_id'] = pk
                items = self.filter(pk=pk)
        else: