/*
  # ***** 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 Microsummarizer.
  #
  # The Initial Developer of the Original Code is Mozilla.
  # Portions created by the Initial Developer are Copyright (C) 2006
  # the Initial Developer. All Rights Reserved.
  #
  # Contributor(s):
  #  Myk Melez <myk@mozilla.org> (Original Author)
  #  Simon Bünzli <zeniko@gmail.com>
  #  Asaf Romano <mano@mozilla.com>
  #  Dan Mills <thunder@mozilla.com>
  #  Ryan Flint <rflint@dslr.net>
  #
  # 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 *****
*/

/*
 * The original file is located here:
 * http://mxr.mozilla.org/mozilla/source/browser/components/microsummaries/src/nsMicrosummaryService.js?raw=1
 *
 */

/**
 * GNU LibreJS - A browser add-on to block nonfree nontrivial JavaScript.
 * *
 * Copyright (C) 2011, 2012 Loic J. Duros
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see  <http://www.gnu.org/licenses/>.
 *
 */

/**
 * html_parser
 *
 * Takes in an http response (string), loads it into a secured iframe
 * so that it can be manipulated as a DOM object. It then returns a
 * modified string to be passed along as a replacement of the original
 * response.
 *
 */

"use strict";

var {Cc, Ci, Cu} = require("chrome");


Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 

var domHandlerModule = require("html_script_finder/dom_handler");

const PR_UINT32_MAX = 2147483647;

// Helper function for XPCOM instanciation.
var CCIN  = function (cName, ifaceName) {
    return Cc[cName].createInstance(Ci[ifaceName]);
};



/**
 *
 * utils
 * 
 * takes care of encoding and decoding utf8, generate a dataURI to
 * load page in a sandbox iframe
 *
 */
var utils = {

    /**
     * encode_utf8
     * @param s string 
     *
     */
    encode_utf8: function (s) {
	return unescape(encodeURIComponent(s));
    },

    /**
     * decode_utf8
     * @param s string 
     *
     */
    decode_utf8: function (s) {
	return decodeURIComponent(escape(s));
    }

};

exports.htmlParser = function () {

    return {
	charset: null,
	htmlText: null,
	pageURL: null,
	fragment: null,
	responseStatus: null,
	usesIframe: null,

	/**
	 * parse
	 * 
	 * initialize properties, and run the iFrame code.
	 * 
	 */
	parse: function (htmlText, charset, url, fragment, responseStatus, parseResult) {
	    
	    var domParser, dom,
	        that = this;
	    
	    this.htmlText = htmlText;
	    this.charset = charset;
	    this.pageURL = url;
	    this.fragment = fragment;
	    this.responseStatus = responseStatus;	    

	    // parse using window.
	    // DOMParser just doesn't seem to
	    // output characters correctly on
	    // certain sites.
	    this.parseWithIframe(parseResult);

	},

	/**
	 * Parse a string of HTML text using a hidden iframe.
	 * 
	 * @param   htmlText
	 *          a string containing the HTML content
	 *
	 */
	parseWithIframe: function (parseResult) {

	    var document, rootElement, window, parseHandler;

 	    // token to allow iFrame content to be serialized
 	    // differently.
	    this.usesIframe = true;


	    window = this.getRecentWindow();

	    document = window.document;
	    rootElement = document.documentElement;
	    
	    // Create an iframe, make it hidden, and secure it against untrusted content.
	    this.iframe = document.createElement('iframe');
	    this.iframe.setAttribute("collapsed", true);
	    this.iframe.setAttribute("type", "content");
	    
	    // Insert the iframe into the window, creating the doc shell.
	    rootElement.appendChild(this.iframe);

	    var webNav = this.iframe.docShell.QueryInterface(Ci.nsIWebNavigation);
	    webNav.stop(Ci.nsIWebNavigation.STOP_NETWORK);

	    // security.
	    this.iframe.docShell.allowJavascript = false;
	    this.iframe.docShell.allowAuth = false;
	    this.iframe.docShell.allowPlugins = false;
	    this.iframe.docShell.allowMetaRedirects = false;
	    this.iframe.docShell.allowSubframes = false;
	    this.iframe.docShell.allowImages = false;


	    parseHandler = {
		self: this,


		handleEvent: function (event) {

		    event.target.removeEventListener("DOMContentLoaded", parseHandler, false);
		    window = null;
		    document = null;
		    rootElement = null;

		    try     { 

			this.self.HandleParse(event, function (stream) {

			    parseResult(stream);

			}); 
		    } 
		    
		    finally { this.self = null; }
		    
		}
	    };


		this.iframe.addEventListener("DOMContentLoaded", parseHandler, true);

		this.iframe.contentDocument.location = this.generateDataURI(this.htmlText, this.charset, this.pageURL);

	},

	/**
	 * 
	 * returns an instance of a window.
	 */
	getRecentWindow: function () {

	    var windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService();
	    var wmInterface = windowMediator.QueryInterface(Ci.nsIWindowMediator);

	    return wmInterface.getMostRecentWindow("navigator:browser");
	    
	},

	/*
	 * destroy iframe after we are done using it.
	 */

	destroy: function () {

	    if (this.iframe != undefined && this.iframe.parentNode) {
		//console.log('killing iframe');
		this.iframe.parentNode.removeChild(this.iframe);
		this.iframe = null;

	    }

	},


	/**
	 * Handle a load event for the iframe-based parser.
	 * 
	 * @param   event
	 *          the event object representing the load event
	 *
	 */
	HandleParse: function (event, callback) {



	    var currentIframe;
	    var that = this, handler;
	    currentIframe = event.originalTarget;


	    try {


		//callback(that.serializeToStream(currentIframe));

		handler = domHandlerModule.domHandler(currentIframe, this.pageURL, this.fragment, this.responseStatus, function (dom) {

		    callback(that.serializeToStream(dom));

		    // get rid of all this stuff.
                    dom = null;
                    handler = null;
		    currentIframe = null;

		});
	    } catch (e) {
		// get rid of all this stuff.
		handler = null;
		currentIframe = null;
		console.log('issue with domHandler,', e, e.lineNumber);		
	    }
	},

	/** 
	 * generateDataURI
	 * Escape a string, return it as data URI with charset if
	 * applicable.
	 */
	generateDataURI: function () {
	    var encoded, datauri;

	    encoded = escape(this.htmlText); 	    


	    if (this.charset != "") {

		datauri =  "data:text/html;charset=" + this.charset + "," + encoded;

	    } else {

		datauri =  "data:text/html," + encoded;

	    }
	    
	    return datauri;
	    
	},

	/**
	 *
	 * reconstitueDoctype
	 * Takes a document.doctype node and reconstitute
	 * a serialized doctype using its properties.
	 * returns a string that corresponds to the doctype
	 * in the source of the page.
	 * 
	 * @param doctypeNode The doctype element.
	 * @return string The doctype string.
	 */
	reconstituteDoctype: function (doctypeNode) {

	    var doctype = "<!DOCTYPE HTML>";

	    if (doctypeNode != undefined &&
		doctypeNode.publicId != "" && 
		doctypeNode.systemId != "") {
		
		doctype = "<!DOCTYPE ";

		doctype += doctypeNode.name + ' PUBLIC "' + doctypeNode.publicId + '" "' + doctypeNode.systemId + '">';

	    }

	    return doctype;

	},

	/**
	 * reconstituteHtmlString
	 * Take the whole <html> node, deep clone it,
	 * place the clone in a div, get the innerHTML from
	 * that div. Get the doctype as well and return it all
	 * as a string.
	 *
	 * @param aDocument obj the DOM to be serialized.
	 * @return the string.
	 */
	serializeHtml: function (aDocument) {
	    var newData;

	    try {
		var wrapperDiv = aDocument.createElement('div'),
		doctype = this.reconstituteDoctype(aDocument.doctype),
		htmlNode = aDocument.getElementsByTagName('html')[0],
		cloneHtml = htmlNode.cloneNode(true);
		
		wrapperDiv.appendChild(cloneHtml);
		
		newData = doctype + '\n' + wrapperDiv.innerHTML;
		
		if (this.usesIframe &&
		    aDocument.characterSet.toLowerCase() === 'utf-8') {

		    newData = utils.encode_utf8(newData);	    

		}

		aDocument = null;
		wrapperDiv = null;
		htmlNode = null;
		
	    } catch (x) {
		console.log(x, x.lineNumber, x.fileName);
		newData = "";
	    }

	    return newData;

	},
	
	/**
	 * serializeToStream
	 * Serializes an HTML DOM into a binary stream. Uses
	 * nsIDOMSerializer only as a backup to when the
	 * reconstituteHtmlString method fails (not sure if/when it
	 * happens).
	 * @param dom obj Reference to the dom object
	 * @param that obj Reference to the object returned by htmlParser.
	 * This allows to give access to the iframe.
	 * @return a binary stream.
	 */
	serializeToStream: function (dom) {

	    var newData,
	    len;

	    try {

		var storageStream = CCIN("@mozilla.org/storagestream;1", "nsIStorageStream");
		var binaryOutputStream = CCIN("@mozilla.org/binaryoutputstream;1",
					      "nsIBinaryOutputStream");

	    } catch (e) {

		console.log('issue with serializer', e);

	    }

	    try {

		storageStream.init(8192, PR_UINT32_MAX, null);
		binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));

		// stuff here.
		newData = this.serializeHtml(dom);

		len = newData.length;
		binaryOutputStream.writeBytes(newData, len);

		// kill window.
		this.destroy();

		return storageStream;

	    } catch (e) {

		console.log('issue with serializeToStream, using xmlserializer', e);

	    }	
	    
	}
    };
};