import json
from ldt.ldt_utils.models import Content, Project
from ldt.ldt_utils.projectserializer import ProjectJsonSerializer
from ldt.ldt_utils.utils import generate_uuid
from ldt.utils.url import reverse_prefix
import math

from django.conf import settings
import lxml.etree
from tastypie.exceptions import NotFound, BadRequest
from tastypie.serializers import Serializer


class CinelabSerializer(Serializer):
    # Thanks to the this serializer, the api will be able to serialize and deserialize a project in cinelab json format
    # See http://liris.cnrs.fr/advene/cinelab/ for more information
    formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist', 'cinelab']
    content_types = {
        'cinelab': 'application/cinelab',
        'json': 'application/json',
        'jsonp': 'text/javascript',
        'xml': 'application/xml',
        'yaml': 'text/yaml',
        'html': 'text/html',
        'plist': 'application/x-plist',
    }
    json_indent = 2
     
    def to_cinelab(self, data, options=None):
        """
        Given some Python data, produces Cinelab/JSON output.
        N.B. : for the moment, this serializer works with api_dispatch_detail only. 
               It does not work with api_dispatch_list. Only from_cinelab deals with api_dispatch_list's multiple objects like {"objects":[...]}
        """
        options = options or {}
        if hasattr(data, 'obj') :
            if isinstance(data.obj, Project) :
                ps = ProjectJsonSerializer(data.obj)
                data = ps.serialize_to_cinelab()
                return json.dumps(data, sort_keys=True, indent=self.json_indent)
        raise NotFound("Project not found")
    

    def from_cinelab(self, content):
        """
        This function takes cinelab/json and transform it into a regular REST API json project data.
        All fields are (* required at rebuilt json) :
        "changed_by": "admin",
        "created_by": "admin",
        "creation_date": "2012-02-11T01:18:48.028534",
        "description": "",
        "image": "/pf/static/media/thumbnails/contents/content_default_icon.png",
        "ldt*": "<iri><project>...<medias>...<annotations>...<displays>...<edits>...</iri>",
        "ldt_id*": "594dc612-544e-11e1-96e5-00145ea49a02",
        "modification_date": "2012-02-11T01:19:40.203322",
        "state": 2,
        "title*": "front project : test DailyMotion"
        "contents*": ["IRI_ID_1","IRI_ID_2"]
        "owner*": "user_id"
        """
        
        cinelab = json.loads(content)
        # We validate the json
        self.validate_cinelab_json(cinelab)
        # We search if the id is set
        meta = cinelab["meta"]
        if "id" in meta and meta["id"]!="":
            ldt_id = meta["id"]
        else:
            ldt_id = generate_uuid()
        # default state = (1, 'edition') OR (2, 'published')
        state = meta.get('iricle:status', 2)
        
        contents = [reverse_prefix("api_dispatch_detail", kwargs={"api_name":"1.0", "resource_name":"contents", "iri_id":c["id"]}) for c in cinelab["medias"]]
        owner_uri = reverse_prefix("api_dispatch_detail", kwargs={"api_name":"1.0", "resource_name":"users", "username":meta["dc:creator"]})
        
        ldt = lxml.etree.tostring(self.cinelab_to_ldt(cinelab, ldt_id), pretty_print=True)
        
        s = {"description": meta["dc:description"], "ldt_id": ldt_id, "title": meta["dc:title"], 
             "created_by": meta["dc:creator"], "changed_by": meta["dc:contributor"], "creation_date": meta["dc:created"], "modification_date": meta["dc:modified"],
             "contents": contents, "owner": owner_uri, "state":state, "ldt":ldt}
        
        return s
    
    
    def cinelab_to_ldt(self, cinelab, ldt_id=None):
        # We suppose that the cinelab json has been validated
        # Start xml
        meta = cinelab["meta"]
        annotation_types = cinelab["annotation-types"]
        annotations = cinelab["annotations"]
        # If the ldt_id is not set, we get in the cinelab meta
        if not ldt_id:
            ldt_id = meta["id"]
        
        # Before creating the xml, we build a dict for tag {"id":"label"}
        # Careful : works with python >= 2.7
        tag_dict = {t["id"]:t["meta"]["dc:title"] for t in cinelab["tags"]}
        # We'll also build a annotation-type to media/ensemble dict to simplify the views node building
        at_media_dict = {}
        # We'll also build a dict from annotation id to media/ensemble/decoupage dict to simplify the edits/mashup node building
        annot_ids_dict = {}
        # We'll also build a dict from media id to media url dict to simplify the edits/mashup node building
        media_url_dict = {}
        
        # create a dom
        iri = lxml.etree.Element('iri')
        
        # Node project
        projectNode = lxml.etree.SubElement(iri, 'project')
        projectNode.set('abstract', meta["dc:description"])
        projectNode.set('title', meta["dc:title"])
        projectNode.set('user', meta["dc:creator"])
        projectNode.set('id', ldt_id)
    
        # Node medias and node annotations
        mediasNode = lxml.etree.SubElement(iri, 'medias')
        annotationsNode = lxml.etree.SubElement(iri, 'annotations')
        for c in cinelab["medias"]:
            # We add the content to the medias node
            content = Content.objects.get(iri_id=c["id"])
            iri_id = content.iri_id
            mediaNode = lxml.etree.SubElement(mediasNode, 'media')
            mediaNode.set('id', iri_id)
            mediaNode.set('src', content.iri_url())
            if content.videopath != None :
                mediaNode.set('video', content.videopath)
                media_url_dict[iri_id] = {'url':content.videopath + content.src, 'pos':None}
            else:
                mediaNode.set('video', settings.STREAM_URL)
                media_url_dict[iri_id] = {'url':settings.STREAM_URL + content.src, 'pos':None}
            mediaNode.set('pict', "")
            mediaNode.set('extra', "")
            # We add the annotations
            contentNode = lxml.etree.SubElement(annotationsNode, 'content')
            contentNode.set('id', iri_id)
            # we search the cinelab lists if they represent a content's list of annotation-type
            for l in cinelab["lists"]:
                if "id-ref" in l["meta"]:
                    if l["meta"]["id-ref"]==iri_id:
                        # We build the ensemble node
                        ensembleNode = lxml.etree.SubElement(contentNode, 'ensemble')
                        ensembleNode.set('id', l["id"])
                        ensembleNode.set('idProject', ldt_id)
                        ensembleNode.set('title', l["meta"]["dc:title"])
                        ensembleNode.set('author', l["meta"]["dc:creator"])
                        ensembleNode.set('abstract', l["meta"]["dc:description"])
                        # We build the decoupage node, equivalent to an annotation-type
                        for at_ref in l["items"]:
                            at_id = at_ref["id-ref"]
                            # We get the annotation-type datas
                            for at in annotation_types:
                                if at["id"]==at_id:
                                    at_media_dict[at_id] = (iri_id, l["id"])
                                    decoupageNode = lxml.etree.SubElement(ensembleNode, 'decoupage')
                                    decoupageNode.set('id', at_id)
                                    decoupageNode.set('author', at["dc:creator"])
                                    titleDec = lxml.etree.SubElement(decoupageNode, 'title')
                                    titleDec.text = at["dc:title"]
                                    abstractDec = lxml.etree.SubElement(decoupageNode, 'abstract')
                                    abstractDec.text = at["dc:description"]
                                    elementsNode = lxml.etree.SubElement(decoupageNode, 'elements')
                                    # We get all the annotations for this media and this annotation-type
                                    for a in annotations:
                                        if a["media"]==iri_id and a["meta"]["id-ref"]==at_id:
                                            annot_ids_dict[a["id"]] = (iri_id, l["id"], at_id, a["begin"], a["end"], a["color"])
                                            elementNode = lxml.etree.SubElement(elementsNode, 'element')
                                            elementNode.set('id', a["id"])
                                            elementNode.set('begin', str(a["begin"]))
                                            elementNode.set('dur', str(a["end"]-a["begin"]))
                                            elementNode.set('author', a["meta"]["dc:creator"])
                                            elementNode.set('date', a["meta"]["dc:created"])
                                            elementNode.set('color', a["color"])
                                            img_src = ""
                                            if "img" in a["content"]:
                                                if "src" in a["content"]["img"]:
                                                    img_src = a["content"]["img"]["src"]
                                            elementNode.set('src', img_src)
                                            titleElm = lxml.etree.SubElement(elementNode, 'title')
                                            titleElm.text = a["content"]["title"]
                                            abstractElm = lxml.etree.SubElement(elementNode, 'abstract')
                                            abstractElm.text = a["content"]["description"]
                                            # Audio node, if the dict has the audio key
                                            audioElm = lxml.etree.SubElement(elementNode, 'audio')
                                            audioElm.set('source', "")
                                            if "audio" in a["content"]:
                                                audioElm.set('source', a["content"]["audio"]["src"])
                                                audioElm.text = a["content"]["audio"]["href"]
                                            # The tags dict has been set before, we just get the labels
                                            tagsNode = lxml.etree.SubElement(elementNode, 'tags')
                                            if a["tags"]:
                                                for t in a["tags"]:
                                                    if t["id-ref"] in tag_dict:
                                                        tagNode = lxml.etree.SubElement(tagsNode, 'tag')
                                                        tagNode.text = tag_dict[t["id-ref"]]
                                            # Last element's node
                                            metaNode = lxml.etree.SubElement(elementNode, 'meta')
                                            # New for metadatacomposer : we add meta informations about extra of different kinds : 
                                            # related video, audio, links and html code which are not the native annotation datas
                                            # like title, description, etc.
                                            if "mimetype" in a["content"]:
                                                # Video or audio bonus
                                                if a["content"]["mimetype"]=="application/x-ldt-video" or a["content"]["mimetype"]=="application/x-ldt-audio":
                                                    typeNode = lxml.etree.SubElement(metaNode, 'type')
                                                    if a["content"]["mimetype"]=="application/x-ldt-video":
                                                        typeNode.text = "video"
                                                    else:
                                                        typeNode.text = "audio"
                                                    urlNode = lxml.etree.SubElement(metaNode, 'url')
                                                    urlNode.text = a["content"]["url"]
                                                    embedcodeNode = lxml.etree.SubElement(metaNode, 'embedcode')
                                                    embedcodeNode.text = lxml.etree.CDATA(a["content"]["embedcode"])
                                                # Text bonus
                                                elif a["content"]["mimetype"]=="application/x-ldt-text":
                                                    typeNode = lxml.etree.SubElement(metaNode, 'type')
                                                    typeNode.text = "text"
                                                    markupNode = lxml.etree.SubElement(metaNode, 'markup')
                                                    markupNode.text = a["content"]["markup"]
                                                    textNode = lxml.etree.SubElement(metaNode, 'text')
                                                    textNode.text = lxml.etree.CDATA(a["content"]["text"])
                                                # Links bonus
                                                elif a["content"]["mimetype"]=="application/x-ldt-links":
                                                    typeNode = lxml.etree.SubElement(metaNode, 'type')
                                                    typeNode.text = "links"
                                                    linksNode = lxml.etree.SubElement(metaNode, 'links')
                                                    for link in a["content"]["links"]:
                                                        linkNode = lxml.etree.SubElement(linksNode, 'link')
                                                        urlNode = lxml.etree.SubElement(linkNode, 'url')
                                                        urlNode.text = link["url"]
                                                        titleNode = lxml.etree.SubElement(linkNode, 'title')
                                                        titleNode.text = link["title"]
                                                # Image slideshow bonus
                                                elif a["content"]["mimetype"]=="application/x-ldt-slideshow":
                                                    typeNode = lxml.etree.SubElement(metaNode, 'type')
                                                    typeNode.text = "slideshow"
                                                    durationNode = lxml.etree.SubElement(metaNode, 'slideduration')
                                                    durationNode.text = str(a["content"]["slideduration"])
                                                    autostartNode = lxml.etree.SubElement(metaNode, 'autostart')
                                                    autostartNode.text = str(a["content"]["autostart"])
                                                    imagesNode = lxml.etree.SubElement(metaNode, 'images')
                                                    for img in a["content"]["images"]:
                                                        imageNode = lxml.etree.SubElement(imagesNode, 'image')
                                                        urlNode = lxml.etree.SubElement(imageNode, 'url')
                                                        urlNode.text = img["url"]
                                                        titleNode = lxml.etree.SubElement(imageNode, 'title')
                                                        titleNode.text = img["title"]
                                                        descriptionNode = lxml.etree.SubElement(imageNode, 'description')
                                                        descriptionNode.text = img["description"]
        
        # Now all medias and annotation-types and annotations are the xml
        # We can set the views/displays node
        displaysNode = lxml.etree.SubElement(iri, 'displays')
        id_sel = None
        i = 1
        for v in cinelab["views"]:
            if "stat" not in v["id"] and "annotation_types" in v:
                displayNode = lxml.etree.SubElement(displaysNode, 'display')
                displayNode.set('id', v["id"])
                displayNode.set('title', "View " + str(i))
                i += 1
                displayNode.set('tc', "0")
                displayNode.set('zoom', "0")
                displayNode.set('scroll', "0")
                audioDis = lxml.etree.SubElement(displayNode, 'audio')
                audioDis.set('source', "")
                last_iri_id = ""
                last_content_node = None
                for at_id in v["annotation_types"]:
                    iri_id, ens_id = at_media_dict[at_id]
                    if iri_id != last_iri_id:
                        last_iri_id = iri_id
                        last_content_node = lxml.etree.SubElement(displayNode, 'content')
                        last_content_node.set('id', iri_id)
                    if last_content_node is not None:
                        decoupageNode = lxml.etree.SubElement(last_content_node, 'decoupage')
                        decoupageNode.set('idens', ens_id)
                        decoupageNode.set('id', at_id)
                        decoupageNode.set('tagsSelect', "")
                    if not id_sel:
                        id_sel = iri_id
                if not id_sel:
                    id_sel = ""
                displayNode.set('idsel', id_sel)
        
        # Now we build the edit node
        editsNode = lxml.etree.SubElement(iri, 'edits')
        i = 0
        for l in cinelab["lists"]:
            if "listtype" in l["meta"]:
                if l["meta"]["listtype"]=="mashup":
                    editingNode = lxml.etree.SubElement(editsNode, 'editing')
                    editingNode.set('id', str(i))
                    i += 1
                    editingNode.set('tags', "")
                    titleEd = lxml.etree.SubElement(editingNode, 'title')
                    titleEd.text = l["meta"]["dc:title"]
                    abstractEd = lxml.etree.SubElement(editingNode, 'abstract')
                    abstractEd.text = l["meta"]["dc:description"]
                    editNode = lxml.etree.SubElement(editingNode, 'edit')
                    editNode.set('id', "edit1")
                    editNode.set('tags', "")
                    # We build the 4 nodes (2 are used in reality : edit list and media list
                    eListNode = lxml.etree.SubElement(editNode, 'eList')
                    lxml.etree.SubElement(editNode, 'caption')
                    lxml.etree.SubElement(editNode, 'audio')
                    mListNode = lxml.etree.SubElement(editNode, 'mList')
                    media_pos = 0
                    edit_fulltime = 0
                    for a_id in l["items"]:
                        # the key is the annotation's id
                        iri_id, ens_id, at_id, begin, end, color = annot_ids_dict[a_id]
                        # We check the media's position in media list. If it was not set, we create the mList node
                        if media_url_dict[iri_id]["pos"] is None:
                            media_url_dict[iri_id]["pos"] = media_pos
                            mNode = lxml.etree.SubElement(mListNode, 'm')
                            mNode.set('ref', iri_id)
                            mNode.set('id', str(media_pos))
                            media_pos += 1
                            mNode.set('t', "v")
                            mNode.set('c', color)
                            contentEd = lxml.etree.SubElement(mNode, 'content')
                            contentEd.text = media_url_dict[iri_id]["url"]
                        # We add the annotation/instruction to the eList
                        instNode = lxml.etree.SubElement(eListNode, 'inst')
                        instNode.set('ref', iri_id + "|;|" + ens_id + "|;|" + at_id + "|;||;||;|" + a_id)
                        b = int(math.floor(begin/1000))
                        instNode.set('begin', str(b))
                        e = int(math.floor(end/1000))
                        instNode.set('end', str(e))
                        instNode.set('m', str(media_url_dict[iri_id]["pos"]))
                        instNode.set('v', "100")
                        instNode.set('eBegin', str(edit_fulltime))
                        edit_fulltime = edit_fulltime + e - b
                        instNode.set('eEnd', str(edit_fulltime))
                        instNode.set('trId', "0")
                        instNode.set('trIc', "0")
                        instNode.set('trOd', "0")
                        instNode.set('trOc', "0")
                    # The second empty edit
                    edit2Node = lxml.etree.SubElement(editingNode, 'edit')
                    edit2Node.set('id', "edit2")
                    edit2Node.set('tags', "")
                    lxml.etree.SubElement(edit2Node, 'eList')
                    lxml.etree.SubElement(edit2Node, 'caption')
                    lxml.etree.SubElement(edit2Node, 'audio')
                    lxml.etree.SubElement(edit2Node, 'mList')
        
        # This is the end
        return iri
    
    
    # This function checks every node and tests if each element has the required fields (i.e. "id", "dc:creator"....)
    def validate_cinelab_json(self, cinelab_json):
        error_string = ""
        do_break = False
        # Meta node
        if not "meta" in cinelab_json:
            error_string += " The cinelab json needs a 'meta' node."
        else:
            mt = cinelab_json["meta"]
            if not "dc:modified" in mt or not "dc:title" in mt or not "dc:description" in mt or not "dc:creator" in mt or not "dc:created" in mt or not "dc:contributor" in mt:
                error_string += " Meta node must have 'dc:modified', 'dc:title', 'dc:description', 'dc:creator', 'dc:created' and 'dc:contributor' field."
        # Medias node
        if not "medias" in cinelab_json:
            error_string += " The cinelab json needs a 'medias' node."
        else:
            for m in cinelab_json["medias"]:
                if not "id" in m:
                    error_string += " Each media must have an 'id' field."
                    break
        # Lists node
        if not "lists" in cinelab_json:
            error_string += " The cinelab json needs a 'lists' node."
        else:
            for l in cinelab_json["lists"]:
                if not "id" in l:
                    error_string += " Each list must have an 'id' field."
                    do_break = True
                if not "items" in l:
                    error_string += " Each list must have an 'items' field."
                    do_break = True
                if not "meta" in l:
                    error_string += " Each list must have a 'meta' field."
                    do_break = True
                else:
                    # 2 types of lists : mashup (list of annotation ids) or list of annotation-types for one media
                    if not "listtype" in l["meta"]:
                        if not "dc:creator" in l["meta"] or not "id-ref" in l["meta"]:
                            error_string += " Each annotation-types list must have 'meta/dc:creator' and 'meta/id-ref' fields."
                            do_break = True
                    if not "dc:title" in l["meta"] or not "dc:description" in l["meta"]:
                        error_string += " Each list must have 'meta/dc:title' and 'meta/dc:description' fields."
                        do_break = True
                if do_break:
                    break
        # Annotation-types node
        if not "annotation-types" in cinelab_json:
            error_string += " The cinelab json needs a 'annotation-types' node."
        else:
            for at in cinelab_json["annotation-types"]:
                if not "id" in at or not "dc:title" in at or not "dc:creator" in at or not "dc:description" in at:
                    error_string += " Each annotation-type must have 'id', 'dc:title', 'dc:creator' and 'dc:description' fields."
                    break
        # Annotations node
        if not "annotations" in cinelab_json:
            error_string += " The cinelab json needs a 'annotations' node."
        else:
            do_break = False
            for a in cinelab_json["annotations"]:
                if not "id" in a or not "begin" in a or not "end" in a or not "tags" in a or not "color" in a or not "media" in a:
                    error_string += " Each annotation must have 'id', 'begin', 'end', 'tags', 'color' and 'media' fields."
                    do_break = True
                if not "meta" in a:
                    error_string += " Each annotation must have 'meta' field."
                    do_break = True
                else:
                    if not "id-ref" in a["meta"] or not "dc:created" in a["meta"] or not "dc:creator" in a["meta"]:
                        error_string += " Each annotation must have 'meta/id-ref', 'meta/dc:created' and 'meta/dc:creator' fields."
                        do_break = True
                if not "content" in a:
                    error_string += " Each annotation must have 'content' field."
                    do_break = True
                else:
                    if not "title" in a["content"] or not "description" in a["content"]:
                        error_string += " Each annotation must have 'content/title' and 'content/description' fields."
                        do_break = True
                    # Test for metadatacomposer extra datas, type video, audio, text, links or slideshow
                    if "mimetype" in a["content"]:
                        if a["content"]["mimetype"]=="application/x-ldt-video" or a["content"]["mimetype"]=="application/x-ldt-audio":
                            if not "url" in a["content"] or not "embedcode" in a["content"]:
                                error_string += " A video or audio annotation must have 'content/url' and 'content/embedcode' fields."
                                do_break = True
                        elif a["content"]["mimetype"]=="application/x-ldt-text":
                            if not "markup" in a["content"] or not "text" in a["content"]:
                                error_string += " A text annotation must have 'content/markup' and 'content/text' fields."
                                do_break = True
                        elif a["content"]["mimetype"]=="application/x-ldt-links":
                            if not "links" in a["content"]:
                                error_string += " A links annotation must have 'content/links' field."
                                do_break = True
                            else:
                                for l in a["content"]["links"]:
                                    if not "url" in l or not "title" in l:
                                        error_string += " Each link in links annotation must have 'url' and 'title' fields."
                                        do_break = True
                        elif a["content"]["mimetype"]=="application/x-ldt-slideshow":
                            if not "slideduration" in a["content"] or not "autostart" in a["content"] or not "images" in a["content"]:
                                error_string += " A slideshow annotation must have 'content/slideduration' and 'content/autostart' and 'content/images' fields."
                                do_break = True
                            else:
                                for i in a["content"]["images"]:
                                    if not "url" in i or not "title" in i or not "description" in i:
                                        error_string += " Each image in slideshow annotation must have 'url' and 'title' and 'description' fields."
                                        do_break = True
                # End test annotations
                if do_break:
                    break
        # Views node
        if not "views" in cinelab_json:
            error_string += " The cinelab json needs a 'views' node."
        else:
            for v in cinelab_json["views"]:
                if "meta" in v:
                    if "stat" in v["meta"]:
                        continue
                if not "id" in v or not "contents" in v or not "annotation_types" in v:
                    error_string += " Each view must have 'id', 'contents', and 'annotation_types' fields."
                    break
        # Tags node
        if not "tags" in cinelab_json:
            error_string += " The cinelab json needs a 'tags' node."
        else:
            do_break = False
            for t in cinelab_json["tags"]:
                if not "id" in t:
                    error_string += " Each tag must have an 'id' field."
                    do_break = True
                if not "meta" in t:
                    error_string += " Each tag must have 'meta' field."
                    do_break = True
                else:
                    if not "dc:title" in t["meta"]:
                        error_string += " Each tag must have a 'meta/dc:title' field."
                        do_break = True
                if do_break:
                    break
        
        # Finally
        if error_string!="":
            raise BadRequest(error_string)
        
        return True