#include <errno.h>
#include <string.h>
#include <stdio.h>

#include "lib.h"
#include "onlinelib.h"
#include "lprintf.h"
#define DMID 2
#include "debug.h"
#include "thread.h"

#include "vsnprintf.h"


#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE 1
#endif

extern int emsg(const char * arg_list, ...);
extern int imsg(const char * arg_list, ...);
extern int dmsg(const char * arg_list, ...);

void ol_errno_error(OnlineCon *ol);
void ol_tcp_error(OnlineCon *ol);
void ol_reset_cb(OnlineCon *ol);

/************/

OnlineCon *ol_new()
{
	OnlineCon *ol = (OnlineCon *)calloc(1,sizeof(OnlineCon));
	return ol;
}
void ol_free(OnlineCon **ol)
{
	if (!ol || !*ol) return;
	ol_quit(*ol);
	ffree((*ol)->user);
	free(*ol);
	*ol = NULL;
}

void ol_set_cb(OnlineCon *ol, void (*cb)(void*,OnlineCon*), void *data)
{
	ol->data = data;
	ol->cb = cb;
}

int ol_connect(OnlineCon *ol, char *host, int port)
{
	if (!ol) return FALSE;
	if (ol->c) ol_disconnect(ol);
	tcp_new(&ol->c);
	if (!tcp_open(ol->c,host,port,NULL)) {
		ol_tcp_error(ol);
		return FALSE;
	}
	return TRUE;
}
void ol_disconnect(OnlineCon *ol)
{
	if (!ol) return ;
	if (!ol->c) return ;
	tcp_close_nice(ol->c);
	tcp_free(&ol->c);
}

int ol_login(OnlineCon *ol, char *user, char *pass, char *devid)
{
	if (!ol) return FALSE;
	if (ol->user) {
		if (strcmpnc(ol->user,user)==0) return TRUE;
		free(ol->user);
	}
	if (!ol_command(ol,TRUE,"LOGIN %s %s %s",user,devid, pass)) return FALSE;
	ol->loggedin = TRUE;
	ol->user = (char *)strdup(user);
	return TRUE;
}
int ol_quit(OnlineCon *ol)
{
	if (!ol) return FALSE;
	if (!ol->c) return TRUE;
	ol_command(ol,TRUE,"QUIT");
	ol_disconnect(ol);	
	if(ol->log) fclose(ol->log);
	return TRUE;
}

int ol_command(OnlineCon *ol, int read_response, char *format, ...)
{
	char bf[BFSZ],tstr[BFSZ];
	va_list args;
	int x;

	if (!ol) return FALSE;
	if (!ol->c) return FALSE;

	va_start(args,format);
	x = vsnprintf(bf,BFSZ,format,args);
	if (x == -1 || x == BFSZ) bf[(x = BFSZ-1)] = 0;

	if(ol->log){
		lib_emsg_time(tstr);
		fprintf(ol->log,"%s:CLIENT:%d(%d){",tstr,th_id(),x);
		fwrite(bf,1,x,ol->log);	// Allow for full debug logging
		fprintf(ol->log,"}\n");
	}
	if (!ol_send_command(ol,bf,x)) return FALSE;
	if (read_response) {
		if (!ol_read_response(ol)) return FALSE;
		if (!ol_ok(ol)) return FALSE;
	}

	return TRUE;
}

int ol_set_status(OnlineCon *ol, char *status)
{
	return ol_command(ol,TRUE,"SET_STATUS \"%s\"",status);
}

int ol_set_device(OnlineCon *ol, char *device)
{
	return ol_command(ol,TRUE,"SET_DEVICE \"%s\"",device);
}

int ol_set_client(OnlineCon *ol, char *client)
{
	return ol_command(ol,TRUE,"SET_CLIENT \"%s\"",client);
}


int ol_idle(OnlineCon *ol)
{	
	int ret;
	ol->idling=TRUE;
	ret=ol_command(ol,TRUE,"IDLE");
	ol->idling=FALSE;
	return ret;
}

/*
 idle
 +IDLE Sending updates...(chrisp@netwin.co.nz)
 *online 1299633324 chrisp@netwin.co.nz devname= pic=0
 
 a
 +OK Idle finished (chrisp@netwin.co.nz)
 
 set_picture 10
 abcdefghij+OK set_picture done
 
 get_picture chrisp@netwin.co.nz
 +BINARY 10
 abcdefghij+OK get_picture sent 10 bytes
 
 idle
 +IDLE Sending updates...(chrisp@netwin.co.nz)
 *online 1299633378 chrisp@netwin.co.nz devname= pic=1299633424
 
 */ 
int ol_set_picture(OnlineCon *ol, int length, void *data, int *timestamp_out)
{
	char *s;
	*timestamp_out=0;
	if (length==0 || length>1000000 || !data)  return FALSE;
	if (!ol_command(ol,FALSE,"set_picture %d", length)) return FALSE;
	if (!tcp_write(ol->c,data,length)) return FALSE;
	if (!ol_read_response(ol)) return FALSE; //...+OK set_picture done
	if (!ol_ok(ol)) return FALSE;
	s=strstr(ol->result, "mtime=");
	if (s) *timestamp_out=atoi(s+6);
	return TRUE;
}
int ol_get_picture(OnlineCon *ol, char *email, char **data_out, int *length_out, int *timestamp_out)
{
	int bytes,done,x;
	char *s;
	*timestamp_out=0;
	if (!email) return FALSE;
	if (!ol_command(ol,FALSE,"get_picture %s ",email)) return FALSE;
	if (!ol_read_response(ol)) return FALSE; //+BINARY 10
	if (!ol_plus(ol)) return FALSE;
	if (strncmpnc(ol->result,"+BINARY ",8)!=0) return FALSE;
	bytes = atoi(ol->result+8);
	if (bytes<=0 || bytes>1000000) return FALSE;
	*data_out=calloc(bytes+10, 1);
	if(!*data_out) return FALSE;

    for(done = 0; done < bytes; done += x) {
        x = tcp_read_timed(ol->c,(char *) (*data_out)+done,bytes-done,60000);
        if (x <= 0) { ol_tcp_error(ol); goto error; }
    }
    
	if (!ol_read_response(ol)) goto error;
	if (!ol_ok(ol)) goto error;
	*length_out=bytes;
	s=strstr(ol->result, "mtime=");
	if (s) *timestamp_out=atoi(s+6);
	return TRUE;
error:
	free(data_out);
	return FALSE;
}


int ol_send_command(OnlineCon *ol, char *bf, int x)
{	
	if (!ol) return FALSE;

	/* Ensure exactly one \r\n on command */
	for(; x > 0 && iseol(bf[x-1]); x--) bf[x-1] = '\0';
	if (x > BFSZ-3) x = BFSZ-3;
	strcpy(&bf[x],"\r\n");
	x += 2;

	/* Send tag, space, command */
	if (/*tcp_write(ol->c,im_tag(ol),TAGSZ-1) == TAGSZ-1 &&
		tcp_write(ol->c," ",1) &&*/
		tcp_write(ol->c,bf,x) == x)
		return TRUE;

	ol_tcp_error(ol);
	return FALSE;	
}
int ol_log_open(OnlineCon *ol, char *fname)
{
	ol->log = fopen(fname,"a");
	return TRUE;
}
int ol_log_server(OnlineCon *ol)
{
	char tstr[BFSZ];
	if(!ol->log) return TRUE;
	lib_emsg_time(tstr);
	fprintf(ol->log,"%s:SERVER:%d(%d){",tstr,th_id(),(int) lib_strlen(ol->result));
	fwrite(ol->result,1, lib_strlen(ol->result),ol->log);	// Allow for full debug logging
	fprintf(ol->log,"}\n");
	fflush(ol->log);
	return TRUE;
}
/*
There are  three possible server completion responses: OK (indicating success),
NO (indicating failure), or BAD (indicating protocol error such as unrecognized 
command or command syntax error).
*/
int ol_read_response(OnlineCon *ol)
{
	int res = FALSE;

	if (!ol) return FALSE;
	if(ol->idling) return ol_idle_response(ol);

	for (ol->ptr = NULL; tcp_readline(ol->c,ol->result,BFSZ); ol->ptr = NULL) {
		ol_log_server(ol);
		if (ol_minus(ol)) { res = FALSE; break; }
		if (ol_plus(ol)) { res = TRUE; break; }
		if (ol->cb) ol->cb(ol->data,ol);
	}
	ol_reset_cb(ol);
	if (!res) ol_tcp_error(ol);
	return res;
}
int ol_ok(OnlineCon *ol)
{
	if (!ol) return FALSE;
	return strncmp(&ol->result[ONLINELIB_TAGSZ],"OK ",3) == 0;
}
int ol_plus(OnlineCon *ol)
{
	if (!ol) return FALSE;
	return strncmp(ol->result,"+",1) == 0;
}
int ol_minus(OnlineCon *ol)
{
	if (!ol) return FALSE;
	return strncmp(ol->result,"-",1) == 0;
}
int ol_star(OnlineCon *ol)
{
	if (!ol) return FALSE;
	return strncmp(ol->result,"*",1) == 0;
}
int ol_idle_response(OnlineCon *ol)
{
	int res = FALSE, len, count=0;
    int  detect_dropout=0;
	time_t sleep_detect=0;
	time_t net_detect=0;
	time_t end_idle=0;
    
	char *done="DONE\r\n";
    ol->dropout_type = ONLINE_DROPOUT_UNKNOWN;
	
	if(!ol->idling) return TRUE;

	// + idling
	if(!tcp_readline(ol->c,ol->result,BFSZ)) goto done;
	ol_log_server(ol);
	if(!ol_plus(ol)) { res = TRUE; goto done; }

	sleep_detect = time(NULL);
	net_detect = time(NULL);
	// loop, trying to read idle line
	for(;;){	
        if(count++>50){
            imsg("online idle loop on thread=%d", th_id());
            count=0;
        }
		lib_msleep(200);
		if(ol->c->sk==INVALID_SOCKET) break;
        
        // Make our channel detect various forms of failures...
        // - Wake from sleep / hibernate - assume channel is no longer valid, dropout immediately and reconnect 
        if((time(NULL)-sleep_detect)>20){
            ol->dropout_type = ONLINE_DROPOUT_SLEEP;
            tcp_close_nice(ol->c);
            break;
        }
        sleep_detect=time(NULL);
        //  - heartbeat of momentarily dropping out of idle loop
        if((time(NULL)-net_detect)>60*1){
            ol->stop_idle = TRUE;
            ol->dropout_type = ONLINE_DROPOUT_HEARTBEAT;
            if(end_idle==0) end_idle = time(NULL);
        }
        if (end_idle && time(NULL)-end_idle>10){
            // idle "done" dropout will not responding, something must be wrong on our channel...
            tcp_close_nice(ol->c);
            break;
        }
        
		// Drop out of idle loop if required for whatever reason
        if(ol->stop_idle && !ol->stop_idle_pending){ 	
			len=lib_strlen(done);
			if (tcp_write(ol->c,done,len) != len) break;
 			ol->stop_idle_pending=TRUE;
        }
        
		if(!tcp_wait_read_buf(ol->c,0)) continue;

		if(!tcp_readline(ol->c,ol->result,BFSZ)) break;
		ol_log_server(ol);
		ol->ptr = NULL;
		

//		if (im_bad(ol)) { res = TRUE; break; }
		if(strncmp(ol->result," (queued)", 9)==0){
			char bf[BFSZ];
			ncpy(bf,ol->result+9,BFSZ-1);
			ncpy(ol->result,bf,lib_strlen(ol->result));
		}

		if (ol_plus(ol)) { res = TRUE; break; }
		if (!ol_star(ol)) { res = TRUE; break; }
		if (ol->cb) ol->cb(ol->data,ol);
	}
done:
    ol->stop_idle_pending=FALSE;
    ol->stop_idle=FALSE;

	if (!res) ol_tcp_error(ol);
	return res;
}

int ol_parse(OnlineCon *ol, char *res, int bfsz)
{
	char *s = ol->ptr;

	if (!s) s = ol->ptr = ol->result;
	*res = 0;

	while(sisspace(*s)) s++;
	if (*s == '\0') return FALSE;

	if (*s == '\"') {
		s++;
		lib_copy_upto(&s,'\"',res,bfsz);
		if (*s != '\"') return FALSE;
		s++;
	}else{
		lib_copy_upto_any(&s," ",res,bfsz);
	}

	ol->ptr = s;
	return TRUE;
}


/************/

void ol_errno_error(OnlineCon *ol)
{
	if (!ol) return ;
	lprintf(ol->result,BFSZ,"%s ERR %s",ol->tag,strerror(errno));
}

void ol_tcp_error(OnlineCon *ol)
{
	if (!ol) return ;
	lprintf(ol->result,BFSZ,"%s ERR ",ol->tag);
	lprintf(ol->c->erradd,200,"");
	tcp_error(ol->c, ol->tag[0]!=0 ? &ol->result[ONLINELIB_TAGSZ+4]:&ol->result[5]);
}

void ol_reset_cb(OnlineCon *ol)
{
	if (!ol) return ;
	ol->cb = NULL;
	ol->data = NULL;
}

/************/

