/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.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 Mouse Gestures (Mappings Service).
 *
 * The Initial Developer of the Original Code is Jochen Schlehuber.
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *  Jochen <bugs@krickelkrackel.de>
 *
 * ***** END LICENSE BLOCK ***** */

const mozGestContractID = "@mousegestures.org/mgMappingsService;1"
const mozGestCID = Components.ID("{fea06ea8-4621-469c-a85c-eedc1f654a4f}")


var mgMappingsService =
{
  QueryInterface : function(iid) {
    if (!iid.equals(Components.interfaces.mgIMappingsService) &&
        !iid.equals(Components.interfaces.nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;

    return this;
  },

  init                   : false,
  activeMappings         : new Array(),
  //actTemp will act as temporary activeMappings while customizing or importing
  actTemp                : new Array(),
  defaultMappings        : new Array(),
  //defTemp will act as temporary removedDefaultMappings while customizing or importing
  defTemp                : new Array(),
  removedDefaultMappings : new Array(),
  functions              : new Array(),
  supportedWindows       : new Array(),
  jsLoader               : Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                                     .getService(Components.interfaces.mozIJSSubScriptLoader),
  history                : new Array(),
  //import...
  curBookmark            : null,
  curBookmarkName        : null,
  curLocation            : null,

  initMappings : function() {
    if (this.init)
      return;

    this.init = true;
    this.jsLoader.loadSubScript('chrome://mozgest/content/defaults.js');

    //mgDefaultMappings comes from defaults.js
    mgDefaultMappings.init();
    this.defaultMappings = mgDefaultMappings.getMappings();
    //load window list
    this.getWindows();
    //load function list
    this.getFunctions();
    //load active mappings
    this.getActiveMappings();
    //register shut down observer
    Components.classes["@mozilla.org/observer-service;1"]
              .getService(Components.interfaces.nsIObserverService)
              .addObserver(mgMappingsService.shutDown, "quit-application", false);
  },

  shutDown :
  {
    observe: function(subject, topic, data) {
      mgMappingsService.writeActiveMappingsToFile(false);

      Components.classes["@mozilla.org/observer-service;1"]
                .getService(Components.interfaces.nsIObserverService)
                .removeObserver(mgMappingsService.shutDown, "quit-application");
    }

  },

  getWindows : function() {
    this.jsLoader.loadSubScript('chrome://mozgest/content/windowTypes.js');

    // mgWindowTypes comes from windowTypes.js
    mgWindowTypes.init();

    var ios = Components.classes["@mozilla.org/network/io-service;1"]
                        .getService(Components.interfaces.nsIIOService);

    var mgShort = mgWindowTypes.wTypes;

    for (winType in mgShort) {
      try {
        var test = ios.newChannel(winType , null, null);
        this.supportedWindows[mgShort[winType][0]] = "true";
        test = null;
      }
      catch (e) {}
    }
  },

  getFunctions : function() {
    //mgDefaultFunctions comes from defaults.js
    mgDefaultFunctions.init();
    var funcTemp = new Array();
    funcTemp = mgDefaultFunctions.getFunctions();

    for (wType in funcTemp) {           // read all "native" functions for this windowtype
      this.functions[wType] = new Array();

      var silent = funcTemp[wType];
      for (func in silent)
        this.functions[wType][func] = func + "()";
    }

    for (wType in funcTemp) {           // read all "foreign" functions for this windowtype
      var silent = funcTemp[wType];

      for (func in silent) {
        if (funcTemp[wType][func].length > 1) {
          for (var x = 1; x < funcTemp[wType][func].length; x++) {
            var short = false;

            if (funcTemp[wType][func][x] == "B")
              short = "browser";
            else if (funcTemp[wType][func][x] == "VS")
              short = "viewsource";
            else if (funcTemp[wType][func][x] == "W")
              short = "window";
            else if (funcTemp[wType][func][x] == "M")
              short = "messenger";
            else if (funcTemp[wType][func][x] == "MC")
              short = "mailcompose";

            if (short)
              this.functions[short][func] = func + "()";
          }
        }
      }
    }
  },

  getActiveMappings : function() {
    var dirService   = Components.classes['@mozilla.org/file/directory_service;1']
                                 .getService(Components.interfaces.nsIProperties);
    var mappingsFile = dirService.get('ProfD', Components.interfaces.nsILocalFile);
    mappingsFile.append("mousegestures.js");

    if (!mappingsFile.exists()) {
      var oldMappingsFile = dirService.get('ProfD', Components.interfaces.nsILocalFile);
      oldMappingsFile.append("mousegestures.rdf");

      var oldMappingsAlreadyImported = dirService.get('ProfD', Components.interfaces.nsILocalFile);
      oldMappingsAlreadyImported.append("mousegestures.rdf.imported");

      if (oldMappingsFile.exists() && !oldMappingsAlreadyImported.exists()) {
        if (!this.importRDFGestures(oldMappingsAlreadyImported))
          this.writeActiveMappingsToFile(true);
      }
      else
        this.writeActiveMappingsToFile(true);
    }

    // ======= Hack around Bug 418356 https://bugzilla.mozilla.org/show_bug.cgi?id=418356
    // ======= hopefully this will change ===============================================
    var ioService  = Components.classes["@mozilla.org/network/io-service;1"]
                               .getService(Components.interfaces.nsIIOService);
    var mgFileSpec = ioService.newFileURI(mappingsFile).spec

    try {
      this.jsLoader.loadSubScript(mgFileSpec);
    }
    catch (e) {
      dump("MozGest: Subscipt loader error. Trying streams and eval\n")
      //Bug 418356
      var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                              .createInstance(Components.interfaces.nsIFileInputStream);
      var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
                              .createInstance(Components.interfaces.nsIScriptableInputStream);
      fstream.init(mappingsFile, -1, 0, 0);
      sstream.init(fstream);

      var data = sstream.read(sstream.available());
      sstream.close();
      fstream.close();
      eval(data);
    }
    // ===================================================================================

    //mgActiveMappings comes from mousegestures.js inside the profile
    mgActiveMappings.init();
    this.activeMappings = new Array();
    this.actTemp = mgActiveMappings.getMappings();

    this.fillMappings(this.actTemp, this.activeMappings);
    this.actTemp = new Array();

    var silent = this.activeMappings
    for (each in silent)
      this.getRemovedDefaults(each);

  },

  fillMappings : function(array1, array2) {
    for (winType in array1) {
      if (winType == "browser_printpreview" || winType == "chatzilla")
        continue;

      array2[winType] = new Array();

      for (mapping in array1[winType]) {
        if (!array1[winType][mapping].type) {
          var func = array1[winType][mapping].func;

          if (!(func in this.functions[winType]) &&
              !(func in this.functions["window"]))
            continue;
        }
        array2[winType][mapping] = array1[winType][mapping];
      }
    }
  },

  addMapping : function(wType, code, type, name, aFunc, count) {
    var actTemp = this.actTemp;
    actTemp[wType][code] = {func : aFunc};

    if (type && type != "")
      actTemp[wType][code].type = type;

    if (name && name != "")
      actTemp[wType][code].name = name;

    if (!count)
      actTemp[wType][code].count = 0;
    else if (count != "")
      actTemp[wType][code].count = count;

    Components.classes["@mozilla.org/observer-service;1"]
              .getService(Components.interfaces.nsIObserverService)
              .notifyObservers(null, "mozgestControl", "tempMappingsUpdated");
  },

  getRemovedDefaults : function(wType) {
    var defSet = this.defaultMappings[wType]
    var actSet = this.activeMappings[wType]

    if (!(wType in this.removedDefaultMappings))
      this.removedDefaultMappings[wType] = new Array();

    for (mapping in defSet) {
      if (!(mapping in actSet))                  // e.g ":20" is not in actveMappings[wType] -> removed
        this.removedDefaultMappings[wType][mapping] = defSet[mapping];
      else if (actSet[mapping].name || actSet[mapping].func != defSet[mapping].func) // -> changed
        this.removedDefaultMappings[wType][mapping] = defSet[mapping];
    }
  },

  cleanUp : function() {
    this.actTemp = new Array()
    this.defTemp = new Array()
  },

  writeActiveMappingsToFile : function(newFile) {
    var gestureSet;

    if (newFile)
      gestureSet = this.defaultMappings;
    else
      gestureSet = this.activeMappings;

    try {
      var txt =
      "var mgActiveMappings =\n"+
      "{\n"+
      "  mappings :\n"+
      "  {\n"+
      "    browser    : new Array(),\n"+
      "    viewsource : new Array(),\n"+
      "    window     : new Array(),\n"+
      "    messenger  : new Array(),\n"+
      "    mailcompose: new Array()\n"+
      "  },\n\n"+
      "  /* for description: chrome://mozgest/content/defaults.js */\n\n"+
      "  init : function()\n"+
      "  {\n";

      for (wType in gestureSet) {
        var short;

        if (wType == "browser")
          short = "b";
        else if (wType == "viewsource")
          short = "vs";
        else if (wType == "window")
          short = "w";
        else if (wType == "messenger")
          short = "m";
        else if (wType == "mailcompose")
          short = "mc";

        txt = txt + "    var " + short + " = this.mappings." + wType +";\n";

        var silent = gestureSet[wType];

        for (mapping in silent) {
          txt = txt + "    " + short;
          txt = txt + '["' + mapping +'"] = {';
          txt = txt + 'func: "' + silent[mapping].func + '"';

          if (silent[mapping].type)
            txt = txt + ', type: "' + silent[mapping].type + '"';

          if (silent[mapping].name)
            txt = txt + ', name: "' + silent[mapping].name + '"';

          if (silent[mapping].count)
            txt = txt + ', count: ' + silent[mapping].count;
          else
            txt = txt + ', count: 0';

          txt = txt + "}\n";

        }
        txt = txt + "\n";
      }
      txt = txt + "  },\n\n";
      txt = txt + "  getMappings : function()\n" +
                    "  {\n" +
                    "    return this.mappings\n" +
                    "  }\n}";

      var dirService   = Components.classes['@mozilla.org/file/directory_service;1']
                                   .getService(Components.interfaces.nsIProperties);
      var mappingsFile = dirService.get('ProfD', Components.interfaces.nsILocalFile);
      mappingsFile.append("mousegestures.js");

      var stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                             .createInstance(Components.interfaces.nsIFileOutputStream);

      stream.init(mappingsFile, 0x02 | 0x08 | 0x20, 0644, 0);
      stream.write(txt, txt.length);
      stream.close();
    }
    catch (e) {}
  },

  importRDFGestures : function(aFile) {
    dump("MozGest: Trying to import old gestures from mousegestures.rdf\n");
    var retVal = true;

    try {
      this.jsLoader.loadSubScript('chrome://mozgest/content/mozgestImport.js');
      var err = importer.initImport();

      if (err)
        throw(err);

      this.fillMappings(importer.actTemp, this.activeMappings);
      this.writeActiveMappingsToFile(false);
    }
    catch (err) {
      dump(err);
      dump("MozGest: defaultMappings will be used instead!\n");
      retVal = false;
    }

    var stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                           .createInstance(Components.interfaces.nsIFileOutputStream);

    stream.init(aFile, 0x02 | 0x08 | 0x20, 0644, 0);
    stream.write(null, 0);
    stream.close();

    return retVal;
  }
}

mgMappingsService.wrappedJSObject = mgMappingsService;

var mozgestModule =
{
  QueryInterface : function(iid) {
    if (!iid.equals(Components.interfaces.nsIModule) &&
        !iid.equals(Components.interfaces.nsIFactory) &&
        !iid.equals(Components.interfaces.nsISupports))
      throw Components.results.NS_ERROR_NO_INTERFACE;

    return this;
  },

  getClassObject : function(compMgr, cid, iid) {
    if (!cid.equals(mozGestCID))
      throw Components.results.NS_ERROR_NO_INTERFACE;

    return this.QueryInterface(iid);
  },

  registerSelf : function(compMgr, fileSpec, location, type) {
    var compReg = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar)
    compReg.registerFactoryLocation(mozGestCID, "MozGest Mappings Service",
                                    mozGestContractID, fileSpec, location, type);
  },

  unregisterSelf : function(compMgr, location, type) {
    var compReg = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
    compReg.unregisterFactoryLocation(mozGestCID, fileSpec);
  },

  canUnload : function(a) {return true},

  createInstance : function(outer, iid) {
    if (outer != null)
      throw Components.results.NS_ERROR_NO_AGGREGATION;

    return mgMappingsService.QueryInterface(iid);
  },

  lockFactory : function(a) {}
}

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