/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is PlainOldFavorites Mozilla Extension and Engine.
 * The Initial Developer of the Original Code is Alex Sirota <alex@elbrus.com>. 
 * Portions created by Alex Sirota are Copyright (C) 2005 Alex Sirota. 
 * All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

//********************* PlainOldFavoritesService *************************

const POF_FAVORITE        = Components.interfaces.IPlainOldFavoritesItem.POF_FAVORITE;
const POF_FOLDER          = Components.interfaces.IPlainOldFavoritesItem.POF_FOLDER;
const POF_GENERIC_FILE    = Components.interfaces.IPlainOldFavoritesItem.POF_GENERIC_FILE;
const POF_FOLDER_SHORTCUT = Components.interfaces.IPlainOldFavoritesItem.POF_FOLDER_SHORTCUT;

const POF_SERVICE_CID = Components.ID("{94B1419F-B8C5-4373-9BCC-E2B841FF1A8B}");
const POF_ITEM_CID = Components.ID("{CC2A9A80-5012-4371-A01E-A894FBB277FF}");

function PlainOldFavoritesService() {
    var directoryService = Components.classes["@mozilla.org/file/directory_service;1"].
                                getService(Components.interfaces.nsIProperties);

    this.favoritesFolder  = Components.classes["@mozilla.org/file/local;1"].
                                createInstance(Components.interfaces.nsILocalFile);
	this.favoritesFolder.initWithPath(directoryService.get("Favs", Components.interfaces.nsILocalFile).path);

    /* 
        Strange, but when using the following, the directoryEntries enumerator misses shortcut file
        having uppercase ".LNK" extension. 
        Probably has something to do with https://bugzilla.mozilla.org/show_bug.cgi?id=216821
        And with the fact that "directoryService.get" returns a file with followLinks set to 'true'

        this.favoritesFolder = directoryService.get("Favs", Components.interfaces.nsILocalFile).clone();
    */

    this.ioService = Components.classes['@mozilla.org/network/io-service;1'].
                                getService(Components.interfaces.nsIIOService);
    this.consoleService = Components.classes['@mozilla.org/consoleservice;1'].
								getService(Components.interfaces.nsIConsoleService);

    try {
        this.pofEngine = Components.classes["@iosart.com/favorites/PlainOldFavorites;1"].
                                        getService(Components.interfaces.IPlainOldFavorites);
    } catch(e) {
        this.pofEngine = new PlainOldFavoritesJSCWrapper();
    }

    this.prefService = Components.classes["@mozilla.org/preferences-service;1"]
                            .getService(Components.interfaces.nsIPrefService);
    this.prefBranch = this.prefService.getBranch("plainoldfavorites."); 

    this.bFollowFolderShortcuts = true;
    try {
         this.bFollowFolderShortcuts = this.prefBranch.getBoolPref("follow_folder_shortcuts");
    } catch (err) {
         this.prefBranch.setBoolPref("follow_folder_shortcuts", true);
    }

    this._filesBlackList = new Array();

    this._sortOrderByType = new Object();
    this._sortOrderByType[POF_FOLDER] = 0;
    this._sortOrderByType[POF_FOLDER_SHORTCUT] = 0;
    this._sortOrderByType[POF_FAVORITE] = 2;
    this._sortOrderByType[POF_GENERIC_FILE] = 2;
}

PlainOldFavoritesService.prototype = {
    // IPlainOldFavoritesService
    getEngine : function() {
        return this.pofEngine;
    },
    getFavoritesFolder : function() {
        return this.favoritesFolder; 
    },

    getFavoriteItemOrdinal : function(relativePathStart, file) {
        var relativeDirPathLength = file.path.length - file.leafName.length - 1 - relativePathStart;
        var relativeDirPath = file.path.substr(relativePathStart, relativeDirPathLength);
        var ordinal = this.pofEngine.getFavoriteOrdinal(relativeDirPath, file.leafName);
        return ordinal;
    },

    getFavoritesFolderEntries : function(folder) {
		var files = folder.directoryEntries;

        var contentsArray = new Array();
        var favoritesFolderPath = this.favoritesFolder.path;
        var relativePathStart = favoritesFolderPath.length + 1;
		while (files.hasMoreElements()) {
			var file = files.getNext().QueryInterface(Components.interfaces.nsILocalFile);

            if (typeof this._filesBlackList[file.path] != 'undefined') {
                // the file is in black list meaning it's problematic, skip it
                continue;
            }

            var isHidden;
            var isSymlink;
            var isDirectory;
            var isFile;
            try {
                isHidden = file.isHidden();
                isSymlink = file.isSymlink();
                isDirectory = file.isDirectory();
                isFile = file.isFile();
            } catch (e) {
                dump("ERROR getting file properties - " + e + "\n");
                this.logError("PlainOldFavorites: Check file or folder: " + file.path);
                this.consoleService.logStringMessage("Problem with " + file.path + " - " + e.message + " " + e.location);
                // add the file to the black list
                this._filesBlackList[file.path] = 1;
                continue;
            }


            if (isHidden) {
                continue;
            }

            var uri = this.ioService.newFileURI(file).QueryInterface(Components.interfaces.nsIURL);

            // test for shortcut to folder            
            if (this.bFollowFolderShortcuts && isSymlink) {
                var tmpFile = file.clone().QueryInterface(Components.interfaces.nsILocalFile);
                tmpFile.followLinks = true;

                if (tmpFile.target) {
                    var target = Components.classes["@mozilla.org/file/local;1"].
                               createInstance(Components.interfaces.nsILocalFile);
       	            target.initWithPath(tmpFile.target);
                    var targetIsDirectory;
                    try {
                            targetIsDirectory = target.isDirectory();
                    } catch(e) {
                            dump("ERROR getting file properties - " + e + "\n");
                            this.logError("PlainOldFavorites: Check file or folder: " + target.path);
                            this.consoleService.logStringMessage("Problem with " + target.path + " - " + 
                                                                                    e.message + " " + e.location);
                            // add the file to the black list
                            this._filesBlackList[file.path] = 1;
                            continue;
                    }
                    if (targetIsDirectory) {
                          var folderItem = Components.classes['@iosart.com/favorites/PlainOldFavoritesItem;1'].
                                       createInstance(Components.interfaces.IPlainOldFavoritesItem); 
                          folderItem.type = POF_FOLDER_SHORTCUT;
                          folderItem.file = target;
                          folderItem.name = file.leafName.substring(0, file.leafName.length - 4);
                          folderItem.ordinal = this.getFavoriteItemOrdinal(relativePathStart, file);
                          folderItem.iconUrl = new String("moz-icon://" + uri.spec + "?size=16");
                          contentsArray.push(folderItem);
                          continue;
                    } // if target is directory
                } // if target not empty
            } // if symbolic link
 
            // shortcuts to folders are also folders, don't want them here:
			if (isDirectory && !isSymlink) {
                var folderItem = Components.classes['@iosart.com/favorites/PlainOldFavoritesItem;1'].
                                    createInstance(Components.interfaces.IPlainOldFavoritesItem); 
                folderItem.type = POF_FOLDER;
                folderItem.file = file.clone();
                folderItem.name = file.leafName;
                folderItem.ordinal = this.getFavoriteItemOrdinal(relativePathStart, file);
                contentsArray.push(folderItem);
                continue;
			}

			if (isFile) {
                if (uri.fileExtension.toLowerCase() == 'url') {
                    var name = new String(file.leafName);
                    name = name.substr(0, name.length-4);


                    var urlItem = Components.classes['@iosart.com/favorites/PlainOldFavoritesItem;1'].
                                    createInstance(Components.interfaces.IPlainOldFavoritesItem); 
                    urlItem.type = POF_FAVORITE;
                    urlItem.file = file.clone();
                    urlItem.name = name;
                    var urlStr = new Object();
                    var iconUrlStr = new Object();
                    this.pofEngine.resolveFavoriteLink(file.path, urlStr, iconUrlStr);
                    urlItem.url = urlStr.value;

                    if (iconUrlStr.value) {
                        if (iconUrlStr.value.substr(0,4) == 'http') {
                                urlItem.iconUrl = iconUrlStr.value;
                        } else {
                               try {
                                    // try to convert the icon file to local file URL
                                    var iconFile = Components.classes["@mozilla.org/file/local;1"].
                                                        createInstance(Components.interfaces.nsILocalFile);
                                	iconFile.initWithPath(iconUrlStr.value);
                                    var iconUri = this.ioService.newFileURI(iconFile).
                                                        QueryInterface(Components.interfaces.nsIURL);
                                    urlItem.iconUrl = iconUri.spec;
                               }  catch(e) {
                                   urlItem.iconUrl = new String("moz-icon://" + uri.spec + "?size=16");
                               }
                        }
                    }

                    urlItem.ordinal = this.getFavoriteItemOrdinal(relativePathStart, file);
                    contentsArray.push(urlItem);
                    continue;
                }
			}

            // else, add as generic file:
            var fileItem = Components.classes['@iosart.com/favorites/PlainOldFavoritesItem;1'].
                                    createInstance(Components.interfaces.IPlainOldFavoritesItem); 
            fileItem.type = POF_GENERIC_FILE;
            fileItem.file = file.clone();
            fileItem.name = file.leafName;
            fileItem.ordinal = this.getFavoriteItemOrdinal(relativePathStart, file);
            fileItem.iconUrl = new String("moz-icon://" + uri.spec + "?size=16");
            contentsArray.push(fileItem);
		}

		var sortFunc;
        if (this.shouldSortByName()) {
            var me = this;
    		sortFunc = function (a,b) { return me.sortByNameCompare(a, b); }
        } else {
    		sortFunc = function (a,b) { return a.ordinal - b.ordinal; }
        }
        contentsArray.sort(sortFunc);
        var contents = Components.classes['@mozilla.org/array;1'].
                                createInstance(Components.interfaces.nsIMutableArray);

        for (var i=0; i < contentsArray.length; i++) {
            contents.appendElement(contentsArray[i], false);
        }

        return contents;
    },

    sortByNameCompare : function(a, b) {
        var typeToOrderA = this._sortOrderByType[a.type];
        var typeToOrderB = this._sortOrderByType[b.type];

        if (typeToOrderA == typeToOrderB) {
            return this.stringCompare(a.name.toLowerCase(),b.name.toLowerCase());
        } else {
            return typeToOrderA - typeToOrderB;
        }
    },

    stringCompare : function(a, b) {
    	if ( a < b ) return -1;
            if ( a > b ) return 1;
                return 0;
    },

    shouldSortByName : function() {
        var bSortByName = false;
        try {
          bSortByName = this.prefBranch.getBoolPref("sort_by_name");
        } catch(e) { } 
        return bSortByName;
    },

    logError : function(message) {
        var scriptError = Components.classes["@mozilla.org/scripterror;1"]
                .createInstance(Components.interfaces.nsIScriptError);
        scriptError.init(message, null, null, null, null, Components.interfaces.nsIScriptError.errorFlag, null);
        this.consoleService.logMessage(scriptError);
    },

    // nsISupports
    QueryInterface: function (iid) {
        if (!iid.equals(Components.interfaces.IPlainOldFavoritesService) 
            && !iid.equals(Components.interfaces.nsISupports))
               throw Components.results.NS_ERROR_NO_INTERFACE;

       return this;
    },

    classID: POF_SERVICE_CID,
}

//********************* PlainOldFavoritesItem *************************
function PlainOldFavoritesItem() {
    this._type = -1;
    this._file = null;
    this._name = null;
    this._url = null;
    this._iconUrl = null;
    this._ordinal = -1;
}

PlainOldFavoritesItem.prototype = {
    // IPlainOldFavoritesItem
    get type() { return this._type; },
    set type(aType) { this._type = aType; },

    get file() { return this._file; },
    set file(aFile) { this._file = aFile; },

    get name() { return this._name; },
    set name(aName) { this._name = aName; },

    get url() { return this._url; },
    set url(aUrl) { this._url = aUrl; },    

    get iconUrl() { return this._iconUrl; },
    set iconUrl(aIconUrl) { this._iconUrl = aIconUrl; },    

    get ordinal() { return this._ordinal; },
    set ordinal(aOrdinal) { this._ordinal = aOrdinal; },    

    // nsISupports
    QueryInterface: function (iid) {
        if (!iid.equals(Components.interfaces.IPlainOldFavoritesItem) 
            && !iid.equals(Components.interfaces.nsISupports))
               throw Components.results.NS_ERROR_NO_INTERFACE;

       return this;
    },

    classID: POF_ITEM_CID,
}


//********************* JSCTypes wrapper code *************************
function PlainOldFavoritesJSCWrapper() {
        Components.utils.import("resource://gre/modules/ctypes.jsm");

        var dirServiceProp = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
        var dllFile = dirServiceProp.get("ProfD", Components.interfaces.nsIFile);
        dllFile.append('extensions');
        dllFile.append('{7E7165E2-0767-448c-852F-5FA8714F2C37}');
        dllFile.append('components');
        dllFile.append('PlainOldFavorites.engine');

        var lib = ctypes.open(dllFile.path);
        this.lib = lib;
    
        this.wrapper = {
                openAddToFavoritesDialog : lib.declare('openAddToFavoritesDialog',
                                                   ctypes.default_abi,
                                                   ctypes.bool,
                                                   ctypes.jschar.ptr,
                                                   ctypes.jschar.ptr,
                                                   ctypes.jschar.ptr),
                openOrganizeFavoritesDialog : lib.declare('openOrganizeFavoritesDialog',
                                                   ctypes.default_abi,
                                                   ctypes.bool),
                resolveFavoriteLink : lib.declare('resolveFavoriteLink',
                                                   ctypes.default_abi,
                                                   ctypes.bool,
                                                   ctypes.jschar.ptr,
                                                   ctypes.char.ptr.ptr,
                                                   ctypes.char.ptr.ptr),
                getFavoriteOrdinal : lib.declare('getFavoriteOrdinal',
                                                   ctypes.default_abi,
                                                   ctypes.int32_t,
                                                   ctypes.jschar.ptr,
                                                   ctypes.jschar.ptr),
                openPropertiesDialog : lib.declare('openPropertiesDialog',
                                                   ctypes.default_abi,
                                                   ctypes.bool,
                                                   ctypes.jschar.ptr),

                freeMemory : lib.declare('freeMemory',
                                                   ctypes.default_abi,
                                                   ctypes.void_t,
                                                   ctypes.char.ptr),

    };
}

PlainOldFavoritesJSCWrapper.prototype = {
    openAddToFavoritesDialog : function(ownerWindow, name, url, iconUrl) {
        return this.wrapper.openAddToFavoritesDialog(name, url, iconUrl);
    },
    
    openOrganizeFavoritesDialog : function(ownerWindow) {
        return this.wrapper.openOrganizeFavoritesDialog();
    },

    resolveFavoriteLink : function(favoriteFileName, url, iconUrl) {
        var aUrl = new ctypes.char.ptr;
        var aIconURL = new ctypes.char.ptr;
        
        var rc = this.wrapper.resolveFavoriteLink(favoriteFileName, aUrl.address(), aIconURL.address());
        
        var outURL = aUrl.readString();
        var outIconURL = aIconURL.readString();

        this.wrapper.freeMemory(aUrl);
        this.wrapper.freeMemory(aIconURL);

        url.value = outURL;
        iconUrl.value = outIconURL;

        return rc;
    },

    getFavoriteOrdinal : function(relativePath, favoriteFileName) {
        return this.wrapper.getFavoriteOrdinal(relativePath, favoriteFileName);
    },

    openPropertiesDialog : function(favoriteFileName) {
        return this.wrapper.openPropertiesDialog(favoriteFileName);
    },

     // nsISupports
    QueryInterface: function (iid) {
        if (!iid.equals(Components.interfaces.IPlainOldFavorites) 
            && !iid.equals(Components.interfaces.nsISupports))
               throw Components.results.NS_ERROR_NO_INTERFACE;

       return this;
    }
}

//********************* Module code *************************
var gPOFModule = {
    components : {
        PlainOldFavoritesService : {
            CID: POF_SERVICE_CID,
            contractID: "@iosart.com/favorites/PlainOldFavoritesService;1",
            className: "Plain Old Favorites Service",
            factory: {
                   createInstance: function (aOuter, aIID) {
                        if (aOuter != null) {
                            throw Components.results.NS_ERROR_NO_AGGREGATION;
                        }
                        return (new PlainOldFavoritesService()).QueryInterface(aIID);
                   } // createInstance
            } // factory
        }, // PlainOldFavoritesService

        PlainOldFavoritesItem : {
            CID: POF_ITEM_CID,
            contractID: "@iosart.com/favorites/PlainOldFavoritesItem;1",
            className: "Plain Old Favorites Item",
            factory: {
                   createInstance: function (aOuter, aIID) {
                        if (aOuter != null) {
                            throw Components.results.NS_ERROR_NO_AGGREGATION;
                        }
                        return (new PlainOldFavoritesItem()).QueryInterface(aIID);
                   } // createInstance
            } // factory
        }, // PlainOldFavoritesItem
    }, // components

    firstTime: true,

    registerSelf: function (aCompMgr, aFileSpec, aLocation, aType) {
        if (this.firstTime) {
            this.firstTime = false;
            throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
        }
    
        aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
        for (var componentName in this.components) {
              var component = this.components[componentName];
              aCompMgr.registerFactoryLocation(component.CID, 
                                               component.className, 
                                               component.contractID,
                                               aFileSpec, 
                                               aLocation, 
                                               aType);
        }
    },

 
    getClassObject: function (aCompMgr, aCID, aIID) {
        if (!aIID.equals(Components.interfaces.nsIFactory))
            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

        for (var componentName in this.components) {
              if (aCID.equals(this.components[componentName].CID)) {
                return this.components[componentName].factory;
              }
        }

        throw Components.results.NS_ERROR_NO_INTERFACE;
    },

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

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

if (Components.utils && Components.utils.import) {
    try {
        Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    } catch (e) {}
}


if (XPCOMUtils && XPCOMUtils.generateNSGetFactory) {
    var NSGetFactory = XPCOMUtils.generateNSGetFactory([PlainOldFavoritesService, PlainOldFavoritesItem]);
}
