/*
 * Copyright 2010 (C) Raster Software Vigo (Sergio Costas)
 * This file is part of CTransmission
 *
 * CTransmission is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * CTransmission is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <stdio.h>

#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <unistd.h>

#include <signal.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdarg.h>

struct sockaddr_in serv_addr;
char *tb_id;

#define TIMEOUT 5
#define elif else if

struct torrent_data {
	int id;
	long long int left;
	int status;
	float ratio;
	struct torrent_data *next;
};

void alarm_handler(int value) {

	printf("Http timeout\n");

}

char http_readline(int fd,char *buffer) {

	char retval,v2;
	int length;

	while(1) {
		length=read(fd,buffer,1);
		if (length<=0) {
			*buffer=0;
			return (-1);
		}
		if ((*buffer==13)||(*buffer==10)) {
			retval=*buffer;
			*(buffer)=0;
			read(fd,&v2,1); // jump over the next char (a newline is always CR+LF)
			return (0);
		} else {
			buffer++;
		}
	}
}

int connect_http(char *uri, char *extra_header,char *data, int data_len) {

	int sockfd,port,length;
	struct hostent *server;
	char *addr, *path, *s_port, *tmp, port_n[10],*petition;
	struct sigaction alarm_action;

	if (uri==NULL) {
		return -1;
	}

	if ((extra_header==NULL)||(extra_header[0]==0)) {
		extra_header=strdup("");
	} else {
		length=strlen(extra_header);
		if (extra_header[length-1]!='\n') {
			tmp=malloc(length+3);
			sprintf(tmp,"%s\r\n",extra_header);
			extra_header=tmp;
		}
	}

	addr=strdup(uri+7);
	tmp=strstr(uri+7,"/");

	if (tmp!=NULL)
		path=strdup(tmp);
	else
		path=strdup("/");

	if (tmp)
		*(strstr(addr,"/"))=0;

	s_port=strstr(addr,":");
	if (s_port) {
		*s_port=0;
		s_port++;
		port=atoi(s_port);
		if ((port<1)||(port>65535)) {
			free(path);
			free(addr);
			free(extra_header);
			return -3;
		}
	} else {
		port=80;
	}

	server=gethostbyname(addr);
	if (server==NULL) {
		free(path);
		free(addr);
		free(extra_header);
		return -4;
	}

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		free(path);
		free(addr);
		free(extra_header);
		return -5;
	}
	memset((char *) &serv_addr, 0,sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;


	memcpy((char *)&serv_addr.sin_addr.s_addr,(char *)server->h_addr,server->h_length);
	serv_addr.sin_port = htons(port);

	alarm_action.sa_handler=alarm_handler;
	alarm_action.sa_flags=0;
	sigemptyset (&alarm_action.sa_mask);
	sigaction(SIGALRM,&alarm_action,NULL);

	alarm(TIMEOUT);

	if (connect(sockfd,(struct sockaddr *)(&serv_addr),sizeof(serv_addr)) < 0) {
		free(path);
		free(addr);
		free(extra_header);
		return -6;
	}
	alarm(0);

	if (port!=80) {
		sprintf(port_n,":%d",port);
	} else {
		port_n[0]=0;
	}
	petition="User-Agent: transmission-remote/2.04 (11151)\r\nContent-Type: application/x-www-form-urlencoded\r\nConnection: close";
	tmp=(char *)malloc(20000);

	if ((data_len==0)||(data==NULL)) {
		sprintf(tmp, "GET %s HTTP/1.1\r\nHost: %s%s\r\n%s\r\n%s\r\n",path,addr,port_n,extra_header,petition);
	} else {
		sprintf(tmp,"POST %s HTTP/1.1\r\nHost: %s%s\r\nContent-Length: %d\r\n%s%s\r\n\r\n",path,addr,port_n,data_len,extra_header,petition);
	}

	free(addr);
	free(path);
	free(extra_header);

	if (-1==write(sockfd,tmp,strlen(tmp))) {
		free(tmp);
		close(sockfd);
		return -7;
	}
	free(tmp);
	if ((data_len!=0)&&(data!=NULL)) {
		if (-1==write(sockfd,data,data_len)) {
			close(sockfd);
			return -7;
		}
	}

	return sockfd;
}

/*	error=0;
	size=0;
	while(1) {
		http_readline(sockfd,buffer);
		if (!buffer[0]) {
			break;
		}
		if (0==strncmp("HTTP/1.1 ",buffer,9)) {
			if (buffer[9]!='2') {
				error=1;
			}
		}
		if (0==strncmp("Content-Length: ",buffer,16)) {
			sscanf(buffer+16,"%lld",&size);
		}
	}
	if(error) {
		close(sockfd);
		return -8;
	}

	socket_fd=sockfd;
	return 0;
}*/

struct torrent_data * get_torrent_string(char *data) {

	struct torrent_data *object;
	char *end,*semi,*colon,*string;

	object=malloc(sizeof(struct torrent_data));
	if(!object) {
		return (NULL);
	}
	string=data;
	while(*string=='{') {
		string++;
	}
	end=strstr(string,"}");
	object->id=-1;
	object->left=-1;
	object->ratio=0.0;
	object->status=-1;
	object->next=NULL;
	while(*string!='}') {
		while(*string!='"') {
			string++;
		}
		semi=strstr(string,":");
		if(!strncmp(string,"\"id\":",5)) {
			sscanf(semi,":%d",&object->id);
		} elif (!strncmp(string,"\"leftUntilDone\":",16)) {
			sscanf(semi,":%lld",&object->left);
		} elif (!strncmp(string,"\"status\":",9)) {
			sscanf(semi,":%d",&object->status);
		} elif (!strncmp(string,"\"uploadRatio\":",14)) {
			sscanf(semi,":%f",&object->ratio);
		}
		colon=strstr(string,",");
		if(colon>end) {
			break;
		} else {
			string=colon;
		}
	}

	return (object);

}

int read_header(char *uri,char *header,int *c_len,int *error) {

	int fd,c_len2;
	char buffer[4000];

	fd=connect_http(uri,tb_id,header,strlen(header));
	if (fd<0) {
		*error=-1;
		return (fd);
	}

	*error=0;
	*c_len=0;
	while(1) {
		http_readline(fd,buffer);
		if (!buffer[0]) {
			break;
		}
		if (0==strncmp("HTTP/1.",buffer,7)) {
			if (buffer[9]!='2') {
				*error=-1;
			}
			if (0==strncmp(buffer+9,"409",3)) {
				*error=-2;
			}
		}
		if (0==strncmp("Content-Length: ",buffer,16)) {
			sscanf(buffer+16,"%d",&c_len2);
			*c_len=c_len2;
		}
		if ((*error==-2)&&(0==strncmp("X-Transmission-Session-Id: ",buffer,27))) {
			if (tb_id) {
				free(tb_id);
			}
			tb_id=strdup(buffer);
		}
	}

	return (fd);
}

struct torrent_data * get_torrent_data_b(char *uri,int *error) {

	char cadena2[1024];
	char *buf,*string;
	int fd,c_len;
	struct torrent_data *list,*last,*tmp;

	list=last=NULL;

	//sprintf(cadena2,"{\"arguments\":{\"fields\":[\"error\",\"errorString\",\"eta\",\"id\",\"isFinished\",\"leftUntilDone\",\"name\",\"peersGettingFromUs\",\"peersSendingToUs\",\"rateDownload\",\"rateUpload\",\"sizeWhenDone\",\"status\",\"uploadRatio\"]},\"method\":\"torrent-get\",\"tag\":%d}\n",t_number);
	sprintf(cadena2,"{\"arguments\":{\"fields\":[\"id\",\"leftUntilDone\",\"status\",\"uploadRatio\"]},\"method\":\"torrent-get\",\"tag\":2}\n");

	fd=read_header(uri,cadena2,&c_len,error);

	if ((*error!=0)||(c_len==0)) {
		close(fd);
		return NULL;
	}

	buf=malloc(c_len+1);
	read(fd,buf,c_len);
	close(fd);
	buf[c_len]=0;

	string=strstr(buf,"[");
	if (string==NULL) {
		free(buf);
		return (NULL);
	}

	string++;
	if (*string==']') {
		free(buf);
		return (NULL);
	}

	do {
		tmp=get_torrent_string(string);
		if (tmp) {
			if (last) {
				last->next=tmp;
				last=tmp;
			} else {
				list=last=tmp;
			}
		}
		string=strstr(string,"}");
		if (string==NULL) {
			break;
		}
		string++;
		if (*string==']') {
			break;
		}
		string++;
	} while(1);


	free(buf);
	return (list);

}

struct torrent_data * get_torrent_data(char *uri,int *error) {

	struct torrent_data *data;

	data=get_torrent_data_b(uri,error);
	if (*error!=0) {
		data=get_torrent_data_b(uri,error);
	}
	return(data);
}

int start_torrent_b(char *uri,int id) {

	char cadena2[1024];
	char *buf;
	int fd,c_len;
	struct torrent_data *list;
	int error;

	list=NULL;

	sprintf(cadena2,"{\"arguments\":{\"ids\":%d},\"method\":\"torrent-start\"}\n",id);

	fd=read_header(uri,cadena2,&c_len,&error);

	if ((error!=0)||(c_len==0)) {
		close(fd);
		return (error);
	}
	buf=malloc(c_len+1);
	read(fd,buf,c_len);
	close(fd);
	free(buf);
	return(0);
}

int start_torrent(char *uri,int id) {

	if (0!=start_torrent_b(uri,id)) {
		return (start_torrent_b(uri,id));
	}
	return 0;
}

int stop_torrent_b(char *uri,int id) {

	char cadena2[1024];
	char *buf;
	int fd,c_len;
	struct torrent_data *list;
	int error;

	list=NULL;

	sprintf(cadena2,"{\"arguments\":{\"ids\":%d},\"method\":\"torrent-stop\"}\n",id);

	fd=read_header(uri,cadena2,&c_len,&error);

	if ((error!=0)||(c_len==0)) {
		close(fd);
		return (error);
	}
	buf=malloc(c_len+1);
	read(fd,buf,c_len);
	close(fd);
	free(buf);
	return(0);
}


int stop_torrent(char *uri,int id) {

	if (0!=stop_torrent_b(uri,id)) {
		return (stop_torrent_b(uri,id));
	}
	return 0;
}


void set_download(char *uri,int maximum,struct torrent_data *data) {

	int counter;
	struct torrent_data *tmp;

	for(tmp=data,counter=0;tmp!=NULL;tmp=tmp->next) {
		if ((counter<maximum)&&(tmp->left)) {
			if (0==start_torrent(uri,tmp->id)) {
				counter++;
			}
		} else {
			stop_torrent(uri,tmp->id);
		}
	}
}

int set_upload_b(char *uri,int maximum,struct torrent_data *data,float ratio) {

	int counter;
	struct torrent_data *tmp;

	for(tmp=data,counter=0;tmp!=NULL;tmp=tmp->next) {
		if ((counter<maximum)&&(tmp->ratio<ratio)) {
			if (tmp->status&0x16) {
				if (0==start_torrent(uri,tmp->id)) {
					counter++;
				}
			} else {
				counter++;
			}
		} else {
			if (!(tmp->status&0x16)) {
				stop_torrent(uri,tmp->id);
			}
		}
	}
	return (counter);
}

void set_upload(char *uri,int maximum,struct torrent_data *data) {

	float ratio=1.0;
	int counter;
	struct torrent_data *tmp;

	while(1) {
		for(tmp=data,counter=0;tmp!=NULL;tmp=tmp->next) {
			if ((counter<maximum)&&(tmp->ratio<ratio)) {
				counter++;
			}
		}
		if (counter<maximum) {
			ratio+=0.5;
		} else {
			break;
		}
	}
	counter=set_upload_b(uri,maximum,data,ratio);
}

int set_torrents(char *uri, int maximum,int inactive) {

	int error,downloading,waiting,ended,serving,total;
	struct torrent_data *data,*tmp,*tmp2;

	data=get_torrent_data(uri,&error);
	if (error!=0) {
		return -1;
	}

	downloading=waiting=ended=serving=total=0;
	for(tmp=data;tmp!=NULL;tmp=tmp->next) {
		total++;
		if (tmp->left!=0) {
			if (tmp->status&0x04) {
				downloading++;
			} else {
				waiting++;
			}
		} else {
			if (tmp->status&0x08) {
				serving++;
			} else {
				ended++;
			}
		}
	}

	if (total<maximum) {
		maximum=total;
	}

	if (maximum==0) {
		maximum=total;
	}
	if (total<inactive) {
		inactive=total;
	}
	if (inactive==0) {
			inactive=total;
		}
	if (total!=0) {
		if (((downloading<maximum)&&(waiting!=0))||(downloading>maximum)||((downloading+waiting>0)&&(serving>0))) {
			set_download(uri,maximum,data);
		} elif ((downloading!=maximum)&&((downloading==0)||(waiting!=0))) {
			set_upload(uri,inactive,data);
		}
	}

	for(tmp=data;tmp!=NULL;tmp=tmp2) {
		tmp2=tmp->next;
		free(tmp);
	}

	return 0;
}

int main(int argc,char **argv) {

	char uri[1024];
	int active;
	int inactive;
	int time;

	tb_id=NULL;

	if (argc!=5) {
		printf("CTransmission 1.0\n");
		printf("Usage: ctransmission address[:port] active_downloads active_uploads time\n\n");
		exit(1);
	}

	sprintf(uri,"http://%s/transmission/rpc",argv[1]);
	active=atoi(argv[2]);
	inactive=atoi(argv[3]);
	time=atoi(argv[4]);

	while(1) {
		set_torrents(uri,active,inactive);
		if (time)
			sleep(time);
		else
			break;
	}
	if (tb_id) {
		free(tb_id);
	}
	return 0;
}
