from StringIO import StringIO
from ldt import settings
from ldt.indexation import object_delete, object_insert, object_run_index
from ldt.ldt_utils.models import Segment, Content, Project
from ldt.ldt_utils.stat import update_stat_project, add_annotation_to_stat
from ldt.ldt_utils.utils import reduce_text_node
from ldt.utils.url import request_with_auth
import logging

from django.apps import apps
from django.contrib.contenttypes.models import ContentType
import lxml.etree  # @UnresolvedImport
from taggit.utils import parse_tags


logger = logging.getLogger(__name__)

def Property(func):
    return property(**func())


class LdtIndexer(object):

    def __init__(self, object_list, decoupage_blackList=settings.DECOUPAGE_BLACKLIST, callback=None):
        self.__object_list = object_list
        self.__decoupage_blacklist = decoupage_blackList
        self.__callback = callback
        self.__segment_cache = []
        self.__all_tags_cache = {}
        self.__segment_tags_cache = {}
        self.__tags_cache = []

    @Property
    def decoupage_blacklist(): #@NoSelf
        doc = """get blacklist""" #@UnusedVariable

        def fget(self):
            if self.__decoupage_blacklist is None:
                self.__decoupage_blacklist = ()
            return self.__decoupage_blacklist

        def fset(self, value):
            self.__decoupage_blacklist = value

        def fdel(self):
            del self.__decoupage_blacklist

        return locals()

    def index_all(self):
        for i,obj in enumerate(self.__object_list):
            if self.__callback:
                self.__callback(i,obj)
            self.index_object(obj)


    def index_object(self, obj):
        self._do_index_object(obj)

        if self.__segment_cache:
            object_insert(Segment, self.__segment_cache, 'id_hash')
            object_run_index(Segment, self.__segment_cache)
            self.__segment_cache = []


    def _do_index_object(self, obj):
        raise NotImplementedError()

    def index_ensemble(self, ensemble, content, project=None):
        ensembleId = ensemble.get(u"id", None)
        ctp = ContentType.objects.get_for_model(Segment)
        TaggedItem = apps.get_model('taggit', 'TaggedItem')
        Tag = apps.get_model('taggit', 'Tag')

        for decoupageNode in ensemble.getchildren():
            if decoupageNode.tag != "decoupage"  or decoupageNode.get(u"id", None) in self.decoupage_blacklist:
                continue

            decoupId = decoupageNode.get(u"id", None)
            res = decoupageNode.xpath("elements/element")
            for elementNode in res:

                elementId = elementNode.get(u"id", None)
                tags = elementNode.get(u"tags", None)

                if tags is None or len(tags) == 0:
                    tags = u""
                    restagnode = elementNode.xpath("tag/text()", smart_strings=False)
                    for tagnode in restagnode:
                        tags = tags + u"," + tagnode

                if tags is None or len(tags) == 0:
                    tags = u""
                    restagnode = elementNode.xpath("tags/tag/text()", smart_strings=False)

                    for tagnode in restagnode:
                        tags = tags + u"," + tagnode

                if tags is None:
                    tags = u""


                title = reduce_text_node(elementNode, "title/text()")
                abstract = reduce_text_node(elementNode, "abstract/text()")
                polemics = elementNode.xpath('meta/polemics/polemic/text()')

                author = elementNode.get("author", "")
                start_ts = min(int(float(elementNode.get("begin", "-1"))), 0xFFFFFFFF/2)
                duration = min(int(float(elementNode.get("dur", "0"))), 0xFFFFFFFF/2)
                date_str = elementNode.get("date", "")
                ldt_id = u""
                if project:
                    ldt_id = project.ldt_id
                # audio annotation management
                audio_src = u""
                audio_href = u""
                audio_node = elementNode.xpath('audio')
                if audio_node:
                    audio_src = audio_node[0].get(u"source", u"")
                    audio_href = audio_node[0].text

                seg = Segment.create(content=content,
                              iri_id=content.iri_id,
                              ensemble_id=ensembleId,
                              cutting_id=decoupId,
                              element_id=elementId,
                              title=title,
                              abstract=abstract,
                              duration=duration,
                              author=author,
                              start_ts=start_ts,
                              date=date_str,
                              project_obj=project,
                              project_id=ldt_id,
                              audio_src=audio_src,
                              audio_href=audio_href)

                tags = parse_tags(tags)
                self.__segment_tags_cache[seg.id_hash] = tags
                seg.polemics = seg.get_polemic(polemics)
                if settings.LDT_INDEXATION_INSERT_BATCH_SIZE < 2:
                    seg.save()
                    seg.tags.add(*tags)
                else:
                    self.__segment_cache.append(seg)
                    self.__tags_cache = set( list(self.__tags_cache) + tags)

                    if not (len(self.__segment_cache)%settings.LDT_INDEXATION_INSERT_BATCH_SIZE):
                        # First we insert/bulk_create the segments
                        object_insert(Segment, self.__segment_cache, 'id_hash')
                        # Filter already existing tags in current dict
                        for t in list(self.__tags_cache):
                            if t in self.__all_tags_cache:
                                self.__tags_cache.remove(t)
                        # Filter already existing tags in database
                        current_tags = Tag.objects.filter(name__in=self.__tags_cache)
                        for t in current_tags:
                            self.__all_tags_cache[t.name] = t
                            self.__tags_cache.remove(t.name)
                        # If the rest of tags were never in the db, we save them
                        if len(self.__tags_cache)>0:
                            for t in self.__tags_cache:
                                tag = Tag.objects.create(name=t)
                                self.__all_tags_cache[t] = tag

                        # Prepare taggeditems
                        ti = []

                        for s in self.__segment_cache:
                            s.tag_list = self.__segment_tags_cache[s.id_hash]
                            for t in self.__segment_tags_cache[s.id_hash]:
                                ti.append( TaggedItem(tag=self.__all_tags_cache[t], content_type=ctp, object_id=s.pk) )
                        TaggedItem.objects.bulk_create(ti)
                        object_run_index(Segment, self.__segment_cache)
                        self.__segment_cache = []

        # last loop if necessary
        if len(self.__segment_cache) > 0:
            # First we insert/bulk_create the segments
            object_insert(Segment, self.__segment_cache, 'id_hash')
            # Filter already existing tags in current dict
            for t in list(self.__tags_cache):
                if t in self.__all_tags_cache:
                    self.__tags_cache.remove(t)
            # Filter already existing tags in database
            current_tags = Tag.objects.filter(name__in=self.__tags_cache)
            for t in current_tags:
                self.__all_tags_cache[t.name] = t
                self.__tags_cache.remove(t.name)
            # If the rest of tags were never in the db, we save them
            if len(self.__tags_cache)>0:
                for t in self.__tags_cache:
                    tag = Tag.objects.create(name=t[0:255])
                    self.__all_tags_cache[t] = tag

            # Prepare taggeditems
            ti = []
            for s in self.__segment_cache:
                s.tag_list = self.__segment_tags_cache[s.id_hash]
                for t in self.__segment_tags_cache[s.id_hash]:
                    ti.append( TaggedItem(tag=self.__all_tags_cache[t], content_type=ctp, object_id=s.pk) )
            TaggedItem.objects.bulk_create(ti)
            object_run_index(Segment, self.__segment_cache)
            # End of batch
            self.__segment_cache = []



class ContentIndexer(LdtIndexer):

    def _do_index_object(self, obj):

        content = obj
        url = content.iri_url()
        res, file_content = request_with_auth(url)
        if res['status'] != "200":
            logger.debug("Problem when getting %s, passing: %r", url, res)
            return
        doc = lxml.etree.parse(StringIO(file_content)) #@UndefinedVariable

        object_delete(Segment, iri_id=content.iri_id, project_id='')

        res = doc.xpath("/iri/body/ensembles/ensemble")

        for ensemble in res:
            self.index_ensemble(ensemble, content)


class ProjectIndexer(LdtIndexer):

    def _do_index_object(self, obj):

        project = obj
        # pocketfilms.utils.log.debug("Indexing project : "+str(project.iri_id))
        doc = lxml.etree.fromstring(project.ldt_encoded) #@UndefinedVariable

        object_delete(Segment, project_obj__ldt_id=project.ldt_id)

        res = doc.xpath("/iri/annotations/content")

        for content in res:
            contentId = content.get(u"id", None)
            content_obj = None

            clist = Content.objects.filter(iri_id = contentId) #@UndefinedVariable
            if len(clist) > 0:
                content_obj = clist[0]

            for ensemble in content.getchildren():
                self.index_ensemble(ensemble, content_obj, project)

def index_project(**kwargs):
    must_reindex = kwargs.get("must_reindex", True)
    if must_reindex and settings.AUTO_INDEX_AFTER_SAVE:
        instance = kwargs['instance']
        if instance.state != Project.PUBLISHED:
            object_delete(Segment, project_obj__ldt_id=instance.ldt_id)
            update_stat_project(instance)
        else:
            projectIndexer = ProjectIndexer([instance])
            projectIndexer.index_all()
            update_stat_project(instance)

def update_or_create_segment(params):
    project = params.get("project", None)
    content = params.get("content", None)

    seg_data = { k: params[k] for k in ['content', 'ensemble_id', 'cutting_id',
         'element_id', 'title', 'abstract', 'start_ts', 'duration', 'date', 'author',
         'audio_src', 'audio_href'] if k in params }

    if params.has_key("content"):
        seg_data["iri_id"] = params["content"].iri_id
    if params.has_key("project"):
        seg_data["project_obj"] = params["project"]
        seg_data["project_id"] = params["project"].ldt_id

    seg, created = Segment.objects.update_or_create(
        project_id=project.ldt_id if project is not None else "",
        iri_id=content.iri_id if content is not None else "",
        ensemble_id=params.get("ensemble_id", ""),
        cutting_id=params.get("cutting_id", ""),
        element_id=params.get("element_id", ""),
        defaults = seg_data
    )

    polemics = params.get("polemics", "")
    seg.polemics = seg.get_polemic(polemics)
    seg.save()

    tags_str = params.get("tags", "")
    for t in parse_tags(tags_str):
        seg.tags.add(t)
    seg.save()
    add_annotation_to_stat(seg.content, seg.start_ts, seg.start_ts+seg.duration)

    return created

def add_segment(params):
    return update_or_create_segment(params)

def edit_segment(project_id, iri_id, ensemble_id, cutting_id, element_id, params):
    seg = Segment.objects.filter(project_id=project_id, iri_id=iri_id, ensemble_id=ensemble_id, cutting_id=cutting_id, element_id=element_id)
    if seg.count() <= 0:
        return False
    created = update_or_create_segment(params)
    return not(created)

def delete_segment(project, project_id, iri_id, ensemble_id, cutting_id, element_id):

    # delete Segment
    for seg in Segment.objects.filter(project_id=project_id, iri_id=iri_id, ensemble_id=ensemble_id, cutting_id=cutting_id, element_id=element_id):
        seg.delete()
        add_annotation_to_stat(seg.content, seg.start_ts, seg.start_ts+seg.duration)
