/*
# 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 Mozilla.org Code.
#
# The Initial Developer of the Original Code is.
# Portions created by the Initial Developer are Copyright (C) 2001
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#   KUMAGAI Kentaro <ku@ido.nu>
#
# 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 *****
*/

var Pack = {
	N: function (n) {
		return [
			String.fromCharCode( Math.floor(n / (1 << 24) ) ),
			String.fromCharCode( Math.floor(n / (1 << 16) ) ),
			String.fromCharCode( Math.floor(n / (1 << 8 ) ) ),
			String.fromCharCode( Math.floor(n % (1 << 8 ) ) ),
		].join('');
	},
	n: function (n) {
		return [
			String.fromCharCode( Math.floor(n / (1 << 8 ) ) ),
			String.fromCharCode( Math.floor(n % (1 << 8 ) ) ),
		].join('');
	},
	C: function (n) {
		return String.fromCharCode( n );
	}
}
var MP3 = {
	ID3v2: {
		enc_frame_text: function (content) {
			var unicode = false;
			for ( var i = 0; i < content.length; i++ ) {
				if ( unicode = ( content.charCodeAt(i) > 0x80 ) ) {
					break;
				}
			}
			if ( unicode ) {
				var s = [];
				for ( var i = 0; i < content.length; i++ ) {
					var c = content.charCodeAt(i);
					s.push( Pack.N(c) );
				}
				return '\x01\xff\xfe' + s.join('') ;
			} else {
				return  '\x00' + content;
			}
		},
		frame: function (id, content) {
			encoded = this.enc_frame_text( content );
			// pack("NCC", (length $enced), 0, 0) .  $enced;
			var n = encoded.length;
			return [
				id,
				Pack.N(n),
				Pack.C(0),
				Pack.C(0),
				encoded
			].join('');
		},
		implant: function (frames) {
			var ret = '';
			for ( var id in frames ) {
				ret += this.frame( id, frames[id] );
			}
			var size = ret.length;
			var id3size =	Math.floor(size / (2 << 21)) * (2 << 24) +
							Math.floor(size / (2 << 14)) * (2 << 16) +
							Math.floor(size / (2 <<  7)) * (2 <<  8) + (size % (2 << 7));
			
			return [
				'ID3',
				String.fromCharCode( 0x03 ),	// version.
				String.fromCharCode( 0 ),
				String.fromCharCode( 0 ),		// flags.
				Pack.N(id3size),
				ret
			].join('');
		}
	}
};

var HybridListener = function () {
	return {
		QueryInterface : function(aIID) {
			trace("QI: " + aIID);
			if (aIID.equals(Components.interfaces.nsISupports) ||
				aIID.equals(Components.interfaces.nsIStreamListener) ||
				aIID.equals(Components.interfaces.nsICancelable) ||
				aIID.equals(Components.interfaces.nsIWebBrowserPersist)) {
				return this;
			} else {
				throw Components.results.NS_NOINTERFACE;
			}
		},
		// nsICancelable
		cancel: function ( ) {
			this.canceling = true;
			this.onStopRequest(null);
		},
		// nsIWebBrowserPersist
		CancelSave: function () {
		},

		// nsIStreamListener
		canceling: false,
		success: false,
		total_downloaded: 0,
		o_stream: null,
		download: null,
		onStartRequest: function ( request, context ) {
			try {
				request.QueryInterface(Components.interfaces.nsIHttpChannel);
				this.success = request.requestSucceeded;
				if ( this.success ) {
					if ( this.onStartCallback ) {
						this.onStartCallback.apply(this);
					}
				}
			} catch(e) {
				trace(e);
			}
		},
		onDataAvailable: function ( request, context ,  inputStream ,  offset ,  count)  {
			trace("onDataAvailable");
			if ( this.canceling || ! this.success ) {
				this.onEndTransfer(request);
				return;
			}

			this.total_downloaded += count;

			var bstream = Components.classes["@mozilla.org/binaryinputstream;1"]
						.createInstance(Components.interfaces.nsIBinaryInputStream);
			bstream.setInputStream(inputStream);
			var bytes = bstream.readBytes(bstream.available());
			this.o_stream.write(bytes, bytes.length);

			request.QueryInterface(Components.interfaces.nsIHttpChannel);
			this.download.onProgressChange64(null, request,
					this.total_downloaded, request.contentLength,
					this.total_downloaded, request.contentLength
			);
		},
		onEndTransfer: function (request) {
			if ( this.o_stream ) {
				this.o_stream.flush();
				this.o_stream.close();
				this.o_stream = null;
			}

			this.download.onStateChange(null, request, this.download.STATE_STOP,
				Components.results.NS_OK);
		},
		onStopRequest: function ( request, context ,  statusCode ) {
			this.onEndTransfer(request, statusCode);
		}
	};
};

var MP3DownloadWithId3v23 = {
	VERSION: "0.0.1",
	counter: 0,
	init: function () {
		this.ioservice = Components.classes["@mozilla.org/network/io-service;1"]
								.getService(Components.interfaces.nsIIOService);
		this.dm = Components.classes["@mozilla.org/download-manager;1"]
								.getService(Components.interfaces.nsIDownloadManager);
	},
	open_download: function (path) {
		// window seems not to open before calling addDownload().
		if( this.counter++ < 1 ) {
			this.dm.open(window, path);
		}
	},
	add_to_dlq: function (uri, artist_name, song_title) {
		var file = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile);
		file.initWithPath( initAutoDownloadDisplay() );

		file.append( song_title + '.mp3' );

		var targetUri = this.ioservice.newFileURI(file);
		var sourceUri = this.ioservice.newURI(uri, null, null);

		var hybridListener = new HybridListener();

		var download = this.dm.addDownload(0, sourceUri, targetUri, song_title, null, null, null, file, hybridListener);
		this.open_download( file.path );

		var o_stream = Components.classes["@mozilla.org/network/file-output-stream;1"]
			.createInstance(Components.interfaces.nsIFileOutputStream);
		o_stream.init(file, 0x04 | 0x08 | 0x20, 0664, 0); // write, create, truncate

		var channel = this.ioservice.newChannelFromURI( sourceUri );
			
		hybridListener.o_stream = o_stream;
		hybridListener.download = download;
		hybridListener.onStartCallback = function () {
			var tag = MP3.ID3v2.implant( {
				TPE1: artist_name,
				TIT2: song_title
			} );
			this.o_stream.write(tag, tag.length);
		};

		channel.asyncOpen(hybridListener, null);
	}
};

new function () {
	var nodes = find_by_xpath('//a[text()="[PREVIEW]"]/ancestor::tr[1]');
	var artist_name = first('//*[@class="showTitle"]');

	MP3DownloadWithId3v23.init();

	for ( var i = 0; i < nodes.length; i++ ) {
		window.setTimeout( function (n) {
			var tds = find_by_xpath('./td', n);
			var a = first('.//a', n);
			var song_title = (tds[1]).textContent;
			
			trace( [a.href, artist_name.textContent, song_title ] );
			
			MP3DownloadWithId3v23.add_to_dlq(a.href, artist_name.textContent, song_title)
		}, i * 500, nodes[i]);
	} 
}



function make_scriptable(input) {
	var SIStream = Components.Constructor(
			'@mozilla.org/scriptableinputstream;1',
			'nsIScriptableInputStream', 'init');
	return new SIStream(input);
}

function find_by_xpath(xpath, ctx) {
	var context = ctx || window.document;
	var result;
	try {
		result = document.evaluate(xpath, context, null, XPathResult.ANY_TYPE, null);
	} catch (e) {
		Firebug.Console.log(e);
	}

	if ( result ) {
		var nodes = [];
		var n;
		while ( n = result.iterateNext() ) {
			nodes.push(n);
		}
		return nodes;
	} else {
		return [];
	} 
}

function first(xpath, ctx) {
	var context = ctx || window.document;
	var result = document.evaluate(xpath, context, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
	return result.singleNodeValue;
}

function trace(e) {
	if ( Firebug && Firebug.Console && Firebug.Console.log  ) {
		Firebug.Console.log(e);
	}
}

//
// too anoying to get default download directory...
// code from mozilla/toolkit/mozapps/downloads/content/downloads.js 
// 
// returns default desktop directory even if changed 
// HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
//
function initAutoDownloadDisplay()
{
  var pref = Components.classes["@mozilla.org/preferences-service;1"]
                       .getService(Components.interfaces.nsIPrefBranch);

  var autodownload = pref.getBoolPref("browser.download.useDownloadDir");
  if (autodownload) {
    function getSpecialFolderKey(aFolderType)
    {
		var rt = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo)
		            .QueryInterface(Components.interfaces.nsIXULRuntime);

	if ( rt.OS == 'WINNT' ) {
      return aFolderType == "Desktop" ? "DeskV" : "Pers";
	 }
	 else if ( rt.OS == 'Darwin' )  {
      return aFolderType == "Desktop" ? "UsrDsk" : "UsrDocs";
	 }
//#endif
//#ifdef XP_MACOSX
//#endif
//#ifdef XP_OS2
//      return aFolderType == "Desktop" ? "Desk" : "Home";
//#endif
      return "Home";
    }

    function getDownloadsFolder(aFolder)
    {
      var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
                                  .getService(Components.interfaces.nsIProperties);
      var dir = fileLocator.get(getSpecialFolderKey(aFolder), Components.interfaces.nsILocalFile);

      var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                              .getService(Components.interfaces.nsIStringBundleService);
      bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");

      var description = bundle.GetStringFromName("myDownloads");
      if (aFolder != "Desktop")
        dir.append(description);

      return dir;
    }

    var displayName = null;
    switch (pref.getIntPref("browser.download.folderList")) {
    case 0:
      folder = getDownloadsFolder("Desktop");
      break;
    case 1:
      folder = getDownloadsFolder("Downloads");
      break;
    case 2:
      folder = pref.getComplexValue("browser.download.dir", Components.interfaces.nsILocalFile);
      break;
    }
	return folder.path;

  }
}



