#include <noatun/playlistsaver.h>
#include <tqdom.h>
#include <tdeio/netaccess.h>
#include <tqfile.h>
#include <tqtextstream.h>
#include <noatun/app.h>
#include "ksaver.h"
#include <tdesimpleconfig.h>
#include <kmimetype.h>
#include <tdelocale.h>
#include <tqregexp.h>
#include <tqxml.h>
#include <kdebug.h>

PlaylistSaver::PlaylistSaver()
{
}

PlaylistSaver::~PlaylistSaver()
{
}

bool PlaylistSaver::save(const KURL &file, int opt)
{
//	kdDebug(66666) << k_funcinfo << "opt=" << opt << endl;

	if(file.isEmpty() || !file.isValid())
		return false;

	switch (opt)
	{
		default:
		case 0:
		case XMLPlaylist:
			return saveXML(file, opt);

		case M3U:
		case EXTM3U:
			return saveM3U(file, opt);

		case PLS:
			return savePLS(file, opt);

		case ASX:
			return false; // No, I won't code that! [mETz]
	}
}

bool PlaylistSaver::load(const KURL &file, int opt)
{
//	kdDebug(66666) << k_funcinfo << "opt=" << opt << endl;

	switch (opt)
	{
		default:
		case 0:
		case XMLPlaylist:
		case ASX:
			return loadXML(file, opt);

		case M3U:
		case EXTM3U:
			return loadM3U(file, opt);

		case PLS:
			return loadPLS(file, opt);
	}
}

bool PlaylistSaver::metalist(const KURL &url)
{
	kdDebug(66666) << k_funcinfo << "url=" << url.url() << endl;

	TQString end=url.filename().right(3).lower();
	/*
	if (end=="mp3" || end=="ogg") // we want to download streams only
	{
		kdDebug(66666) << k_funcinfo << "I can only load playlists" << endl;
		return false;
	}
	*/

/*
.wax	audio/x-ms-wax		Metafiles that reference Windows Media files with the
								.asf, .wma or .wax file extensions.

.wvx	video/x-ms-wvx		Metafiles that reference Windows Media files with the
								.wma, .wmv, .wvx or .wax file extensions.

.asx	video/x-ms-asf		Metafiles that reference Windows Media files with the
								.wma, .wax, .wmv, .wvx, .asf, or .asx file extensions.
*/

	// it's actually a stream!
	if (end!="pls" &&
		end!="m3u" &&
		end!="wax" && // windows mediaplayer metafile
		end!="wvx" && // windows mediaplayer metafile
		end!="asx" && // windows mediaplayer metafile
		url.protocol().lower()=="http")
	{
		KMimeType::Ptr mimetype = KMimeType::findByURL(url);
		TQString type=mimetype->name();

		if (type!="application/octet-stream")
			return false;

		TQMap<TQString,TQString> map;
		map["playObject"]="Arts::StreamPlayObject";
		map["title"] = i18n("Stream from %1").arg(url.host());

		KURL u(url);
		if (!u.hasPath())
			u.setPath("/");

		map["stream_"] = map["url"] = u.url();

		reset();
		readItem(map);
		return true;
	}

	// it is a pls, m3u or ms-media-player file by now
	if(loadXML(url, XMLPlaylist))
		return true;

	if(loadXML(url,ASX))
		return true;

	if(loadPLS(url))
		return true;

	if(loadM3U(url))
		return true;

	return false;
}

bool PlaylistSaver::saveXML(const KURL &file, int )
{
	TQString localFile;
	if (file.isLocalFile())
		localFile = TQFile::encodeName(file.path());
	else
		localFile = napp->tempSaveName(file.path());

	// TQDom is a pain :)
	TQDomDocument doc("playlist");
	doc.setContent(TQString("<!DOCTYPE XMLPlaylist><playlist version=\"1.0\" client=\"noatun\"/>"));

	TQDomElement docElem=doc.documentElement();

	reset();
	PlaylistItem i;
	TQStringList props;
	while ((i=writeItem()))
	{
		// write all properties
		props=i.properties();
		TQDomElement elem=doc.createElement("item");
		for (TQStringList::Iterator pi(props.begin()); pi!=props.end(); ++pi)
		{
			TQString val=i.property(*pi);
			elem.setAttribute(*pi, val);

			if ((*pi)=="url")
			{
				KURL u(val);
				if (u.isLocalFile())
				{
					elem.setAttribute("local", u.path());
				}
			}
		}

		docElem.appendChild(elem);
		props.clear();
	}
	Noatun::KSaver saver(localFile);
	if (!saver.open())
		return false;
	saver.textStream().setEncoding(TQTextStream::UnicodeUTF8);
	saver.textStream() << doc.toString();
	saver.close();

	return true;
}

class NoatunXMLStructure : public TQXmlDefaultHandler
{
public:
	PlaylistSaver *saver;
	bool fresh;

	NoatunXMLStructure(PlaylistSaver *s)
		: saver(s), fresh(true)
	{
	}

	bool startElement(
			const TQString&, const TQString &,
			const TQString &name, const TQXmlAttributes &a
					 )
	{
		if (fresh)
		{
			if (name=="playlist")
			{
				fresh=false;
				return true;
			}
			else
			{
				return false;
			}
		}

		if (name != "item")
			return true;

		TQMap<TQString,TQString> propMap;

		for (int i=0; i<a.count(); i++)
		{
			propMap[a.qName(i)] = a.value(i);
		}
		saver->readItem(propMap);

		return true;
	}
};


class MSASXStructure : public TQXmlDefaultHandler
{
public:
	PlaylistSaver *saver;
	bool fresh;
	bool inEntry, inTitle;
	TQMap<TQString,TQString> propMap;
	TQString mAbsPath;

	MSASXStructure(PlaylistSaver *s, const TQString &absPath)
		: saver(s), fresh(true), inEntry(false),
		inTitle(false), mAbsPath(absPath)
	{
		//kdDebug(66666) << k_funcinfo << endl;
	}

	bool startElement(const TQString&, const TQString &,
		const TQString &name, const TQXmlAttributes &a)
	{
		if (fresh)
		{
			if (name.lower()=="asx")
			{
 				//kdDebug(66666) << "found ASX format" << endl;
				fresh=false;
				return true;
			}
			else
			{
				kdDebug(66666) << "This is NOT an ASX style playlist!" << endl;
				return false;
			}
		}

		if (name.lower()=="entry")
		{
			if(inEntry) // WHOOPS, we are already in an entry, this should NEVER happen
			{
				kdDebug(66666) << "STOP, ENTRY INSIDE ENTRY!" << endl;
				return false;
			}
//			kdDebug(66666) << "<ENTRY> =====================" << endl;
			inEntry=true;
		}
		else
		{
			if (inEntry) // inside entry block
			{
				// known stuff inside an <entry> ... </entry> block:
				// <title>blah</title>
				// <param album="blub" />
				// <param artist="blah" />
				// <ref HREF="file:/something" />
				if(name.lower()=="ref")
				{
					for (int i=0; i<a.count(); i++)
					{
						if(a.qName(i).lower()=="href")
						{
							TQString filename=a.value(i);
							if (filename.find(TQRegExp("^[a-zA-Z0-9]+:/"))==0)
							{
								KURL url(filename);
								KMimeType::Ptr mimetype = KMimeType::findByURL(url);
								TQString type=mimetype->name();
								if (type != "application/octet-stream")
								{
									propMap["url"]=filename;
								}
								else
								{
									propMap["playObject"]="SplayPlayObject";
									propMap["title"] = i18n("Stream from %1").arg(url.host());
									if (!url.hasPath())
										url.setPath("/");
									propMap["url"] = url.url();
									propMap["stream_"]=propMap["url"];
//									readItem(propMap);
//									continue;
								}
							}
							else
							{
								KURL u1;
								// we have to deal with a relative path
								if (filename.find('/'))
								{
									u1.setPath(mAbsPath); //FIXME: how to get the path in this place?
									u1.setFileName(filename);
								}
								else
								{
									u1.setPath(filename);
								}
								propMap["url"]=u1.url();
							}
//							kdDebug(66666) << "adding property url, value='" << propMap["url"] << "'" << endl;
						}
					}
				}
				else if(name.lower()=="param")
				{
					TQString keyName="", keyValue="";

					for (int i=0; i<a.count(); i++)
					{
						if(a.value(i).lower()=="album")
							keyName="album";
						else if(a.value(i).lower()=="artist")
							keyName="author";
						else if(!keyName.isEmpty()) // successfully found a key, the next key=value pair has to be the value
						{
//							kdDebug(66666) << "keyName=" << keyName << ", next value is '" << a.value(i) << "'" << endl;
							keyValue=a.value(i);
						}
					}

					if (!keyName.isEmpty() && !keyValue.isEmpty())
					{
//						kdDebug(66666) << "adding property; key='" << keyName << "', value='" << keyValue << "'" << endl;
						propMap[keyName]=keyValue;
					}
				}
				else if(name.lower()=="title")
				{
					if(inTitle) // WHOOPS, we are already in an entry, this should NEVER happen
					{
						kdDebug(66666) << "STOP, TITLE INSIDE TITLE!" << endl;
						return false;
					}
//					kdDebug(66666) << "<TITLE> ======" << endl;
					inTitle=true;
				}
/*				else
				{
					kdDebug(66666) << "Unknown/unused element inside ENTRY block, NAME='" << name << "'" << endl;
					for (int i=0; i<a.count(); i++)
						kdDebug(66666) << " | " << a.qName(i) << " = '" << a.value(i) << "'" << endl;
				}*/
			}
/*			else
			{
				kdDebug(66666) << "Uninteresting element, NAME='" << name << "'" << endl;
				for (int i=0; i<a.count(); i++)
					kdDebug(66666) << "  | " << a.qName(i) << " = '" << a.value(i) << "'" << endl;
			}*/
		}

		return true;
	}

	bool endElement(const TQString&,const TQString&, const TQString& name)
	{
//		kdDebug(66666) << k_funcinfo << "name='" << name << "'" << endl;
		if (name.lower()=="entry")
		{
			if(inEntry)
			{
/*				kdDebug(66666) << "</ENTRY> =====================" << endl;
				for (TQMap<TQString,TQString>::ConstIterator it=propMap.begin(); it!=propMap.end(); ++it )
					kdDebug(66666) << "key='" << it.key() << "', val='" << it.data() << "'" << endl;
*/
				saver->readItem(propMap);
				propMap.clear();
				inEntry=false;
			}
			else // found </entry> without a start
			{
				kdDebug(66666) << "STOP, </ENTRY> without a start" << endl;
				return false;
			}
		}
		else if (name.lower()=="title")
		{
			if(inTitle && inEntry)
			{
//				kdDebug(66666) << "</TITLE> ======" << endl;
				inTitle=false;
			}
			else if (inTitle) // found </title> without a start or not inside an <entry> ... </entry>
			{
				kdDebug(66666) << "STOP, </TITLE> without a start" << endl;
				return false;
			}
		}
		return true;
	}

	bool characters(const TQString &ch)
	{
		if(inTitle)
		{
			if (!ch.isEmpty())
			{
				propMap["title"]=ch;
//				kdDebug(66666) << "adding property; key='title', value='" << ch << "'" << endl;
			}
		}
		return true;
	}
};


bool PlaylistSaver::loadXML(const KURL &url, int opt)
{
	kdDebug(66666) << k_funcinfo <<
		"file='" << url.url() << "', opt=" << opt << endl;

	TQString dest;
	if(TDEIO::NetAccess::download(url, dest, 0L))
	{
		TQFile file(dest);
		if (!file.open(IO_ReadOnly))
			return false;

		reset();

		// TQXml is horribly documented
		TQXmlInputSource source(&file);
		TQXmlSimpleReader reader;

		if (opt == ASX ||
			url.path().right(4).lower()==".wax" ||
			url.path().right(4).lower()==".asx" ||
			url.path().right(4).lower()==".wvx")
		{
			MSASXStructure ASXparser(this, url.path(0));
			reader.setContentHandler(&ASXparser);
			reader.parse(source);
			return !ASXparser.fresh;
		}
		else
		{
			NoatunXMLStructure parser(this);
			reader.setContentHandler(&parser);
			reader.parse(source);
			return !parser.fresh;
		}
	} // END download()

	return false;
}

bool PlaylistSaver::loadM3U(const KURL &file, int /*opt*/)
{
	kdDebug(66666) << k_funcinfo << "file='" << file.path() << endl;

	TQString localFile;
	if(!TDEIO::NetAccess::download(file, localFile, 0L))
		return false;

	// if it's a PLS, transfer control, again (TDEIO bug?)
#if 0
	{
		TDESimpleConfig list(local, true);
		list.setGroup("playlist");

		// some stupid Windows lusers like to be case insensitive
		TQStringList groups=list.groupList().grep(TQRegExp("^playlist$", false));
		if (groups.count())
		{
			KURL l;
			l.setPath(local);
			return loadPLS(l);
		}
	}
#endif

	TQFile saver(localFile);
	saver.open(IO_ReadOnly);
	TQTextStream t(&saver);

	bool isExt = false; // flag telling if we load an EXTM3U file
	TQString filename;
	TQString extinf;
	TQMap<TQString,TQString> prop;
	reset();

	while (!t.eof())
	{
		if (isExt)
		{
			extinf = t.readLine();
			if (!extinf.startsWith("#EXTINF:"))
			{
//				kdDebug(66666) << "EXTM3U extinf line != extinf, assuming it's a filename." << endl;
				filename = extinf;
				extinf="";
			}
			else
			{
				filename = t.readLine(); // read in second line containing the filename
			}
			//kdDebug(66666) << "EXTM3U filename = '" << filename << "'" << endl;
		}
		else // old style m3u
		{
			filename = t.readLine();
		}

		if (filename == "#EXTM3U") // on first line
		{
//			kdDebug(66666) << "FOUND '#EXTM3U' @ " << saver.at() << "." << endl;
			isExt=true;
			continue; // skip parsing the first (i.e. this) line
		}

		if (filename.isEmpty())
			continue;

		if (filename.find(TQRegExp("^[a-zA-Z0-9]+:/"))==0)
		{
			//kdDebug(66666) << k_funcinfo << "url filename = " << filename << endl;

			KURL protourl(filename);
			KMimeType::Ptr mimetype = KMimeType::findByURL(protourl);

			if (mimetype->name() != "application/octet-stream")
			{
				prop["url"] = filename;
			}
			else
			{
				prop["playObject"]="SplayPlayObject";
				// Default title, might be overwritten by #EXTINF later
				prop["title"] = i18n("Stream from %1").arg(protourl.host());

				if (!protourl.hasPath())
					protourl.setPath("/");

				prop["url"] = protourl.url();
				prop["stream_"] = prop["url"];
			}
		}
		else // filename that is not of URL style (i.e. NOT "proto:/path/somefile")
		{
			KURL u1;
			// we have to deal with a relative path
			if (filename.find('/'))
			{
				u1.setPath(file.path(0));
				u1.setFileName(filename);
			}
			else
			{
				u1.setPath(filename);
			}
			prop["url"] = u1.url();
		}

		// parse line of the following format:
		//#EXTINF:length,displayed_title
		if (isExt)
		{
			extinf.remove(0,8); // remove "#EXTINF:"
			//kdDebug(66666) << "EXTM3U extinf = '" << extinf << "'" << endl;
			int timeTitleSep = extinf.find(',', 0);

			int length = (extinf.left(timeTitleSep)).toInt();
			if (length>0)
				prop["length"]=TQString::number(length*1000);

			TQString displayTitle=extinf.mid(timeTitleSep+1);
			if (!displayTitle.isEmpty())
			{
				int artistTitleSep = displayTitle.find(" - ",0);
				if (artistTitleSep == -1) // no "artist - title" like format, just set it as title
				{
					prop["title"] = displayTitle;
				}
				else
				{
					prop["author"] = displayTitle.left(artistTitleSep);
					prop["title"] = displayTitle.mid(artistTitleSep+3);
				}
				/*kdDebug(66666) << "EXTM3U author/artist='" << prop["author"] <<
					"', title='" << prop["title"] << "'" << endl;*/
			} // END !displayTitle.isEmpty()
		} // END if(isExt)

//		kdDebug(66666) << k_funcinfo << "adding file '" << prop["url"] << "' to playlist" << endl;
		readItem(prop);
		prop.clear();
	} // END while()

	TDEIO::NetAccess::removeTempFile(localFile);

//	kdDebug(66666) << k_funcinfo << "END" << endl;
	return true;
}

bool PlaylistSaver::saveM3U(const KURL &file, int opt)
{
//	kdDebug(66666) << k_funcinfo << "file='" << file.path() << "', opt=" << opt << endl;

	bool isExt=(opt==EXTM3U); // easier ;)

	TQString local(napp->tempSaveName(file.path()));
	TQFile saver(local);
	saver.open(IO_ReadWrite | IO_Truncate);
	TQTextStream t(&saver);

	reset();
	PlaylistItem i;
	TQStringList props;

	// this is more code but otoh faster than checking for isExt inside the loop
	if(isExt)
	{
		t << "#EXTM3U" << '\n';

		while ((i=writeItem()))
		{
			int length = static_cast<int>(((i.property("length")).toInt())/1000);
			if(length==0) length=-1; // special value in an EXTM3U file, means "unknown"
			KURL u(i.property("url"));
			TQString title;

			// if a playlistitem is without a tag or ONLY title is set
			if((i.property("author").isEmpty() && i.property("title").isEmpty())
			|| (i.property("author").isEmpty() && !i.property("title").isEmpty()) )
				title = u.filename().left(u.filename().length()-4);
			else
				title = i.property("author") + " - " + i.property("title");

//			kdDebug(66666) << "#EXTINF:"<< TQString::number(length) << "," << title << endl;
			t << "#EXTINF:"<< TQString::number(length) << "," << title << '\n';

			if (u.isLocalFile())
				t << u.path() << '\n';
			else
				t << u.url() << '\n';
		}
	}
	else
	{
		while ((i=writeItem()))
		{
			KURL u(i.property("url"));
			if (u.isLocalFile())
				t << u.path() << '\n';
			else
				t << u.url() << '\n';
		}
	}

	saver.close();
	TDEIO::NetAccess::upload(local, file, 0L);
	saver.remove();
	return true;
}

static TQString findNoCase(const TQMap<TQString,TQString> &map, const TQString &key)
{
	for (TQMap<TQString,TQString>::ConstIterator i=map.begin(); i!=map.end(); ++i)
	{
		if (i.key().lower() == key.lower())
			return i.data();
	}
	return 0;
}

bool PlaylistSaver::loadPLS(const KURL &file, int /*opt*/)
{
	kdDebug(66666) << k_funcinfo << "file='" << file.path() << endl;

	TQString localFile;
	if(!TDEIO::NetAccess::download(file, localFile, 0L))
		return false;

	TQFile checkFile(localFile);
	checkFile.open(IO_ReadOnly);
	TQTextStream t(&checkFile);
	TQString firstLine = t.readLine();
	if(firstLine.lower() != "[playlist]")
	{
		kdDebug(66666) << k_funcinfo << "PLS didn't start with '[playlist]', aborting" << endl;
		return false;
	}


	TDESimpleConfig list(localFile, true);
	//list.setGroup("playlist");

	// some stupid Windows lusers like to be case insensitive
	TQStringList groups = list.groupList().grep(TQRegExp("^playlist$", false));
	/*
	if (!groups.count()) // didn't find "[playlist]", it's not a .pls file
		return false;
	*/

	TQMap<TQString,TQString> group = list.entryMap(groups[0]);

	TQString numOfEntries = findNoCase(group, "numberofentries");
	if(numOfEntries.isEmpty())
		return false;

	reset();

	unsigned int nEntries = numOfEntries.toInt();
	for(unsigned int entry = 1; entry <= nEntries; ++entry )
	{
		TQString str;
		str.sprintf("file%d", entry);
		TQString cast = findNoCase(group, str.utf8());
		str.sprintf("title%d", entry);
		TQString title = findNoCase(group, str.utf8());

		// assume that everything in a pls is a streamable file
		TQMap<TQString,TQString> map;

		KURL url(cast);
		if (!url.hasPath())
			url.setPath("/");

		map["playObject"]="SplayPlayObject";
		if (title.isEmpty())
			map["title"] = i18n("Stream from %1 (port: %2)").arg( url.host() ).arg( url.port() );
		else
			map["title"] = i18n("Stream from %1, (ip: %2, port: %3)").arg( title ).arg( url.host() ).arg(url.port() );

		map["url"] = map["stream_"]= url.url();

		readItem(map);
	}
	return true;
}

bool PlaylistSaver::savePLS(const KURL &, int)
{
	return false;
}

void PlaylistSaver::setGroup(const TQString &)
{
}


