var EXPORTED_SYMBOLS = ["YOONO_SIDEBAR"];

Components.utils.import("resource://yoono/yoonoLog.js");

// This component handles sidebar related stuff


const CI = Components.interfaces;
const CL = Components.classes;

const WMED = CL['@mozilla.org/appshell/window-mediator;1'].getService(CI.nsIWindowMediator);
const SCRIPT = CL["@mozilla.org/moz/jssubscript-loader;1"].getService(CI.mozIJSSubScriptLoader);
const PREFSSERVICE = CL["@mozilla.org/preferences-service;1"].getService(CI.nsIPrefService);
const PREFS = PREFSSERVICE.getBranch("extensions.yoono.");

const MARK_SELECTION_START = '\u200B\u200B\u200B\u200B\u200B';
const MARK_SELECTION_END = '\u200B\u200B\u200B\u200B\u200B';


var navigator = {userAgent: ""}; // prototype

// fake document object to supply mandatory methods
/*
var document = {
  implementation: {
    createDocument : function() {
      var doc = CL["@mozilla.org/xml/xml-document;1"].createInstance();
      doc.QueryInterface(CI.nsIDOMDocument);
      return(doc);
    }
  }
};
*/
var document = null;

// Use the evil hack of the hidden window
var window = null;
var navigator = null;
var Node = null;
var Element = null;
var XMLDocument = null;
var DOMParser = null;
var XMLSerializer = null;
var XMLHttpRequest = null;
var Document = null;
var Event = null;
var HTMLElement = null;

function alert(msg) {
  return(window.alert(msg));
}

/*
function mkTimer(handler, delay, type) {
  var timer = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer);
  var obs = {
    notify: function() {handler.call();}
  };
  timer.initWithCallback(obs, delay , type);
}

function setInterval(handler, delay) {
  return(mkTimer(handler, delay, CI.nsITimer.TYPE_REPEATING_PRECISE));
}

function setTimeout(handler, delay) {
  return(mkTimer(handler, delay, CI.nsITimer.TYPE_ONE_SHOT));
}

function clearTimeout(timer) {
  timer.cancel();
}
*/
function setInterval(handler, delay) {
  return(window.setInterval(handler, delay));
}
function setTimeout(handler, delay) {
  return(window.setTimeout(handler, delay));
}
function clearTimeout(timer) {
  return(window.clearTimeout(timer));
}
function clearInterval(timer) {
  return(window.clearInterval(timer));
}



// SidebarComponent : le composant en lui meme
function SidebarComponent() {
  // objet magique qui permet l appel depuis le chrome
  this.wrappedJSObject = this;
  this.started = false;
  this._started = false;
  this.memoMgr = null;
  this.eventMgr = null;
  this.UTF8_MD5 = null;
  this.serializer = null;

  this.init = function(yoono_cmpt) {
    if(this._started) return;
    this._started = true;
    try {
      window = Components.classes["@mozilla.org/appshell/appShellService;1"]
         .getService(Components.interfaces.nsIAppShellService)
         .hiddenDOMWindow;
    } catch(e) {
    }
    // Some platforms may not have a hiddenDOMWindow...
    if(!window) {
      window = WMED.getMostRecentWindow("navigator:browser");
    }

    Node = window.Node;
    Element = window.Element;
    XMLDocument = window.XMLDocument;
    DOMParser = window.DOMParser;
    XMLSerializer = window.XMLSerializer;
    this.serializer = new XMLSerializer();
    XMLHttpRequest = window.XMLHttpRequest;
    // MemoMgr needs to access navigator for the locale
    navigator = window.navigator;

    Document = window.Document;
    Event = window.Event;
    HTMLElement = window.HTMLElement;

    document = CL["@mozilla.org/xml/xml-document;1"].createInstance();
    document.QueryInterface(CI.nsIDOMDocument);

    try  {
      // ne s'execute qu'une seule fois par session
      if (this.started) {
        return;
      }
      YOONO_LOG.debug('Initializing YOONO_SIDEBAR');
      this.started = true;
      this.yoono_cmpt = yoono_cmpt;
    } catch(e) {
      YOONO_LOG.error('YOONO_SIDEBAR.init: ' + e  + '\n' + e.stack);
    }
  };

  this.loadScript = function(file) {
    YOONO_LOG.debug('SidebarComponent.loadScript: Loading ' + file);
    try {
      SCRIPT.loadSubScript(file);
    } catch(aError){
      YOONO_LOG.error('SidebarComponent.loadScript : Loading ' + file + ' failed : '+aError);
      throw aError;
    }
  };

  ////////////////////////////////////////////////////////////////////////////////
  // Get container with marked selection 
  this.getContainerForSelection = function(selection) {
    var range = selection.getRangeAt(0);
    var ancestorContainer = range.commonAncestorContainer;
    var doc = ancestorContainer.ownerDocument;

    var startContainer = range.startContainer;
    var endContainer = range.endContainer;
    var startOffset = range.startOffset;
    var endOffset = range.endOffset;

    // let the ancestor be an element
    if (ancestorContainer.nodeType == ancestorContainer.TEXT_NODE ||
        ancestorContainer.nodeType == ancestorContainer.CDATA_SECTION_NODE)
      ancestorContainer = ancestorContainer.parentNode;

    // for selectAll, let's use the entire document, including <html>...</html>
    // @see DocumentViewerImpl::SelectAll() for how selectAll is implemented
    try {
      if (ancestorContainer == doc.body)
        ancestorContainer = doc.documentElement;
    } catch (e) { }

    // each path is a "child sequence" (a.k.a. "tumbler") that
    // descends from the ancestor down to the boundary point
    var startPath = this.getPath(ancestorContainer, startContainer);
    var endPath = this.getPath(ancestorContainer, endContainer);

    // clone the fragment of interest and reset everything to be relative to it
    // note: it is with the clone that we operate/munge from now on
    ancestorContainer = ancestorContainer.cloneNode(true);
    startContainer = ancestorContainer;
    endContainer = ancestorContainer;

    // Only bother with the selection if it can be remapped. Don't mess with
    // leaf elements (such as <isindex>) that secretly use anynomous content
    // for their display appearance.
    var canDrawSelection = ancestorContainer.hasChildNodes();
    if (canDrawSelection) {
      var i;
      for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) {
        startContainer = startContainer.childNodes.item(startPath[i]);
      }
      for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) {
        endContainer = endContainer.childNodes.item(endPath[i]);
      }

      // add special markers to record the extent of the selection
      // note: |startOffset| and |endOffset| are interpreted either as
      // offsets in the text data or as child indices (see the Range spec)
      // (here, munging the end point first to keep the start point safe...)
      var tmpNode;
      if (endContainer.nodeType == endContainer.TEXT_NODE ||
          endContainer.nodeType == endContainer.CDATA_SECTION_NODE) {
        // do some extra tweaks to try to avoid the view-source output to look like
        // ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
        // To get a neat output, the idea here is to remap the end point from:
        // 1. ...<tag>]...   to   ...]<tag>...
        // 2. ...]</tag>...  to   ...</tag>]...
        if ((endOffset > 0 && endOffset < endContainer.data.length) ||
            !endContainer.parentNode || !endContainer.parentNode.parentNode)
          endContainer.insertData(endOffset, MARK_SELECTION_END);
        else {
          tmpNode = doc.createTextNode(MARK_SELECTION_END);
          endContainer = endContainer.parentNode;
          if (endOffset == 0)
            endContainer.parentNode.insertBefore(tmpNode, endContainer);
          else
            endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
        }
      }
      else {
        tmpNode = doc.createTextNode(MARK_SELECTION_END);
        endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
      }

      if (startContainer.nodeType == startContainer.TEXT_NODE ||
          startContainer.nodeType == startContainer.CDATA_SECTION_NODE) {
        // do some extra tweaks to try to avoid the view-source output to look like
        // ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
        // To get a neat output, the idea here is to remap the start point from:
        // 1. ...<tag>[...   to   ...[<tag>...
        // 2. ...[</tag>...  to   ...</tag>[...
        if ((startOffset > 0 && startOffset < startContainer.data.length) ||
            !startContainer.parentNode || !startContainer.parentNode.parentNode ||
            startContainer != startContainer.parentNode.lastChild)
          startContainer.insertData(startOffset, MARK_SELECTION_START);
        else {
          tmpNode = doc.createTextNode(MARK_SELECTION_START);
          startContainer = startContainer.parentNode;
          if (startOffset == 0)
            startContainer.parentNode.insertBefore(tmpNode, startContainer);
          else
            startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
        }
      }
      else {
        tmpNode = doc.createTextNode(MARK_SELECTION_START);
        startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
      }
    }

    return ancestorContainer;
  };

  ////////////////////////////////////////////////////////////////////////////////
  // helper to get a path like FIXptr, but with an array instead of the "tumbler" notation
  // see FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
  this.getPath = function(ancestor, node) {
    var n = node;
    var p = n.parentNode;
    if (n == ancestor || !p)
      return null;
    var path = new Array();
    if (!path)
      return null;
    do {
      for (var i = 0; i < p.childNodes.length; i++) {
        if (p.childNodes.item(i) == n) {
          path.push(i);
          break;
        }
      }
      n = p;
      p = n.parentNode;
    } while (n != ancestor && p);
    return path;
  };

  
}


var YOONO_SIDEBAR = new SidebarComponent();
