/*
** Napster Protocol
*/

#include "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 <error.h>

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

#define NAP_SWAPIP(x) ( ((x&0xFF)<<24) | ((x&0xFF00)<<8) | ((x&0xFF0000)>>8) | (x>>24) )

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

int nap_state= -1;
#define NAPS_OFF -1
#define NAPS_MAINCON 0
#define NAPS_MAINON 1
#define NAPS_CON 2
#define NAPS_ON 3

char nap_buffer[AMSTER_NAPBUFSIZE];
char *nap_buf = &nap_buffer[4];

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_connect(char *server);
int nap_recv(u_char *buf);
void nap_interpret(u_int com, char *data);
void nap_parseresult(int type, char *data);
char *nap_token(char **s);
u_long nap_ltoken(char **str);
int nap_itoken(char **str);


void nap_login(void)
{
	gui_state(0);
	SocketBase = OpenLibrary("bsdsocket.library",0);
	if(!SocketBase) {
		gui_stat((char *)MSG_NO_TCPIP);
		nap_logout();
		return;
	}
	SocketBaseTags(SBTM_SETVAL(SBTC_SIGIOMASK),(char *)nap_sigmask,TAG_DONE);

	if(prf->server) {
		nap_state = NAPS_CON;
		gui_stat((char*)MSG_STATUS2_CONNECTINGTO,prf->server);
		if(!nap_connect(prf->server)) {
			nap_logout();
			return;
		}
	} else {
		nap_state = NAPS_MAINCON;
		gui_stat((char*)MSG_STATUS2_FINDINGOPTIMAL);
		if(!nap_connect(prf->mainserver)) {
			nap_logout();
			return;
		}
	}

}


void nap_logout(void)
{
	nap_state = NAPS_OFF;
	if(nap_sock>=0) CloseSocket(nap_sock);
	nap_sock = -1;
	if(SocketBase) CloseLibrary(SocketBase);
	SocketBase = NULL;
	gui_state(-1);
}


void nap_listen(void)
{
	static u_char buf[AMSTER_NAPBUFSIZE];
	static struct fd_set fds;
	static struct timeval tv;

	if(nap_state == NAPS_OFF) return;

	FD_ZERO(&fds);
	FD_SET(nap_sock,&fds);
	tv.tv_sec = 0;
	tv.tv_usec = 0;

	switch(nap_state) {
		case NAPS_MAINCON:
			if(WaitSelect(nap_sock+1,NULL,&fds,NULL,&tv,0)==1) {
				long tmp=0;
				IoctlSocket(nap_sock,FIONBIO,(char*)&tmp);
				nap_state = NAPS_MAINON;
			}
			break;

		case NAPS_MAINON:
			if(WaitSelect(nap_sock+1,&fds,NULL,NULL,&tv,0)==1) {
				int r;
				char *col;
				r = recv(nap_sock,buf,255,0);
				CloseSocket(nap_sock);
				nap_sock = -1;
				if(r<=0) {
					gui_stat((char*)MSG_ERR_RECVERROR);
					nap_logout();
					return;
				}
				buf[r] = 0;
				col = strchr(buf,'\n');
				if(col) *col=0;
				if (strncmp(buf,"wait",4)==0 || strncmp(buf,"busy",4)==0 || strncmp(buf,"127.0.0.1",9)==0) {
					/* Sometimes the server returns "127.0.0.1:1111" - quite odd. */
					gui_stat((char *)MSG_INFO_SERVERBUSY);
					nap_logout();
					return;
				}
				gui_debugf((char *)MSG_INFO_OPTIMAL,buf);
				gui_stat((char*)MSG_STATUS2_CONNECTINGTO,buf);
				if(!nap_connect(buf)) {
					gui_stat((char*)MSG_STATUS2_CONNECTFAILED,buf);
					nap_logout();
					return;
				}
				nap_state = NAPS_CON;
			}
			break;

		case NAPS_CON:
			if(WaitSelect(nap_sock+1,NULL,&fds,NULL,&tv,0)==1) {
				long tmp=0;
				IoctlSocket(nap_sock,FIONBIO,(char*)&tmp);
				nap_state = NAPS_ON;
				gui_state(1);
				if(prf->regflag) nap_sendbuf(NAPC_CREATEUSER,prf->user);
				sprintf(nap_buf,"%s %s %d \"%s\" %d",prf->user,prf->pass,prf->port,prf->napvers,prf->link);
				nap_send(NAPC_LOGIN);
			}
			break;

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

				r = nap_recv(buf);
				if(r == -1) {
					gui_stat((char *)MSG_ERR_NETWORKERROR);
					nap_logout();
					return;
				}
				if(r==1) nap_interpret(buf[2]+(buf[3]<<8),buf+4);
			}
	}
}


void nap_send(u_int com)
{
	int len;

	len = strlen(nap_buf);
	nap_buffer[0] = len&0xFF;
	nap_buffer[1] = (len&0xFF00)>>8;
	nap_buffer[2] = com&0xFF;
	nap_buffer[3] = (com&0xFF00)>>8;
	send(nap_sock,nap_buffer,4+len,0);
}


void nap_sendbuf(u_int com, char *buf)
{
	static u_char hdr[4];
	int len;

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


void nap_songfree(song s)
{
	if(!s) return;
	if(s->title) free(s->title);
	if(s->md5) free(s->md5);
	if(s->user) free(s->user);
	free(s);
}


song nap_songdup(song s)
{
	song sn;

	sn = malloc(sizeof(_song));
	if(!sn) return(NULL);
	memset(sn,0,sizeof(_song));

	sn->title = strdup(s->title);
	sn->md5 = strdup(s->md5);
	sn->size = s->size;
	sn->bit = s->bit;
	sn->freq = s->freq;
	sn->time = s->time;
	sn->user = strdup(s->user);
	sn->ip = s->ip;
	sn->link = s->link;

	if(!sn->title || !sn->md5 || !sn->user) {
		nap_songfree(sn);
		return(NULL);
	}

	return(sn);
}


char *nap_strippath(char *name)
{
	int i;
	char tmp;

	for(i=strlen(name)-1;i>=0;i--) {
		tmp = name[i];
		if(tmp==':' || tmp=='/' || tmp=='\\') return(name+i+1);
	}
	return(name);
}


/* private functions */

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

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

	host = gethostbyname(addr);
	free(addr);
	if(!host) {
		gui_debug((char *)MSG_ERR_LOOKUPFAILED);
		return(0);
	}
	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);

	nap_sock = socket(AF_INET,SOCK_STREAM,0);
	if(nap_sock<0) {
		gui_debug((char *)MSG_ERR_SOCKETERROR);
		return(0);
	}

	IoctlSocket(nap_sock,FIOASYNC,(char *)&tmp);
	IoctlSocket(nap_sock,FIONBIO,(char *)&tmp);

	tmp = connect(nap_sock,(struct sockaddr *)&sin,sizeof(struct sockaddr_in));
	if(tmp != -1) return(1);
	if(Errno()==EINPROGRESS) return(1);

	gui_debug((char *)MSG_ERR_CONNECTERROR);
	return(0);
}


int nap_recv(u_char *buf)
{
	static u_int blen=0;
	u_int len=4;
	int ret;

	if(blen>=4) {
		len = buf[0] + (buf[1]<<8) + 4;
		if(len==0) {
			buf[blen] = 0;
			blen = 0;
			return(1);
		}
	}


	ret = recv(nap_sock,buf+blen,len-blen,0);
	if(ret<=0) {
		blen = 0;
		return(-1);
	}
	blen += ret;


	if(blen==4) {
		len = buf[0] + (buf[1]<<8) + 4;
		if(len==0) {
			buf[blen] = 0;
			blen = 0;
			return(1);
		}
	}

	if(blen==len) {
		buf[blen] = 0;
		blen = 0;
		return(1);
	}

	return(0);
}


void nap_interpret(u_int com, char *data)
{
	switch(com) {
		case NAPC_REGUSED:
			gui_debug((char *)MSG_INFO_REGUSED);
			break;
		case NAPC_SEARCHRESULT:
			nap_parseresult(0,data);
			break;
		case NAPC_SEARCHCOMPLETE:
			gui_found(NULL);
			break;
		case NAPC_BROWSERESULT:
			nap_parseresult(1,data);
			break;
		case NAPC_BROWSECOMPLETE:
			gui_found(NULL);
			break;
		case NAPC_FILECOUNT:
			{
			int a,b,c;
			a = nap_itoken(&data);
			b = nap_itoken(&data);
			c = nap_itoken(&data);
			gui_srvstat(a,b,c);
			break;
			}
		case NAPC_PRIVATEMSG:
			{
			char *nick,*msg;
			nick = nap_token(&data);
			msg = nap_token(&data);
			msg_got(nick,msg);
			break;
			}
		case NAPC_FILEINFO:
			{
			char *title,*user;
			u_long ip;
			int port;
			user = nap_token(&data);
			ip = nap_itoken(&data);
			port = nap_itoken(&data);
			title = nap_token(&data);
			dl_startq(title,user,NAP_SWAPIP(ip),port);
			}
			break;
		case NAPC_LOGINRESP:
			gui_debugf((char *)MSG_INFO_EMAIL,data);
			break;
		case NAPC_WHOISRESP:
			msg_gotwhois(data);
			break;
		case NAPC_UPLOADREQ:
			{
			char *user,*fname;
			user = nap_token(&data);
			fname = nap_token(&data);
			upload_req(user,fname);
			}
			break;
		case NAPC_SYSMSG:
			/* gui_debug(data); */
			break;
		default:
			gui_debugf("<<%d:%s>>",com,data);
	}
}


void nap_parseresult(int type, char *data)
{
	song s;
	char *tmp=data;

	s = malloc(sizeof(_song));
	if(!s) return;
	memset(s,0,sizeof(_song));

	switch(type) {
		case 0:
			s->title = strdup(nap_token(&tmp));
			s->md5 = strdup(nap_token(&tmp));
			s->size = nap_ltoken(&tmp);
			s->bit = nap_itoken(&tmp);
			s->freq = nap_itoken(&tmp);
			s->time = nap_itoken(&tmp);
			s->user = strdup(nap_token(&tmp));
			s->ip = nap_ltoken(&tmp);
			s->ip = NAP_SWAPIP(s->ip);
			s->link = nap_itoken(&tmp);
			break;

		case 1:
			s->user = strdup(nap_token(&tmp));
			s->title = strdup(nap_token(&tmp));
			s->md5 = strdup(nap_token(&tmp));
			s->size = nap_ltoken(&tmp);
			s->bit = nap_itoken(&tmp);
			s->freq = nap_itoken(&tmp);
			s->time = nap_itoken(&tmp);
			break;
	}

	if(!s->user || !s->title || !s->md5) {
		nap_songfree(s);
		return;
	}

	gui_found(s);
}


u_long nap_ltoken(char **str)
{
	char *t;

	t = nap_token(str);
	if(!t) return(0);
	return((u_long)atol(t));
}


int nap_itoken(char **str)
{
	char *t;

	t = nap_token(str);
	if(!t) return(0);
	return(atoi(t));
}


char *nap_token(char **str)
{
	int sf=0,i,len=0;
	char c,*t=NULL;

	if(!*str) return(NULL);

	for(i=0;;i++) {
		c = *(*str + i);
		switch(c) {
			case '\0':
				*str = NULL;
				if(len==0 || sf) return(NULL);
				return(t);
			case '"':
				if(sf) {
					*(*str+i) = 0;
					*str = *str+i+1;
					return(t);
				}
				sf=1;
				break;
			case ' ':
				if(len==0) break;
				if(!sf) {
					*(*str+i) = 0;
					*str = *str+i+1;
					return(t);
				}
				break;
			default:
				if(!t) t=*str+i;
				len++;
		}
	}
}
