/*
# 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 <ku0522a*gmail.com>
#   id:brazil http://d.hatena.ne.jp/brazil/
#
# 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 = {
	VERSION: "0.0.3",
	_range: function (n) {
		var a = [];
		while (n--) { a.push(n) }
		return a;
	},
	_bytes: function (value, bytes) {
		return this._range(bytes).map( function(n) {
					return String.fromCharCode( (value & (0xFF << ( 8 * n )) ) >> (8 * n) );
		} );
	},
	_16: function (n) { return this._bytes(n, 2) },
	_32: function (n) { return this._bytes(n, 4) },
	N: function (n) { return this._32(n).join(""); },
	n: function (n) { return this._16(n).join(""); },
	v: function (n) { return this._16(n).reverse().join(""); },
	C: function (n) { return String.fromCharCode( n ); }
};

var MP3 = {
	VERSION: "0.0.2",
	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.v(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 / (1 << 21)) * (1 << 24) +
							Math.floor(size / (1 << 14)) * (1 << 16) +
							Math.floor(size / (1 <<  7)) * (1 <<  8) + (size % (1 << 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) {
			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)  {
			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 |
				this.download.STATE_IS_NETWORK,
				Components.results.NS_OK);
		},
		onStopRequest: function ( request, context ,  statusCode ) {
			this.onEndTransfer(request, statusCode);
		}
	};
};

var MySpaceMP3Downloader = {
	VERSION: "0.0.6",
	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 ) {
			if ( this.dm.open ) {
				this.dm.open(window, path);
			}
		}
	},
	getDownloadDirectory: function () {
		if (this.dm.defaultDownloadsDirectory) {
			// fx3.
			return this.dm.userDownloadsDirectory.path;
		} else {
			return initAutoDownloadDisplay();
		}
	},
	// Original: http://mxr.mozilla.org/mozilla/source/toolkit/content/contentAreaUtils.js#811
	normalize_filename: function ( filename ) {
		if (navigator.appVersion.indexOf("Windows") != -1) {
			return filename.
				replace(/[\"]+/g, "'").
				replace(/[\*\:\?]+/g, " ").
				replace(/[\<]+/g, "(").
				replace(/[\>]+/g, ")").
				replace(/[\\\/\|]+/g, "_");
		} else if (navigator.appVersion.indexOf("Macintosh") != -1) {
			return filename.replace(/[\:\/]+/g, "_");
		}
		return filename.replace(/[\/]+/g, "_");
	},
	add_to_dlq: function (uri, artist_name, song_title) {
		var file = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile);
		file.initWithPath( this.getDownloadDirectory() );
		

		var filename = this.normalize_filename(song_title);
		file.append( filename + '.mp3' );

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

		var hybridListener = new HybridListener();

		// fx3 nsIDownloadManager#addDownload takes 8 args. fx2 takes 9.
		var args = [
			this.dm.DOWNLOAD_TYPE_DOWNLOAD,
			sourceUri, targetUri, song_title,
			null, null
		];

		if ( ! this.dm.userDownloadsDirectory )
			args.push(null);
		
		args = args.concat( file, hybridListener );


		var download = this.dm.addDownload.apply(this, args);
		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,
				TALB: 'MySpace',
				TCON: 'Web',
				COMM: 'eng\x00' + window.location.href
			} );
			this.o_stream.write(tag, tag.length);
		};

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

new function () {
	var nodes = find_by_xpath('id("downloads")/li/a');
	var artist_name = first('//span[@class="nametext"]');
try {
	MySpaceMP3Downloader.init();

	for ( var i = 0; i < nodes.length; i++ ) {
		window.setTimeout( function (n) {
			try {
				MySpaceMP3Downloader.add_to_dlq(n.href, artist_name.textContent, n.textContent)
			} catch(e) {
				trace(e);
			}
		}, i * 500, nodes[i]);
	} 
} catch(e) {
	trace(e);
}
}



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

function find_by_xpath(xpath) {
	var context = window.document;
	var result;
	try {
		result = document.evaluate(xpath, context, null, XPathResult.ANY_TYPE, null);
	} catch (e) {
		trace(e);
	}

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

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

function trace() {
	if ( window.console ) {
		window.console.log.apply(this, arguments)
	} else if ( Firebug && Firebug.Console && Firebug.Console.log  ) {
		Firebug.Console.log.apply(this, arguments);
	}
}

		
//
// 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;

  }
}


