/**
 * web_servicecontroller.c: Delivers handshake, GetPlaylist, audioplayer->Next,...
 * 
 * Copyright: See COPYING file that comes with this distribution
 *
 * Author: Matthias Feistel <hitman_47@users.sourceforge.net>, (C) 2007
 */

#include <vdr/plugin.h> // Setup.OSD$Language
#include <sstream> //getline, stringstream
#include <map> //postdata
#include "web_serviceconnector.h"
#include "ext/xmlParser/xmlParser.h"  // in order to parse metadata
#include "utils.h"
#include "config.h"
#include "settings.h"
#include "md5.h"
#include "web_metadata.h"

#include "audio_playlist.h" // cAudioPlaylist

#include "audio_control.h" // cControl::Control()

#include <time.h>  // for Scrobbler

#include "web_networkcontroller.h"

#include <iostream> //cout

#include <queue> // FIFO list
#include <string>
#include <map>

#include "web_serviceconnector_scrobbler.h" // Submission()

/** 
 * 
 * Handshake
 *
 * @param: username
 * @param: password
 * 
 * @return: 0==noerror;
 */
int cWebServiceConnector::Handshake( const std::string username,
		const std::string password_md5 )
{
	std::string buffer_HttpGetResponse, stream_url, session, subscriber, str_tmp;
	std::stringstream url_stream, sstream_tmp;
	
	url_stream << "http://"<< AUDIOSCROBBLER_DOMAIN << ":"
			<< AUDIOSCROBBLER_PORT << AUDIOSCROBBLER_BASEPATH
			<< "/handshake.php?" << "version=" << AUDIOSCROBBLER_CLIENTVERSION
			<< "&" << "platform=linux"
			<< "&username="<< username <<"&passwordmd5="<< password_md5
			<< "&debug=0";

	cNetworkController NetworkController;

	if( !NetworkController.HttpGetRequest( buffer_HttpGetResponse, url_stream.str() ) )
	{
		return 2;
	}

	// Here, we parse the response for streamurl, session and subscriber state:
	sstream_tmp << buffer_HttpGetResponse;
	
	std::map<std::string,std::string>::iterator elHandshakedataMap;
	
	while( getline( sstream_tmp, str_tmp ) )
	{
		
		for( elHandshakedataMap = cSettings::GetInstance().HandshakedataMap.begin( ); elHandshakedataMap
				!= cSettings::GetInstance().HandshakedataMap.end( ); elHandshakedataMap++ )
		{
			std::stringstream sstream_tmp2;
			std::string str_tmp2;
			
			sstream_tmp2 << elHandshakedataMap->first<< "=";
			str_tmp2 = sstream_tmp2.str();

			if( str_tmp.find( str_tmp2 ) != std::string::npos )
			{
				cSettings::GetInstance().HandshakedataMap[ elHandshakedataMap->first ]
						= str_tmp.substr( elHandshakedataMap->first.length( ) + 1 ); // +1 means overriding " = "
			}
			
		}
		
	}
	
	// the following codeline clears stream_url and forces the use of playlists provided by last.fm,
	// so skipping, banning tracks and changing stations will be reliable:
	cSettings::GetInstance().HandshakedataMap[ "stream_url" ].clear( );

	if( cSettings::GetInstance().HandshakedataMap[ "session" ].empty( ) ||
			!cSettings::GetInstance().HandshakedataMap[ "session" ].compare("FAILED") )
		return 1;
	
	
	return 0;
}

/**
 * 
 * AudioPlayerSkip
 * does shutdown the control/player and restarts it, so it seems to skip
 * does submit tracks to Scrobbler::Submission()
 * 
 * @param: bool clearplaylist
 * @return: errorcode
 */
int cWebServiceConnector::AudioPlayerSkip( const bool clearplaylist )
{
	
	if(!cControl::Control())
		return 2;
	
	if( clearplaylist )
		cAudioPlaylist::GetInstance().Clear( );

	cAudioControl::GetInstance().Next();
	
	return 0;
}

/**
 * 
 * Changestation
 * 
 * @param lastfm_url
 * @return 0 on success; 1 on Network failure; 2 on: artist not found; 3 on: no subscription
 */
int cWebServiceConnector::Changestation( std::string lastfm_url )
{
	std::string buffer_HttpGetResponse, url, session, base_url, base_path,
			stream_url;
	std::stringstream url_stream;
	
	session = cSettings::GetInstance().HandshakedataMap[ "session" ];
	base_url = cSettings::GetInstance().HandshakedataMap[ "base_url" ];
	base_path = cSettings::GetInstance().HandshakedataMap[ "base_path" ];
	stream_url = cSettings::GetInstance().HandshakedataMap[ "stream_url" ];
	
	// while encoding, spaces will be replaced by a  " + "  and
	// lastfm-Server does not recognize it; but encoding seems not to be needed
	// at all.
	lastfm_url = cUtils::rfc1738_encode( lastfm_url ); 

	url_stream << "http://"<< base_url << ":"<< AUDIOSCROBBLER_PORT
			<< base_path << "/adjust.php?"<<"session="<< session
			<< "&url=lastfm://"<< lastfm_url << "&debug=0";
	
	cNetworkController NetworkController;
	
	if( !NetworkController.HttpGetRequest( buffer_HttpGetResponse, url_stream.str() ) )
	{
		return false;
	}
	
	
	// Pruefen, ob der Response Code OK ist
	/* Auszug aus offiziellem last.fm-Client:
	// (last.fm-1.3.1.0/src/libLastFmTools/WebService/ChangeStationRequest.cpp

	 case 1:
	 setFailed( ChangeStation_NotEnoughContent,
	 tr( "Sorry, there is not enough content to play this station. Please choose a different one." ) );
	 break;
	 
	 case 2:
	 setFailed( ChangeStation_TooFewGroupMembers,
	 tr( "This group does not have enough members for radio." ) );
	 break;

	 case 3:
	 setFailed( ChangeStation_TooFewFans,
	 tr( "This artist does not have enough fans for radio." ) );
	 break;                    

	 case 4:
	 setFailed( ChangeStation_Unavailable,
	 tr( "This item is not available for streaming." ) );
	 break;

	 case 5:
	 setFailed( ChangeStation_SubscribersOnly,
	 tr( "This station is available to subscribers only."
	 "<p>" "You can subscribe here: <a href='http://www.last.fm/subscribe/'>http://www.last.fm/subscribe/</a>" ) );
	 break;

	 case 6:
	 setFailed( ChangeStation_TooFewNeighbours,
	 tr( "There are not enough neighbours for this radio mode." ) );
	 break;

	 case 7:
	 setFailed( ChangeStation_StreamerOffline,
	 tr( "The streaming system is offline for maintenance, please try again later." ) );
	 break;

	 case 8:
	 setFailed( ChangeStation_InvalidSession,
	 tr( "The streaming system is offline for maintenance, please try again later." ) );
	 break;

	 default:
	 setFailed( ChangeStation_UnknownError,
	 tr( "Starting radio failed. Unknown error." ) );
	 break;
	 */

	//also  " ERROR - no such artist "  in Zeichenkette vorhanden/gefunden
	if( strstr( buffer_HttpGetResponse.c_str( ), "ERROR - no such artist" )!= 0 )
	{
		return 2;
	}
	
	if( strstr( buffer_HttpGetResponse.c_str( ), "error=5" ) != 0 )
	{
		return 3;
	}
	
	
	// Finally, we have to trigger Next(), so the Decoder skips the nowplaying title:
	AudioPlayerSkip( true );  // clear the playlist, too
	
	return 0;
}

/**
 * 
 * GetPlaylist
 * 
 * for now (2007-09-xx) we have two options:
 * 	1) loading the usual streamfile last.mp3
 * 	2) loading a playlist with up to five tracks
 * 	we prefer the streaming file and while not getting that file we fallback to playlist
 * 
 * @param: session
 * @return: 0==noerror; 2==network error; 3==error while parsing data
 */
int cWebServiceConnector::GetPlaylist( const std::string session_in )
{

	/* from bmpx project (lastfm.cc):
	 std::string uri ((boost::format ("http://%s/%s/xspf.php?sk=%s&discovery=%d&desktop=1.3.0.58")
	 % m_session.base_url
	 % m_session.base_path 
	 % m_session.session 
	 % int (mcs->key_get<bool>("lastfm","discoverymode"))).str());
	 */

	std::string buffer_HttpPostResponse, url, subscriber;
	std::stringstream url_stream, sstream_tmp;
	std::map<std::string,std::string> postdata;
	
	cNetworkController NetworkController;
	
	url_stream <<"http://"<< AUDIOSCROBBLER_DOMAIN << ":"
			<< AUDIOSCROBBLER_PORT << AUDIOSCROBBLER_BASEPATH << "/xspf.php"
			<< "?" << "sk="<< session_in
			<< "&" << "discovery-mode=" << "0"
			<< "&" << "desktop=" << "1.3.0.58";
	
	if( !NetworkController.HttpPostRequest( buffer_HttpPostResponse, url_stream.str(),
			postdata ) )
	{
		return 2;
	}

	// erhaltene XML-Daten durchforsten:

	XMLNode xMainNode, xNode;
	XMLResults xe;
	
	xMainNode
			= XMLNode::parseString( buffer_HttpPostResponse.c_str( ), "", &xe );
	
	if( xe.error )
		return 3;
	
	int n = xMainNode.getChildNode( "trackList" ).nChildNode( "track" );
	
	if( !n )
		return 3; // no tracklist found

	for( int i = 0; i < n; i++ )
	{
		std::string track_location, track_lastfm_trackauth, track_creator,
		track_title, track_duration_str, track_album;
		long track_id, track_duration_long;
		
		sstream_tmp.clear();
		sstream_tmp.str("");
		sstream_tmp << xMainNode.getChildNode( "trackList" ).getChildNode("track", i).getChildNode("id").getText( );
		sstream_tmp >> track_id; 
		
		track_location = xMainNode.getChildNode( "trackList" ).getChildNode("track", i).getChildNode("location").getText( );

		sstream_tmp.clear();
		sstream_tmp.str("");
		sstream_tmp << xMainNode.getChildNode( "trackList" ).getChildNode("track", i).getChildNode("duration").getText( );
		sstream_tmp >> track_duration_long;

		
		track_lastfm_trackauth = xMainNode.getChildNode( "trackList" ).getChildNode("track", i).getChildNode("lastfm:trackauth").getText( );

		track_creator = xMainNode.getChildNode( "trackList" ).getChildNode("track", i).getChildNode("creator").getText( );

		track_title = xMainNode.getChildNode( "trackList" ).getChildNode("track", i).getChildNode("title").getText( );
		
		// album can be empty:
		if( xMainNode.getChildNode( "trackList" ).getChildNode("track", i).getChildNode("album").nElement() )
			track_album = xMainNode.getChildNode( "trackList" ).getChildNode("track", i).getChildNode("album").getText( );
		
		////
		// copy the gained values to the public cAudioPlaylist
		std::map<std::string,std::string> trackdata;
		std::map<long,std::map<std::string,std::string> > track;
		
		
		trackdata[ "track_location" ] = track_location;
		
		sstream_tmp.clear();
		sstream_tmp.str("");		
		sstream_tmp << track_duration_long;//// / 1000;
		sstream_tmp >> track_duration_str;
		trackdata[ "track_duration" ] = track_duration_str;
		
		trackdata[ "track_lastfm_trackauth" ] = track_lastfm_trackauth;
		trackdata[ "track_creator" ] = track_creator;
		trackdata[ "track_title" ] = track_title;
		trackdata[ "track_album" ] = track_album;

		track[ track_id ] = trackdata;
		
		cAudioPlaylist::GetInstance().trackList.push(track);
	}
	
	return 0;
}

/**
 * 
 * Control $0 = (const char *) one of "love", "skip", "ban", "rtp" and "nortp"
 * 
 * @param cmd 
 * @return 0==OK  1==unknown CMD  2==network failure
 */
int cWebServiceConnector::Control( const char *cmd )
{
	std::string buffer_HttpGetResponse, url_in, session, base_url, base_path,
			stream_url;
	std::stringstream url_in_stream;
	
	session = cSettings::GetInstance().HandshakedataMap[ "session" ];
	base_url = cSettings::GetInstance().HandshakedataMap[ "base_url" ];
	base_path = cSettings::GetInstance().HandshakedataMap[ "base_path" ];
	stream_url = cSettings::GetInstance().HandshakedataMap[ "stream_url" ];
	
	if( !( !strcmp( cmd, "love" ) ||!strcmp( cmd, "skip" ) ||!strcmp( cmd,
			"ban" ) ) )
	{
		return 1;
	}
	
	
	// since audioscrobbler-protocol V1.2, because we have several files instead of a stream:
	if( !strcmp( cmd, "skip" ) )
	{
		if( stream_url.empty( ) )
		{
			AudioPlayerSkip();
		}

	}
	else if( !strcmp( cmd, "ban" ) )
	{
		if( stream_url.empty( ) )
		{
			AudioPlayerSkip();
		}
	}
	
	url_in_stream << "http://"<< base_url << ":"<< AUDIOSCROBBLER_PORT
			<< base_path << "/control.php?"<<"session="<< session <<"&command="
			<< cmd <<"&debug=1";
	url_in = url_in_stream.str();

	cNetworkController NetworkController;
	
	if( !NetworkController.HttpGetRequest( buffer_HttpGetResponse, url_in ) )
	{
		return 2;
	}
	
	return 0;
}





























/*******************************************************************************
 * 
 * Meta
 * 
 * @return cMetaData
 */
cMetaData cWebServiceConnector::Meta( void )
{
	std::string buffer_HttpGetResponse, url_in, session, base_url, base_path;
	std::stringstream url_in_stream;
	cMetaData meta;
	std::string s2, str2;
	std::stringstream sstream;
	std::map<std::string, std::string>::iterator elMetadataMap;
	
	session = cSettings::GetInstance().HandshakedataMap[ "session" ];
	base_url = cSettings::GetInstance().HandshakedataMap[ "base_url" ];
	base_path = cSettings::GetInstance().HandshakedataMap[ "base_path" ];
	
	meta.mp_MetadataMap[ "artist" ] = "";
	meta.mp_MetadataMap[ "track" ] = "";
	meta.mp_MetadataMap[ "album" ] = "";
	meta.mp_MetadataMap[ "station" ] = "";
	meta.mp_MetadataMap[ "albumcover_small" ] = "";
	meta.mp_MetadataMap[ "albumcover_medium" ] = "";
	meta.mp_MetadataMap[ "albumcover_large" ] = "";
	
	url_in_stream << "http://"<< base_url << ":"<< AUDIOSCROBBLER_PORT
			<< base_path << "/np.php?"<<"session="<< session <<"&debug=0";
	url_in = url_in_stream.str();
	
	cNetworkController NetworkController;
	
	if( !NetworkController.HttpGetRequest( buffer_HttpGetResponse, url_in ) )
	{
		meta.mp_MetadataMap[ "artist" ] = "no meta data available"; //return "no meta data available";

		return meta;
	}
	
	
	// Here, we parse the response for streamurl, session and subscriber state:
	sstream << buffer_HttpGetResponse;
	
	while( getline( sstream, s2 ) )
	{
		
		for( elMetadataMap = meta.mp_MetadataMap.begin( ); elMetadataMap
				!= meta.mp_MetadataMap.end( ); elMetadataMap++ )
		{
			std::stringstream sstream2;
			
			sstream2 << elMetadataMap->first<< "=";
			str2 = sstream2.str();
			
			if( s2.find( str2 ) != std::string::npos )
			{
				meta.mp_MetadataMap[ elMetadataMap->first ]
						= s2.substr( elMetadataMap->first.length( ) + 1 );
			}
			
		}
		
	}
	
	return meta;
}

/**
 * 
 * recentTracks
 * @param filter
 * @return cMetaData_recentTracks
 */
cMetaData_recentTracks cWebServiceConnector::recentTracks(
		std::string filterword )
{
	std::string buffer_HttpGetResponse, url_in, session, base_url, base_path,
			filter, username;
	std::stringstream url_in_stream;
	cMetaData_recentTracks meta;
	XMLNode xMainNode, xNode;
	
	session = cSettings::GetInstance().HandshakedataMap[ "session" ];
	base_url = cSettings::GetInstance().HandshakedataMap[ "base_url" ];
	base_path = cSettings::GetInstance().HandshakedataMap[ "base_path" ];
	username = cSettings::GetInstance().ParameterMap[ "Username" ];
	
	if( !filterword.compare( "loved" ) )
		filter = "recentlovedtracks";
	else if( !filterword.compare( "banned" ) )
		filter = "recentbannedtracks";
	else
		filter = "recenttracks";
	
	url_in_stream << "http://"<< base_url << ":"<< AUDIOSCROBBLER_PORT<<"/1.0/"
			<<"user/"<< username <<"/"<< filter << ".xml";
	url_in = url_in_stream.str();
	
	cNetworkController NetworkController;
	
	if( !NetworkController.HttpGetRequest( buffer_HttpGetResponse, url_in ) )
	{
		///      meta.m_rececentLovedTracks.push_back("No data available");
		///      return meta;
	}
	
	
	// erhaltene XML-Daten durchforsten:

	XMLResults xe;
	xMainNode = XMLNode::parseString( buffer_HttpGetResponse.c_str( ),
			filter.c_str( ), &xe );
	
	if( xe.error )
		return meta;
	
	int n = xMainNode.nChildNode( "track" );
	
	for( int i = 0; i < n; i++ )
	{
		meta.m_artist.push_back( xMainNode.getChildNode( "track",i ).getChildNode("artist").getText( ) );
		meta.m_name.push_back( xMainNode.getChildNode( "track",i ).getChildNode("name").getText( ) );
		meta.m_date.push_back( xMainNode.getChildNode( "track",i ).getChildNode("date").getText( ) );
	}
	
	return meta;
}

/**
 * 
 * relatedArtists
 * @param filter
 * @return cMetaData_relatedArtists
 */
cMetaData_relatedArtists cWebServiceConnector::relatedArtists( void )
{
	std::string buffer_HttpGetResponse, url, session, base_url, base_path;
	std::stringstream url_stream;
	cMetaData_relatedArtists meta;
	cMetaData meta2;
	XMLNode xMainNode, xNode;
	XMLResults xe;
	
	base_url = cSettings::GetInstance().HandshakedataMap[ "base_url" ];
	
	
	///aktuellen Artist mittels Meta-Fkt. ermitteln:
	meta2 = Meta( );
	std::string artistname(meta2.mp_MetadataMap[ "artist" ]);
	
	if( artistname.empty( ) ) // nothing found, so do not go further
		return meta;
	
	url_stream << "http://"<< base_url << ":"<< AUDIOSCROBBLER_PORT<<"/1.0/"
			<<"artist/"<< cUtils::rfc1738_encode( artistname ) <<"/"
			<< "similar"<< ".xml";
	url = url_stream.str();

	
	cNetworkController NetworkController;
	
	if( !NetworkController.HttpGetRequest( buffer_HttpGetResponse, url ) )
	{
		///      meta.m_rececentLovedTracks.push_back("No data available");
		///      return meta;
		std::cout << "NETERROR"<< std::endl;
	}
	
	
	// erhaltene XML-Daten durchforsten:

	xMainNode = XMLNode::parseString( buffer_HttpGetResponse.c_str( ),
			"similarartists", &xe );
	
	if( xe.error )
		return meta;
	
	int n = xMainNode.nChildNode( "artist" );
	
	if( n > 60 ) // limit the output
		n = 60;
	
	for( int i = 0; i < n; i++ )
	{
		meta.m_name.push_back( xMainNode.getChildNode( "artist",i ).getChildNode("name").getText( ) );
		meta.m_match.push_back( xMainNode.getChildNode( "artist",i ).getChildNode("match").getText( ) );
		meta.m_streamable.push_back( xMainNode.getChildNode( "artist",i ).getChildNode("streamable").getText( ) );
	}
	
	return meta;
}

/**
 * XmlRpcCall; POSTrequest to audioscrobbler dataserver
 * @param	&output returns received xmldata
 * @param	method	xml-Method (artistmetadata)
 * @param	tag		xml-Searchtag
 * @return	errorcode
 */
int cWebServiceConnector::XmlRpcCall( std::string &output,
		const std::string method, const std::string tag )
{
	std::string buffer_HttpPostXmlResponse, stream_url, url, subscriber, str_tmp;
	std::stringstream url_stream, sstream_tmp;
	std::map<std::string,std::string> postdata;
	XMLNode xMainNode, xNode_methodName, xNode_params, xNode_param,
			xNode_value, xNode_string;
	cNetworkController NetworkController;
	
	
	// Language selection for wiki (english by default):
	std::string lang = "en", OSDLanguage_str;
	
		
	// since VDR version 1.5.6 OSDLanguage changed to a const char* :		
	sstream_tmp.clear();
	sstream_tmp.str("");
	sstream_tmp << Setup.OSDLanguage;
	sstream_tmp >> OSDLanguage_str;

	
	if( !OSDLanguage_str.compare( "1" )||!OSDLanguage_str.compare( "de_DE" ) )
		lang = "de";
	
	////	std::cout << "language: " << lang << std::endl;

	
	//???AUDIOSCROBBLER_POST_DOMAIN???
	url_stream <<"http://"<< AUDIOSCROBBLER_DOMAIN << ":"<< AUDIOSCROBBLER_PORT
			<< "/1.0/rw/xmlrpc.php";
	url = url_stream.str();
	
	
	// prepare xml data:
	xMainNode = XMLNode::createXMLTopNode( "methodCall" );
	
	xNode_methodName = xMainNode.addChild( "methodName" );
	xNode_methodName.addText( method.c_str( ) );
	
	xNode_params = xMainNode.addChild( "params" );
	
	xNode_param = xNode_params.addChild( "param" );
	xNode_value = xNode_param.addChild( "value" );
	xNode_string = xNode_value.addChild( "string" );
	xNode_string.addText( tag.c_str( ) ); // searchword

	xNode_param = xNode_params.addChild( "param" );
	xNode_value = xNode_param.addChild( "value" );
	xNode_string = xNode_value.addChild( "string" );
	xNode_string.addText( lang.c_str( ) ); // language
	

	std::string xmldata;
	xmldata = xMainNode.createXMLString( false ); // false means no formatted output
	

	if( !NetworkController.HttpPostXmlRequest( buffer_HttpPostXmlResponse, url,
			xmldata ) )
	{
		return 2;
	}
	
	
	//////////////////////////////////////////////
	// Fill received data into Metadata-container:
	XMLNode xMainNode_response, xNode_response_params, xNode_response_param,
			xNode_response_value, xNode_response_struct, xNode_response_member,
			xNode_response_member_wikiText, xNode_response_member_wikiPageUrl;
	XMLResults xe;
	cMetaData meta;
	

	// erhaltene XML-Daten durchforsten:

	xMainNode_response = XMLNode::parseString(
			buffer_HttpPostXmlResponse.c_str( ), "methodResponse", &xe );
	xNode_response_params = xMainNode_response.getChildNode( "params" );
	xNode_response_param = xNode_response_params.getChildNode( "param" );
	xNode_response_value = xNode_response_param.getChildNode( "value" );
	xNode_response_struct = xNode_response_value.getChildNode( "struct" );
	xNode_response_member = xNode_response_struct.getChildNode( "member" );
	

	////	xNode_response_member_wikiText = xNode_response_struct.getChildNodeWithAttribute( "member", "name", "similar" ).getParentNode().getChildNode( "value" ).getChildNode( "string" );
	xNode_response_member_wikiText = xNode_response_struct.getChildNode("member",3).getChildNode( "value" ).getChildNode( "string" );
	////	std::cout << "OUT: " << xNode_response_member_wikiText.getText() << std::endl;
	xNode_response_member_wikiPageUrl = xNode_response_struct.getChildNode("member",5).getChildNode( "value" ).getChildNode( "string" );
	
	if( xe.error )
		return 3;
	
	
	std::string str2;
	if( xNode_response_member_wikiText.getText( ) )
		str2 = xNode_response_member_wikiText.getText( );
	else if( xNode_response_member_wikiPageUrl.getText( ) ) // so we have no wiki entry here, but a wikiPageUrl is available:
	{
		str_tmp = xNode_response_member_wikiPageUrl.getText( );
		
		sstream_tmp.clear();
		sstream_tmp.str("");
		sstream_tmp << tr("There is no wiki entry yet, please add it: ")
				<<"<"<< str_tmp << ">";
		
		str2 = sstream_tmp.str();
	}
	
	output = str2;
	
	return 0;
}


