from django.conf import settings
from django.core.serializers import json
from django.core.urlresolvers import reverse
from django.utils import simplejson
from ldt.ldt_utils.models import Content, Project
from ldt.ldt_utils.projectserializer import ProjectJsonSerializer
from ldt.ldt_utils.utils import generate_uuid
from tastypie.serializers import Serializer
from tastypie.exceptions import NotFound, BadRequest
import math
import lxml.etree
import logging
import tastypie


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 simplejson.dumps(data, cls=json.DjangoJSONEncoder, 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 = simplejson.loads(content)
        meta = cinelab["meta"]
        ldt_id = generate_uuid()
        # default state = (1, 'edition') OR (2, 'published')
        state = 2
        contents = [reverse("api_dispatch_detail", kwargs={"api_name":"1.0", "resource_name":"contents", "iri_id":c["id"]}) for c in cinelab["medias"]]
        owner_uri = reverse("api_dispatch_detail", kwargs={"api_name":"1.0", "resource_name":"users", "username":meta["dc:creator"]})
        
        ldt = lxml.etree.tostring(self.cinelab_to_ldt(cinelab), 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}
        
        #s = '{"description": "", "ldt": "<iri><project/><medias/><annotations/><displays/><edits/></iri>", "000ldt_id": "gen_by_tc","title": "aaa GEN BY TC"}'
        #s = '{"description": "", "ldt": "<iri><project/><medias/><annotations/><displays/><edits/></iri>", "title": "aaaGEN BY TC"}'
        #return simplejson.loads(simplejson.dumps(s))
        return s
    
    
    def cinelab_to_ldt(self, cinelab, ldt_id=None):
        # We validate the json
        self.validate_cinelab_json(cinelab)
        # 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 l["meta"].has_key("id-ref"):
                    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 a["content"].has_key("img"):
                                                if a["content"]["img"].has_key("src"):
                                                    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 a["content"].has_key("audio"):
                                                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 tag_dict.has_key(t["id-ref"]):
                                                        tagNode = lxml.etree.SubElement(tagsNode, 'tag')
                                                        tagNode.text = tag_dict[t["id-ref"]]
                                            # Last element's node
                                            lxml.etree.SubElement(elementNode, 'meta')
        
        # 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 v.has_key("annotation_types"):
                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 l["meta"].has_key("listtype"):
                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 cinelab_json.has_key("meta"):
            error_string += " The cinelab json needs a 'meta' node."
        else:
            mt = cinelab_json["meta"]
            if not mt.has_key("dc:modified") or not mt.has_key("dc:title") or not mt.has_key("dc:description") or not mt.has_key("dc:creator") or not mt.has_key("dc:created") or not mt.has_key("dc:contributor"):
                error_string += " Meta node must have 'dc:modified', 'dc:title', 'dc:description', 'dc:creator', 'dc:created' and 'dc:contributor' field."
        # Medias node
        if not cinelab_json.has_key("medias"):
            error_string += " The cinelab json needs a 'medias' node."
        else:
            for m in cinelab_json["medias"]:
                if not m.has_key("id"):
                    error_string += " Each media must have an 'id' field."
                    break
        # Lists node
        if not cinelab_json.has_key("lists"):
            error_string += " The cinelab json needs a 'lists' node."
        else:
            for l in cinelab_json["lists"]:
                if not l.has_key("id"):
                    error_string += " Each list must have an 'id' field."
                    do_break = True
                if not l.has_key("items"):
                    error_string += " Each list must have an 'items' field."
                    do_break = True
                if not l.has_key("meta"):
                    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 l["meta"].has_key("listtype"):
                        if not l["meta"].has_key("dc:creator") or not l["meta"].has_key("id-ref"):
                            error_string += " Each annotation-types list must have 'meta/dc:creator' and 'meta/id-ref' fields."
                            do_break = True
                    if not l["meta"].has_key("dc:title") or not l["meta"].has_key("dc:description"):
                        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 cinelab_json.has_key("annotation-types"):
            error_string += " The cinelab json needs a 'annotation-types' node."
        else:
            for at in cinelab_json["annotation-types"]:
                if not at.has_key("id") or not at.has_key("dc:title") or not at.has_key("dc:creator") or not at.has_key("dc:description"):
                    error_string += " Each annotation-type must have 'id', 'dc:title', 'dc:creator' and 'dc:description' fields."
                    break
        # Annotations node
        if not cinelab_json.has_key("annotations"):
            error_string += " The cinelab json needs a 'annotations' node."
        else:
            do_break = False
            for a in cinelab_json["annotations"]:
                if not a.has_key("id") or not a.has_key("begin") or not a.has_key("end") or not a.has_key("tags") or not a.has_key("color") or not a.has_key("media"):
                    error_string += " Each annotation must have 'id', 'begin', 'end', 'tags' and 'media' fields."
                    do_break = True
                if not a.has_key("meta"):
                    error_string += " Each annotation must have 'meta' field."
                    do_break = True
                else:
                    if not a["meta"].has_key("id-ref") or not a["meta"].has_key("dc:created") or not a["meta"].has_key("dc:creator"):
                        error_string += " Each annotation must have 'meta/id-ref', 'meta/dc:created' and 'meta/dc:creator' fields."
                        do_break = True
                if not a.has_key("content"):
                    error_string += " Each annotation must have 'content' field."
                    do_break = True
                else:
                    if not a["content"].has_key("title") or not a["content"].has_key("description"):
                        error_string += " Each annotation must have 'content/title' and 'content/description' fields."
                        do_break = True
                if do_break:
                    break
        # Views node
        if not cinelab_json.has_key("views"):
            error_string += " The cinelab json needs a 'views' node."
        else:
            for v in cinelab_json["views"]:
                if v.has_key("meta") :
                    if v["meta"].has_key("stat") :
                        continue
                if not v.has_key("id") or not v.has_key("contents") or not v.has_key("annotation_types"):
                    error_string += " Each view must have 'id', 'contents', and 'annotation_types' fields."
                    break
        # Tags node
        if not cinelab_json.has_key("tags"):
            error_string += " The cinelab json needs a 'tags' node."
        else:
            do_break = False
            for t in cinelab_json["tags"]:
                if not t.has_key("id"):
                    error_string += " Each tag must have an 'id' field."
                    do_break = True
                if not t.has_key("meta"):
                    error_string += " Each tag must have 'meta' field."
                    do_break = True
                else:
                    if not t["meta"].has_key("dc:title"):
                        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