/*
** 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/gui.h"
#include "include/msg.h"
#include "include/napster.h"
#include "include/navigator.h"
#include "include/protos.h"
#include "include/prefs.h"
#include "include/share.h"
#include "include/transfer.h"
#include "include/download.h"
#include "include/upload.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;
long loc_sock= -1;

char nap_host[512];
char nap_server[512+6];

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
};

BOOL nap_logininit(void);
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);
int createlistener(void);
char *getneterror(int type);


void nap_login_fromlist(char *server)
{
	if (!nap_logininit()) return;

	nap_state = NAPS_CON;
	gui_stat((char*)MSG_STATUS2_CONNECTINGTO, server);
	if (!nap_connect(server)) {
		nap_logout();
		return;
	}
}


void nap_login(void)
{
	if (!nap_logininit()) return;

	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;
		}
	}
}


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

	return TRUE;
}


void nap_logout(void)
{
	nap_state = NAPS_OFF;
	if(nap_sock>=0) CloseSocket(nap_sock);
	nap_sock = -1;
	if(loc_sock>=0) CloseSocket(loc_sock);
	loc_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) break;
			{
				long tmp=0;
				IoctlSocket(nap_sock,FIONBIO,(char*)&tmp);
				nap_state = NAPS_MAINON;
			}

		case NAPS_MAINON:
			if (WaitSelect(nap_sock+1,&fds,NULL,NULL,&tv,0) != 1) break;
			{
				int r;
				char *col;
				r = recv(nap_sock, buf, 255,0);
				CloseSocket(nap_sock);
				nap_sock = -1;
				if (r <= 0) {
					gui_stat((char *)MSG_STATUS2_CONNECTFAILED_TMP, nap_server, getneterror(Errno()));
					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)) {
					nap_logout();
					return;
				}
				nap_state = NAPS_CON;
			}
			break;

		case NAPS_CON:
			if (WaitSelect(nap_sock+1,NULL,&fds,NULL,&tv,0) != 1) break;
			{
				long tmp=0;
				int r = 0;

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

				nap_state = NAPS_ON;
				gui_stat((char *)MSG_STATUS2_LOGGINGINTO, nap_server);
				gui_state(1);

				DoMethod(gui->WI_Navigator, NAVI_MARKSERVER, nap_server);

				if (prf->regflag > 0) r = nap_sendbuf(NAPC_CREATEUSER, prf->user);
				/* Create user before we attempt to log in */

				if (r == -1) {
					prf->regflag = 1;
					if (Errno() == EPIPE) {
						gui_stat((char *)MSG_STATUS2_CONNECTFAILED_TMP, nap_server, getneterror(Errno()));
						nap_logout();
						return;
					}
				}
				if (prf->regflag == 1) {
					sprintf(nap_buf, "%s %s %d \"%s\" %d", prf->user, prf->pass, 0 /*prf->port */, prf->napvers, prf->link);
				}
				else {
					sprintf(nap_buf, "%s %s %d \"%s\" %d", gui->ConnectUser, gui->ConnectPw, 0 /*prf->port */, prf->napvers, prf->link);
				}
				prf->regflag = 1;
				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;

	if (!gui_napon) return;
	/* Avoid crash - but calling functions should check this first! */

	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);
}


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

	if (!gui_napon) return -1;
	/* Avoid crash - but calling functions should check this first! */

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

	r = send(nap_sock, hdr, 4, 0);
	if (r <= 0) return r;

	r = send(nap_sock, buf, len, 0);
	if (r <= 0) return r;
	else return 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 = 0;

	addr = strdup(server);
	col = strtok(addr, ":");
	if (col) col = strtok(NULL,"");
	if (col) port = atoi(col);

	if (!port) {
		gui_stat((char *)MSG_INFO_PORTNEEDED);
		free(addr);
		return(0);
	}

	host = gethostbyname(addr);
	free(addr);
	if (!host) {
		gui_stat((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);

	strcpy(nap_host, host->h_name);
	strcpy(nap_server, server);

	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_stat((char *)MSG_STATUS2_CONNECTFAILED_TMP, nap_host, getneterror(Errno()));
	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_PUBLICMSGRECV:
		case NAPC_JOINACK:
		case NAPC_JOINMSG:
		case NAPC_USERPART:
		case NAPC_USERLIST:
		case NAPC_USERLISTEND:
		case NAPC_CHANNELTOPIC:
			chat_interpret(com, data);
			break;
		case NAPC_LOGINERROR:
			gui_debugf("\33bLogin error:\33n %s\n", data);
			break;
		case NAPC_REGSUCCESS:
			gui_debug((char *)MSG_INFO_REGSUCCESS);
			break;
		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 = nick + strlen(nick) + 1;
			msg_got(nick, msg);
			break;
			}
		case NAPC_FILEINFO:
			{
			char *title,*user;
			u_long ip;
			int port;
			user = nap_token(&data);
			ip = nap_ltoken(&data);
			port = nap_itoken(&data);
			title = nap_token(&data);
			dl_startq(title, user, NAP_SWAPIP(ip), port);
			}
			break;
		case NAPC_LOGINRESP:
			DoMethod(gui->shwin, SHARE_NOTIFYALL);
			gui_state(2);
			break;
		case NAPC_WHOISRESP:
			msg_gotwhois(data);
			break;
		case NAPC_WHOWASRESP:
			{
			char *user, *level;
			u_long lastseen;

			user = nap_token(&data);
			level = nap_token(&data);
			lastseen = nap_ltoken(&data);

			DoMethod(gui->mwin, MSG_WHOWAS, user, level, lastseen);
			}
			break;
		case NAPC_UPLOADREQ:
			{
			char *user,*fname;
			user = nap_token(&data);
			fname = nap_token(&data);
			upload_req(user,fname);
			}
			break;
		case NAPC_ALTDLACK:
			{
			char *nick, *fname, *md5;
			u_long ip;
			int port, speed;

			nick = nap_token(&data);
			ip = nap_ltoken(&data);
			port = nap_itoken(&data);
			fname = nap_token(&data);
			md5 = nap_token(&data);
			speed = nap_itoken(&data);

			ul_startq(fname, nick, NAP_SWAPIP(ip), port, speed);
			}
			break;
		case NAPC_REMOTEQUEUEFULL:
			{
			char *nick, *fname;
			int size, limit;
			nick = nap_token(&data);
			fname = nap_token(&data);
			size = nap_itoken(&data);
			limit = nap_itoken(&data);

			DoMethod(gui->dwin, DL_RETRY, fname, nick, limit);
			}
			break;
		case NAPC_GETERROR:
			{
			char *user,*fname;
			user = nap_token(&data);
			fname = nap_token(&data);
			DoMethod(gui->dwin, DL_SETERROR, fname, user, ERROR_LOGGEDOUT);
			}
			break;
		case NAPC_GENERALERROR:
			gui_debugf("\33bError:\33n %s\n", data);
			break;
		case NAPC_SYSMSG:
			gui_debug(data);
			break;
		case NAPC_GLOBALMSG:
			{
			char *nick, *text, buf[50];

			nick = nap_token(&data);
			text = nick+strlen(nick)+1;
			sprintf(buf, MSG_GLOBALMESSAGE_TITLE, nick);

			MUI_Request(gui->app, gui->win, 0L,
				buf,
				(char *)MSG_OK_GAD,
				text);
			}
			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));
			strtok(s->md5,"-");
			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));
			strtok(s->md5,"-");
			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++;
		}
	}
}


/*
int createlistener(void)
{
	struct sockaddr_in server;

	if (loc_sock != -1) return 1;

	loc_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (loc_sock < 0) {
		gui_debug("Error creating listener socket!\n");
		return 0;
	}

	server.sin_family = AF_INET;
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	server.sin_port = htons(prf->port);

	if (bind(loc_sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
		gui_debug("Error binding listening socket\n");
		CloseSocket(loc_sock);
		loc_sock = -1;
		return 0;
	}

	listen(loc_sock, 0);	/ Number of allowed connections /

	return 1;
}
*/


char *getneterror(int type)
{
	char *message, buf[512];

	switch (type) {
		case ENETDOWN:
			message = (char *)MSG_NETERROR_ENETDOWN;
			break;
		case ENETUNREACH:
			message = (char *)MSG_NETERROR_ENETUNREACH;
			break;
		case ECONNRESET:
			message = (char *)MSG_NETERROR_ECONNRESET;
			break;
		case ETIMEDOUT:
			message = (char *)MSG_NETERROR_ETIMEDOUT;
			break;
		case ECONNREFUSED:
			message = (char *)MSG_NETERROR_ECONNREFUSED;
			break;
		case EPIPE:
			message = (char *)MSG_NETERROR_EPIPE;
			break;
		default:
			sprintf(buf, MSG_NETERROR_UNKNOWN, type);
			message = buf;
	}

	return message;
}
