/* Copyright 2013 Akira Ohta (akohta001@gmail.com)
    This file is part of ntch.

    The ntch 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.

    The ntch 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 ntch.  If not, see <http://www.gnu.org/licenses/>.
    
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>

#include "utils/nt_std_t.h"
#include "utils/text.h"
#include "utils/db.h"
#include "net/nt_cookie.h"


static void cookie_free_func(void *ptr)
{
	nt_key_value_tp kvp;
	if(!ptr)
		return;
	kvp = (nt_key_value_tp)ptr;
	if(kvp->key)
		free(kvp->key);
	if(kvp->value)
		free(kvp->value);
	free(kvp);
}


nt_cookie_tp nt_load_cookies(const char *db_path)
{
	DBM *dbm;
	datum d_key, d_data;

	int len;
	char *file_name;
	char *key, *data;
	nt_cookie_tp cookiep;
	nt_link_tp cookie_linkp, linkp;
	nt_key_value_tp kvp;

	cookie_linkp = NULL;

	memset(&d_key, '\0', sizeof(datum));
	memset(&d_data, '\0', sizeof(datum));

	assert(db_path);

 	dbm = dbm_open((char*)db_path, O_RDWR | O_CREAT, 0600);
	if(dbm == NULL){
		return NULL;
	}

	for(d_key = dbm_firstkey(dbm);
				NULL != d_key.dptr;
				d_key = dbm_nextkey(dbm)){
		d_data = dbm_fetch(dbm, d_key);
		if(!d_data.dptr){
			continue;
		}
		key = malloc(d_key.dsize+1);
		if(!key){
			continue;
		}
		memcpy(key, d_key.dptr, d_key.dsize);
		key[d_key.dsize] = '\0';
		data = malloc(d_data.dsize+1);
		if(!data){
			free(key);
			continue;
		}
		memcpy(data, d_data.dptr, d_data.dsize);
		data[d_data.dsize] = '\0';
		kvp = nt_key_value_alloc(key, data);
		if(!kvp){
			free(key);
			free(data);
			continue;
		}
		linkp = nt_link_add_data(cookie_linkp, kvp);
		if(!linkp){
			free(key);
			free(data);
			free(kvp);
			continue;
		}
		if(!cookie_linkp)
			cookie_linkp = linkp;
	}/* end for */

	dbm_close(dbm);

	cookiep = malloc(sizeof(nt_cookie_t));
	if(!cookiep){
		if(cookie_linkp)
			nt_all_link_free(
					cookie_linkp, cookie_free_func);
		return NULL;
	}

	len = strlen(db_path);
	if(len == 0){
		free(cookiep);
		if(cookie_linkp)
			nt_all_link_free(
					cookie_linkp, cookie_free_func);
		return NULL;
	}
	file_name = malloc(len + 1);
	if(!file_name){
		free(cookiep);
		if(cookie_linkp)
			nt_all_link_free(
					cookie_linkp, cookie_free_func);
		return NULL;
	}
	strcpy(file_name, db_path);
	cookiep->db_file_name = file_name;
	cookiep->cookies = cookie_linkp;
	return cookiep;
}


void nt_unload_cookie(nt_cookie_tp cookiep)
{
	assert(cookiep);
	if(cookiep->cookies)
		nt_all_link_free(
				cookiep->cookies, cookie_free_func);
	free(cookiep->db_file_name);
	free(cookiep);

}

static int cmp_link_func(void *lhs, void *rhs)
{
	nt_key_value_tp l, r;

	l = (nt_key_value_tp)lhs;
	r = (nt_key_value_tp)rhs;

	return strcmp(l->key, r->key);
}

static void kvp_free_func(void *ptr)
{
	nt_key_value_tp kvp =
		(nt_key_value_tp) ptr;
	if(kvp->key)
		free(kvp->key);
	if(kvp->value)
		free(kvp->value);
	free(kvp);
}

static char* merge_cookie(const char *val1, const char *val2)
{
	char *cptr;
	int len1, len2, len;
	int i, j, state;
	int start, end;
	const char *val;
	char *key, *value;
	nt_key_value_tp kvp;
	nt_link_tp linkp , wrk_linkp;
	char c;
	
	linkp = NULL;
	key = NULL;
	value = NULL;

	len1 = strlen(val1);
	len2 = strlen(val2);
	cptr = calloc(1, len1 + len2 + 1);
	if(!cptr){
		return NULL;
	}

	for(i = 0; i < 2; i++){
		if(i == 0){
			val = val1;
			len = len1;
		}else {
			val = val2;
			len = len2;
		}
		state = 0;
		start = 0;
		end = 0;
		for(j = 0; j < len; j++){
			c = val[j];
			switch(c){
			case ';':
				if(start >= end){
					state = 0;
					start = j + 1;
					end = start;
					break;
				}
				if(state == 1){
					key = nt_substr(val, start, end);
					value = nt_str_clone(" ");
				}else if(state == 3){
					value = nt_substr(val, start, end);
				}else{
					state = 0;
					start = j + 1;
					end = start;
					break;
				}
				if(strstr(key, "expires") ||
					strstr(key, "path")){
					free(key); free(value);
					state = 0;
					start = j + 1;
					end = start;
					break;
				}
				kvp = nt_key_value_alloc(key, value);
				if(linkp){
					wrk_linkp = nt_link_find(linkp, kvp, cmp_link_func);
					if(wrk_linkp){
						linkp = nt_link_remove2(linkp, wrk_linkp);
						kvp_free_func(wrk_linkp->data);
						free(wrk_linkp);
					}
				}
				wrk_linkp = nt_link_add_data(linkp, kvp);
				if(!linkp)
					linkp = wrk_linkp;
				state = 0;
				start = j + 1;
				end = start;
				break;
			case '=':
				key = nt_substr(val, start, end);
				state = 2;
				start = j+1;
				end = start;
				break;
			case ' ':
				if(state == 0 || state == 2){
					start = j+1;
				}else if(state == 1 || state == 3){
					end = j;
				}
				break;
			default:
				if(state == 0){
					state = 1;
				}else if(state == 2){
					state = 3;
				}
				end = j+1;
				break;
			}/* end switch */
		} /* end for */
		if(start < end){
			if(state == 0 || state == 1){
				key = nt_substr(val, start, end);
				value = nt_str_clone(" ");
			}else if(state == 2 || state == 3){
				value = nt_substr(val, start, end);
			}
			if(strstr(key, "expires") ||
				strstr(key, "path")){
				free(key); free(value);
			}else{
				kvp = nt_key_value_alloc(key, value);
				if(linkp){
					wrk_linkp = nt_link_find(linkp, kvp, cmp_link_func);
					if(wrk_linkp){
						linkp = nt_link_remove2(linkp, wrk_linkp);
						kvp_free_func(wrk_linkp->data);
						free(wrk_linkp);
					}
				}
				wrk_linkp = nt_link_add_data(linkp, kvp);
				if(!linkp)
					linkp = wrk_linkp;
			}
		}
	}
	if(!linkp){
		cptr[0] = '\0';
		return cptr;
	}
	wrk_linkp = linkp;
	do{
		kvp = (nt_key_value_tp)wrk_linkp->data;
		if(wrk_linkp != linkp)
			strcat(cptr, "; ");
		strcat(cptr, kvp->key);
		strcat(cptr, "=");
		strcat(cptr, kvp->value);
		wrk_linkp = wrk_linkp->next;
	}while(linkp != wrk_linkp);
	nt_all_link_free(linkp, kvp_free_func);
	return cptr;
}

BOOL nt_add_cookie(nt_cookie_tp cookiep,
		const char *domain, const char *value)
{
	DBM *dbm;
	datum d_key, d_data;

	int result;
	nt_link_tp linkp;
	nt_key_value_tp kvp;
	nt_key_value_tp old_kvp;
	char *new_cookie;
	char *domainp , *valuep;

	assert(cookiep);
	if(!cookiep->db_file_name)
		return FALSE;

	domainp = nt_str_clone(domain);
	if(!domainp)
		return FALSE;
	valuep = nt_str_clone(value);
	if(!valuep){
		free(domainp);
		return FALSE;
	}

	memset(&d_key, 0, sizeof(d_key));
	memset(&d_data, 0, sizeof(d_data));

	kvp = nt_key_value_alloc(domainp, valuep);
	if(!kvp){
		return FALSE;
	}

	new_cookie = valuep;

	if(cookiep->cookies){
		linkp = nt_link_find(cookiep->cookies, kvp, cmp_link_func);
		if(linkp){
			old_kvp = (nt_key_value_tp)linkp->data;
			new_cookie = merge_cookie(old_kvp->value, valuep);
			free(kvp);free(domainp);free(valuep);
			if(!new_cookie){
				return FALSE;
			}
			free(old_kvp->value);
			old_kvp->value = new_cookie;
			domainp= old_kvp->key;
		}else{
			linkp = nt_link_add_data(cookiep->cookies, kvp);
		}
	}else{
		linkp = nt_link_add_data(cookiep->cookies, kvp);
		cookiep->cookies = linkp;
	}
	
	dbm = dbm_open(cookiep->db_file_name, O_RDWR | O_CREAT, 0600);
	if(dbm == NULL){
	    return TRUE;
	}

	d_key.dsize = strlen(domainp);
	d_key.dptr = domainp;
	d_data.dsize = strlen(new_cookie);
	d_data.dptr = new_cookie;

	result = dbm_store(dbm, d_key, d_data, DBM_REPLACE);
	if(result != 0){
	    dbm_close(dbm);
		return TRUE;
	}
	dbm_close(dbm);

	return TRUE;
}


nt_link_tp  nt_get_cookies(nt_cookie_tp cookiep, const char *host_name)
{
	nt_link_tp cookies, linkp;
	nt_link_tp result_linkp, valuep;
	nt_key_value_tp kvp;
	char *cptr;

	cookies = cookiep->cookies;
	result_linkp = NULL;

	if(!cookies)
		return NULL;
	
	linkp = cookies;
	do{
		kvp = (nt_key_value_tp)linkp->data;

		cptr = strstr(host_name, kvp->key);
		if(cptr){
			valuep = nt_link_add_data(result_linkp, kvp->value);
			if(!result_linkp)
				result_linkp = valuep;
		}
		linkp = linkp->next;
	}while(linkp != cookies);

	return result_linkp;
}

