/*
# 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.4",
	ID3v2: {
		frames: {},
		v2tov3: {
			TBP: 'TBPM',
			TOT: 'TOAL',
			WAR: 'WOAR',
			TP4: 'TPE4',
			GEO: 'GEOB',
			TRK: 'TRCK',
			EQU: 'EQUA',
			WXX: 'WXXX',
			TYE: 'TYER',
			TCO: 'TCON',
			MLL: 'MLLT',
			CNT: 'PCNT',
			TRD: 'TRDA',
			TDY: 'TDLY',
			TP2: 'TPE2',
			TP1: 'TPE1',
			TFT: 'TFLT',
			WCM: 'WCOM',
			TOL: 'TOLY',
			CRA: 'AENC',
			COM: 'COMM',
			TSI: 'TSIZ',
			POP: 'POPM',
			TLE: 'TLEN',
			IPL: 'IPLS',
			REV: 'RVRB',
			TDA: 'TDAT',
			STC: 'SYTC',
			TRC: 'TSRC',
			TAL: 'TALB',
			TSS: 'TSSE',
			TOR: 'TORY',
			TT2: 'TIT2',
			TXT: 'TEXT',
			TP3: 'TPE3',
			MCI: 'MDCI',
			TCR: 'TCOP',
			TIM: 'TIME',
			RVA: 'RVAD',
			TLA: 'TLAN',
			ULT: 'USLT',
			SLT: 'SYLT',
			TPB: 'TPUB',
			TCM: 'TCOM',
			WAF: 'WOAF',
			TXX: 'TXXX',
			TOA: 'TOPE',
			TKE: 'TKEY',
			UFI: 'UFID',
			BUF: 'RBUF',
			WPB: 'WPUB',
			TT3: 'TIT3',
			WAS: 'WOAS',
			ETC: 'ETCO',
			TT1: 'TIT1',
			TMT: 'TMED',
			TEN: 'TENC',
			TPA: 'TPOS',
			WCP: 'WCOP',
			TOF: 'TOFN',
		},
		extendedHeaderSize: null,
		toID3Size: function (n) {
			var size = 0;
			for ( var i = 0; i < 4; i++ ) {
				var v =  (n & 0x7f);
				n >>= 7;
				size += v << (8*i);
			}
			return size;
		},
		dumpFrame: function (id) {
			var frame = this.frames[id];
			var n = frame.length;
			return [
				id,
				Pack.N( this.toID3Size(n) ),
				Pack.C(0),
				Pack.C(0),
				frame.content
			].join('');
		},
		dump: function () {
			var ret = '';
			var content = '';
			for ( var id in this.frames ) {
				ret += this.dumpFrame( id );
			}

			var s = this.toID3Size( ret.length )
			
			return [
				'ID3',
				String.fromCharCode( 0x03 ),	// version.
				String.fromCharCode( 0 ),
				String.fromCharCode( 0 ),		// flags.
				Pack.N( s ),
				ret
			].join('');
		},
		parseHeader: function (buffer) {
			var m;
			if ( m = buffer.match(/^ID3(..)(.)(....)/) ) {
				this.version = (m[1].charCodeAt(0) * 100) + m[2].charCodeAt(0);
				this.flags = m[2];
				return this.extendedHeaderSize =
					(m[3].charCodeAt(0) << 21) +
					(m[3].charCodeAt(1) << 14) +
					(m[3].charCodeAt(2) << 7) +
					(m[3].charCodeAt(3) << 0) ;
			}
			return null;
		},
		parse: function (buffer) {
			var length = this.parseHeader(buffer);

			var offset = 10;
			// skip extended header.
			if ( this.version >= 300 && this.flags & 0x40 ) {
				offset += 6 + 
					(buffer.charCodeAt(offset + 0) << 21) +
					(buffer.charCodeAt(offset + 1) << 14) +
					(buffer.charCodeAt(offset + 2) << 7) +
					(buffer.charCodeAt(offset + 3) << 0) ;
			}

			while ( length > offset ) {
				var key;
				if ( this.version >= 300 ) {
					k = buffer.slice(offset, offset + 4);

					var frameLength =
						(buffer.charCodeAt((offset + 4) + 0) << 21) +
						(buffer.charCodeAt((offset + 4) + 1) << 14) +
						(buffer.charCodeAt((offset + 4) + 2) << 7) +
						(buffer.charCodeAt((offset + 4) + 3) << 0) ;
					this.frames[k] = {
						length: frameLength,
						encoding: null,
						content: buffer.slice(offset + 10, (offset + 10) + frameLength),
					};
					offset += frameLength + 10;

				} else if ( this.version >= 200 ) {
					k = buffer.slice(offset, offset + 3);

					if ( k in this.v2tov3 ) {
						k = this.v2tov3[k];
					//} else if ( k == 'PIC' ) {
					} else {
						return;
					}

					var frameLength =
						(buffer.charCodeAt((offset + 3) + 0) << 16) +
						(buffer.charCodeAt((offset + 3) + 1) << 8) +
						(buffer.charCodeAt((offset + 3) + 2) << 0) ;

					var textEncoding = buffer.charCodeAt(offset + 6);

					var content;
					var contentLength;

					if ( textEncoding ) {
						// -1 for textEncoding. it is included in frameLength. 
						content = '\x01\xff\xfe' + buffer.slice(offset + 7, (offset + 7) + frameLength - 1);
						contentLength = 3 + (frameLength - 1);
					} else {
						content = buffer.slice(offset + 6, (offset + 6) + frameLength);
						contentLength = frameLength ;
					}

					this.frames[k] = {
						length: contentLength,
						content: content
					};
					offset += frameLength + 6;
				}
			}
		},
		addFrame: function (id, content) {
			var c = this.encodeFrameText(content);
			this.frames[id] = {
				length: c.length,
				content: c
			}
		},
		encodeFrameText: 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;
			}
		},
	},
};

var HybridListener = function () {
	var VERSION = '0.0.2-mp3';
	var hasID3 = false;
	var extendedHeaderSize = 0;
	var self;
	return self = {
		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,
		addCustomFrames: function () {
		},

		onStartRequest: function ( request, context ) {
			MP3.ID3v2.frames = {};
			this.success = request.requestSucceeded;
			this.writemeta = false;
		},
		onDataAvailable: function ( request, context ,  inputStream ,  offset ,  count)  {
			if ( this.canceling || ! this.success ) {
				this.onEndTransfer(request);
				return;
			}

			this.total_downloaded += count;

			if ( typeof(Application) != 'undefined' && Application.Console ) {
				Application.Console.log(this.total_downloaded);
			}

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

			if ( offset == 0 ) {
				if ( this.extendedHeaderSize = MP3.ID3v2.parseHeader( bytes ) ) {
					//hasID3 = true;
					this.writemeta = true;
				} else {
						for ( var name in this.metadata ) {
							if ( !( name in MP3.ID3v2.frames) ) {
							log("name", name);
								MP3.ID3v2.addFrame(name, this.metadata[name]);
							}
						}
				}
			}

			if ( this.writemeta ) {
				if ( offset + count >= this.extendedHeaderSize + 10 ) {
					this.done = true;
					var left = (this.extendedHeaderSize + 10) - offset;
					this.headerBuffer += bytes.slice(0, left);

					MP3.ID3v2.parse(this.headerBuffer);


			for ( var name in this.metadata ) {
				if ( !( name in MP3.ID3v2.frames) ) {
				log("name", name);
					MP3.ID3v2.addFrame(name, this.metadata[name]);
				}
			}
					var ret = MP3.ID3v2.dump();

					this.o_stream.write(ret, ret.length);
					var left = bytes.slice(left);
					this.o_stream.write(left, left.length);

					this.writemeta = false;
					return;
				} else {
					this.headerBuffer += bytes;
				}

			}
			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.metadata = {
			TPE1: artist_name,
			TIT2: song_title,
			COMM: 'eng\x00' +window.location.href
		};

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

new function () {
	var nodes = find_by_xpath('//a[./img[starts-with(@src,"data:")]]');
try {
	MySpaceMP3Downloader.init();
	
	for ( var i = 0; i < nodes.length; i++ ) {
		( Components.classes["@mozilla.org/appshell/appShellService;1"]
			.getService(Components.interfaces.nsIAppShellService)
			.hiddenDOMWindow || window ).setTimeout( function (a) {
			try {
				var name = find_by_xpath('./ancestor::li//div[@class="name"]', a);
				var t = name[0].textContent.split(/ - /);
				var artist = t.shift().replace(/^\s*/, '').replace(/\s*$/, '');
				var songTitle = t.join(" - ").replace(/^\s*/, '').replace(/\s*$/, '')

				MySpaceMP3Downloader.add_to_dlq(a.href, artist, songTitle)
				return;
			} catch(e) {
				log(e);
			}
		}, i * 500, nodes[i]);
	} 
} catch(e) {
	log(e);
}
}



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

function find_by_xpath(xpath, context) {
	var context = context || window.document;
	var result;
	try {
		result = document.evaluate(xpath, context, null, XPathResult.ANY_TYPE, null);
	} catch (e) {
		log(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 log() {
	if ( Firebug && Firebug.Console && FirebugContext ) {
		Firebug.Console.logFormatted.call(Firebug.Console, arguments, FirebugContext, 'log');
	}
}

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

  }
}

