/*
   lfm_helper.c - Misc helper functions
   See README for Copyright and License
*/

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <curl/curl.h>
#include <curl/easy.h>

#include "lfm_helper.h"

extern int VERBOSE;

extern int LFM_progress_cb(void *data, 
	double dltotal, double dlnow, 
	double ultotal, double ulnow);

const char *HTML_ESCAPE[] = {
	"&amp;", "&",
	"&quot;","\"",
	"&ndash;","-",
	NULL
};

/* Element name and content buffer */
#define BUFFER_SIZE	1024*1024

extern void md5_buffer (const char *buffer, size_t len, void *resblock);

char *unescape_HTML(char *original){
	int i;
	char *cptr;
	if(original == NULL) return NULL;

	for(i=0;HTML_ESCAPE[i];i+=2){
		cptr = strstr(original,HTML_ESCAPE[i]);
		while(cptr){
			// This may not work on older systems
			sprintf(cptr,"%s%s",
				HTML_ESCAPE[i+1],
				cptr+strlen(HTML_ESCAPE[i]));
			cptr = strstr(original,HTML_ESCAPE[i]);
		}
	}
	return original;
}

void string2MD5(const char *string, char *buffer){
	char hash[16]; // 128bit md5
	int i;
	char *cptr;
	md5_buffer(string,strlen(string),(unsigned char *)hash);
	cptr = buffer;
	for(i=0;i<16;i++){
		sprintf(cptr,"%02hhx",(unsigned char)hash[i]);
		cptr+=2;
	}
	buffer[32]=0; // 128bit x 2
}

/* Returns 0 if not entirely white space */
int strisspace(const char *string){
	char *i;
	if(string == NULL)return 1;
	for(i=(char *)string;*i;i++){
		if(!isspace(*i))return 0;
	}
	return 1;
}

static void *myrealloc(void *ptr, unsigned int size)
{
  /* There might be a realloc() out there that doesn't like reallocing
     NULL pointers, so we take care of it here */
  if(ptr)
    return realloc(ptr, size);
  else
    return malloc(size);
}

/* return 1 for an error or bad status, 0 otherwise */
int lfm_helper_get_status(LASTFM_SESSION *s,XMLNode *xml){
	XMLNode *xi=NULL,*xj=NULL;

	if(xml == NULL) {
		s->status[0] = 0;
		return 1;
	}

	xi = xmlnode_get(xml,CCA { "lfm",NULL } , "status", NULL );
	if(xi){
		xj = xmlnode_get(xml,CCA { "lfm","error",NULL}, NULL, NULL );
		if(xj){
			s->error_code = atoi(xj->attributes->content);
			strncpy(s->error_text,xj->content,SMALL_BUFFER);
			s->error_text[SMALL_BUFFER-1] = 0;
		}else{
			s->error_code = 0;
			s->error_text[0] = 0;
		}
		
		strcpy(s->status,xi->content);
		return strcmp(s->status,"ok");
	}else {
		s->status[0] = 0;
		return 1;
	}
}

static void xmlnode_add(XMLNode **head,XMLNode *node){
	XMLNode *walk = NULL;
	XMLNode *walk_p = NULL;

	if(*head == NULL){
		*head = node;
	}else{
		for(walk = *head ; walk ; walk = walk->next){
			walk_p = walk;
		}
		walk_p->next  = node;
	}
}

static XMLNode *xmlnode_new(char *name){
	XMLNode *node;
	node = malloc(sizeof(XMLNode));
	node->name       = strdup(name);
	node->content    = NULL;
	node->attributes = NULL;
	node->children   = NULL;
	node->next       = NULL;
	return node;
}

static char * _tinycxml_parse(char *xml,XMLNode *parent, char *buffer){
	char *c = NULL;
	char *n = NULL;
	char *p = NULL;
	XMLNode *node = NULL;
	
	if(xml == NULL) return NULL;
	if(*xml == 0)   return xml;
	c = strstr(xml,"<");
	if(c == NULL)   return NULL;
	c++;
	if(*c == '/' )  return c;

	p = buffer;
	for(;*c && *c != '>' && *c != ' '; c++){
		*(p++) = *c;
	}
	*p = 0;

	node = xmlnode_new(buffer);
	
	xmlnode_add(&parent->children,node);
	
	XMLNode *attrib;
	if(*c == ' '){ // Attributes
		p = buffer;
		n = c;
		c = strstr(c,">");
		for(n++;n<c;n++){
			if(*n == '='){
				*p = 0;
				attrib = xmlnode_new(buffer);
				xmlnode_add(&node->attributes,attrib);
				p = buffer;
				// n+=2 // Get past the opening " char
				for(n+=2;n<c && *n!='"';n++){
					*(p++) = *n;
				}
				*p = 0;
				attrib->content = strdup(buffer);
				p = buffer;
			}else if (*n != ' '){
				*(p++) = *n;	
			}
		}
		// Self closing tag?
		if( *(c-1) == '/' ) return c;
	}
	c++;

	/* There is a lot of XML out there that invalidates
	 * XML spec by using '<' and friends inside a CDATA tag.
	 * Although according to the spec we should just error
	 * out, I prefer to mitigate errors in well known conditions. */

	/* Raw copy in the CDATA tag */
	/* Assume CDATA is the only content */
	if(strncmp(c,"<![CDATA[",9) == 0){
		n = strstr(c,"]]>");
		n += 3;
		node->content = strndup(c,n-c);
		n = strstr(n,">");
		return (n) ?  n+1 : NULL;
	}

	while(1){
		n = _tinycxml_parse(c,node,buffer);
		if (n == NULL ) break;
		if(*n == '/' ) {
			memcpy(buffer,c,n-c-1);
			buffer[n-c-1] = 0;
			if(!strisspace(buffer)){
				node->content = strdup(buffer);
			}
			n = strstr(n,">");
			return (n) ?  n+1 : NULL;
		}
		if(*n == 0) break;
		c = n;
	}
	return NULL;
}

XMLNode *tinycxml_parse(char *xml){
	char *c = NULL;
	char *buffer = NULL;
	XMLNode node;

	if(xml == NULL) return NULL;

	node.children = NULL;

	c = strstr(xml,"<?xml");
	if(c){
		c = strstr(xml+5,"?>") + 2;
	}
	if (c == NULL) c = xml;

	buffer = malloc(sizeof(char) * BUFFER_SIZE);
	_tinycxml_parse(c,&node,buffer);
	free(buffer);
	return node.children;
}

void xmlnode_free(XMLNode *node){
	if(node == NULL) return;
	if(node->name) free(node->name);
	if(node->content) free(node->content);
	xmlnode_free(node->attributes);
	xmlnode_free(node->children);
	xmlnode_free(node->next);
	free(node);
}

void tinycxml_dump(XMLNode *root){
	if(root == NULL) return;
	printf("[%p] name = %s, content = %s\n",
		root,root->name,root->content);
	tinycxml_dump(root->attributes);
	tinycxml_dump(root->children);
	tinycxml_dump(root->next);
}

static XMLNode *_xmlnode_get_wcontent(XMLNode *root, const char *name, const char *value){
	XMLNode *i,*j;
	for(i = root; i ; i= i->next){
		for(j = i->attributes ; j ; j=j->next){
			if( strcmp(j->name,name)     == 0) {
				if(value == NULL) return j;
				if(strcmp(j->content,value) == 0)return i;
			}
		}
	}
	return NULL;
}

static XMLNode *_xmlnode_get(XMLNode *root, const char **path, int pos){
	XMLNode *walk;
	if ( root == NULL) return NULL;
	for(walk = root ; walk ; walk=walk->next){
		if(strcmp(walk->name,path[pos])== 0) {
			if(path[pos + 1] == NULL) return walk;
			return _xmlnode_get(walk->children,path,pos+1);	
		}
	}
	return NULL;
}

XMLNode *xmlnode_get(XMLNode *root, const char **path, const char *name, const char *value){
	XMLNode *node;

	node = _xmlnode_get(root,path,0);

	if(name){
		return _xmlnode_get_wcontent(node,name,value);
	}else{
		return node;
	}
	
}

size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data){
	size_t realsize = size * nmemb;
	WebData *mem = data;
	char *page = NULL;

	/* Realloc the existing size + the new data size + 1 for the null terminator */
	page = myrealloc(mem->page, mem->size + realsize + 1);
	if (page) {
		mem->page = page;
		memcpy(mem->page+mem->size, ptr, realsize);
		mem->size += realsize;
		mem->page[mem->size] = 0;
		return realsize;
	}else {
		perror("write_cb: Could not realloc");
		return 0;
	}
}

WebData *lfm_helper_post_page(CURL *curl,  void *cb_data, const char *url,
							const char *args){
	WebData *wpage;
	int own_curl=0;
	
	if(VERBOSE)
		printf("liblastfm: POST %s\n",url);

	wpage = malloc(sizeof(WebData));
	wpage->size = 0;
	wpage->page = NULL;

	/* Are we using our own handle ? */
	if(!curl){
		own_curl=1;
		curl = curl_easy_init();
		curl_easy_setopt(curl, CURLOPT_POST, 1) ;
		/* send all data to this function  */
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
		/* Allow redirection */
		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); 
		/* Progress bar */
		curl_easy_setopt(curl,CURLOPT_NOPROGRESS,0);
		curl_easy_setopt(curl,CURLOPT_PROGRESSFUNCTION,LFM_progress_cb);
		curl_easy_setopt(curl,CURLOPT_PROGRESSDATA,cb_data);
	}

	/* we pass our 'WebData' struct to the callback function */
	curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)wpage);
	curl_easy_setopt(curl, CURLOPT_URL, url);
	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, args);

	curl_easy_perform(curl);

	/* Did we use our curl, and was the result not bad */
	if(own_curl){
		curl_easy_cleanup(curl);
	}

	if(wpage->size == 0){
		if(wpage->page){
			free(wpage->page);
			wpage->page = NULL;
		}
	}
	return wpage;
}
	
int lfm_helper_free_page(WebData *wpage){
	if(wpage == NULL)return 1;
	if(wpage->page)	free(wpage->page);
	free(wpage);
	return 0;
}
	
WebData *lfm_helper_get_page(const char *url, void *cb_data)
{
	/* libcurl handle */
	CURL *curl_handle;

	WebData *chunk = NULL;
	
	if(url == NULL) return NULL;

	if(VERBOSE)
		printf("liblastfm: GET %s\n",url);

	chunk = malloc(sizeof(WebData));

	chunk->page	= NULL; 
	chunk->size	= 0; 

	/* init the curl session */
	curl_handle = curl_easy_init();

	/* specify URL to get */
	curl_easy_setopt(curl_handle, CURLOPT_URL, url);

	/* send all data to this function  */
	curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_cb);

	/* we pass our 'chunk' struct to the callback function */
	curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)chunk);

	/* some servers don't like requests that are made without a user-agent
	 * field, so we provide one */
	/* Just in case somone out there doesnt like us */
	//curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
	curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, 
"Mozilla/5.0 (X11; U; Linux 2.4.2-2 i586; en-US; m18) Gecko/20010131 Netscape6/6.01");

	/* progress bar */
	curl_easy_setopt(curl_handle,CURLOPT_NOPROGRESS,0);
	curl_easy_setopt(curl_handle,CURLOPT_PROGRESSFUNCTION,LFM_progress_cb);
	curl_easy_setopt(curl_handle,CURLOPT_PROGRESSDATA,cb_data);

	/* get it! */
	curl_easy_perform(curl_handle);

	/* cleanup curl stuff */
	curl_easy_cleanup(curl_handle);

	if(chunk->size == 0){
		if(chunk->page){
			free(chunk->page);
			chunk->page = NULL;
		}
	}
	return chunk;
}
