/*
** Napster Protocol
*/

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <proto/exec.h>
#include <proto/socket.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h>
#include <bsdsocket/socketbasetags.h>

#include "napster.h"
#include "gui.h"
#include "search.h"
#include "download.h"
#include "prefs.h"
#include "info.h"
#include "msg.h"
#include "amster_Cat.h"

struct Library *SocketBase=NULL;
unsigned long nap_sigmask;
long nap_sock= -1;

char *nap_linktype[] = {
	(char*)_MSG_LINE_UNKNOWN,
	(char*)_MSG_LINE_14400,
	(char*)_MSG_LINE_28800,
	(char*)_MSG_LINE_33600,
	(char*)_MSG_LINE_57600,
	(char*)_MSG_LINE_64000,
	(char*)_MSG_LINE_128000,
	(char*)_MSG_LINE_CABLE,
	(char*)_MSG_LINE_DSL,
	(char*)_MSG_LINE_T1,
	(char*)_MSG_LINE_T3,
	NULL
};

int _nap_lala(void);
void _nap_bibi(void);
long _nap_connect(char *server);
void _nap_send(unsigned int type, char *buf);
int _nap_read(unsigned int *type, char **buf);

void _nap_parsesearch(char *buf);
void _nap_parsebrowseuser(char *buf);
char *_nap_findserver(void);


int nap_login(void)
{
	char *srv;
	char buf[256], tmp[100];
	char *rep;
	unsigned int type;

	if(pref_server) srv=pref_server;
	else
	{
		set(gui->stat,MUIA_Text_Contents,MSG_STATUS2_FINDINGOPTIMAL);
		srv=_nap_findserver();
	}
	if(!srv || strcmp(srv,"127.0.0.1:1111") == 0) {
		/* Sometimes the server returns "127.0.0.1:1111" - quite odd. */
		set(gui->stat,MUIA_Text_Contents,MSG_STATUS2_OPTIMALFAILED);
		return(0);
	}

	sprintf(tmp,MSG_STATUS2_CONNECTINGTO,srv);
	set(gui->stat,MUIA_Text_Contents,tmp);

	nap_sock = _nap_connect(srv);
	if(nap_sock<0) {
		sprintf(tmp,MSG_STATUS2_CONNECTFAILED,srv);
		set(gui->stat,MUIA_Text_Contents,tmp);
		return(0);
	}

	gui_debug((char *)MSG_INFO_CONNECTED);

	if(pref_regflag) {
		sprintf(buf,"%s",pref_user);
		_nap_send(NAP_CREATEUSER,buf);
	}

	sprintf(buf,"%s %s %d \"%s\" %d",pref_user,pref_pass,0,pref_napvers,pref_link);
	_nap_send(NAP_LOGIN,buf);
	if(!_nap_read(&type,&rep)) {
		gui_debug((char *)MSG_ERR_RECVERROR);
		nap_logout();
		return(0);
	}
	if(type==NAP_ERROR) {
		gui_debug((char *)MSG_INFO_LOGINFAILED);
		nap_logout();
		return(0);
	}

	gui_debug((char *)MSG_INFO_LOGGEDIN);
	gui_state(1);
	return(1);
}


void nap_updateaccount(void)
{
	_nap_send(NAP_EMAILCHANGE,pref_email);
}


void nap_logout(void)
{
	if(nap_sock>=0) CloseSocket(nap_sock);
	nap_sock = -1;
	_nap_bibi();
	gui_state(0);
}


int nap_listen(void)
{
	char *buf;
	unsigned int type;
	struct fd_set fds;
	struct timeval tv;

	dl_pollall();

	if(nap_sock== -1) return(1);

	while(1) {
		FD_ZERO(&fds);
		FD_SET(nap_sock,&fds);
		tv.tv_sec=0;
		tv.tv_usec=15;
		if(WaitSelect(nap_sock+1,&fds,NULL,NULL,&tv,0)<1) return(0);

		if(!_nap_read(&type,&buf)) {
			gui_debug((char *)MSG_ERR_NETWORKERROR);
			nap_logout();
			return(1);
		}

		switch(type) {
			case NAP_SEARCHRESULT:
				_nap_parsesearch(buf);
				break;
			case NAP_SEARCHCOMPLETE:
				gui_found(NULL);
				break;
			case NAP_BROWSERESULT:
				_nap_parsebrowseuser(buf);
				break;
			case NAP_BROWSECOMPLETE:
				gui_found(NULL);
				break;
			case NAP_FILECOUNT:
				{
				char *tmp,*tmp2;
				strtok(buf," ");
				tmp=strtok(NULL," ");
				tmp2=strtok(NULL,"");
				gui_srvstat(atoi(buf),atoi(tmp),atoi(tmp2));
				break;
				}
			case NAP_PRIVATEMSG:
				{
				char *nick,*msg;
				nick = strtok(buf," ");
				msg = strtok(NULL,"");
				msg_got(nick,msg);
				break;
				}
			case NAP_FILEINFO:
				{
				char title[256],user[20];
				unsigned long ip;
				int port;
				sscanf(buf, "%s %u %d \"%[^\"]\"",user,&ip,&port,title);
				dl_startq(title,user,NAP_SWAPIP(ip),port);
				}
				break;
			case NAP_LOGINRESP:
				gui_debugf((char *)MSG_INFO_EMAIL,buf);
				break;
			case NAP_SYSMSG:
				gui_debug(buf);
				break;
			default:
				gui_debugf("<<%d:%s>>",type,buf);
				break;
		}
		free(buf);
	}
}


void nap_browseuser(char *nick)
{
	_nap_send(NAP_BROWSEUSER,nick);
}


void nap_search(char *keys, int max, int speed, int speedval, int bit, int bitval, int freq, int freqval)
{
	static char *opts[] = {
		"AT LEAST",
		"EQUAL TO",
		"AT BEST"
	};
	char buf[1024];
	char *tmp;

	tmp = buf + sprintf(buf,"FILENAME CONTAINS \"%s\" MAX_RESULTS %d",keys,max);

	if(speed) tmp=tmp+sprintf(tmp," LINESPEED \"%s\" %d",opts[speed-1],speedval);
	if(bit) tmp=tmp+sprintf(tmp," BITRATE \"%s\" %d",opts[bit-1],bitval);
	if(freq) sprintf(tmp," FREQ \"%s\" %d",opts[freq-1],freqval);

	_nap_send(NAP_SEARCH,buf);
}


void nap_request(songdata sd)
{
	char buf[1024];

	sprintf(buf,"\"%s\" \"%s\"",sd->user,sd->title);
	_nap_send(NAP_FILEINFOREQ,buf);
	dl_addq(sd);
}


void nap_msg(char *nick, char *msg)
{
	char buf[1024];

	sprintf(buf,"%s %s",nick,msg);
	_nap_send(NAP_PRIVATEMSG,buf);
}


char *nap_getlinktype(int link)
{
	return(nap_linktype[link]);
}


/* private functions */

void _nap_parsesearch(char *buf)
{
	songdata sd;
	int tmp;

	sd = malloc(sizeof(_songdata));
	if(!sd) return;
	memset(sd,0,sizeof(_songdata));
	sscanf(buf,"\"%[^\"]\" %s %lu %d %d %d %s %d %d",sd->title,sd->chksum,&sd->size,&sd->bitrate,&sd->freq,&sd->time,sd->user,&tmp,&sd->link);
	gui_found(sd);
}


void _nap_parsebrowseuser(char *buf)
{
	songdata sd;

	sd = malloc(sizeof(_songdata));
	if(!sd) return;
	memset(sd,0,sizeof(_songdata));
	sscanf(buf,"%s \"%[^\"]\" %s %lu %d %d %d %d",sd->user,sd->title,sd->chksum,&sd->size,&sd->bitrate,&sd->freq,&sd->time);
	gui_found(sd);
}


char *_nap_findserver(void)
{
	long s;
	static char buf[256];
	char *col;
	int len;

	s = _nap_connect(pref_mainserver);
	if(s<0) return(NULL);

	len = recv(s,buf,255,0);
	CloseSocket(s);
	if(len<=0) {
		gui_debug((char *)MSG_ERR_RECVERROR);
		_nap_bibi();
		return(NULL);
	}

	buf[len]=0;
	col = strchr(buf,'\n');
	if(col) *col=0;

	if (strncmp(buf,"wait",4)==0 || strncmp(buf,"busy",4)==0) {
		gui_debug((char *)MSG_INFO_SERVERBUSY);
		_nap_bibi();
		return(NULL);
	}

	gui_debugf((char *)MSG_INFO_OPTIMAL,buf);
	return(buf);
}




/* low level private functions */

int _nap_lala(void)
{
	if(SocketBase) return(1);
	SocketBase = OpenLibrary("bsdsocket.library",0);
	if(!SocketBase) {
		gui_debug((char *)MSG_NO_TCPIP);
		return(0);
	}
	SocketBaseTags(SBTM_SETVAL(SBTC_SIGIOMASK),(char *)nap_sigmask,TAG_DONE);
	return(1);
}


void _nap_bibi(void)
{
	if(dl_list!=NULL) return;
	if(SocketBase) CloseLibrary(SocketBase);
	SocketBase = NULL;
}


long _nap_connect(char *server)
{
	long s;
	struct hostent *host;
	struct sockaddr_in sin;
	char *col,*addr;
	int port;
	long temp=1;

	if(!_nap_lala()) return(-1);

	addr = strdup(server);
	col = strtok(addr,":");
	if(!col) {
		gui_debug((char *)MSG_INFO_PORTNEEDED);
		_nap_bibi();
		return(-1);
	}
	col = strtok(NULL,"");
	port = atoi(col);

	host = gethostbyname(addr);
	if(!host) {
		gui_debug((char *)MSG_ERR_LOOKUPFAILED);
		_nap_bibi();
		return(-1);
	}
	memcpy(&sin.sin_addr,host->h_addr,host->h_length);
	sin.sin_family = host->h_addrtype;
	sin.sin_port = port;
	sin.sin_len = sizeof(sin);

	s = socket(AF_INET,SOCK_STREAM,0);
	if(s<0) {
		gui_debug((char *)MSG_ERR_SOCKETERROR);
		_nap_bibi();
		return(-1);
	}

	if(connect(s,(struct sockaddr *)&sin,sizeof(struct sockaddr_in))<0) {
		gui_debug((char *)MSG_ERR_CONNECTERROR);
		_nap_bibi();
		CloseSocket(s);
		return(-1);
	}

	IoctlSocket(s,FIOASYNC,(char *)&temp);
	return(s);
}


int _nap_read(unsigned int *type, char **buf)
{
	static u_char hdr[4];
	char *tmp;
	unsigned int len,rlen=0,i;
	len = recv(nap_sock,hdr,4,0);
	if(len!=4) return(0);
	len = hdr[0] + (hdr[1]<<8);
	*type = hdr[2] + (hdr[3]<<8);

	tmp = malloc(len+2);
	if(!tmp) return(0);
	while(rlen<len) {
		i = recv(nap_sock,tmp+rlen,len-rlen,0);
		if(i<0) {
			free(tmp);
			return(0);
		}
		rlen += i;
	}
	tmp[rlen]=0;
	*buf = tmp;
	return(1);
}


void _nap_send(unsigned int type, char *buf)
{
	static u_char hdr[4];
	int len;

	len = strlen(buf);
	hdr[0] = len&0xFF;
	hdr[1] = (len&0xFF00)>>8;
	hdr[2] = type&0xFF;
	hdr[3] = (type&0xFF00)>>8;
	send(nap_sock,hdr,4,0);
	send(nap_sock,buf,len,0);
}
