/*
	cddb.c
	31.5.99 tn

	stuff to access the cddb-database via the cddbp-protocol
	standalone version
*/

#include "largefile.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#if defined(linux) || defined(__CYGWIN32__)
#include <getopt.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include "xcdrdata.h"
#include "xcdroast.h"
#include "main.h"

#ifndef INADDR_NONE
#define INADDR_NONE 0xFFFFFFFF
#endif

gchar hostname[MAXLINE];
gchar username[MAXLINE];
cddb_match_t **cddbmatch;
gint matchnr;
gint oldtnr;

/* extract the 3 digit code from a cddb-answer */

gint get_cddb_code(gchar *line) {
gchar tmp[MAXLINE];

	if (line == NULL || strlen(line) < 3) {
		return 0;
	}

	strncpy(tmp,line,3);
	tmp[3] = '\0';
	
	return(atoi(tmp));
}


/* connect to the cddb-server and return socket-fd: 
   return -1 on "hostname lookup failure" 
          -2 on "cant open stream socket"
	  -3 on "connection refused"
	  -4 on "no response from server"
	  -5 on "timeout while connect"
*/

gint connect_cddb_server(gchar *server, gint port) {
struct sockaddr_in serv_addr;
struct hostent *myhost;
gchar tmp[MAXLINE];
gint sockfd,n;

	/* resolv ipnumber from hostname if neccessary */
	if ((serv_addr.sin_addr.s_addr = inet_addr(server)) == INADDR_NONE) {
		/* no valid ip-nummer, look up name */
		if ((myhost = gethostbyname(server)) == NULL) {
			return -1;
		}
		memcpy(&serv_addr.sin_addr, myhost->h_addr, myhost->h_length);
	}

	/* fill structure serv_addr */
	serv_addr.sin_family      = AF_INET;
	serv_addr.sin_port        = htons(port);

	/* open tcp socket (internet stream socket) */
	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		return -2;
	}

	/* connect */
	if (connect(sockfd, (struct sockaddr *) &serv_addr,
		sizeof(serv_addr)) < 0) {
		close(sockfd);
		return -3;
	}

	/* read connect response */
	n = read_line2(sockfd, tmp, MAXLINE, 1);
	if (n == -2) {
		/* timeout */
		close(sockfd);
		return -5;
	}
	if (n < 0) {
		g_print("-11: Error: read error on socket when connecting\n");
		fflush(stdout);
		close(sockfd);
		return -4;
	}

	return sockfd;
}


/* perform cddbp-handshaking */
/* return 0 on success, 1 on error */

gint cddb_handshake(gint sockfd) {
gchar tmp[MAXLINE];
gint n, code;

	g_snprintf(tmp,MAXLINE,"cddb hello %s %s xcdroast %s",
		username,hostname,XCDROAST_VERSION);

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb handshaking\n");
		fflush(stdout);
		return 1;
	}

	/* read response */
	n = read_line2(sockfd, tmp, MAXLINE, 1);
	if (n < 0) {
		g_print("-11: Error: read error on socket when cddb handshaking\n");
		fflush(stdout);
		return 1;
	}

	code = get_cddb_code(tmp);
	switch(code) {
		/* all ok */
		case 200:
			return 0;

		/* handshake failed */
		case 431:
			return 1;

		/* alread shook hands */
		case 402:
			return 0;	

		default:
			return 1;
	}
}


/* extract a match of the cddb-query command */

void extract_cddb_match(gchar *match, gint exact) {
gchar *p1;

	/* allocate memory and free old */
	if (cddbmatch[matchnr] != NULL) {
		g_free(cddbmatch[matchnr]->categ);
		g_free(cddbmatch[matchnr]->discid);
		g_free(cddbmatch[matchnr]->dtitle);
		g_free(cddbmatch[matchnr]);
	}
	cddbmatch[matchnr] = g_new0(cddb_match_t,1);

	cddbmatch[matchnr]->exact = exact;
	p1 = strtok(match," ");
	cddbmatch[matchnr]->categ = g_strdup(p1);
	p1 = strtok(NULL," ");
	cddbmatch[matchnr]->discid = g_strdup(p1);
	p1 = strtok(NULL,"");
	cddbmatch[matchnr]->dtitle = g_strdup(p1);
	
	matchnr++;
}


/* perform cddbp-query */
/* return 0 on success, 1 on error */

gint cddb_query(gint sockfd, gchar *querystring) {
gchar tmp[MAXLINE];
gint n, code;

	matchnr = 0;
	oldtnr = -1;

	/* construct query */
	strcpy(tmp,querystring);

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb query\n");
		fflush(stdout);
		return 1;
	}

	/* read response */
	n = read_line2(sockfd, tmp, MAXLINE, 1);
	if (n < 0) {
		g_print("-11: Error: read error on socket when cddb query\n");
		fflush(stdout);
		return 1;
	}

	code = get_cddb_code(tmp);
	switch(code) {
		/* exact match */
		case 200:
			extract_cddb_match(tmp+4,1);
			return 0;
		/* no match found */
		case 202:
			return 0; 
		/* Database entry is corrupt */
		case 403:
			return 0;
		/* No handshake */
		case 409:
			return 1;
		/* list of matches found */
		case 210:
		case 211: {
			while (matchnr < MAXCDDB) {
				n = read_line2(sockfd, tmp, MAXLINE, 1);
				if (n < 0) {
					g_print("-11: Error: read error on socket when cddb query\n");
					fflush(stdout);
					return 1;
				}
				if (tmp[0] == '.') {
					/* done with list */
					return 0;
				}

				if (code == 210) {
					extract_cddb_match(tmp,1);
				} else {
					extract_cddb_match(tmp,0);
				}
			
			}	
		}
		default: 
			return 1;
	}
}


/* parse the received cddb-entry into info-structures */
/* return 0 on success, 1 on error */

gint parse_cddb_entry(gint sockfd) {
gchar tmp[MAXLINE];
gchar tmp2[MAXLINE];
gchar *p1;
gint n, tnr;

	while (1) {
		n = read_line2(sockfd, tmp, MAXLINE, 1);
		if (n < 0) {
			g_print("-11: Error: read error on socket when cddb read\n");
			fflush(stdout);
			return 1;
		}
		
		/* end marker found */
		if (tmp[0] == '.') {
			/* no more tracks, close old track */
			g_print("\"\n");
			fflush(stdout);
			return 0;
		}

		if (strncmp(tmp,"DTITLE",6) == 0) {
			p1 = strtok(tmp,"=");
			p1 = strtok(NULL,"");
			strcpy(tmp2,p1);
			strip_string(tmp2);
			g_print("07: \"%s\"\n", convert_escape(tmp2));
			fflush(stdout);
			continue;
		}

		if (strncmp(tmp,"TTITLE",6) == 0) {
			strcpy(tmp2,tmp+6);
			p1 = strtok(tmp2,"=");
			tnr = atoi(p1);

			/* got a new track? close line of previous one */
			if (oldtnr != -1 && oldtnr != tnr) {
				g_print("\"\n");
				fflush(stdout);
			}

			p1 = strtok(NULL,"");
			strcpy(tmp,p1);
			strip_string(tmp);

			if (oldtnr != -1 && oldtnr == tnr) {
				/* continue old line */
				g_print("%s", convert_escape(tmp));
			} else {
				/* new track line (to be continue) */
				g_print("08: \"%s", convert_escape(tmp));
			}	
			oldtnr = tnr;

			continue;
		}
	}
}


/* perform cddbp-read */
/* return 0 on success, 1 on error */

gint cddb_read(gint sockfd, gint nr) {
gchar tmp[MAXLINE];
gint n, code;

	if (cddbmatch[nr] == 0) {
		/* no such match */
		return 1;
	}

	/* construct query */
	g_snprintf(tmp,MAXLINE,"cddb read %s %s",cddbmatch[nr]->categ,
		cddbmatch[nr]->discid);

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb read\n");
		fflush(stdout);
		return 1;
	}

	/* read response */
	n = read_line2(sockfd, tmp, MAXLINE, 1);
	if (n < 0) {
		g_print("-11: Error: read error on socket when cddb read\n");
		fflush(stdout);
		return 1;
	}

	code = get_cddb_code(tmp);
	switch(code) {
		/* database entry follow */
		case 210:
			parse_cddb_entry(sockfd);
			return 0;
		/* no entry found */
		case 401:
			return 1;
		/* server error */
		case 402:
			return 1;
		/* Database entry is corrupt */
		case 403:
			return 1;
		/* No handshake */
		case 409:
			return 1;
		/* Access limit exceeded, explanation follows */
		case 417: {
			while (1) {
				n = read_line2(sockfd, tmp, MAXLINE, 1);
				if (n < 0) {
					g_print("-11: Error: read error on socket when cddb query\n");
					fflush(stdout);
					return 1;
				}
				if (tmp[0] == '.') {
					/* done with list */
					return 1;
				}
				/* output error-message line of server */
				g_print("-12: Server: %s\n",tmp);	
				fflush(stdout);
			}
		}
		default: 
			return 1;
	}
}


/* perform cddbp-quit */
/* return 0 on success, 1 on error */

gint cddb_quit(gint sockfd) {
gchar tmp[MAXLINE];

	/* construct query */
	strcpy(tmp,"quit");

	/* now send string */
	if (writen(sockfd,tmp,strlen(tmp), 1) != strlen(tmp)) {
		g_print("-10: Error: write error on socket when cddb quit\n");
		fflush(stdout);
		return 1;
	}

	return 0;
}


/* close the socket when user canceled-out */

void close_cddb(sock) {

	if (sock > 0) {
		close(sock);
	}

}


/* do the cddb-work and output info to keep user informed */
/* return 1 on trouble, 0 if all ok */

gint start_cddb_query(gint *sockpnt, gchar *host, gint port, gchar *querystring) {
gint i;
gint sock;
gchar tmptmp[MAXLINE];

	/* connect */
	g_print("00: Connecting to %s:%d\n",host,port);
	fflush(stdout);

	sock = connect_cddb_server(host,port);
	*sockpnt = sock;

	if (sock < 0) {
		switch (sock) {
		case -1: 
			/* lookup error */
			g_print("-1: Error: Hostname lookup failure\n");
			break;
		case -2:
			/* socket error */
			g_print("-2: Error: Can't open stream socket\n");
			break;
		case -3:
			/* connection refused */
			g_print("-3: Error: Connection refused\n");
			break;
		case -4: 
			/* no response from server */
			g_print("-4: Error: No response from server\n");
			break;
		case -5:
			/* timeout */
			g_print("-5: Error: No answer within timeout\n");
			break;
		}
		fflush(stdout);
		return 1;
	}	


	/* connect was ok, now send handshake */
	g_print("01: Connect ok: Sending handshake\n");
	fflush(stdout);
	if (cddb_handshake(sock) != 0) {
		g_print("-6: Handshake failed - No valid CDDB-server?\n");
		fflush(stdout);
		return 1;
	}

	/* start query */
	g_print("02: Sending CDDB-query\n");
	fflush(stdout);
	if (cddb_query(sock, querystring) != 0) {
		g_print("-7: Error: CDDB-query failed\n");
		fflush(stdout);
		return 1;
	}

	/* now the global match-structure contains all hits */

	if (matchnr == 0) {
		/* no matches found */
		g_print("03: No matches found - unknown CD\n");
		fflush(stdout);
		/* all the user can do now is to cancel */
		return 1;
	} else {
		/* output number of matches */
		g_print("%% %d\n",matchnr);
		fflush(stdout);

		/* what type of matches */
		if (cddbmatch[0]->exact == 0) {
			/* close */
			g_print("04: Found %d close matches - Please select (enter number and press enter)\n", matchnr);
			fflush(stdout);
		} else {
			/* exact */
			g_print("05: Found %d exact matches - Please select (enter number and press enter)\n", matchnr);
			fflush(stdout);
		}
	
		/* now add all matches to clist */
		for (i = 0; i < matchnr; i++) {
			strcpy(tmptmp,cddbmatch[i]->dtitle);
			g_print("#%02d: \"%s\"\n", i, 
				convert_escape(tmptmp));
		}	
		g_print("#--\n");
		fflush(stdout);
	}

	/* so..now wait until the user select a match */
	return 0;
}


/* user selected a match - fetch the corresponding data */
/* return 1 on trouble, 0 if all ok */

gint continue_cddb_query(gint sock, gint match) {

	/* get data only when user manually not forbid it */
	if (match != -1) {
		g_print("06: Requesting data - Please wait\n");
		fflush(stdout);

		/* get the data */
		if (cddb_read(sock, match) != 0) {
			g_print("-8: Error: CDDB-read failed\n");
			fflush(stdout);
			return 1;
		}
	}

	if (cddb_quit(sock) != 0) {
		g_print("-9: Warning: CDDB-logout failed\n");
		fflush(stdout);
		return 1;
	}
	close_cddb(sock);

	return 0;
}

void usage(gchar *cmd) {

	g_print("Usage: %s [options] (Version: %s)\n",cmd, XCDROAST_VERSION);
	g_print("Options:\n");
	g_print("\t-s <cddb-server>\n");
	g_print("\t-p <cddb-port>\n");
	g_print("\t-u <id username>\n");
	g_print("\t-h <id hostname>\n");
	g_print("\t-m <match nr> get info for this match number\n\t   (Do not wait for user input - set to -1 for match list only)\n");
	g_print("\t-q <cddb-query-string>\n");
}

gint main(gint argc, gchar **argv) {
gint c;
gchar server[MAXLINE];
gchar selectbuffer[MAXLINE];
gchar querystring[MAXLINE];
gint port;
gint ret, match;
gint sock;
gint automatch;

	/* do some defaults */
	strcpy(server,"freedb.freedb.org");
	port = 888;
	strcpy(username,"undef");
	strcpy(hostname,"localhost");
	strcpy(querystring, "");
	automatch = -2;
	cddbmatch = g_new0(cddb_match_t *,MAXCDDB);

	while ((c = getopt(argc,argv,"s:p:u:h:q:m:")) != EOF) 
	switch((gchar)c) {

	case 's':	
		strncpy(server,optarg,MAXLINE);
		break;

	case 'u':	
		strncpy(username,optarg,MAXLINE);
		break;

	case 'h':	
		strncpy(hostname,optarg,MAXLINE);
		break;

	case 'q':	
		strncpy(querystring,optarg,MAXLINE);
		break;

	case 'p':
		port = atoi(optarg);
		break;

	case 'm':
		automatch = atoi(optarg);
		break;

	default:
		usage(argv[0]);
		exit(-1);
	}

	/* any parameters at all given? */
	if (strcmp(querystring,"") == 0) {
		usage(argv[0]);
		exit(-1);
	}

	ret = start_cddb_query(&sock, server, port, querystring);
	if (ret == 0) {
		/* ok, now let the user select an match */
		if (automatch == -2) {
			read_line2(STDIN_FILENO, selectbuffer, MAXLINE, 0);
			match = atoi(selectbuffer);
		} else {
			match = automatch;
		} 
		ret = continue_cddb_query(sock, match);
		if (ret != 0) {
			/* some error */
			exit(1);
		}
	}

	return 0;
} 
