#ifdef MAKE_XMMS
#include <xmms/plugin.h>
#include <xmms/configfile.h>
#include <xmms/xmmsctrl.h>
#endif
#ifdef MAKE_BMP
#include <bmp/plugin.h>
#include <bmp/configdb.h>
#include <bmp/beepctrl.h>
#define ConfigFile ConfigDb
#define xmms_cfg_open_default_file bmp_cfg_db_open
#define xmms_cfg_read_string bmp_cfg_db_get_string
#define xmms_cfg_free bmp_cfg_db_close
#endif
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <wchar.h>
#include <sys/time.h>

#include "tags/include/tags.h"
#include "scrobbler.h"
#include "gtkstuff.h"
#include "config.h"
#include "fmt.h"
#include "tags/include/unicode.h"

#define XS_CS xmms_scrobbler.xmms_session

typedef struct submit_t
{
	int dosubmit, pos_c, len;
} submit_t;

static void init(void);
static void cleanup(void);
static void *xs_thread(void *);
static void *hs_thread(void *);
static int going;

static pthread_t pt_scrobbler;
static pthread_mutex_t m_scrobbler = PTHREAD_MUTEX_INITIALIZER;
static pthread_t pt_handshake;

static GeneralPlugin xmms_scrobbler =
{
	NULL,
	NULL,
	-1,
	NULL,
	init,
	about_show,
	configure_dialog,
	cleanup
};

static void init(void)
{
	char *username = NULL, *password = NULL;
	ConfigFile *cfgfile;
	going = 1;

	if ((cfgfile = xmms_cfg_open_default_file()) != NULL) {
		xmms_cfg_read_string(cfgfile, "audioscrobbler", "username",
				&username);
		xmms_cfg_read_string(cfgfile, "audioscrobbler", "password",
				&password);
		xmms_cfg_free(cfgfile);
	}
	if ((!username || !password) || (!*username || !*password)) {
		pdebug("username/password not found - not starting",
			DEBUG);
		going = 0;
		return;
	}
	sc_init(username, password);
	g_free(username);
	g_free(password);
	if (pthread_create(&pt_scrobbler, NULL, xs_thread, NULL)) {
		pdebug("Error creating scrobbler thread", DEBUG);
		going = 0;
		return;
	}
	init_errorbox_done();
	if (pthread_create(&pt_handshake, NULL, hs_thread, NULL)) {
		pdebug("Error creating handshake thread", DEBUG);
		going = 0;
		return;
	}
	pdebug("plugin started", DEBUG);
}

static void cleanup(void)
{
	void *dummy;

	if (!going)
		return;
	pdebug("about to lock mutex", DEBUG);
	pthread_mutex_lock(&m_scrobbler);
	pdebug("locked mutex", DEBUG);
	going = 0;
	pthread_mutex_unlock(&m_scrobbler);
	pdebug("joining threads", DEBUG);
	pthread_join(pt_scrobbler, &dummy);

	pthread_cancel(pt_handshake);

	pthread_join(pt_handshake, &dummy);

	sc_cleaner();
}

static char ishttp(const char *a)
{
	char *tmp, *bp;
	int status = 0;

	if (!a || !*a)
		return 0;

	tmp = strdup(a);
	for (bp = tmp; *bp; bp++)
		*bp = toupper(*bp);
	if (strstr(tmp, "HTTP://"))
		status = -1;
	free(tmp);
	return status;
}

/* Following code thanks to nosuke 
 *
 * It should probably be cleaned up
 */
static submit_t get_song_status(void)
{
	static int pos_c, playlistlen_c, playtime_c, time_c,
		pos_p = 0, playlistlen_p = 0, playtime_p = 0,
		playtime_i = 0, time_i = 0,
		playtime_ofs = 0;
	static char *file_c = NULL, *file_p = NULL; 

	static enum playstatus {
		ps_stop, ps_play, ps_pause
	} ps_c, ps_p = ps_stop;

	static int submitted = 0, changed, seeked, repeat,
		filechanged, rewind, len = 0;

	static enum state {
		start, stop, pause, restart, playing, pausing, stopping
	} playstate;

	submit_t dosubmit;

	struct timeval timetmp;

	/* clear dosubmit */
	dosubmit.dosubmit = dosubmit.pos_c = dosubmit.len = 0;

	/* current music number */
	pos_c = xmms_remote_get_playlist_pos(XS_CS);
	/* current file name */
	file_c = xmms_remote_get_playlist_file(XS_CS, pos_c); 
	/* total number */
	playlistlen_c = xmms_remote_get_playlist_length(XS_CS);
	/* current playtime */
	playtime_c = xmms_remote_get_output_time(XS_CS); 
	/* total length */
	len = xmms_remote_get_playlist_time(XS_CS, pos_c); 

	/* current time (ms) */
	gettimeofday(&timetmp, NULL);
	time_c = timetmp.tv_sec * 1000 + timetmp.tv_usec / 1000; 

	/* current status */
	if( xmms_remote_is_paused(XS_CS) ) {
		ps_c = ps_pause;
	}else if( xmms_remote_is_playing(XS_CS) ) {
		ps_c = ps_play;
	}else{
		ps_c = ps_stop;
	}

	/* repeat setting */
	repeat = xmms_remote_is_repeat(XS_CS);

	/*
#ifdef MAKE_XMMS
	// advance setting (required xmms-1.2.11 or over)
	advance = xmms_remote_is_advance(XS_CS);
#else
	advance = 1;
#endif
	*/

	if( ps_p == ps_stop && ps_c == ps_stop )        playstate = stopping;
	else if( ps_p == ps_stop && ps_c == ps_play )   playstate = start;
	/* else if( ps_p == ps_stop && ps_c == ps_pause ) ; */
	else if( ps_p == ps_play && ps_c == ps_play )   playstate = playing;
	else if( ps_p == ps_play && ps_c == ps_stop )   playstate = stop;
	else if( ps_p == ps_play && ps_c == ps_pause )  playstate = pause;
	else if( ps_p == ps_pause && ps_c == ps_pause ) playstate = pausing;
	else if( ps_p == ps_pause && ps_c == ps_play )  playstate = restart;
	else if( ps_p == ps_pause && ps_c == ps_stop )  playstate = stop;
	else playstate = stopping;

	/* filename has changed */
	if( !(file_p == NULL && file_c == NULL) &&
	    ((file_p == NULL && file_c != NULL) ||
		 (file_p != NULL && file_c == NULL) ||
		 (file_p != NULL && file_c != NULL && strcmp(file_c, file_p))) ){
		filechanged = 1;
		pdebug("*** filechange ***", SUB_DEBUG);
	}else{
		filechanged = 0;
	}
	if( file_c == NULL ){ len = 0; }

	/* whole rewind has occurred (maybe) */
	if( len != 0 && len - (playtime_p - playtime_c) < 3000 ){
		rewind = 1;
		pdebug("*** rewind ***", SUB_DEBUG);
	}else{
		rewind = 0;
	}


	changed = 0;
	seeked = 0;

	switch( playstate ){
	case start:
	  pdebug("*** START ***", SUB_DEBUG);
	  break;
	case stop:
	  pdebug("*** STOP ***", SUB_DEBUG);
	  len = 0;
	  break;
	case pause: 
	  pdebug("*** PAUSE ***", SUB_DEBUG);
	  playtime_ofs += playtime_c - playtime_i; /* save playtime */
	  break;
	case restart: 
	  pdebug("*** RESTART ***", SUB_DEBUG);
	  playtime_i  = playtime_c; /* restore playtime */
	  break;
	case playing:
	  if( (playtime_c < playtime_p) || /* back */
		  ( (playtime_c - playtime_i) - (time_c - time_i) > 3000 )
	          /* forward */
		  ) { 
		seeked = 1;
	  }

	  if( filechanged || /* filename has changed */
		  ( !filechanged && /* filename has not changed... */
			/* (( rewind && (repeat && (!advance ||
	                   (pos_c == 0 && playlistlen_c == 1 )))) || */
			/* looping with only one file */
			(( pos_c == 0 && playlistlen_c == 1 && repeat
				&& rewind ) || 
			 /* looping? */
			 ( pos_p == pos_c && rewind ) ||
			 
			 ( pos_p != pos_c && seeked ) ||
			 /* skip from current music to next music, 
			    which has the same filename as previous one */
			 ( pos_p < pos_c && playtime_c < playtime_p ) || 
			 /* current song has removed from playlist 
			    but the next (following) song has the same
	                    filename */
			 ( playlistlen_p > playlistlen_c
				&& playtime_c < playtime_p )))){
		pdebug("*** CHANGE ***",SUB_DEBUG);
		pdebug(fmt_vastr(" filechanged = %d",filechanged),SUB_DEBUG);
		pdebug(fmt_vastr(" pos_c = %d",pos_c),SUB_DEBUG);
		pdebug(fmt_vastr(" pos_p = %d",pos_p),SUB_DEBUG);
		pdebug(fmt_vastr(" rewind = %d", rewind),SUB_DEBUG);
		pdebug(fmt_vastr(" seeked = %d", seeked),SUB_DEBUG);
		pdebug(fmt_vastr(" playtime_c = %d", playtime_c),SUB_DEBUG);
		pdebug(fmt_vastr(" playtime_p = %d", playtime_p),SUB_DEBUG);
		pdebug(fmt_vastr(" playlistlen_c = %d", playlistlen_p),
			SUB_DEBUG);
		pdebug(fmt_vastr(" playlistlen_p = %d", playlistlen_p),
			SUB_DEBUG);
		changed = 1;
		seeked = 0;
	  }else if( seeked ) { 
		seeked = 1;
		pdebug("*** SEEK ***", SUB_DEBUG);
	  }

	  break;
	case pausing: 
	  if(playtime_c != playtime_p){
		pdebug("*** SEEK ***", SUB_DEBUG);
		seeked = 1;
	  }
	  break;
	case stopping:
	  len = 0;
	  break;
	default:
	  pdebug("*** unknown state tranfer!!! ***", SUB_DEBUG);
	  break;
	}

	
	if( playstate == start || changed || (seeked && !submitted) ){
	  /* reset counter */
	  pdebug(" <<< reset counter >>>", SUB_DEBUG);

	  submitted = 0;
	  playtime_ofs = 0;
	  playtime_i = playtime_c;
	  time_i = time_c;

	}else{
	  /* check playtime for submitting */
	  if( !submitted ){
		if( len > 30 * 1000 &&
			/* len < 30 *60 * 1000 &&  // crazy rule!!! */
			( 
			 (playtime_ofs + playtime_c - playtime_i > len / 2) ||
			 (playtime_ofs + playtime_c - playtime_i > 240 * 1000) 
			 /* (playtime_c - playtime_i > 10 * 1000)// for debug */
			 )){
		  pdebug("*** submitting requirements are satisfied.",
			SUB_DEBUG);
		  pdebug(fmt_vastr("    len = %d, playtime = %d",
			len / 1000, (playtime_c - playtime_i)/1000 ),
			SUB_DEBUG);
		  submitted = 1;
		  dosubmit.dosubmit = 1;
	          dosubmit.pos_c = pos_c;
	          dosubmit.len = len;
		}
	  }
	}

	/* keep current value for next iteration */
	ps_p = ps_c;
	if ( file_p != NULL ) 
		g_free(file_p);
	file_p = file_c;
	playtime_p = playtime_c;
	pos_p = pos_c;
	playlistlen_p = playlistlen_c;

	return dosubmit;
}

static void *xs_thread(void *data)
{
	int run = 1, i;
	char *charpos, *dirname;
	gboolean direxists;
	submit_t dosubmit;
	struct timespec request, remaining;
	
	while (run) {
		/* Error catching */
		if(sc_catch_error())
		{
			errorbox_show(sc_fetch_error());
			if(get_errorbox_done())
			{
				init_errorbox_done();
				sc_clear_error();
			}
		}

		/* Check for ability to submit */
		dosubmit = get_song_status();

		if(dosubmit.dosubmit) {
			char *fname, /**title, *artist,*/ *tmp = NULL; /**sep*/
			int track = 0;
			metatag_t *meta;

			pdebug("Submitting song.", DEBUG);
			
			meta = metatag_new();

			fname = xmms_remote_get_playlist_file(XS_CS,dosubmit.pos_c);
			if (ishttp(fname)) {
				g_free(fname);
				continue;
			}
			charpos = strrchr(fname, '.');
			if(charpos != NULL &&
				!fmt_strncasecmp(charpos + 1, "cda", 3))
			{
				ConfigFile *cfgfile;

				if ((cfgfile = xmms_cfg_open_default_file())
					!= NULL)
				{
					char *direntry = calloc(32, 1);

					dirname = fname;
					tmp = strrchr(fname, '.');
					*tmp = '\0';
					track = (char)atoi(tmp - 2);
					pdebug(fmt_vastr("Track: %d", track),
						DEBUG);
					tmp = strrchr(dirname, '/');
					*(tmp + 1) = '\0';
					direxists = xmms_cfg_read_string(
						cfgfile, "CDDA",
						"directory", &fname);
					for(i = 0; direxists == TRUE
						&& strcmp(dirname, fname) == 0;)
					{
						i++;
						snprintf(direntry, 31,
							"directory%d", i);
						g_free(fname);
						direxists =
							xmms_cfg_read_string(
							  cfgfile, "CDDA",
							  direntry, &fname);
					}
					if(i > 0)
					{
						snprintf(direntry, 31,
							"device%d", i);
					}
					else
					{
						snprintf(direntry, 31,
							"device");
					}
					g_free(fname);
					xmms_cfg_read_string(cfgfile, "CDDA",
						direntry, &fname);
					xmms_cfg_free(cfgfile);
					free(direntry);
					pdebug(fmt_vastr("CD Device: %s",
						fname), DEBUG);
				}
			}

			get_tag_data(meta, fname, track);

			if(meta->artist != NULL && meta->title != NULL)
			{
				pdebug(fmt_vastr(
					"submitting artist: %s, title: %s",
					meta->artist, meta->title), DEBUG);
				sc_addentry(m_scrobbler, meta,
					dosubmit.len/1000);
			}
			else
				pdebug("couldn't determine artist - "
						"title, not submitting",
						DEBUG);
			/* g_free(tmp); */
			g_free(fname);
			metatag_free(meta);
		}
		pthread_mutex_lock(&m_scrobbler);
		run = going;
		pthread_mutex_unlock(&m_scrobbler);
		request.tv_sec = 0;
		request.tv_nsec = 100000000;
		while(nanosleep(&request, &remaining) < 0)
			request = remaining;
	}
	pdebug("scrobbler thread: exiting", DEBUG);
	pthread_exit(NULL);
}

static void *hs_thread(void *data)
{
	int run = 1;
	struct timespec request, remaining;
	
	while(run)
	{
		if(sc_idle(m_scrobbler))
		{
			pdebug("Giving up due to fatal error", DEBUG);
			pthread_mutex_lock(&m_scrobbler);
			going = 0;
			pthread_mutex_unlock(&m_scrobbler);
		}
		pthread_mutex_lock(&m_scrobbler);
		run = going;
		pthread_mutex_unlock(&m_scrobbler);
		request.tv_sec = 1;
		request.tv_nsec = 0;
		while(nanosleep(&request, &remaining) < 0)
			request = remaining;
	}
	pdebug("handshake thread: exiting", DEBUG);
	pthread_exit(NULL);
}

GeneralPlugin *get_gplugin_info(void)
{
#ifdef MAKE_XMMS
	xmms_scrobbler.description = g_strdup_printf("XMMS-Scrobbler %s",
							VERSION);
#endif
#ifdef MAKE_BMP
	xmms_scrobbler.description = g_strdup_printf("BMP-Scrobbler %s",
							VERSION);
#endif
	return &xmms_scrobbler;
}
