/* You may find the license in the LICENSE file */
 
const EXPORTED_SYMBOLS = ['Decompressor'];


const BUFFER_SIZE = 5 * 1024 * 1024;
const FREQ = 250;

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const Ctor = Components.Constructor;
const module = Cu.import;
const Exception = Components.Exception;

const DTA = {};
module("resource://dta/utils.jsm");
module("resource://dta/api.jsm", DTA);
module("resource://dta/support/timers.jsm");

const IOService = DTA.IOService;
const Prefs = DTA.Preferences;

const File = new Ctor('@mozilla.org/file/local;1', 'nsILocalFile', 'initWithPath');
const FileOutputStream = new Ctor('@mozilla.org/network/file-output-stream;1', 'nsIFileOutputStream', 'init');
const BinaryOutputStream = new Ctor('@mozilla.org/binaryoutputstream;1', 'nsIBinaryOutputStream', 'setOutputStream');
const BufferedOutputStream = new Ctor('@mozilla.org/network/buffered-output-stream;1', 'nsIBufferedOutputStream', 'init');
const BinaryInputStream = new Ctor('@mozilla.org/binaryinputstream;1', 'nsIBinaryInputStream', 'setInputStream');

const Timers = new TimerManager();

function Decompressor(download) {
	this.download = download;
	this.to = new File(download.destinationFile);
	this.from = download.tmpFile.clone();

	try {
		this._outStream = new FileOutputStream(this.to, 0x04 | 0x08, Prefs.getExt('permissions', 384), 0);
		this.outStream = new BinaryOutputStream(new BufferedOutputStream(this._outStream, BUFFER_SIZE));

		let converter = Cc["@mozilla.org/streamconv;1?from=" + download.compression + "&to=uncompressed"]
			.createInstance(Ci.nsIStreamConverter);

		converter.asyncConvertData(
			download.compression,
			"uncompressed",
			this,
			null
		);

		IOService.newChannelFromURI(IOService.newFileURI(this.from)).asyncOpen(converter, null);
	}
	catch (ex) {
		try {
			if (this.outStream) {
				outStream.close();
			}
			if (this.to.exists()) {
				this.to.remove(false);
			}
			if (this.from.exists()) {
				this.from.remove(false);
			}
		}
		catch (exx) {
			// XXX: what now?
		}
		Debug.log("err. :p", ex);
		download.complete(ex);
	}
}
Decompressor.prototype = {
	exception: null,
	QueryInterface: function(iid) {
		if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIStreamListener) || iid.equals(cI.nsIRequestObserver)) {
			return this;
		}
		throw Cr.NS_ERROR_NO_INTERFACE;
	},
	onStartRequest: function(r, c) {
		this._timer = Timers.createRepeating(FREQ, this.download.invalidate, this.download); 
	},
	onStopRequest: function(request, c) {
		Timers.killTimer(this._timer);
		// important, or else we don't write out the last buffer and truncate too early. :p
		this.outStream.flush();
		try {
			this._outStream.QueryInterface(Ci.nsISeekableStream).setEOF();
		}
		catch (ex) {
			this.exception = ex;
		}
		try {
			this.outStream.close();
			this._outStream.close();
		}
		catch (ex) {
			// huh?
			Debug.log("Decompressor: close streams", ex);
		}
		if (this.exception) {
			try {
				this.to.remove(false);
			}
			catch (ex) {
				// no-op: we're already bad :p
			}
		}
		try {
			this.from.remove(false);
		}
		catch (ex) {
			Debug.log("Failed to remove tmpFile", ex);
		}

		this.download.complete(this.exception);
	},
	onDataAvailable: function(request, c, stream, offset, count) {
		try {
			var binStream = new BinaryInputStream(stream);
			if (count != this.outStream.write(binStream.readBytes(count), count)) {
				throw new Exception("Failed to write!");
			}
			this.download.partialSize = offset;
		}
		catch (ex) {
			this.exception = ex;
			var reason = 0x804b0002; // NS_BINDING_ABORTED;
			request.cancel(reason);
		}
	}
};