import pickle
import zlib

import BTrees
from zope.interface import implementer

from pyramid.traversal import resource_path

from ..interfaces import (
    ICatalogFactory,
    IIndexFactory,
    )
from ..util import get_dotted_name

from .indexes import (
    TextIndex,
    FieldIndex,
    KeywordIndex,
    FacetIndex,
    AllowedIndex,
    PathIndex,
    )

from .discriminators import IndexViewDiscriminator

@implementer(IIndexFactory)
class IndexFactory(object):

    def __init__(self, **kw):
        self.kw = kw

    def __call__(self, catalog_name, index_name):
        discriminator = IndexViewDiscriminator(catalog_name, index_name)
        index = self.index_type(discriminator=discriminator, **self.kw)
        index.__factory_hash__ = hash(self)
        return index

    def hashvalues(self):
        values = {}
        values.update(self.kw)
        values['class'] = get_dotted_name(self.__class__)
        family = values.get('family', None)
        if family is not None:
            if family == BTrees.family64:
                family = 'family64'
            elif family == BTrees.family32:
                family = 'family32'
            else:
                raise ValueError(family)
            values['family'] = family
        return values

    def __hash__(self):
        data = pickle.dumps(tuple(sorted(self.hashvalues().items())))
        return zlib.crc32(data) & 0xffffffff

    def is_stale(self, index):
        index_hash = getattr(index, '__factory_hash__', None)
        return index_hash != hash(self)

class Text(IndexFactory):
    index_type = TextIndex

    def hashvalues(self):
        values = IndexFactory.hashvalues(self)
        for name in ('lexicon', 'index'):
            attr = values.get(name, None)
            if attr is not None:
                clsname = attr.__class__.__name__
                values[name] = clsname
        return values

class Field(IndexFactory):
    index_type = FieldIndex
    
class Keyword(IndexFactory):
    index_type = KeywordIndex

class Facet(IndexFactory):
    index_type = FacetIndex

    def hashvalues(self):
        values = IndexFactory.hashvalues(self)
        facets = values.get('facets', ())
        values['facets'] = tuple(sorted([(x,y) for x, y in facets]))
        return values

class Allowed(IndexFactory):
    index_type = AllowedIndex

class Path(IndexFactory):
    index_type = PathIndex

@implementer(ICatalogFactory)
class CatalogFactory(object):
    def __init__(self, name, index_factories):
        self.name = name
        self.index_factories = index_factories

    def _remove_stale(self, catalog, output=None):
        catalog_path = resource_path(catalog)
        result = False
        for index_name, index in list(catalog.items()):
            if not index_name in self.index_factories:
                output and output(
                    '%s: removing stale index named %r' % (
                        catalog_path,
                        index_name,
                        )
                    )
                catalog.remove(index_name)
                result = True
        return result

    def replace(self, catalog, reindex=False, output=None, **reindex_kw):
        catalog_path = resource_path(catalog)

        to_reindex = set()

        changed = False

        for index_name, index_factory in self.index_factories.items():
            if index_name in catalog:
                verb = 'replacing'
            else:
                verb = 'adding'

            output and output(
                '%s: %s index named %r' % (catalog_path, verb, index_name),
                )

            index = index_factory(self.name, index_name)
            index.__sdi_deletable__ = False
            catalog.replace(index_name, index)
            to_reindex.add(index_name)
            changed = True

        removed_stale = self._remove_stale(catalog, output=output)

        if changed and reindex:
            catalog.reindex(indexes=to_reindex, output=output, **reindex_kw)

        return removed_stale or changed

    def sync(self, catalog, reindex=False, output=None, **reindex_kw):

        catalog_path = resource_path(catalog)

        to_reindex = set()
        changed = False

        for index_name, index_factory in self.index_factories.items():
            if not index_name in catalog:
                output and output(
                    '%s: adding index named %r' % (catalog_path, index_name),
                    )
                index = index_factory(self.name, index_name)
                catalog.add(index_name, index)
                changed = True
                to_reindex.add(index_name)

            index = catalog[index_name]

            if index_factory.is_stale(index):
                output and output(
                    '%s: replacing stale index named %r' % (
                        catalog_path,
                        index_name,
                        )
                    )
                index = index_factory(self.name, index_name)
                index.__sdi_deletable__ = False
                catalog.replace(index_name, index)
                to_reindex.add(index_name)
                changed = True

        removed_stale = self._remove_stale(catalog, output=output)

        if changed and reindex:
            catalog.reindex(indexes=to_reindex, output=output, **reindex_kw)

        return changed or removed_stale

