/* this is the goo code file 
   written by l0om
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>

#include "goo.h"

goo_t	init_goo()	// init the goo stuff
{
	goo_t res;
	res.cli = malloc(sizeof(goo_client));
	if(res.cli == NULL) {
		res.resultc = ALLOC_ERR;
		return res;
	} 
	res.cli->id = 0;
	res.cli->reqtyp = 0;
	res.cli->mq_id = 0;	
	res.cli->next = NULL;
	res.resultc = SUCCESS;
	if((res.goo_results = msgget(IPC_PRIVATE, 0655 | IPC_CREAT)) == -1) {
		res.resultc = MQ_ERR;
		return res;
	}
	return res;
}


int	goo_cli(int ncli, goo_t *ginf) 
{
	cli_ident iam;
	goo_client *new, cinf;
	srv_cmd cmd;
	cli_ans answer;
	int i, j, srv_cli_mq, goo_urls, n = 1;
	int init = 0;
	static int singleton;
	pid_t pid;

	if(!singleton) singleton = 1;
	else {					// this function can be called only once jet...
		ginf->resultc = FAILD;
		return 0;
	}

	j = ncli;
	if( (srv_cli_mq = msgget(IPC_PRIVATE, 0655 | IPC_CREAT)) == -1) {
		ginf->resultc = MQ_ERR;
		return 0;
	}
		
	if(signal(SIGCHLD, SIG_IGN) == SIG_ERR) 
		printf("waring: cannot ignore SIGCHLD\n");

	for(i = 0; i < ncli; i++) {
		pid=fork();
		if(pid==0) {
			iam.mtyp = CLIDENT;
			iam.cli_mq = msgget(IPC_PRIVATE, 0655 | IPC_CREAT);
			if(iam.cli_mq == -1) {
				perror("msgget");
				free_clis(ginf);
				exit(0);
			}
			iam.id = n++;
			iam.pid = getpid();
			//printf("%d  waiting for server...\n",getpid());
			//printf("%d  done\n",getpid());

			if(msgsnd(srv_cli_mq, &iam, sizeof(cli_ident), 0) == -1) {
				perror("msgsnd\n");
				free_clis(ginf);
				exit(0);
			}
			while(1) {
				if(msgrcv(iam.cli_mq, &cmd, sizeof(cmd), CMD, 0) == -1) {
				//	if(errno == ENOMSG) { sleep(2); continue; } //IPC_NOWAIT
					perror("msgrcv");
					free_clis(ginf);
					exit(0);
				}
				memset(answer.req,0x0,MAXREQ);
				answer.mtyp = CLIANW;
				switch(cmd.cmdtyp) {
					case GETRESLT:
						//printf("%d get results...\n",getpid());
						answer.goo_res = get_result(cmd.req, &answer, cmd.c, ginf);
						answer.resultc = ginf->resultc;
						answer.urls = 0;
						strncpy(answer.req,cmd.req,strlen(cmd.req));
						if(msgsnd(ginf->goo_results, &answer, sizeof(cli_ans), 0) == -1) {
							printf("%d cannot send\n",getpid());
							exit(0);
						}
						break;
					case GETURLS:
						for(goo_urls = 0; goo_urls < cmd.urls; goo_urls += answer.urls) {
							answer.urls = get_urls(cmd.req, &answer, cmd.c, ginf, goo_urls);
							answer.resultc = ginf->resultc;
							strncpy(answer.req,cmd.req,strlen(cmd.req));
							if(msgsnd(ginf->goo_results, &answer, sizeof(cli_ans), 0) == -1) {
								printf("cannot send\n");
								exit(0);
							}
							if(answer.urls+1 < 10) break;
						}
						break;
					default :  //QUIT
						free_clis(ginf);
						exit(0);
				}
			
			}
			exit(0);
		}
		else if(pid== -1) {
			ginf->resultc = FORK_ERR;
			msgctl(srv_cli_mq, IPC_RMID, NULL);
			return (n-1);
		}
		else {		// parent process

			if(msgrcv(srv_cli_mq, &iam, sizeof(cli_ident), 0, 0) == -1) {
				printf("cannot msgrcv\n");
				return(0);
			} else new = malloc(sizeof(goo_client));
			new->id = iam.id;
			new->reqtyp = 0;
			new->mq_id = iam.cli_mq;
			new->pid = iam.pid;
			new->next = NULL;
			memset(new->proxy,0x0,MAXPRXY);
			n++; //parent has its own stack segment
			//printf("got new goo_client: pid:%d id:%d\n",new->pid,new->id,new->mq_id);
			append(ginf, new);
		}
	}
	msgctl(srv_cli_mq, IPC_RMID, NULL);
	return (n-1);
}

void	append(goo_t *ginf, goo_client *new)
{
	goo_client *p;
	
	p = ginf->cli;
	while(p->next != NULL)
		p = p->next;
	p->next = new;
}

void	print_list(goo_t *ginf)		//debugging function
{
	goo_client *p;
	
	p = ginf->cli;
	while(p != NULL) {
		if(p->mq_id)
			printf("id:%d  mq_id:%d  pid:%d\n",p->id,p->mq_id,p->pid);
		p = p->next;
	}
}


goo_client *ret_cli_inf(u_char id, goo_t *ginf)
{
	goo_client *p;
	
	p = ginf->cli;
	while(p != NULL) {
		if(p->id == id) return p;
		p = p->next;
	}
	return NULL;
}


void	goo_cli_set(int cli, u_char opt, void *data, goo_t *ginf)
{
	goo_client *p;

	p = ret_cli_inf(cli, ginf);
	if(p != NULL) {
		switch(opt) {
			case GETURLS:
			case GETRESLT:
			case QUIT:
				p->reqtyp = opt;
				ginf->resultc = SUCCESS;
				return;
			case SETPRXY:
				strncpy(p->proxy,(char *)&data,MAXPRXY); //set proxy for this client
				break;
			case NOPRXY:
				memset(p->proxy,0x0,MAXPRXY);  //clean out proxy
				break;
			default:
			ginf->resultc = UNKNOWN;
		}
	}
	ginf->resultc = NO_SUCH_CLI;
}

int	goo_cli_req(int cli, char *req, goo_t *ginf, u_int urls)
{
	goo_client *p;
	srv_cmd cmd;
	cli_ans answer;
	pid_t pid;

	cmd.mtyp = CMD;

	p = ret_cli_inf(cli, ginf);
	if(p != NULL) {
		pid = fork();
		if(!pid) {			//child send CMD
			if(p->id == cli) {
				cmd.cmdtyp = p->reqtyp;
				cmd.c = *p;
				cmd.urls = urls;
				memset(cmd.req, 0x0, MAXREQ);
				strncpy(cmd.req, req, strlen(req));
		
				if(msgsnd(p->mq_id, &cmd, sizeof(srv_cmd), 0) == -1) {
					perror("msgsnd");
					free_clis(ginf);
					exit(0);
				}
				free_clis(ginf);
				exit(0);
			}
		}
		else if(pid == -1) {	//fork error
				ginf->resultc = FORK_ERR;
				return 0;
		}	
		else	{
			ginf->resultc = SUCCESS;
			return 1;	//client found && fork done
		}
	}
	ginf->resultc = NO_SUCH_CLI;
	return 0;		//client not found 
}

cli_ans	goo_results(goo_t *ginf) 
{
	cli_ans rcv;
	if(msgrcv(ginf->goo_results, &rcv, sizeof(cli_ans), CLIANW, IPC_NOWAIT) == -1) {
		if(errno == ENOMSG) {
			ginf->resultc = NOANSWER;
			return rcv;
		}	
		perror("msgrcv");
		exit(0);
	}
	ginf->resultc = rcv.resultc;
	return rcv;
}

/*
void	usleep(long usec)
{
	struct timeval tv;
	tv.tv_sec = 0;
	tv.tv_usec = usec;
	select(0,NULL,NULL,NULL,&tv);
}
*/

int	goo_cli_cnt(goo_t *ginf)
{
	int i = 0;
	goo_client *p;

	p = ginf->cli;
	while (p != NULL) {
		if(p->mq_id) i++;
		p = p->next;
	} 
	return i;
}

int	connect_me(char *dest, int port, int *sock, goo_t *ginf)
{
	struct sockaddr_in servaddr;
	struct hostent *he;
	
	if( (he = gethostbyname(dest)) == NULL) {
		ginf->resultc = DNSERROR;
		return -1;
	}
	
	servaddr.sin_addr   = *((struct in_addr *) he->h_addr);
	servaddr.sin_port = htons(port);
	servaddr.sin_family = AF_INET;
	//printf("connecting...\n");
	if(connect(*sock, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)) == -1) {
		if(errno == EISCONN) {
			ginf->resultc = SUCCESS;
			//printf("still connected\n");
			return 1;
		}
		ginf->resultc = CONNFAILD;
		return -1;
	}
	//printf("connected!\n");
	ginf->resultc = SUCCESS;
	return 1;
}

char	*encode(char *str)
{
	char *query, *ptr;
	int nlen, i;

	if(str == NULL) return NULL;

	nlen = strlen(str)*3+5;
	if( (query = malloc(nlen)) == NULL) {
		perror("malloc");
		return NULL;
	}
	ptr = str;
	for(i = 0; i <= nlen-5, *ptr != '\0'; i+=3) {
		sprintf(&query[i], "%c%X",'%',*ptr++);
	}
	query[i] = '\0';
	return query;
}

void	free_clis(goo_t *ginf)
{
	goo_client *p, *n;

	p = ginf->cli;
	while(p != NULL) {
		n = p;
		p = p->next;
		free(n);
	}
}

int	delete_goo(goo_t *ginf)
{
	goo_client *p;
	srv_cmd quit;

	quit.mtyp = CMD;
	quit.cmdtyp = QUIT;

	sleep(1);

	if(msgctl(ginf->goo_results, IPC_RMID, NULL) == -1)
		printf("warning: cannot delete queue %d\n",p->mq_id);

	p = ginf->cli;
	while(p != NULL) {
		if(p->mq_id) 
			if(msgsnd(p->mq_id, &quit, sizeof(srv_cmd),0) == -1) 
				printf("warning: cannot kill clients\n");
		p = p->next;
	} 
	p = ginf->cli;
	while(p != NULL) {
		if(p->mq_id) 
			if(msgctl(p->mq_id, IPC_RMID, NULL) == -1)
				printf("warning: cannot delete queue %d\n",p->mq_id);
		p = p->next;
	} 
	free_clis(ginf);
	return 1;
}


char	*http_response(int sockfd, int *nbytes, goo_t *ginf)
{
	int n;
	char *ptr, *recv, *sav;
	char buff[BUFFLEN];

	recv = NULL;
	*nbytes = 1;

	while(n = read(sockfd, buff, BUFFLEN)) {
		buff[n] = '\0';
		
		if( (sav = realloc(recv, *nbytes += n)) == NULL) {
			ginf->resultc = ALLOC_ERR;
			if(strlen(recv))
				free(recv);
			return NULL;
		} else { 
			recv = sav;
			strcpy(&recv[*nbytes-(n+1)], buff);

			ptr = buff;
			while(n--)
				if( memcmp(ptr++, "0\r\n\r\n", strlen("0\r\n\r\n")) == 0) {
					n = -2;
					break;
				}
			if(n == -2) break;  
			else {
				memset(buff,0x0,255);
				continue;
			} 
		}
		if(n == -1) {
			ginf->resultc = READERR;
			if(strlen(recv))
				free(recv);
			return NULL;
		}
	}
	return recv;
}


int     get_urls(char *req, cli_ans *ans, goo_client me, goo_t *ginf, int start)
{
	char *host, *q, http[HTTPREQ], *rcv, *sav, buff[BUFFLEN];
	char *ptr, results[20];
	int  j = 0, port, i = 0;
	static int sockfd;
	u_int n, nbytes;
	long res;

	if(!sockfd) {
		sockfd = socket(AF_INET, SOCK_STREAM, 0);
		if(sockfd == -1) {
			ginf->resultc = SOCKETERR;
			return 0;
		}
	}

	q = encode(req);
	if(q == NULL) {
		ginf->resultc = ALLOC_ERR;
		return 0;
	}

	memset(http,0x0,HTTPREQ);
	if(me.proxy[0] != 0x0) {
		if( (host = strchr(me.proxy, ':')) == NULL) {
			ginf->resultc = ILLGL_PXY;
			return 0;
		}
		port = atoi(strtok(host, ":"));
		host = strtok(me.proxy, ":");
		snprintf(http, HTTPREQ, "GET http://%s/search?q=%s&start=%d  HTTP/1.0\n\n",DEF_GOOGLE,q,start);
	} else {
		host = malloc(strlen(DEF_GOOGLE)+1);
		j=1;	//free host space
		strcpy(host,DEF_GOOGLE);
		port = 80;
		snprintf(http, HTTPREQ, "GET /search?q=%s&start=%d HTTP/1.1\n\n",q,start);
	}
	free(q);  //free query heap space 

	rcv = NULL;
	
	if(connect_me(host, port, &sockfd, ginf) == -1) {
		if(j) free(host);
		return 0;
	}

	if(j) free(host);
	//if(sockfd == -1) 
	//	return 0;

	n = write(sockfd, http, strlen(http));
	if(n == -1) {
		ginf->resultc = SENDERR;
		return 0;
	}
	
	rcv = http_response(sockfd, &nbytes, ginf);
	if(rcv == NULL) return 0;	

	i = j = 0;
	ptr = rcv;
	while(nbytes--) {
		if(i == 10) break;
		if(memcmp(ptr++, URL_PATTN, strlen(URL_PATTN)) == 0) {
			ptr += strlen(URL_PATTN);
			nbytes -= strlen(URL_PATTN);
			while(*ptr != '\"') {
				ans->data[i][j++] = *ptr++;
				nbytes--;
			}
			ans->data[i][j] = '\0';
			j=0;
			i++;
		}
	} 
	free(rcv);
	return i;
}


u_long	get_result(char *req, cli_ans *ans, goo_client me, goo_t *ginf)
{
	char *host, *q, http[HTTPREQ], *rcv, *sav, buff[BUFFLEN];
	char *ptr, results[20];
	int j = 0, port, i = 0;
	static int sockfd;
	int n, nbytes;
	long res;


	if(!sockfd) {
		sockfd = socket(AF_INET, SOCK_STREAM, 0);
		if(sockfd == -1) {
			ginf->resultc = SOCKETERR;
			return 0;
		}
	}


	q = encode(req);
	if(q == NULL) {
		ginf->resultc = ALLOC_ERR;
		return 0;
	}

	memset(http,0x0,HTTPREQ);
	if(me.proxy[0] != 0x0) {
		if( (host = strchr(me.proxy, ':')) == NULL) {
			ginf->resultc = ILLGL_PXY;
			return 0;
		}
		port = atoi(strtok(host, ":"));
		host = strtok(me.proxy, ":");
		snprintf(http, HTTPREQ, "GET http://%s/search?q=%s  HTTP/1.0\n\n",DEF_GOOGLE,q);
	} else {
		host = malloc(strlen(DEF_GOOGLE)+1);
		j=1;	//free host space
		strcpy(host,DEF_GOOGLE);
		port = 80;
		snprintf(http, HTTPREQ, "GET /search?q=%s HTTP/1.1\n\n",q);
	}
	free(q);  //free query heap space 

	rcv = NULL;

	if(connect_me(host, port, &sockfd, ginf) == -1) {
		if(j) free(host);
		return 0;
	}

	if(j) free(host);
	//if(sock == -1) 
	//	return 0;

	n = write(sockfd, http, strlen(http));
	if(n == -1) {
		ginf->resultc = SENDERR;
		return 0;
	}


	rcv = http_response(sockfd, &nbytes,  ginf);
	if(recv == NULL) return 0;	

	//close(sock);

	ptr = rcv;
	while(nbytes--) {
		if(memcmp(ptr++, RES_PATTN, strlen(RES_PATTN)) == 0) {
			ptr += strlen(RES_PATTN)-1;
			while(memcmp(ptr, "</b>", 4)) {
				if(*ptr == ',' || *ptr == '.') { ptr++; continue; }
				results[i++] = *ptr++;
			}
			break;
		}
	} results[i] = '\0';
	res = atol(results);  // STRTOUL
	if(errno == 34)
		ginf->resultc = TOO_LARGE;
	else ginf->resultc = SUCCESS;
	free(rcv);
	return (res);
}

void	goo_prnt_er(goo_t *ginf)
{
	switch(ginf->resultc) {
	case FAILD:
		printf("action faild");
		break;
	case SUCCESS:
		printf("no error");
		break;
	case ALLOC_ERR:
		printf("memory allocation error");
		break;
	case FORK_ERR:
		printf("fork error");
		break;
	case NO_SUCH_CLI:
		printf("unknown client");
		break;
	case UNKNOWN:
		printf("unknown error");
		break;
	case ILLGL_PXY:
		printf("wrong proxy syntax");
		break;
	case CONNFAILD:
		printf("connection faild");
		break;
	case SOCKETERR:
		printf("socket error");
		break;
	case SENDERR:
		printf("send error");
		break;
	case READERR:
		printf("read error");
		break;
	case TOO_LARGE:
		printf("detected too large data");
		break;
	default:
		printf("(null)");
	}
	printf("\n");
}


