/*******************************************************************************
*                         Goggles Music Manager                                *
********************************************************************************
*           Copyright (C) 2007-2010 by Sander Jansen. All Rights Reserved      *
*                               ---                                            *
* This program is free software: you can redistribute it and/or modify         *
* it under the terms of the GNU General Public License as published by         *
* the Free Software Foundation, either version 3 of the License, or            *
* (at your option) any later version.                                          *
*                                                                              *
* This program is distributed in the hope that it will be useful,              *
* but WITHOUT ANY WARRANTY; without even the implied warranty of               *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                *
* GNU General Public License for more details.                                 *
*                                                                              *
* You should have received a copy of the GNU General Public License            *
* along with this program.  If not, see http://www.gnu.org/licenses.           *
********************************************************************************/
#include "gmdefs.h"
#include "GMAudioScrobbler.h"

#ifdef HAVE_GCRYPT
#include "gcrypt.h"
#else
#include "md5.h"
#endif


/* Needed for sockets */
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>


/******************************************************************************
 *
 * D E F I N E S
 *
 ******************************************************************************/

#define MAX_HANDSHAKE_TIMEOUT 7200
#define MIN_HANDSHAKE_TIMEOUT 60
#define DISABLE_HANDSHAKE_TIMEOUT 0

#define CLIENT_ID "gmm"
#define CLIENT_VERSION "0.1"

#define SCROBBLER_CACHE_FILE PATHSEPSTRING ".goggles" PATHSEPSTRING "scrobbler.cache"

#define LASTFM_URL "http://post.audioscrobbler.com:80"


/******************************************************************************
 *
 * H E L P E R  F U N C T I O N S
 *
 ******************************************************************************/

static void checksum(FXString & io){
  if (io.empty()) return;
#ifdef HAVE_GCRYPT
  FXuchar digest[16];
  gcry_md_hash_buffer(GCRY_MD_MD5,(void*)digest,(const void*)io.text(),io.length());
#else
  md5_state_t pms;
  md5_byte_t digest[16];
  md5_init(&pms);
  md5_append(&pms,(const md5_byte_t*)io.text(),io.length());
  md5_finish(&pms,digest);
#endif

  io.length(32);
#if FOXVERSION < FXVERSION(1,7,0)
  for (FXint i=0,d=0;i<32;i+=2,d++) {
    io[i]=FXString::hex[(digest[d]/16)%16];
    io[i+1]=FXString::hex[digest[d]%16];
    }
#else
  for (FXint i=0,d=0;i<32;i+=2,d++) {
    io[i]=Ascii::toLower(FXString::value2Digit[(digest[d]/16)%16]);
    io[i+1]=Ascii::toLower(FXString::value2Digit[digest[d]%16]);
    }
#endif
  }

static FXbool parseurl(const FXString & url,GMHost & host){
  FXTRACE((70,"parseurl: %s\n",url.text()));
  host.name=GMURL::host(url);
  host.port=GMURL::port(url);
  host.path=GMURL::path(url);
  FXTRACE((70,"hostname=%s\nport=%d\npath=%s\n",host.name.text(),host.port,host.path.text()));
  if (host.name.empty()) return false;
  if (host.port==0) host.port=80;
  return true;
  }


#define URL_UNSAFE   "#$-_.+!*'><()\\,%\""          // Always Encode
#define URL_RESERVED ";/?:@=&"              // Only encode if not used as reserved by scheme


static FXint tryconnect(struct addrinfo * entry) {
  FXTRACE((60,"GMAudioScrobbler::tryconnect\n"));

  FXint server = socket(entry->ai_family,entry->ai_socktype,entry->ai_protocol);
  if (server==-1) return -1;

  FXint result = connect(server,entry->ai_addr,entry->ai_addrlen);
  if (result!=-1)
      return server;

  ::close(server);
  return -1;
  }

/**********************************************************************************************************/


void GMAudioScrobblerTrack::save(FXStream & store) const {
  store << artist;
  store << album;
  store << title;
  store << duration;
  store << no;
  store << timestamp;
  }

void GMAudioScrobblerTrack::load(FXStream & store) {
  store >> artist;
  store >> album;
  store >> title;
  store >> duration;
  store >> no;
  store >> timestamp;
  }




/******************************************************************************
 *
 * C O N S T R U C T O R
 *
 ******************************************************************************/


GMAudioScrobbler::GMAudioScrobbler(FXObject* tgt,FXSelector msg) :
  flags(FLAG_NONE),
  feedback(FXApp::instance()),
  target(tgt),
  message(msg),
  timeout(DISABLE_HANDSHAKE_TIMEOUT),
  server(-1),
  nsubmitted(0),
  nfailed(0) {

  username=FXApp::instance()->reg().readStringEntry("LastFM","username",NULL);
  password=FXApp::instance()->reg().readStringEntry("LastFM","password",NULL);
  parseurl(FXApp::instance()->reg().readStringEntry("LastFM","handshake-url",LASTFM_URL),host_handshake);

  /// Check if we're banned
  if (compare(FXApp::instance()->reg().readStringEntry("LastFM","client-id",CLIENT_ID),CLIENT_ID)==0 &&
      compare(FXApp::instance()->reg().readStringEntry("LastFM","client-version",CLIENT_VERSION),CLIENT_VERSION)==0){
    if (FXApp::instance()->reg().readBoolEntry("LastFM","client-banned",false))
      flags|=FLAG_BANNED;
    }
  else { /// using different client, reset banned flag
    FXApp::instance()->reg().writeBoolEntry("LastFM","client-banned",false);
    }

  /// Check if we should scrobble tracks or not.
  if (!FXApp::instance()->reg().readBoolEntry("LastFM","scrobble-tracks",true))
    flags|=FLAG_DISABLED;

  FXApp::instance()->reg().writeStringEntry("LastFM","client-id",CLIENT_ID);
  FXApp::instance()->reg().writeStringEntry("LastFM","client-version",CLIENT_VERSION);
  load_queue();
  }

GMAudioScrobbler::~GMAudioScrobbler(){
  for (FXint e=dnscache.first();e<dnscache.size();e=dnscache.next(e)){
    freeaddrinfo((struct addrinfo*)dnscache.data(e));
    }
  save_queue();
  }


/******************************************************************************
 *
 *  P U B L I C  A P I
 *
 ******************************************************************************/


void GMAudioScrobbler::login(const FXString & user,const FXString & pass) {
  FXTRACE((60,"GMAudioScrobbler::login\n"));
  FXMutexLock lock(mutex_data);
  flags&=~FLAG_DISABLED;
  if (can_login()) {
    FXString newpass=pass;
    checksum(newpass);
    if (user!=username || newpass!=password) {
      username=user;
      password=newpass;
      FXApp::instance()->reg().writeStringEntry("LastFM","username",username.text());
      FXApp::instance()->reg().writeStringEntry("LastFM","password",password.text());

      flags|=FLAG_LOGIN_CHANGED;
      flags&=~FLAG_BADAUTH;

      if (username.empty() || password.empty())
        flags|=FLAG_SHUTDOWN;

      runTask();
      }
    }
  }

void GMAudioScrobbler::nowplaying(GMTrack & info){
  FXMutexLock lock(mutex_data);
  if (can_submit()) {
    nowplayingtrack=GMAudioScrobblerTrack(1,info);
    runTask();
    }
  }

void GMAudioScrobbler::nudge(){
  FXTRACE((60,"GMAudioScrobbler::nudge\n"));
  FXMutexLock lock(mutex_data);
  if (running() && mutex_task.trylock()) {
    flags|=FLAG_NETWORK;
    condition_task.signal();
    mutex_task.unlock();
    }
  }

void GMAudioScrobbler::submit(FXlong timestamp,GMTrack & info){
  FXMutexLock lock(mutex_data);
  if (can_submit() && info.time>30) {
    submitqueue.append(GMAudioScrobblerTrack(timestamp,info));
    runTask();
    }
  }

void GMAudioScrobbler::runTask() {
  FXASSERT(mutex_data.locked());
  if (!running()) {
    start();
    }
  else if (mutex_task.trylock()){
    condition_task.signal();
    mutex_task.unlock();
    }
  }


FXbool GMAudioScrobbler::can_login() {
  if ( (flags&FLAG_BANNED) || (flags&FLAG_BADTIME) ) {
    flags|=FLAG_SHUTDOWN;
    if (running()) {
      if (mutex_task.trylock()) {
        condition_task.signal();
        mutex_task.unlock();
        }
      }
    return false;
    }
  return true;
  }

FXbool GMAudioScrobbler::can_submit() {
  if ( (flags&FLAG_DISABLED) || (flags&FLAG_BANNED) || (flags&FLAG_BADAUTH) || (flags&FLAG_BADTIME) || username.empty() || password.empty() ) {
    flags|=FLAG_SHUTDOWN;
    if (running()) {
      if (mutex_task.trylock()) {
        condition_task.signal();
        mutex_task.unlock();
        }
      }
    return false;
    }
  return true;
  }


void GMAudioScrobbler::shutdown(){
  mutex_data.lock();
  flags|=FLAG_SHUTDOWN;
  mutex_data.unlock();
  mutex_task.lock();
  condition_task.signal();
  mutex_task.unlock();
  join();
  }

void GMAudioScrobbler::disable(){
  mutex_data.lock();
  flags|=FLAG_SHUTDOWN|FLAG_DISABLED;
  mutex_data.unlock();
  FXApp::instance()->reg().writeBoolEntry("LastFM","scrobble-tracks",false);
  mutex_task.lock();
  condition_task.signal();
  mutex_task.unlock();
  join();
  }

void GMAudioScrobbler::enable() {
  mutex_data.lock();
  flags&=~FLAG_DISABLED;
  flags&=~FLAG_SHUTDOWN;
  mutex_data.unlock();
  FXApp::instance()->reg().writeBoolEntry("LastFM","scrobble-tracks",true);
  }


FXString GMAudioScrobbler::getUsername() {
  FXMutexLock lock(mutex_data);
  return username;
  }

FXbool GMAudioScrobbler::hasPassword(){
  FXMutexLock lock(mutex_data);
  return !password.empty();
  }


FXbool GMAudioScrobbler::isBanned(){
  FXMutexLock lock(mutex_data);
  return (flags&FLAG_BANNED);
  }

FXbool GMAudioScrobbler::isEnabled(){
  FXMutexLock lock(mutex_data);
  return !(flags&FLAG_DISABLED);
  }

/******************************************************************************
 *
 *  P R O T E C T E D  A P I
 *
 ******************************************************************************/


void GMAudioScrobbler::load_queue(){
  FXTRACE((60,"GMAudioScrobbler::load_queue\n"));
  FXuint version,size;
  FXString filename = FXSystem::getHomeDirectory() + SCROBBLER_CACHE_FILE;
  FXFileStream store;
  if (store.open(filename,FXStreamLoad)){
    store >> version;
    if (version==20080501) {
      store >> size;
      submitqueue.no(size);
      for (FXint i=0;i<submitqueue.no();i++){
        submitqueue[i].load(store);
        }
      }
    store.close();
    }
  FXFile::remove(filename);
  }


void GMAudioScrobbler::save_queue(){
  FXTRACE((60,"GMAudioScrobbler::save_queue\n"));
  FXuint version=20080501;
  FXString filename = FXSystem::getHomeDirectory() + SCROBBLER_CACHE_FILE;
  if (submitqueue.no()) {
    FXFileStream store;
    if (store.open(filename,FXStreamSave)){
      store << version;
      store << submitqueue.no();
      for (FXint i=0;i<submitqueue.no();i++)
        submitqueue[i].save(store);
      }
    }
  else {
    FXFile::remove(filename);
    }
  }

FXbool GMAudioScrobbler::waitForTask() {
  FXTRACE((60,"GMAudioScrobbler::waitForTask\n"));
  if (timeout>0) {
    FXlong wakeuptime = FXThread::time()+((FXlong)timeout*1000000000LL);
    while(1) {
      FXTRACE((60,"GMAudioScrobbler::waitForTask => %ld\n",timeout));
      if (!condition_task.wait(mutex_task,wakeuptime))
        return true;
      else  {
        FXMutexLock lock(mutex_data);
        if (flags&FLAG_SHUTDOWN)
          return false;
        if (flags&FLAG_NETWORK) {
          flags&=~FLAG_NETWORK;
          return true;
          }
        }
      }
    }
  else {
    condition_task.wait(mutex_task);
    }
  return true;
  }

FXuchar GMAudioScrobbler::getNextTask() {
  FXTRACE((60,"GMAudioScrobbler::getNextTask\n"));
  FXMutexLock lock(mutex_data);


  if (flags&FLAG_SHUTDOWN){
    flags&=~FLAG_SHUTDOWN;
    return TASK_SHUTDOWN;
    }

  if (flags&FLAG_BADAUTH)
    return TASK_NONE;

  if (flags&FLAG_TIMEOUT) {
    flags&=~FLAG_TIMEOUT;
    return TASK_NONE;
    }

  if (flags&FLAG_LOGIN_CHANGED) {
    session.clear();
    return TASK_LOGIN;
    }

  if (submitqueue.no() || nowplayingtrack.timestamp) {
    if (session.empty()) return TASK_LOGIN;
    else if (submitqueue.no()) return TASK_SUBMIT;
    else return TASK_NOWPLAYING;
    }

  return TASK_NONE;
  }



FXint GMAudioScrobbler::run() {
  FXTRACE((60,"GMAudioScrobbler::run\n"));
  FXuchar next=TASK_NONE;
  mutex_task.lock();
  do {
    while((next=getNextTask())!=TASK_NONE) {
      switch(next){
        case TASK_LOGIN     : handshake();  break;
        case TASK_SUBMIT    : submit();     break;
        case TASK_NOWPLAYING: nowplaying(); break;
        case TASK_SHUTDOWN  : goto done;    break;
        }
      }
    }	while(waitForTask());
done:
  mutex_task.unlock();
  flags&=~FLAG_SHUTDOWN;
  flags&=~FLAG_LOGIN_CHANGED;
  flags&=~FLAG_TIMEOUT;
  FXTRACE((60,"GMAudioScrobbler::run -> shutdown\n"));
  return 0;
  }


FXbool GMAudioScrobbler::open_connection(GMHost & host) {
  FXTRACE((60,"GMAudioScrobbler::open_connection %s %d %s\n",host.name.text(),host.port,host.path.text()));

  if (host.name.empty() || host.port==0)
    return false;

  FXString lookupname = host.name+GMStringVal(host.port);
  struct addrinfo   hints;
  struct addrinfo * available=NULL,*entry=NULL;
  int result=0;

  memset(&hints,0,sizeof(struct addrinfo));
  hints.ai_family=AF_UNSPEC;
  hints.ai_socktype=SOCK_STREAM;

  FXASSERT(server==-1);

  available = (struct addrinfo *) dnscache.find(lookupname.text());
  if (available) {
    for (entry=available;entry;entry=entry->ai_next){
      server = tryconnect(entry);
      if (server!=-1) break;
      }
    if (server==-1) {
      dnscache.remove(lookupname.text());
      freeaddrinfo(available);
      }
    }

  if (server==-1) {
    if ((result=getaddrinfo(host.name.text(),GMStringVal(host.port).text(),&hints,&available))!=0){
      FXTRACE((60,"getaddrinfo: %s\n", gai_strerror(result)));
      return false;
      }
    for (entry=available;entry;entry=entry->ai_next){
      server = tryconnect(entry);
      if (server!=-1) break;
      }
    if (server!=-1) {
      dnscache.insert(lookupname.text(),available);
      }
    else {
      freeaddrinfo(available);
      }
    }

  if (server==-1)
    return false;

  return true;
  }

void GMAudioScrobbler::close_connection(){
  if (server!=-1) {
    ::close(server);
    server=-1;
    }
  }

void GMAudioScrobbler::set_handshake_timeout(){
  flags|=FLAG_TIMEOUT;
  if (timeout==0)
    timeout=MIN_HANDSHAKE_TIMEOUT;
  else
    timeout=FXMIN(MAX_HANDSHAKE_TIMEOUT,timeout<<1);
  }

void GMAudioScrobbler::reset_handshake_timeout(){
  timeout=DISABLE_HANDSHAKE_TIMEOUT;
  }


void GMAudioScrobbler::set_submit_failed() {
  FXTRACE((60,"GMAudioScrobbler::set_failed\n"));
  nfailed++;
  if (nfailed==3) {
    session.clear();
    nfailed=0;
    }
  }


void GMAudioScrobbler::create_handshake_request(FXString & msg) {
  FXMutexLock lock(mutex_data);
  FXTRACE((60,"GMAudioScrobbler::create_handshake_request\n"));
  FXString token;
  FXlong timestamp = FXThread::time()/1000000000;
  FXString timestamp_text = GMStringVal(timestamp);

  token = password + timestamp_text;
  checksum(token);

  msg=GMStringFormat("GET /?hs=true&p=1.2&c="CLIENT_ID"&v="CLIENT_VERSION"&u=%s&t=%s&a=%s HTTP/1.1\r\nHOST:%s\r\n\r\n",username.text(),timestamp_text.text(),token.text(),host_handshake.name.text());

  flags&=~FLAG_LOGIN_CHANGED;
  }



void GMAudioScrobbler::process_handshake_response(const FXchar * buffer,FXint len){
  FXMutexLock lock(mutex_data);
  if (flags&FLAG_LOGIN_CHANGED) return;
  FXTRACE((60,"GMAudioScrobbler::process_handshake_response\n"));
  if (len>0 && ( (compare("HTTP/1.1 200",buffer,12)==0) || (compare("HTTP/1.0 200",buffer,12)==0) )) {
    FXStringList headers;
    FXint i,s;
    for (i=0,s=0;i<len;i++){
      if (buffer[i]=='\r' &&  (buffer[i+1]=='\n' || buffer[i+1]=='\0')){
        if (i-s==0) break;
        headers.append(FXString(&buffer[s],i-s));
        s=i+2;
        i++;
        }
      }
    s=i=i+2;
    FXString response(&buffer[s],len-i);
    FXString code=response.section('\n',0);
    if (compare(code,"OK",2)==0) {
      session = response.section('\n',1);
      parseurl(response.section('\n',2),host_nowplaying);
      parseurl(response.section('\n',3),host_submit);
      flags&=~FLAG_BADAUTH;
      reset_handshake_timeout();
      }
    else if (compare(code,"BANNED",6)==0){
      FXTRACE((60,"\t=> BANNED\n"));
      const FXchar msg[] = "This version of Goggles Music Manager is not supported\nby Last-FM. Please upgrade to a newer version of GMM.";
      feedback.message(target,FXSEL(SEL_COMMAND,message),msg,ARRAYNUMBER(msg));
      flags|=(FLAG_BANNED|FLAG_SHUTDOWN);
      FXApp::instance()->reg().writeBoolEntry("LastFM","client-banned",true);
      }
    else if (compare(code,"BADTIME",7)==0){
      FXTRACE((60,"\t=> BADTIME\n"));
      const FXchar msg[] = "Unable submit tracks to Last.FM. The system time doesn't match\n"
                           "the Last.FM server time. Please adjust your system time\n"
                           "and restart GMM to start scrobbling.";
      feedback.message(target,FXSEL(SEL_COMMAND,message),msg,ARRAYNUMBER(msg));
      flags|=(FLAG_BADTIME|FLAG_SHUTDOWN);
      }
    else if (compare(code,"BADAUTH",7)==0){
      FXTRACE((60,"\t=> BADAUTH\n"));
      const FXchar msg[] = "Unable to login to Last.FM.\nUsername and password do not match.";
      feedback.message(target,FXSEL(SEL_COMMAND,message),msg,ARRAYNUMBER(msg));
      flags|=FLAG_BADAUTH;
      }
    else if (compare(code,"FAILED",6)==0){
      FXTRACE((60,"\t=> FAILED\n"));
      set_handshake_timeout();
      }
    else {
      FXTRACE((60,"\t=> Unknown\n"));
      FXTRACE((60,"%s\n",buffer));
      set_handshake_timeout();
      }
    }
  else {
    FXTRACE((60,"\t=> HTTP error\n"));
    set_handshake_timeout();
    }
  }


void GMAudioScrobbler::handshake() {
  FXTRACE((60,"GMAudioScrobbler::handshake\n"));

  FXString msg;
  FXint ncount,nread,nwritten;
  FXchar buffer[1024]={0};

  if (!open_connection(host_handshake)){
    FXTRACE((60,"GMAudioScrobbler::open_connection failed\n"));
    set_handshake_timeout();
    return;
    }

  create_handshake_request(msg);

  ncount=0;
  do {
    nwritten=::send(server,&msg[ncount],msg.length()-ncount,MSG_NOSIGNAL);
    if (nwritten==-1) {
      close_connection();
      set_handshake_timeout();
      return;
      }
    ncount+=nwritten;
    } while(ncount<msg.length());

  ncount=0;
  do {
    nread=::recv(server,&buffer[ncount],1023-ncount,0);
    if (nread<0) {
      close_connection();
      set_handshake_timeout();
      return;
      }
    ncount+=nread;
    } while(nread>0);

  process_handshake_response(buffer,ncount);

  close_connection();
  return;
  }




void GMAudioScrobbler::create_nowplaying_request(GMHost & host,FXString & msg) {
  FXMutexLock lock(mutex_data);
  FXTRACE((60,"GMAudioScrobbler::create_nowplaying_request\n"));
  FXString nowplaying_payload;
  nowplaying_payload+="s=";
  nowplaying_payload+=gm_url_encode(session);
  nowplaying_payload+=GMStringFormat("&a=%s",gm_url_encode(nowplayingtrack.artist).text());
  nowplaying_payload+=GMStringFormat("&t=%s",gm_url_encode(nowplayingtrack.title).text());
  nowplaying_payload+=GMStringFormat("&b=%s",gm_url_encode(nowplayingtrack.album).text());
  nowplaying_payload+=GMStringFormat("&l=%d",nowplayingtrack.duration);
  nowplaying_payload+=GMStringFormat("&n=%d",nowplayingtrack.no);
  nowplaying_payload+="&m";
  nowplayingtrack.artist.clear();
  nowplayingtrack.title.clear();
  nowplayingtrack.album.clear();
  nowplayingtrack.timestamp=0;
  FXTRACE((71,"Payload: %s\n\n",nowplaying_payload.text()));
  msg="POST " + host.path + " HTTP/1.1\r\nHOST:" + host.name + "\r\nContent-Type: application/x-www-form-urlencoded\r\n";
  msg+=GMStringFormat("Content-Length: %d\r\n",nowplaying_payload.length());
  msg+="\r\n";
  msg+=nowplaying_payload;
  }


void GMAudioScrobbler::process_nowplaying_response(const FXchar * buffer,FXint len){
  FXMutexLock lock(mutex_data);
  if (flags&FLAG_LOGIN_CHANGED) return;
  FXTRACE((60,"GMAudioScrobbler::process_nowplaying_response\n"));
  if (len>0 && ( (compare("HTTP/1.1 200",buffer,12)==0) || (compare("HTTP/1.0 200",buffer,12)==0) )) {
    FXStringList headers;
    FXint i,s;
    for (i=0,s=0;i<len;i++){
      if (buffer[i]=='\r' &&  (buffer[i+1]=='\n' || buffer[i+1]=='\0')){
        if (i-s==0) break;
        headers.append(FXString(&buffer[s],i-s));
        s=i+2;
        i++;
        }
      }
    s=i=i+2;
    FXString response(&buffer[s],len-i);
    FXString code=response.section('\n',0);
    FXTRACE((70,"Now Playing Response: %s\n\n",response.text()));
    if (compare(code,"BADSESSION",10)==0) {
      session.clear();
      }
    }
  else {
    FXTRACE((70,"Now Playing Response:\n%s\n\n",buffer));
    }
  }


void GMAudioScrobbler::nowplaying() {
  FXTRACE((60,"GMAudioScrobbler::nowplaying\n"));
  FXchar buffer[1024]={0};
  FXString msg;
  FXint ncount,nread,nwritten;


  if (!open_connection(host_nowplaying)){
    set_submit_failed();
    return;
    }

  create_nowplaying_request(host_nowplaying,msg);

  ncount=0;
  do {
    nwritten=::send(server,&msg[ncount],msg.length()-ncount,MSG_NOSIGNAL);
    if (nwritten==-1) {
      close_connection();
      set_handshake_timeout();
      return;
      }
    ncount+=nwritten;
    } while(ncount<msg.length());

  ncount=0;
  do {
    nread=::recv(server,&buffer[ncount],1023-ncount,0);
    if (nread<0) {
      close_connection();
      set_handshake_timeout();
      return;
      }
    ncount+=nread;
    } while(nread>0);

  process_nowplaying_response(buffer,ncount);

  close_connection();
  return;
  }



void GMAudioScrobbler::create_submit_request(GMHost & host,FXString & msg) {
  FXMutexLock lock(mutex_data);
  FXTRACE((60,"GMAudioScrobbler::create_submit_request\n"));
  FXString payload;
  nsubmitted=0;
  payload+="s=";
  payload+=gm_url_encode(session);
  for (FXint i=0;i<submitqueue.no() && i<50;i++) {
    payload+=GMStringFormat("&a[%d]=%s",i,gm_url_encode(submitqueue[i].artist).text());
    payload+=GMStringFormat("&t[%d]=%s",i,gm_url_encode(submitqueue[i].title).text());
    payload+=GMStringFormat("&i[%d]=%u",i,(FXuint)(submitqueue[i].timestamp/1000000000));
    payload+=GMStringFormat("&o[%d]=P",i);
    payload+=GMStringFormat("&r[%d]",i);
    payload+=GMStringFormat("&l[%d]=%d",i,submitqueue[i].duration);
    payload+=GMStringFormat("&b[%d]=%s",i,gm_url_encode(submitqueue[i].album).text());
    payload+=GMStringFormat("&n[%d]=%d",i,submitqueue[i].no);
    payload+=GMStringFormat("&m[%d]",i);
    nsubmitted++;
    }
  FXTRACE((71,"Payload: %s\n\n",payload.text()));
  msg="POST " + host.path + " HTTP/1.1\r\nHOST:" + host.name + "\r\nContent-Type: application/x-www-form-urlencoded\r\n";
  msg+=GMStringFormat("Content-Length: %d\r\n",payload.length());
  msg+="\r\n";
  msg+=payload;
  }


void GMAudioScrobbler::process_submit_response(const FXchar * buffer,FXint len){
  FXMutexLock lock(mutex_data);
  FXTRACE((60,"GMAudioScrobbler::process_submit_response\n"));
  if (len>0 && ( (compare("HTTP/1.1 200",buffer,12)==0) || (compare("HTTP/1.0 200",buffer,12)==0) )) {
    FXStringList headers;
    FXint i,s;
    for (i=0,s=0;i<len;i++){
      if (buffer[i]=='\r' &&  (buffer[i+1]=='\n' || buffer[i+1]=='\0')){
        if (i-s==0) break;
        headers.append(FXString(&buffer[s],i-s));
        s=i+2;
        i++;
        }
      }
    s=i=i+2;
    FXString response(&buffer[s],len-i);
    FXString code=response.section('\n',0);
    FXTRACE((70,"Submit Response: %s\n\n",response.text()));
    if (compare(code,"OK",2)==0) {
      submitqueue.erase(0,nsubmitted);
      nsubmitted=0;
      }
    else if (compare(code,"BADSESSION",10)==0) {
      session.clear();
      nsubmitted=0;
      }
    else if (compare(code,"FAILED",6)==0) {
      session.clear();
      nsubmitted=0;
      }
    else {
      set_submit_failed();
      nsubmitted=0;
      }
    }
  else {
    FXTRACE((70,"Submit Response: %s\n\n",buffer));
    set_submit_failed();
    nsubmitted=0;
    }
  }


void GMAudioScrobbler::submit() {
  FXTRACE((60,"GMAudioScrobbler::submit\n"));
  FXchar buffer[1024]={0};
  FXString msg;
  FXint ncount,nread,nwritten;

  if (!open_connection(host_submit)){
    set_submit_failed();
    return;
    }

  create_submit_request(host_submit,msg);

  ncount=0;
  do {
    nwritten=::send(server,&msg[ncount],msg.length()-ncount,MSG_NOSIGNAL);
    if (nwritten==-1) {
      close_connection();
      set_handshake_timeout();
      return;
      }
    ncount+=nwritten;
    } while(ncount<msg.length());

  ncount=0;
  do {
    nread=::recv(server,&buffer[ncount],1023-ncount,0);
    if (nread<0) {
      close_connection();
      set_handshake_timeout();
      return;
      }
    ncount+=nread;
    } while(nread>0);

  process_submit_response(buffer,ncount);

  close_connection();
  return;
  }
