/* Threading stuff for outgoing sendthread option (always use with dfeed) */
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#include "mutex.h"


#ifdef VMS
#include <unixio.h>
#define unlink remove
#endif
#ifndef NO_FCNTL
#include <fcntl.h>
#else
/*RHP not for mac */
#ifndef Mac
#include <file.h>
#endif
#endif
#include "emsg.h"
#include "sary.h"
#include "str.h"
#include "lib.h"
#include "group.h"
#include "hist.h"
#include "suck.h"
#include "nntp.h"
#include "chan.h"
#include "feed.h"
#include "expire.h"
#include "control.h"
#include "item.h"
#include "db.h"
#include "ncmd.h"
#include "thin.h"
#include "thout.h"

#undef mymalloc
#undef myfree
#define mymalloc(x) mymalloc2(x,M_THIN)
#define myfree(x) myfree2(x,M_THIN)

#ifdef DOTHREADS
void nntp_setthread(int c, int v);
int tout_close(Thout *th, char *reason);
int thmsg(const char * arg_list, ...);
int tout_write_bf(Thout *th, char *data, int len);
int tout_write_now(Thout *th);

void nntp_close_later(int c);
static int isdebug;
int tout_mutex_wait(void)
{
	mutex_wait(M_OUT);
	return TRUE;
}
int tout_mutex_release(void)
{
	mutex_release(M_OUT);
	return TRUE;
}

int tout_open(Thout *th)
{
	tout_mutex_wait();
	
	if (th->chan<0) {
		tout_mutex_release();
		return FALSE;
	}
	if (!chan_isopen(th->chan)) {
		tout_close(th,"inopen");
		tout_mutex_release();
		return FALSE;
	}

	tout_mutex_release();
	return TRUE;
}
Zitem *tout_getref(Thout *th, String *item, char *groupnum, char *mid)
{
	static char lastgnum[BFSZ];
	static Zitem *last;
	
	if (strcmp(groupnum,lastgnum)==0) {
		tout_mutex_wait();
		last->nref++;
		tout_mutex_release();
		if (isdebug) thmsg("thout: getref %p (%s) %d\n",last,mid,last->nref);
		return last;
	}
	ncpy(lastgnum,groupnum,200);
	tout_mutex_wait();
	last = mymalloc(sizeof(Zitem)); memset(last,0,sizeof(Zitem));
	if (isdebug) thmsg("thout: getref %p (%s) 1\n",last,mid);
	last->nref = 1;
	last->body = mymalloc(str_len(item)+1);
	last->body_len = str_len(item);
	ncpy(last->mid,mid,200);
	memcpy(last->body,str_s(item),str_len(item));
	last->body[last->body_len] = 0;
	tout_mutex_release();
	return last;
}
int thout_start_setup(int chan)
{
	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
	thin_setbusy(chan,TRUE);
	nntp_setthread(chan,TRUE);
	ncmd_set_mode(chan,M_THREAD);
	nntp_setblock(chan,TRUE);
	return TRUE;
}
int tout_start(Thout *th)
{
	Thread *t;

	t = th_new(tout_main,th);
	if (t==NULL) {
		emsg("Thread creation failed\n");
		return FALSE;
	}
	th_free(&t); /* Closes the handle, not the thread itself */
	return TRUE;
}
int tout_main(void *arg)
{
	Thout *th = (Thout *) arg;

	thmsg("thout: Output thread starting\n");
	for (;!th->finish;) {
		if (!tout_open(th)) {
			if (isdebug) thmsg("thout: main thread running - wait for open attempt\n");
			lib_msleep(10000);	
		} else if (th->state==TOUT_OPEN) {
			if (isdebug) thmsg("thout: main thread running - check for open response\n");
			tout_read_open(th);
		} else if (th->state==TOUT_SEND) {
			if (th->next[0]!=NULL) {
				if (isdebug) thmsg("thout: main thread running - send message\n");
				tout_send_one(th);
			}
		}
		if (th->next[0]==NULL) lib_msleep(10);
	}
	tout_mutex_wait();
	tout_finish(th);
	tout_mutex_release();
	return TRUE;
}
int tout_close(Thout *th, char *reason)
{
	if (isdebug) thmsg("thout: tout_close(%s)\n",reason);
	if (th->chan>=0) {
		chan_close(th->chan);
		thin_setbusy(th->chan,FALSE);
		nntp_setthread(th->chan,FALSE);
	}
	th->chan = -1;
	return TRUE;
}
int tout_finish(Thout *th)
{
	int i;
	if (th->chan>=0) nntp_setthread(th->chan,FALSE);
	for (i=0; i<THMAX; i++) {
		if (th->next[i]!=NULL) tout_release(&th->next[i]); 
	}
	tout_close(th,"finish");
	if (th->outbf!=NULL) myfree(th->outbf);
	myfree(th);
	return TRUE;
}
int tout_read_open(Thout *th)
{
	char bf[BFSZ];
	int code;
	if (!tout_read_line(th,bf)) return FALSE;
	lprintf(th->reason,100,"Open (%s) %s\n",th->to,bf);
	if (isdebug) thmsg("thout: %s\n",th->reason);		
	code = atoi(bf);
	th->state = TOUT_SEND;
	if ((code<199) || (code>299)) {
		lprintf(th->reason,100,"ERROR Open (%s) %s\n",th->to,bf);
		if (isdebug) thmsg("thout: %s\n",th->reason);		
		th->state = TOUT_NONE;
		tout_close(th,"read open");
	}
	return TRUE;
}
int tout_read_line(Thout *th, char *bf)
{
	char *s=NULL;
	int r;
	int upto;
	int start;
	char reason[BFSZ];

	tout_write_now(th);
	s = strchr(th->inbf,'\n');
	for (;s==NULL;s=strchr(th->inbf,'\n')) {
		upto = strlen(th->inbf);
		if (isdebug) thmsg("thout: read line upto %d\n",upto);
		start = time(NULL);
		r = thin_socket_read(th->chan,th->inbf+upto,BFSZ-upto-100,40000,reason);
		if (r<=0) {
			thmsg("thout: socked closed on read after %d seconds chan=%d %s\n",time(NULL)-start,th->chan,reason);
			tout_close(th,"read_line");
			if (isdebug) thmsg("thout: socket closedon read line\n");
			return FALSE;
		}
		th->inbf[upto+r] = 0;
		if (isdebug) thmsg("thout: read line in loop (%s)\n",th->inbf);
	}
	*s = 0;
	ncpy(bf,th->inbf,BFSZ-1);
	memmove(th->inbf,s+1,strlen(s+1)+1);
	s = strchr(bf,'\r');
	if (s!=NULL) *s = 0;
	return TRUE;
}
static int dostream = TRUE;
#define TOUTSZ 127000
int tout_write_now(Thout *th)
{
	int r;
	if (th->outlen==0) return TRUE;
	r = nntp_socket_write(th->chan,th->outbf,th->outlen);
	if (r<th->outlen) {
		th->outlen = 0;		
		return FALSE;
	}
	th->outlen = 0;		
	return TRUE;
}
int tout_write_bf(Thout *th, char *data, int len)
{
	if (th->outbf==NULL) {
		th->outbf = mymalloc(TOUTSZ);
		th->outmax = TOUTSZ-20;
		th->outlen = 0;
	}
	if ((th->outlen+len)>=th->outmax) {
		if (!tout_write_now(th)) return -1;
	}
	if (th->outlen+len>=th->outmax) return nntp_socket_write(th->chan,data,len);
	memcpy(th->outbf+th->outlen,data,len);
	th->outlen += len;
	return len;
}
int tout_send_one(Thout *th)
{
	int r;
	int ret=TRUE;
	int i;
	int code;
	char bf[BFSZ];
	tout_mutex_wait();
	if (th->next[0]==NULL) {
		tout_mutex_release();
		return FALSE;
	}		
	if (isdebug) thmsg("thout: Send one item %p next %p\n",th->item,th->next[0]);
	th->item = th->next[0];
	th->next[0] = NULL;
	for (i=0; i<(THMAX-1); i++) {
		th->next[i] = th->next[i+1];
	}
	th->next[THMAX-1] = NULL;
	tout_mutex_release();
	lprintf(bf,BFSZ-1,"TAKETHIS %.200s\r\n",th->item->mid);
	if (isdebug) thmsg("thout: Sending: TAKETHIS %s\n",th->item->mid);
	r = tout_write_bf(th,bf,strlen(bf));
	if (r<=0) {
		if (isdebug) thmsg("thout: takethis socket write failed\n");
		tout_close(th,"takethis");
		tout_release(&th->item); 
		th->nbad++;
		return FALSE;
	}
	if (isdebug) thmsg("thout: sending body %d bytes\n",th->item->body_len);
	r = tout_write_bf(th,th->item->body,th->item->body_len);
	if (r<=0) {
		if (isdebug) thmsg("thout: body socket write failed\n");
		tout_close(th,"body");
		tout_release(&th->item); 
		th->nbad++;
		return FALSE;
	}
	th->ntoread++; 

	if (th->item->body[th->item->body_len-1]!='\n') r = tout_write_bf(th,"\r\n.\r\n",5);
	else r = tout_write_bf(th,".\r\n",3);

	if (isdebug) thmsg("thout: read line\n");
	if ((!dostream) || (th->ntoread>10)) {
	  th->ntoread--; 
	  if (!tout_read_line(th,bf)) {
		tout_close(th,"response");
		tout_release(&th->item); 
		th->nbad++;
		if (isdebug) thmsg("thout: read line failed\n");
		return FALSE;
	  }
	  if (isdebug) thmsg("thout: read line got %s\n",bf);
	  code = atoi(bf);
	  if (code<300) th->nok++;
	  else if (code==439) th->ndup++;
	  else th->nbad++;
	}

	th->ksent += th->item->body_len/1000;
	tout_release(&th->item); 
	lprintf(th->reason,100,"%s\n",bf);
	if (isdebug) thmsg("thout: response %s\n",th->reason);
	return TRUE;
}
int tout_release(Zitem **xitem)
{
	Zitem *item;
	tout_mutex_wait();
	item = *xitem;
	item->nref--;
	if (isdebug) thmsg("thout: Release ref for %p %d\n",item,item->nref);
	if (item->nref<=0) {
		myfree(item->body); item->body = NULL;
		myfree(item);
	}
	*xitem = NULL;
	tout_mutex_release();
	return TRUE;
}
Thout *tout_new(char *to, char *bind)
{
	Thout *t = mymalloc(sizeof(Thout));
	memset(t,0,sizeof(Thout));
	if (bind!=NULL) ncpy(t->bind,bind,100);
	if (to!=NULL) ncpy(t->to,to,100);
	t->chan = -1;
	if (!t->thread_running) {
		t->thread_running = TRUE;
		tout_start(t); /* Create the thread too */
	}
	t->start = lib_gmtime()-1;
	return t;
}

int thmsg(const char * arg_list, ...)
{
        va_list arg_ptr;
        char *format;
        int len;
	char output[4000];
	static FILE *f;
        va_start(arg_ptr, arg_list);
        format = (char *) arg_list;
        len = vsnprintf(output,BFSZ-1, format, arg_ptr);
	tout_mutex_wait();
	if (f==NULL) f = fopen(file_log("thout.log"),"a");
	if (f!=NULL) {
	        fprintf(f,"th=%d %.900s",th_id(),output);
		if (file_length(fileno(f))>200000) {
			fclose(f);
			remove(file_log("thout.log"));
			f = NULL;
		}
/*
		fflush(f);
		fclose(f);
		f = NULL;
*/
	}
	tout_mutex_release();
	return 0;
}
int tout_setdebug(int x)
{
	isdebug = x;
	return TRUE;
}
/*

  Leaking file handles
  Corrupting data in socket read line thing




*/
#else
int tout_mutex_wait(void){return FALSE;}
int tout_mutex_release(void){return FALSE;}
int tout_open(Thout *th){return FALSE;}
Zitem *tout_getref(Thout *th, String *item, char *groupnum, char *mid){return FALSE;}
int thout_start_setup(int chan){return FALSE;}
int tout_start(Thout *th){return FALSE;}
int tout_main(void *arg){return FALSE;}
int tout_read_open(Thout *th){return FALSE;}
int tout_read_line(Thout *th, char *bf){return FALSE;}
int tout_send_one(Thout *th){return FALSE;}
Thout *tout_new(char *to, char *bind){return FALSE;}
int tout_release(Zitem **item){return FALSE;}
int tout_finish(Thout *th){return FALSE;}
int tout_feed(int ff, char *msgid, char *groupnum, String *item){return FALSE;}
int tout_setdebug(int x) {return TRUE;}

#endif
