from datetime import datetime
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from collections import OrderedDict
from ldt.ldt_utils.models import Content, Project
from ldt.ldt_utils.stat import get_string_from_buckets
from ldt.ldt_utils.utils import reduce_text_node
import lxml.etree
import hashlib
import uuid

User = get_user_model()

DATE_FORMATS = ["%d/%m/%Y", "%Y-%m-%d"]

import logging
logger = logging.getLogger(__name__)

"""
Serialize a project object to a cinelab compatible array
"""
class ProjectJsonSerializer(object):

    def __init__(self, project, from_contents=True, from_display=True, first_cutting=None, only_one_cutting=False):
        self.project = project
        self.parsed = False
        self.ldt_doc = None
        self.medias_dict = OrderedDict()
        self.annotations_dict = OrderedDict()
        self.annotations_by_annotation_types = {}
        self.tags = {}
        self.tags_dict = OrderedDict()
        self.annotation_types_dict = OrderedDict()
        self.views_dict = OrderedDict()
        self.lists_dict = OrderedDict()
        self.serialize_contents = from_contents
        self.from_display = from_display
        self.display_contents_list = []
        self.display_cuttings_list = []
        self.display_ensemble_list = []
        self.first_cutting = first_cutting
        self.only_one_cutting = only_one_cutting
        # if first_cutting, it means that we limit to the concerned media=
        self.one_content= False


    def __parse_views(self, display_node_list):

        for display_node in display_node_list:
            display_id = display_node.get(u"id", None)
            if not display_id:
                continue
            content_list = []
            cuttings_list = []

            new_display = {
                "id": display_id,
                "contents": content_list,
                "annotation_types": cuttings_list,
            }

            for content_node in display_node.xpath("content"):
                content_id = content_node.get("id")
                if content_id not in content_list:
                    content_list.append(content_id)
                if content_id not in self.display_contents_list:
                    self.display_contents_list.append(content_id)
                for cutting_node  in content_node.xpath("decoupage"):
                    cutting_id = cutting_node.get("id")
                    if cutting_id not in cuttings_list:
                        cuttings_list.append(cutting_id)
                    if cutting_id not in self.display_cuttings_list:
                        self.display_cuttings_list.append(cutting_id)
                    ensemble_id = cutting_node.get("idens")
                    if ensemble_id not in self.display_ensemble_list:
                        self.display_ensemble_list.append(ensemble_id)


            # sets cutting to display in first position for the metadataplayer
            if self.first_cutting:

                annotation_types = new_display['annotation_types']

                if self.first_cutting not in annotation_types:
                    annotation_types.append(self.first_cutting)

                index = -1
                for i, s in enumerate(annotation_types):
                    if s == self.first_cutting:
                        index = i
                        break

                annotation_types[0], annotation_types[index] = annotation_types[index], annotation_types[0]

            if self.only_one_cutting:
                new_display['annotation_types'] = [new_display['annotation_types'][0]]

            self.views_dict[display_id] = new_display


    def __parse_ensemble(self, ensemble_node, content, cutting_only=None):

        ensemble_id = ensemble_node.attrib[u"id"]

        ensemble_author = ensemble_node.attrib[u"author"]
        ensemble_title = ensemble_node.attrib[u"title"]
        ensemble_description = ensemble_node.attrib[u"abstract"]
        ensemble_created = datetime.utcnow().isoformat()
        ensemble_modified = ensemble_created

        list_items = []
        new_list = {
            "id" : ensemble_id,
            "items" : list_items,
            "meta" : {
                "dc:creator":ensemble_author,
                "dc:created": ensemble_created,
                "dc:contributor":"undefined",
                "dc:modified": ensemble_modified,
                "dc:title":ensemble_title,
                "dc:description": ensemble_description,
                "id-ref":content.iri_id,
                "editable":"false"
            }
        }

        if cutting_only:
            cuttings_list = cutting_only
        else:
            cuttings_list = ensemble_node

        for decoupage_node in cuttings_list:
            if decoupage_node.tag != "decoupage" :
                continue

            decoupage_id = decoupage_node.attrib[ u"id"]
            if not cutting_only and self.from_display and decoupage_id not in self.display_cuttings_list:
                continue
            decoupage_creator = decoupage_node.attrib[u"author"]
            if decoupage_creator=="perso":
                decoupage_creator = self.project.owner.username
            if not decoupage_creator:
                decoupage_creator = "IRI"

            decoupage_contributor = decoupage_creator
            date_str = decoupage_node.get(u"date")
            decoupage_created = None
            if date_str :
                for date_format in DATE_FORMATS:
                    try:
                        decoupage_created = datetime.strptime(date_str, date_format).isoformat()
                        break
                    except Exception:
                        decoupage_created = None
            if decoupage_created is None:
                decoupage_created = datetime.utcnow().isoformat()
            decoupage_modified = decoupage_created

            decoupage_title = ""
            for txtRes in decoupage_node.xpath("title/text()", smart_strings=False):
                decoupage_title += txtRes

            decoupage_description = ""
            for txtRes in decoupage_node.xpath("abstract/text()", smart_strings=False):
                decoupage_description += txtRes

            list_items.append({"id-ref":decoupage_id})

            new_annotation_types = {
                "id":decoupage_id,
                "dc:creator":decoupage_creator,
                "dc:created":decoupage_created,
                "dc:contributor":decoupage_contributor,
                "dc:modified":decoupage_modified,
                "dc:title":decoupage_title,
                "dc:description":decoupage_description
            }

            self.annotation_types_dict[decoupage_id] = new_annotation_types
            self.annotations_by_annotation_types[decoupage_id] = []

            res = decoupage_node.xpath("elements/element")
            for element_node in res:

                element_id = element_node.attrib[u"id"]
                element_begin = element_node.attrib[u"begin"]
                element_duration = element_node.attrib[u"dur"]
                element_media = content.iri_id
                element_color = element_node.attrib.get(u"color", "")
                element_ldt_src = element_node.attrib.get(u"src", "")
                element_created = element_node.attrib["date"]

                element_title = reduce_text_node(element_node, "title/text()")
                element_description = reduce_text_node(element_node, "abstract/text()")

                element_source_node_list = element_node.xpath("meta/source")

                if len(element_source_node_list) > 0:
                    element_source_node = element_source_node_list[0]
                    element_source = {"mimetype" :element_source_node.get(u'mimetype'), "url":element_source_node.get(u'url'), "content":reduce_text_node(element_source_node)}
                else:
                    element_source = None

                element_polemic_version = reduce_text_node(element_node, "meta/polemic_version/text()")

                element_audio_src = ""
                element_audio_href = ""
                res = element_node.xpath("audio")
                if len(res) > 0:
                    element_audio_src = res[0].get(u"source", u"")
                    element_audio_href = res[0].text

                element_tags = []

                tags = element_node.get(u"tags", u"")

                tags_list = map(lambda s: s.strip(), tags.split(","))

                #tags
                if tags is None or len(tags) == 0:
                    tags_list = []
                    restagnode = element_node.xpath("tag/text()", smart_strings=False)
                    for tagnode in restagnode:
                        tags_list.append(tagnode)

                if tags_list is None or len(tags_list) == 0:
                    tags_list = []
                    restagnode = element_node.xpath("tags/tag/text()", smart_strings=False)
                    for tagnode in restagnode:
                        tags_list.append(tagnode)

                tag_date = datetime.utcnow().isoformat()
                for tag_title in tags_list:
                    if tag_title not in self.tags:
                        # md5 instead of uuid to get an almost unicity
                        tag_id = unicode(hashlib.md5(tag_title.encode('utf-8')).hexdigest())
                        new_tag = {
                            "id":tag_id,
                            "meta" : {
                                "dc:creator":"IRI",
                                "dc:created": tag_date,
                                "dc:contributor":"IRI",
                                "dc:modified": tag_date,
                                "dc:title":tag_title
                            }
                        }
                        self.tags[tag_title] = new_tag
                        self.tags_dict[tag_id] = new_tag
                    else:
                        tag_id = self.tags[tag_title]["id"]
                    element_tags.append({"id-ref":tag_id})

                if not element_tags:
                    element_tags = None

                annot_creator = element_node.attrib[u"author"]
                if annot_creator=="perso":
                    annot_creator = decoupage_creator
                if not annot_creator:
                    annot_creator = decoupage_creator

                new_annotation = {
                    "begin": int(float(element_begin)),
                    "end": int(float(element_begin)) + int(float(element_duration)),
                    "id": element_id,
                    "created": element_created,
                    "media": element_media,
                    "color": element_color,
                    "content": {
                        "mimetype": "application/x-ldt-structured",
                        "title": element_title,
                        "description": element_description,
                        #"color": element_color,
                        "img": {
                            "src": element_ldt_src,
                        },
                        "audio": {
                            "src" : element_audio_src,
                            "mimetype": "audio/mp3",
                            "href": element_audio_href
                        },
                        "polemics" :[pol_elem.text for pol_elem in element_node.xpath("meta/polemics/polemic")],
                    },
                    "tags": element_tags,
                    "meta": {
                        "id-ref": decoupage_id,
                        "dc:creator": annot_creator,
                        "dc:contributor": decoupage_contributor,
                        "dc:created": decoupage_created,
                        "dc:modified": decoupage_modified,
                    }
                }

                if element_source:
                    new_annotation['meta']['dc:source'] = element_source
                if element_polemic_version:
                    new_annotation['content']['polemic_version'] = element_polemic_version

                # Metadatacomposer features. An annotation can have the usual datas (title, description...)
                # and new kinds of extra metas : video, audio, text, links array, images slideshow
                # Get type
                meta_type_node = element_node.xpath("meta/type")
                if len(meta_type_node) > 0:
                    meta_type = reduce_text_node(meta_type_node[0], "text()")
                    # Update mimetype and add datas
                    if meta_type=="video":
                        new_annotation["content"]["mimetype"] = "application/x-ldt-video"
                        new_annotation["content"]["url"] = reduce_text_node(element_node, "meta/url/text()")
                        new_annotation["content"]["embedcode"] = reduce_text_node(element_node, "meta/embedcode/text()")
                    elif meta_type=="audio":
                        new_annotation["content"]["mimetype"] = "application/x-ldt-audio"
                        new_annotation["content"]["url"] = reduce_text_node(element_node, "meta/url/text()")
                        new_annotation["content"]["embedcode"] = reduce_text_node(element_node, "meta/embedcode/text()")
                    elif meta_type=="text":
                        new_annotation["content"]["mimetype"] = "application/x-ldt-text"
                        new_annotation["content"]["markup"] = reduce_text_node(element_node, "meta/markup/text()")
                        new_annotation["content"]["text"] = reduce_text_node(element_node, "meta/text/text()")
                    elif meta_type=="links":
                        new_annotation["content"]["mimetype"] = "application/x-ldt-links"
                        new_annotation["content"]["links"] = []
                        link_nodes = element_node.xpath("meta/links/link")
                        for link in link_nodes:
                            new_annotation["content"]["links"].append({"url": reduce_text_node(link, "url/text()"), "title":reduce_text_node(link, "title/text()")})
                    elif meta_type=="slideshow":
                        new_annotation["content"]["mimetype"] = "application/x-ldt-slideshow"
                        new_annotation["content"]["slideduration"] = reduce_text_node(element_node, "meta/slideduration/text()")
                        new_annotation["content"]["autostart"] = {'true': True, 'false': False, "0": False, "1": True}.get(reduce_text_node(element_node, "meta/autostart/text()").lower())
                        new_annotation["content"]["images"] = []
                        image_nodes = element_node.xpath("meta/images/image")
                        for image in image_nodes:
                            new_annotation["content"]["images"].append({"url": reduce_text_node(image, "url/text()"), "title":reduce_text_node(image, "title/text()"), "description":reduce_text_node(image, "description/text()")})

                self.annotations_dict[element_id] = new_annotation
                self.annotations_by_annotation_types[decoupage_id].append(new_annotation)

        if not list_items:
            new_list["items"] = None
        self.lists_dict[ensemble_id] = new_list



    def __parse_ldt(self):

        self.ldt_doc = lxml.etree.fromstring(self.project.ldt_encoded)

        if self.from_display:
            xpath_str = "/iri/displays/display[position()=1]"
            if isinstance(self.from_display, basestring):
                xpath_str = "/iri/displays/display[@id='%s']" % self.from_display

            self.__parse_views(self.ldt_doc.xpath(xpath_str))

        # getting all contents at once
        # If self.one_content, we remove the other content
        if self.first_cutting and self.one_content:
            contents_iri_id = list(
                set(self.ldt_doc.xpath('/iri/annotations/content[ensemble/decoupage/@id=\'%s\']/@id' % self.first_cutting))
            )
        else:
            contents_iri_id = list(
                set(self.ldt_doc.xpath("/iri/medias/media/@id")) |
                set(self.ldt_doc.xpath("/iri/annotations/content/@id")) |
                (set(self.ldt_doc.xpath('/iri/annotations/content[ensemble/decoupage/@id=\'%s\']/@id' % self.first_cutting)) if self.first_cutting and self.first_cutting not in self.display_cuttings_list else set())
            )

        contents =  dict([ (c.iri_id, c) for c in Content.objects.filter(iri_id__in=contents_iri_id).select_related('media_obj', 'stat_annotation').prefetch_related("authors")])
        m_cls = ContentType.objects.get(model='media')
        m_cls = m_cls.model_class()
        medias = dict([ (m.id, m) for m in m_cls.safe_objects.filter(id__in = [c.media_obj.id for c in contents.values() if c.media_obj])])


        res = self.ldt_doc.xpath("/iri/medias/media")
        for mediaNode in res:
            iri_id = mediaNode.attrib[u"id"]
            if (self.from_display and iri_id not in self.display_contents_list) or iri_id not in contents_iri_id:
                continue
            content = contents[iri_id]#Content.objects.get(iri_id=iri_id) #@UndefinedVariable
            self.__parse_content(content, medias)

        res = self.ldt_doc.xpath("/iri/annotations/content")
        for content_node in res:
            content_id = content_node.attrib[u"id"]
            if (self.from_display and content_id not in self.display_contents_list) or content_id not in contents_iri_id:
                continue
            content = contents[content_id]#Content.objects.get(iri_id=content_id) #@UndefinedVariable
            for ensemble_node in content_node:
                if ensemble_node.tag != "ensemble" :
                    continue
                ensemble_id = ensemble_node.get("id")
                if self.from_display and ensemble_id not in self.display_ensemble_list:
                    continue
                self.__parse_ensemble(ensemble_node, content)

        if self.first_cutting and self.first_cutting not in self.display_cuttings_list:
            cutting_node= self.ldt_doc.xpath('/iri/annotations/content/ensemble/decoupage[@id=\'%s\']' % self.first_cutting)[0]
            ensemble_node = cutting_node.xpath('..')[0]
            content_node = ensemble_node.xpath('..')[0]
            iri_id = content_node.get("id")
            content = contents[iri_id]#Content.objects.get(iri_id=iri_id)
            self.__parse_ensemble(ensemble_node, content, cutting_only=[cutting_node])


        #reorder annotations and annotation type from view
        if self.from_display and len(self.views_dict) > 0:
            new_annotation_types_dict = OrderedDict()
            new_annotations_dict = OrderedDict()
            for annotation_type in self.display_cuttings_list + [self.first_cutting]:
                if annotation_type in self.annotation_types_dict:
                    new_annotation_types_dict[annotation_type] = self.annotation_types_dict[annotation_type]
                    for annot in self.annotations_by_annotation_types[annotation_type]:
                        new_annotations_dict[annot['id']] = annot

            self.annotations_dict = new_annotations_dict
            self.annotation_types_dict = new_annotation_types_dict

        # We add the first "bout a bout". It is an "edit" in ldt format and list/listtype="mashup" in json format
        self.__parse_edits()

        self.parsed = True


    def __parse_edits(self):

        editings = self.ldt_doc.xpath("/iri/edits/editing")
        if not editings:
            return False
        editing = self.ldt_doc.xpath("/iri/edits/editing[position()=1]")[0]
        e_id = editing.get("id")
        eList = editing.xpath("edit[position()=1]/eList")[0]
        d = datetime.utcnow().isoformat()
        e_title = ""
        for txtRes in editing.xpath("title/text()", smart_strings=False):
            e_title += txtRes
        e_description = ""
        for txtRes in editing.xpath("abstract/text()", smart_strings=False):
            e_description += txtRes
        list_items = []
        for item in eList:
            # ref is structured with contenated ids : id_content|;|id_ensemble|;|id_annot-type|;||;||;|id_SEG
            ids = item.get("ref").split('|;|')
            list_items.append(ids[-1])
        # We build the jsonable object if necessary
        if len(list_items)>0:
            new_list = {
                "id" : e_id,
                "items" : list_items,
                "meta" : {
                    "dc:creator":"IRI",
                    "dc:created": d,
                    "dc:contributor":"IRI",
                    "dc:modified": d,
                    "dc:title": e_title,
                    "dc:description": e_description,
                    "listtype":"mashup"
                }
            }
            self.lists_dict[e_id] = new_list

    def __parse_content(self, content, medias):

        doc = lxml.etree.parse(content.iri_file_path())

        authors = content.authors.all()

        if len(authors) > 0 :
            author = authors[0].handle
        else :
            author = "IRI"

        if len(authors) > 1 :
            contributor = authors[1].handle
        else :
            contributor = author

        content_author = ""

        res = doc.xpath("/iri/head/meta[@name='author']/@content")
        if len(res) > 0:
            content_author = res[0]


        content_date = ""

        res = doc.xpath("/iri/head/meta[@name='date']/@content")
        if len(res) > 0:
            content_date = res[0]

        url = ""
        meta_item_value = ""

        if content.media_obj and content.media_obj.id not in medias:
            url = settings.FORBIDDEN_STREAM_URL
        elif content.videopath:
            url = content.videopath.rstrip('/') + "/" + content.src
            meta_item_value = content.videopath.rstrip('/') + "/"
        else:
            url = content.src

        new_media = {
            "http://advene.liris.cnrs.fr/ns/frame_of_reference/ms" : "o=0",
            "id" : content.iri_id,
            "url" : url,
            "unit" : "ms",
            "origin" : "0",
            "meta": {
                "dc:creator" : author,
                "dc:created" : content.creation_date.isoformat(),
                "dc:contributor" : contributor,
                "dc:modified" : content.update_date.isoformat(),
                "dc:creator.contents" : content_author,
                "dc:created.contents" : content_date,
                "dc:title" : content.title,
                "dc:description" : content.description,
                "dc:duration" : content.get_duration(),
                "item": {
                    "name" : "streamer",
                    "value": meta_item_value,
                },
            }
        }

        self.medias_dict[content.iri_id] = new_media

        new_display = {
            "id": "stat",
            "contents": [content.iri_id],
            "meta": {
                "stat": get_string_from_buckets(content.annotation_volume),
            }
        }

        self.views_dict['test'] = new_display

        if self.serialize_contents:
            res = doc.xpath("/iri/body/ensembles/ensemble")
            for ensemble_node in res:
                self.__parse_ensemble(ensemble_node, content)


    def serialize_to_cinelab(self, one_content_param=False):

        res = {}

        self.one_content = one_content_param

        if not self.parsed:
            self.__parse_ldt()

        project_main_media = ""
        if len(self.medias_dict) > 0:
            project_main_media = self.medias_dict.iteritems().next()[1]["id"]

        res['meta'] = {
            'id': self.project.ldt_id,
            'dc:created':self.project.creation_date.isoformat(),
            'dc:modified':self.project.modification_date.isoformat(),
            'dc:contributor':self.project.changed_by,
            'dc:creator':self.project.created_by,
            'dc:title':self.project.title,
            'dc:description':self.project.get_description(self.ldt_doc), # get from doc, parse ldt
            'main_media': {"id-ref":project_main_media}
        }


        res['medias'] = self.medias_dict.values() if len(self.medias_dict) > 0 else None
        res['lists'] = self.lists_dict.values() if len(self.lists_dict) > 0 else None
        res['tags'] = self.tags.values() if len(self.tags) > 0 else None
        res['views'] = self.views_dict.values() if len(self.views_dict) > 0 else None

        res['annotation-types'] = self.annotation_types_dict.values() if len(self.annotation_types_dict) > 0 else None
        res['annotations'] = self.annotations_dict.values() if len(self.annotations_dict) > 0 else None

        res['@context'] = { "dc": "http://purl.org/dc/elements/1.1/" }

        return res

    def get_annotations(self, first_cutting=True):

        if not self.parsed:
            self.__parse_ldt()

        annotations = []

        current_cutting = None
        uri = None
        for annot in self.annotations_dict.values():
            logging.debug("current cutting" + repr(current_cutting) + " : annot " + annot['meta']['id-ref']) #@UndefinedVariable
            if first_cutting and current_cutting and current_cutting != annot['meta']['id-ref'] :
                break
            current_cutting = annot['meta']['id-ref']
            content_id = annot['media']
            content = Content.objects.get(iri_id=content_id) #@UndefinedVariable
            if annot['tags']:
                tags_list = map(lambda tag_entry: self.tags_dict[tag_entry['id-ref']]['meta']['dc:title'], annot['tags'])
            else:
                tags_list = []
            begin = int(annot['begin'])
            duration = int(annot['end']) - begin
            if content.media_obj and content.media_obj.external_publication_url:
                uri = "%s#t=%d" % (content.media_obj.external_publication_url, begin)


            annotations.append({
                'begin': begin,
                'duration':duration,
                'title':annot['content']['title'],
                'desc':annot['content']['description'],
                'tags': tags_list,
                'id':annot['id'],
                'uri':uri
            })

        return annotations

"""
Quick and dirty converter from cinelab JSON to ldt format.
Does not support imports, mutliple medias, or media creation
"""
class JsonCinelab2Ldt:

    def create_json(self, json):

        medias = json['medias']
        contentList = []
        for media in medias:
            c = Content.objects.get(iri_id=media['id'])
            if c != None:
                contentList.append(c)

        meta = json['meta']
        creator = meta['creator']
        contributor = meta['contributor']

        user = User.objects.get(username=creator)
        project = Project.create_project(user, creator + '_' + contributor, contentList)
        project.changed_by = contributor

        ldtdoc = lxml.etree.fromstring(project.ldt_encoded)
        element = ldtdoc.xpath('/iri/annotations')

        for media in contentList:
            content = lxml.etree.Element('content')
            content.set('id', media.iri_id)

        annotation_types = json['annotation_types']
        cuttings = {}
        if len(annotation_types) > 0:
            media = lxml.etree.SubElement(element[0], 'content')
            media.set('id', medias[0]['id'])

            ens = lxml.etree.SubElement(media, 'ensemble')
            ens.set('title', 'Decoupages personnels')
            ens.set('idProject', project.ldt_id)
            ens.set('abstract', '')
            ens.set('id', 'g_' + str(uuid.uuid1()))

            for i in annotation_types:
                cutting_infos = {'desc' : i['meta']['description']}

                dec = lxml.etree.SubElement(ens, 'decoupage')
                dec.set('author', contributor)
                dec.set('id', 'c_' + str(uuid.uuid1()))
                elements_list = lxml.etree.SubElement(dec, 'elements')

                title = lxml.etree.SubElement(dec, 'title')
                title.text = i['id']

                abstract = lxml.etree.SubElement(dec, 'abstract')
                abstract.text = i['meta']['description']

                cutting_infos['xml_node'] = elements_list
                cuttings[i['id']] = cutting_infos


        annotations = json['annotations']
        for i in annotations:
            cutting_infos = cuttings[i['type']]
            elements_node = cutting_infos['xml_node']
            element = lxml.etree.SubElement(elements_node, 'element')

            element.set('begin', str(i['begin']))
            element.set('dur', str(i['end'] - i['begin']))
            element.set('id', 's_' + str(uuid.uuid1()))

            title = lxml.etree.SubElement(element, 'title')
            audio = lxml.etree.SubElement(element, 'audio')
            audio.set('source', 'undefined')
            abstract = lxml.etree.SubElement(element, 'abstract')
            abstract.text = i['content']['data']
            tags = lxml.etree.SubElement(element, 'tags')
            for tag in i['tags']:
                tag_xml = lxml.etree.SubElement(tags, 'tag')
                tag_xml.text = tag


        project.ldt = lxml.etree.tostring(ldtdoc, pretty_print=True)
        project.save()

        return project.ldt
