/****************************************************************************
* SKCD  > skAnnotation                                                      *
*---------------------------------------------------------------------------*
* store specifics personal annotations for each document on the user disk.  *
* An annotation is composed by a title and paragraphs - its contents can be *
* saved as HTML - and by a path in order to identify the document in a tree *
* hierarchy. They can be displayed, modified or removed. The annotation     *
* index is the same than the document index.                                *
*---------------------------------------------------------------------------*
* (c) IDM. 2003                                                             *
* Project  : sk                                                             *
* Language : javascript                                                     *
* Created  : MikaelLebrun, 16/04/2003                                       *
****************************************************************************/

/*XPCOM Object used*/
const L_File = Components.classes["@mozilla.org/file/local;1"].createInstance().QueryInterface(Components.interfaces.nsILocalFile);
const MyProfile = Components.classes["@mozilla.org/profile/manager;1"].createInstance().QueryInterface(Components.interfaces.nsIProfileInternal);
const I_File = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance().QueryInterface(Components.interfaces.nsIFileInputStream);
const I_Stream = Components.classes['@mozilla.org/scriptableinputstream;1'].createInstance(Components.interfaces.nsIScriptableInputStream);
const O_File = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance().QueryInterface(Components.interfaces.nsIFileOutputStream);
const nsiArray = Components.classes["@mozilla.org/supports-array;1"].createInstance().QueryInterface(Components.interfaces.nsISupportsArray);

//prio.h
const MODE_CREATE = 0x08;
const MODE_WRONLY = 0x02;
const MODE_APPEND = 0x10;
const MODE_TRUNCATE = 0x20;

const cParsePart = String.fromCharCode(10);//Character to separate the title and each element
const cParseField = String.fromCharCode(9); //Character to separate the query and the wording of the element


function skAnnotation() {
    /*this.skIBase64 = Components.classes["@idm.fr/sk_base64_encoding;1"].createInstance().QueryInterface(Components.interfaces.skIBase64);*/
    this.hasAnnotation  = true;						//Allows to know so at least one annotation exists.
    this.mainArray = new Array();
    this.AnnotationContentArray = new Array();   //Contains the content of all annotations files
    var xoPathProfile = MyProfile.getProfileDir(MyProfile.currentProfile); 
    L_File.initWithFile(xoPathProfile);
    L_File.appendRelativePath("annotation");
    //The annotation directory doesn't exists
    if (!L_File.exists()) {
        this.hasAnnotation = false;
        return;
    } 
    this.AnnotationDirectory = L_File;			//Objet nsiFIle : Directory path where all annotations are saved
    this.SetupAnnotation();
}

skAnnotation.prototype = {

    QueryInterface: function(iid) {
        if (!iid.equals(Components.interfaces.skIAnnotation)
            &&!iid.equals(Components.interfaces.nsISupports)) {
            throw Components.results.NS_ERROR_NO_INTERFACE;
        }
        return this;
    },

    /*********************************************************************************************
    FUNCTION : CreateNewAnnotationFile()
    This function generates a file name and verify if it doesn't exist in the annotation directory,
    while the file name exists.
    ****INPUT: NOTHING
    ****OUPUT: an nsiFile XPCOM object [an annotation file]
    ********************************************************************************************/
    CreateNewAnnotationFile:function() {
        var xiDirectory = this.AnnotationDirectory.clone().QueryInterface(Components.interfaces.nsILocalFile);
        var FilesList = xiDirectory.directoryEntries;
        var iIndice = 1;
        var FileName = "annotation1.txt";
        if (FilesList.hasMoreElements()) {
            while(true) {
                FileName = "annotation" + iIndice + ".txt";
                xiDirectory.appendRelativePath(FileName);
                if (!xiDirectory.exists())
                    break;
                xiDirectory = this.AnnotationDirectory.clone().QueryInterface(Components.interfaces.nsILocalFile);
                iIndice += 1;
            }
        }
        xiDirectory = this.AnnotationDirectory.clone().QueryInterface(Components.interfaces.nsILocalFile);
        xiDirectory.appendRelativePath(FileName);
        xiDirectory.create(xiDirectory.NORMAL_FILE_TYPE, 0700);
        return xiDirectory;
    },

    SetWindow:function(pxiWindow) {
        this.window = pxiWindow;
    },

    AddAnnotation:function(psAnnotationTitle) {
        //The annotation isn't created yet 
        if (!this.hasAnnotation) {
            var xoPathProfile = MyProfile.getProfileDir(MyProfile.currentProfile);
            L_File.initWithFile(xoPathProfile);
            L_File.appendRelativePath("annotation"); //Create the annotation directory in user profile
            L_File.create(L_File.DIRECTORY_TYPE, 0700);
            this.hasAnnotation = true;
            this.AnnotationDirectory = L_File;
        }
        if (!this.isAnnotationExist(psAnnotationTitle)) {
            var xoMyNewFile = this.CreateNewAnnotationFile();
            O_File.init(xoMyNewFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, 0664, 0);//Open the file to write (Truncate the content)
            var sAnnotationContent = psAnnotationTitle + cParsePart;
            O_File.write(sAnnotationContent, sAnnotationContent.length)//Write title
            O_File.flush()
            O_File.close()
            this.SetupAnnotation();
        }
    },

    /*********************************************************************************************
    FUNCTION : isAnnotationExist()
    This function test if an annotation of the given title has been created.
    ****INPUT: a string [psAnnotationTitle - an annotation title]
    ****OUPUT: a boolean [true if the annotation exists, false otherwise]
    ********************************************************************************************/
    isAnnotationExist:function(psAnnotationTitle) {
        for (var i=0; i<this.GetAnnotationFileNumber(); i++) {
            if (this.GetAnnotationFileTitle(i) == psAnnotationTitle)
                return true;
        }
        return false;
    },

    /*********************************************************************************************
    FUNCTION : ReturnAnnotationFileContent()
    This function return an annotation file content.
    ****INPUT: an nsiFile XPCOM object [pxiAnnotationFile - an annotation file]
    ****OUPUT: a string [the content of the annotation file]
    ********************************************************************************************/
    ReturnAnnotationFileContent:function(pxiAnnotationFile) {
        I_File.init(pxiAnnotationFile, 1, 0, true)
        I_Stream.init(I_File);
        var iFileSize = I_Stream.available();
        var sFileContents = I_Stream.read(iFileSize);
        I_Stream.close();
        I_File.close();
        return sFileContents;
    },


    /*********************************************************************************************
    FUNCTION : ReturnAnnotationTitle()
    This function split an annotation file content the and return its title.
    ****INPUT: a string [psContent - an annotation file content]
    ****OUPUT: a string [the annotation title]
    ********************************************************************************************/
    ReturnAnnotationTitle:function(psContent) {
        var aContent = psContent.split(cParsePart);
        return aContent[0];
    },

    /*********************************************************************************************
    FUNCTION : SetupAnnotation()
    This function is used to set all annotations.
    ****INPUT: NOTHING
    ****OUPUT: NOTHING
    ********************************************************************************************/
    SetupAnnotation:function() {
        var xoFiles = this.AnnotationDirectory.directoryEntries;
        var xiFile;
        var i=0;
        this.AnnotationContentArray = new Array();
        this.mainArray = new Array(); 
        while(xoFiles.hasMoreElements()) {
            xiFile = xoFiles.getNext().QueryInterface(Components.interfaces.nsIFile);
            if (xiFile.isFile()) {
                this.AnnotationContentArray[i] = new Array();
                var sFileContents = this.ReturnAnnotationFileContent(xiFile);
                this.LoadAnnotationArray(this.ReturnAnnotationTitle(sFileContents), xiFile);
                this.LoadAnnotationContentArray(sFileContents);
                i+=1;
            }
        }
    },

    /*********************************************************************************************
    FUNCTION : LoadAnnotationArray()
    This function load an annotation into the annotation list (i.e its title and its corresponding file)
    ****INPUT: 1/ a string [psAnnotationTitle - the annotation title]
               2/ an nsiFile XPCOM object [pxiAnnotationFile - an annotation file]
    ****OUPUT: NOTHING
    ********************************************************************************************/
    LoadAnnotationArray:function(psAnnotationTitle, pxiAnnotationFile) {
        this.mainArray[psAnnotationTitle] = new Array();
        this.mainArray[psAnnotationTitle]['index'] = this.GetAnnotationFileNumber()-1;
        this.mainArray[psAnnotationTitle]['file'] = pxiAnnotationFile;
    },

    /*********************************************************************************************
    FUNCTION : LoadAnnotationContentArray()
    This function split an annotation file content in order to retrieve its elements and put them 
    in the annotation list.
    ****INPUT: a string [psContent - an annotation file content]
    ****OUPUT: NOTHING
    ********************************************************************************************/
    LoadAnnotationContentArray:function(psContent) {
        var aContent = psContent.split(cParsePart);
        var aLine, iContentArrayLength;
        for (var i=1; i<aContent.length-1; i++) {
            aLine = aContent[i].split(cParseField);
            iContentArrayLength = this.AnnotationContentArray.length - 1;
            this.AnnotationContentArray[iContentArrayLength][aLine[0]] = new Array();
            this.AnnotationContentArray[iContentArrayLength][aLine[0]][0] = aLine[1];
            this.AnnotationContentArray[iContentArrayLength][aLine[0]][1] = aLine[2];
            this.AnnotationContentArray[iContentArrayLength][aLine[0]][2] = aLine[3];
        }
    },

    RenameAnnotation:function(psAnnotationTitle, psNewAnnotationTitle) {
        this.mainArray[psNewAnnotationTitle] = this.mainArray[psAnnotationTitle];
        this.mainArray = this.removeEltFromHash(this.mainArray, psAnnotationTitle);
        this.writeInFile(psNewAnnotationTitle);
    },

    RemoveAnnotation:function(psAnnotationTitle) {
        this.GetAnnotationFile(psAnnotationTitle).remove(true);
        this.mainArray = this.removeEltFromHash(this.mainArray, psAnnotationTitle);
        this.SetupAnnotation();
    },

    SetFieldContent:function(psAnnotationTitle, psId, psTitle, psHtml, psTreePath) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index'];
        this.AnnotationContentArray[iAnnotationIndex]["'" + psId + "'"] = new Array();
        this.AnnotationContentArray[iAnnotationIndex]["'" + psId + "'"][0] = this.window.btoa(encodeURIComponent(psTitle)); /*this.skIBase64.Encode*/
        this.AnnotationContentArray[iAnnotationIndex]["'" + psId + "'"][1] = this.window.btoa(encodeURIComponent(psHtml)); /*this.skIBase64.Encode*/
        this.AnnotationContentArray[iAnnotationIndex]["'" + psId + "'"][2] = this.window.btoa(encodeURIComponent(psTreePath)); /*this.skIBase64.Encode*/
        this.writeInFile(psAnnotationTitle)
    },

    EmptyAnnotation:function(psAnnotationTitle) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index']; 
        var sAnnotationContent = this.GetAnnotationFileTitle(iAnnotationIndex) + cParsePart;
        //Open the file in write mode
        O_File.init(this.GetAnnotationFile(psAnnotationTitle), MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, 0664, 0);
        O_File.write(sAnnotationContent, sAnnotationContent.length);
        O_File.flush();
        O_File.close();
        this.SetupAnnotation();
    },
    
    //Change the content of a given field
    ChangeFieldContent:function(psAnnotationTitle, psElementId, psFieldType, psNewContent) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index']; 
        switch (psFieldType){
            case "title":
                var iFieldIndex = 0;
                break;
            case "content":
                var iFieldIndex = 1;
                break;
            case "path":
                var iFieldIndex = 2;
                break;
            default:
                return;
        }
        this.AnnotationContentArray[iAnnotationIndex]["'" + psElementId + "'"][iFieldIndex] = this.window.btoa(encodeURIComponent(psNewContent)); /*this.skIBase64.Encode*/
        this.writeInFile(psAnnotationTitle)
    },

    RemoveElement:function(psAnnotationTitle, psElementId) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index']; 
        this.AnnotationContentArray[iAnnotationIndex] = this.removeEltFromHash(this.AnnotationContentArray[iAnnotationIndex], "'" + psElementId + "'");
        this.writeInFile(psAnnotationTitle)
    },

    /*********************************************************************************************
    FUNCTION : removeEltFromHash()
    This function remove an element from a hash
    ****INPUT: 1/ a array [paOldHash - the hash from which we'll remove an element]
               2/ a string [psKey - an annotation file content]
    ****OUPUT: an array [the new hash]
    ********************************************************************************************/
    removeEltFromHash:function(paOldHash, psKey) {
        var aNewHash = new Array();
        var elt;
        for (elt in paOldHash) {
            if (elt != psKey)
                aNewHash[elt] = paOldHash[elt];
        }
        return aNewHash;
    },

    /*********************************************************************************************
    FUNCTION : writeInFile()
    This function writes an annotation into the annotation file.
    ****INPUT: a string [psAnnotationTitle - the annotation position in the annotation list]
    ****OUPUT: NOTHING
    ********************************************************************************************/
    writeInFile:function(psAnnotationTitle) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index'];
        O_File.init(this.GetAnnotationFile(psAnnotationTitle), MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, 0664, 0);
        var sAnnotationContent = this.GetAnnotationFileTitle(iAnnotationIndex) + cParsePart;
        O_File.write(sAnnotationContent, sAnnotationContent.length)
        var elt, aContent;
        for (elt in this.AnnotationContentArray[iAnnotationIndex]) {
            aContent = this.AnnotationContentArray[iAnnotationIndex][elt];
            sAnnotationContent = elt + cParseField
                               + aContent[0] + cParseField 
                               + aContent[1] + cParseField 
                               + aContent[2] + cParseField 
                               + cParsePart;
            O_File.write(sAnnotationContent, sAnnotationContent.length);
        }
        O_File.flush();
        O_File.close();
    },

    /*********************************************************************************************
    FUNCTION : GetAnnotationFile()
    This function return the nsiFile XPCOM object corresponding to the desired annotation
    ****INPUT: a string [psAnnotationTitle - the annotation title]
    ****OUPUT: an nsiFile XPCOM object [the annotation file]
    ********************************************************************************************/
    GetAnnotationFile:function(psAnnotationTitle) {
        return this.mainArray[psAnnotationTitle]['file'];
    },

    /*********************************************************************************************
    FUNCTION : GetAnnotationFileTitle()
    This function return an annotation title
    ****INPUT: an integer [piAnnotationIndex - the annotation index (in the annotation list)]
    ****OUPUT: a string [the title of the annotation]
    ********************************************************************************************/
    GetAnnotationFileTitle:function(piAnnotationIndex) {
        var elt;
        for (elt in this.mainArray) {
            if (this.mainArray[elt]['index'] == piAnnotationIndex)
                return elt;
        }
    },

    /*********************************************************************************************
    FUNCTION : GetAnnotationFileNumber()
    This function return the number of registered annotations
    ****INPUT: NOTHING
    ****OUPUT: an integer (the number of annotation files)
    ********************************************************************************************/
    GetAnnotationFileNumber:function() {
        var elt;
        var i = 0;
        for (elt in this.mainArray)
            i++;
        return i;
    },

    /*********************************************************************************************
    FUNCTION : GetFieldContent()
    This function return an element name knowing its annotation index and its element index
    ****INPUT: 1/ a string [psAnnotationTitle - the annotation title]
               2/ an integer [piElementIndex - the element position in the element list]
               3/ a boolean [pbNoDecode - not decode the name to return]
    ****OUPUT: a string [the element name]
    ********************************************************************************************/
    GetFieldContent:function(psAnnotationTitle, psElementId, psFieldType, pbNoDecode) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index']; 
        var aContent = this.AnnotationContentArray[iAnnotationIndex]["'" + psElementId + "'"];
        switch (psFieldType){
            case "title":
                var iFieldIndex = 0;
                break;
            case "content":
                var iFieldIndex = 1;
                break;
            case "path":
                var iFieldIndex = 2;
                break;
            default:
                return "";
        }
        var sField = aContent[iFieldIndex];
        if(!pbNoDecode)
            sField = decodeURIComponent(this.window.atob(sField)); /*this.skIBase64.Decode*/
        return sField;
    },

    /*********************************************************************************************
    FUNCTION : GetListOfId()
    This function return the list of id from all elements in an annotation
    ****INPUT: a string [psAnnotationTitle - the annotation title]
    ****OUPUT: an xpcom object nsISupportsArray [a list of id]
    ********************************************************************************************/
    GetListOfId:function(psAnnotationTitle) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index']; 
        var xiListOfId = nsiArray.clone();
        var elt;
        for (elt in this.AnnotationContentArray[iAnnotationIndex]) {
            elt = elt.substring(1, elt.length-1);
            xiListOfId.AppendElement(this.CreateXPCOMstring(elt));
        }
        return xiListOfId;
    },

    /*********************************************************************************************
    FUNCTION : GetListOfAnnotationTitle()
    This function return the list of annotation titles
    ****INPUT: NOTHING
    ****OUPUT: an xpcom object nsISupportsArray [a list of annotation titles]
    ********************************************************************************************/
    GetListOfAnnotationTitle:function() {
        var xiListOfTitle = nsiArray.clone();
        var sTitle;
        for (var i=0; i<this.GetAnnotationFileNumber(); i++) {
            sTitle = this.GetAnnotationFileTitle(i);
            xiListOfTitle.AppendElement(this.CreateXPCOMstring(sTitle));
        }
        return xiListOfTitle;
    },

    /*********************************************************************************************
    FUNCTION : GetElementNumber()
    This function return the number of elements in an annotation
    ****INPUT: a string [psAnnotationTitle - the annotation title]
    ****OUPUT: an integer [the number of elements in an annotation]
    ********************************************************************************************/
    GetElementNumber:function(psAnnotationTitle) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index']; 
        var elt;
        var i = 0;
        for (elt in this.AnnotationContentArray[iAnnotationIndex])
            i++;
        return i;
    },

    /*********************************************************************************************
    FUNCTION : IsElementExist()
    This function test if an element with the id passed in argument is already contained in the annotation.
    ****INPUT: 1/ a string [psAnnotationTitle - an annotation title]
               2/ a string [psElementId - an element id]
    ****OUPUT: a boolean [true if the element exists, false otherwise]
    ********************************************************************************************/
    IsElementExist:function(psAnnotationTitle, psElementId) {
        var iAnnotationIndex = this.mainArray[psAnnotationTitle]['index']; 
        if (this.AnnotationContentArray[iAnnotationIndex]["'" + psElementId + "'"]) {
            return true;
        }
        return false;
    },

    /*********************************************************************************************
    FUNCTION : CreateXPCOMstring()
    This function creatre, initialize and return xpcom object in order to insert it in a XPCOM 
    nsiSupportArray object.
    ****INPUT: a string [psValue - the string to transform into the xpcom relevant format]
    ****OUPUT: an XPCOM object [an interface nsISupportsString]
    ********************************************************************************************/
    CreateXPCOMstring:function(psValue) {
        var xiString = Components.classes["@mozilla.org/supports-string;1"].createInstance().QueryInterface(Components.interfaces.nsISupportsString);
        xiString.data = psValue;
        return xiString;
    }
    
    /*
    prompt:function(psTitle, psText) {
        var xiPromptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].createInstance().QueryInterface(Components.interfaces.nsIPromptService);
        xiPromptService.alert(this.window, psTitle, psText);
    }
    */
}

var skAnnotationModule = {

    registerSelf: function (compMgr, fileSpec, location, type) {
        compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
        compMgr.registerFactoryLocation(this.myCID,
                                        "Sk Annotation Component",
                                        this.myProgID, 
                                        fileSpec,
                                        location,
                                        type);
    },

    getClassObject: function (compMgr, cid, iid) {
        if (!cid.equals(this.myCID))
            throw Components.results.NS_ERROR_NO_INTERFACE;
        if (!iid.equals(Components.interfaces.nsIFactory))
            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        return this.myFactory;
    },

    myCID: Components.ID("965ed419-98f1-4b1b-978a-000be9121e0f"),

    myProgID: "@idm.fr/sk_annotation;1",

    myFactory: {
        createInstance: function (outer, iid) {
            if (outer != null)
                throw Components.results.NS_ERROR_NO_AGGREGATION;
            return (new skAnnotation()).QueryInterface(iid);
        }
    },

    canUnload: function(compMgr) {
        return true;
    }
};

function NSGetModule(compMgr, fileSpec) {
    return skAnnotationModule;
}
