#define SOL_BOTH 1 // So it will use real FILE handles since it calls library stuff that does.

//////////////////////////////////////////////////////////////////
// Image based human verification (as opposed to robot)
// Author: Marijn Weehuizen 
//
// Note: Requires the use of gd graphics library 
//       (which in turn requires libpng and zlib)
//		 also requires mutex protection else would have been made a SAM
//////////////////////////////////////////////////////////////////

#include <time.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <limits.h>

#define DMID 26
#include "debug.h"

#ifndef GFORM_GD
#define GFORM_GD gdImagePng
#endif

#include "lib.h"
#include "mlog.h"
#include "debug.h"
#include "thread.h"
#include "lprintf.h"

#include "../gif/gdi.h"
#include "../gif/gdfonts.h"
#include "../gif/gdfontg.h"

#include "verify.h"
#include "../dftp/mail/xfile2.h"

#include "../sam/qrcodegen.h"


#define gdFree(x) gdFreex(x,__FILE__,__LINE__)


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

#define MAX(a,b)	((a) > (b) ? (a) : (b))
#define MIN(a,b)	((a) < (b) ? (a) : (b))


#define c_blank 245,245,245
#define c_light 194,194,194
#define c_dark 100,100,100
#define c_major 255,0,0
#define c_in    0,235,12
#define c_out   0,94,255
#define c_grid  0,0,0
#define c_inm   0,166,33
#define c_outm  255,0,255
#define c_outp  239,159,79

int verify_orig=FALSE;		// Use original weak verification image code
int verify_hard=FALSE;		// Use extra hard verification code


int col_vfytext[3],col_vfytext_deltas[3];

#define COL_RANGE 60

#ifdef DO_THREADS
//Mutex *th_mutex_new(char *name);
static Mutex *m;
#endif

static int mutex_i;
static int (*mutex_cb)(int vm, int isget, int seconds);
int verify_set_mutex(int (*cb)(int vm, int isget, int seconds), int m)
{
		mutex_i = m;
		mutex_cb = cb;
		return TRUE;
}

int verify_mutex_init(void)
{
#ifdef DO_THREADS
	if (m==NULL) m = th_mutex_new("mutex_init");
#endif
	return TRUE;
}
int v_mutex_wait(void)
{
	if (mutex_cb) return mutex_cb(mutex_i,TRUE,600);
#ifdef DO_THREADS
	if (m==NULL) lib_abort();
	if (!th_mutex_wait(m,60000)) { /* 10 minutes */
		lib_abort(); // this could be removed later probably
		return FALSE;
	}
#endif
	return TRUE;
}
void v_mutex_release(void)
{
	if (mutex_cb) { mutex_cb(mutex_i,FALSE,600); return; }
#ifdef DO_THREADS
	if (m==NULL) return;
	th_mutex_release(m);
#endif
}

int my_rand(int start, int end);

// Normalising funtion to get varying RGB colour of same desity
int vfyimg_colfn(int col,int offset, int min, int max)
{
	int lookup=col+offset;
	
	// normalise
	if (lookup<0) lookup+=COL_RANGE;
	if (lookup>COL_RANGE) lookup-=COL_RANGE;

	if (lookup<COL_RANGE/6*1) 
		return (max-min)*(lookup-0)/(COL_RANGE/6) + min;

	if (lookup<COL_RANGE/6*3) 
		return max;

	if (lookup<COL_RANGE/6*4) 
		return (max-min)*(COL_RANGE/6*4-lookup)/(COL_RANGE/6) + min;

	return min;
}
void vfyimg_freemem(void *p)
{	
	if (p) gdFree(p); p = NULL;
}

int vfyimg_imgcreate_old(char* text, void**p, int *size)
{

	int xsize=64,ysize=28;

	long i;

	/* GD LIB declarations */
	gdImagePtr image,brush_out,brush_outm,brush_outp;
	int i_light,i_dark,i_blank, i_grid;

	// variables related to scaling
	int ytics=4;
	int options=0xFFFF;

	int col,step;

	image = brush_out = brush_outm = brush_outp = 0;
	
	image = gdImageCreate(xsize, ysize);
	brush_out = gdImageCreate(1,2);
	brush_outm = gdImageCreate(1,2);
	brush_outp = gdImageCreate(1,2);

	i_blank = gdImageColorAllocate(image,c_blank);
	i_light = gdImageColorAllocate(image,c_light);
	i_dark = gdImageColorAllocate(image,c_dark);

	gdImageColorTransparent(image, i_blank);
	gdImageInterlace(image, 1);

	col_vfytext[0]=30+myrand()%80;
	col_vfytext[1]=30+myrand()%80;
	col_vfytext[2]=30+myrand()%80;

	col=myrand()%60;
	step=(myrand()%1) ? -5 : +5 ;
	for(i=0;text[i]!=0 && i<5;i++){
		col-=step;
		col_vfytext[0]=vfyimg_colfn(col,COL_RANGE/3*0,177,226);
		col_vfytext[1]=vfyimg_colfn(col,COL_RANGE/3*1,177,226);
		col_vfytext[2]=vfyimg_colfn(col,COL_RANGE/3*2,177,226);

		i_grid = gdImageColorAllocate(image, col_vfytext[0], col_vfytext[1], col_vfytext[2]);
		gdImageLine(image,0,myrand()%ysize,xsize,myrand()%ysize,i_grid);
	}
	for(i=0;text[i]!=0 && i<5;i++){
		col+=step;
		col_vfytext[0]=vfyimg_colfn(col,COL_RANGE/3*0,27,135);
		col_vfytext[1]=vfyimg_colfn(col,COL_RANGE/3*1,27,135);
		col_vfytext[2]=vfyimg_colfn(col,COL_RANGE/3*2,27,135);

		i_grid = gdImageColorAllocate(image, col_vfytext[0], col_vfytext[1], col_vfytext[2]);
		gdImageChar(image, gdFontGiant, 10+i*10, ysize/2-5, text[i], i_grid);
	}

	*p=gdImagePngPtr(image,size);

	emsg("VERIFY, OLD STYLE %p\n",p);
	xfile_gdImageDestroy(image);
	xfile_gdImageDestroy(brush_out);
	xfile_gdImageDestroy(brush_outm);
	xfile_gdImageDestroy(brush_outp);

	return TRUE;
}

// Maintain the list of token - text entries serverside to improve security

#define VFYHASH 512
VfyEntry *vfylist[VFYHASH];

VfyEntry *vfyimg_new()
{
	VfyEntry *e;
	char bf[BFSZ];
	
	e=calloc(1,sizeof(VfyEntry));
	if (e==NULL){
		return NULL;
	}
	
	e->next=NULL;
	e->t=time(NULL);

	// id = big random string used as 'token'
	lprintf(bf,BFSZ-1,"%c%c%03d%03d-%c%c%03d%03d",myrand()%26+'a',myrand()%26+'a',myrand()%1000,myrand()%1000,myrand()%26+'a',myrand()%26+'a',myrand()%1000,myrand()%1000);
	e->id=strdup(bf);

	// text = text we expect user to type matching token (5 digit random string in range 10000-99999
	lprintf(bf,BFSZ-1,"%01d%04d",myrand()%9+1,myrand()%10000);
	e->text=strdup(bf);

	return e;
}

int vfyimg_lookupid(char *id, char* text)
{
	int h;
	VfyEntry *e;
	int ret=FALSE;
	
	h=lib_str_hash(id,VFYHASH);

	if (!v_mutex_wait()) return FALSE;
	for (e=vfylist[h];e!=NULL;e=e->next) {
		if (strcmp(e->id,id)==0) {
			ncpy(text,e->text,BFSZ-1);
			ret=TRUE;
			break;
		}
	}
	v_mutex_release();

	if(ret==FALSE){
		ncpy(text,"NONE",BFSZ-1);
	}
	return ret;
}


int vfyimg_store(VfyEntry *e)
{
	int h=lib_str_hash(e->id,VFYHASH);

	if (!v_mutex_wait()) return FALSE;
	e->next=vfylist[h];
	vfylist[h]=e;
	v_mutex_release();

	return TRUE;
}

// Purge any entries older than nn seconds
int vfyimg_purge()
{
	VfyEntry *e,**last,*next;
	int h;
	time_t now;
	int sec=60*60;

	now=time(NULL);
	if (!v_mutex_wait()) return FALSE;
	for (h=0;h<VFYHASH;h++){
		last = &vfylist[h];
		for (e=vfylist[h];e!=NULL;e=next) {
			next = e->next;
			if (now-e->t>60*60) {
				free(e->id);
				free(e->text);
				free(e);
				*last=next;
			}else {
				last = &e->next; 
			}
		}
	}
	v_mutex_release();

	return TRUE;
}

int vfyimg_init(int orig,int hard)
{
	verify_orig=orig;
	verify_hard=hard;
	return TRUE;
}
int vfyimg_free()
{
	VfyEntry *e,**last,*next;
	int h;

	for (h=0;h<VFYHASH;h++){
		last = &vfylist[h];
		for (e=vfylist[h];e!=NULL;e=next) {
			next = e->next;
			free(e->id);
			free(e->text);
			free(e);
		}
		vfylist[h] = NULL;
	}
	return TRUE;
}


// Generate really big random integers
#define RND_CHUNK 10000
int my_rand(int start, int end)
{
	int result,range,i,scale;
	if(RAND_MAX<RND_CHUNK) return -1;
	result=0; scale=1;

	range=end-start;
	for (i=0;i<4;i++){
		if (range>RND_CHUNK){
			result+=(myrand()%RND_CHUNK)*scale;
			range/=10000;
			scale+=10000;
		}else{
			result+=(myrand()%range)*scale;
			break;
		}
	}
	return start+result;
}


// Simple variable width font implementation as GD has single crap small font
enum {vfy_font_start,vfy_font_end};
char *alphabet = "0123456789";	// must match font files!!
int *font_metrics=NULL,alphabet_length,fontfile_width, fontfile_height, font_height, fonts;
// Just load font info once
gdImagePtr font=NULL;
int vfy_font_init=FALSE;

int alphabet_index(char c){
	char *p;
	int ret=0;
	for (p=alphabet;*p;p++){
		if (*p==c){
			ret=(int)(p-alphabet);
			break;
		}
	}
	return ret;
}
int *font_metric(int nfont, int symbol, int type){
	if (!font_metrics||alphabet_length==0) return 0;
	return &(font_metrics[nfont*alphabet_length*2 + symbol*2 + type]);
}

int vfyimg_imgcreate(char* text, void**pret, int *size)
{

	gdImagePtr image=NULL,image2=NULL;
	char *file;
	FILE *f;

	// Setup defaults in an efficient manner
	int xsize=120,ysize=60;
	int length=lib_strlen(text);
	int fluctuation_amplitude = 5;
	int no_spaces = verify_hard;

	int i,j,nfont,symbol,reading_symbol,transparent;
	int white;

	// Temp distortion generation values
	int font_start,font_end;
	int x,y,shift,_sy,_sx,rgb,opacity,color,left,py,px,center;

	double rand1, rand2, rand3, rand4, rand5, rand6, rand7, rand8, rand9, rand10;
	double sy,sx;
	double frsx, frsy, frsx1, frsy1;
	double newcolor, newcolor0;
	int color_x, color_y, color_xy;
	int newred, newgreen, newblue, mynewcolor;
	
	int foreground_color[3],background_color[3];
	*pret = NULL;

	if (verify_orig) goto orig;
	if (vfy_font_init && !font ) goto error;

	imsg("generating new style verification image\n");

	// Initialise font info
	if (!v_mutex_wait()) goto error;
	if (!font){
		file="verify_fonts.png";
		if ((f = fopen(file,"rb")) == NULL) {
			emsg("verify: cannot open image files (%s)\n",file);
			v_mutex_release();
			goto error;
		}
		
		font=gdImageCreateFromPng(f);
		fclose(f);

		gdImageAlphaBlending(font, true);
		fontfile_width=gdImageSX(font);
		fontfile_height=gdImageSY(font);
		alphabet_length=lib_strlen(alphabet);
		font_height=45;
		fonts=fontfile_height/font_height;

		font_metrics=calloc(fonts*alphabet_length*2,sizeof(int));  // chrisp, memory corruption detected here, version:  version 5.3g-6 - built on May 27 2011 11:31:12
		if (font_metrics==NULL){
			v_mutex_release();
			goto error;
		}

		//index the font file
		for(j=0;j<fonts;j++){
			symbol=0;reading_symbol=false;
			for(i=0;i<fontfile_width && symbol<alphabet_length;i++){
				transparent = (gdImageGetPixel(font, i, j*font_height) >> 24) == 127;
				if(!reading_symbol && !transparent){
					*font_metric(j,symbol,vfy_font_start)=i;
					reading_symbol=true;
					continue;
				}
				if(reading_symbol && transparent){
					*font_metric(j,symbol,vfy_font_end)=i;
					reading_symbol=false;
					symbol++;
					continue;
				}
			}
		}
	}
	v_mutex_release();

//	nfont=my_rand(0,fonts-1);
	nfont=myrand()%fonts;

	// Print it as a first image with random heights
	image = gdImageCreateTrueColor(xsize, ysize);
	gdImageAlphaBlending(image, true);
	
	white=gdImageColorAllocate(image, 255, 255, 255);
	gdImageFilledRectangle(image, 0, 0, xsize-1, ysize-1, white);

	x=1;
	for(i=0;i<length;i++){
		// Draw each letter with a random upward jump factor
		font_start=*font_metric(nfont, alphabet_index(text[i]), vfy_font_start);
		font_end=*font_metric(nfont, alphabet_index(text[i]), vfy_font_end);

		y=my_rand(-fluctuation_amplitude, fluctuation_amplitude)+(ysize-font_height)/2+2;
		if(!no_spaces){
			shift=(i>0)?my_rand(-3,5):0;
		}else{
			// No spaces mode
			shift=0;
			if(i>0){
				shift=1000;
				for(_sy=nfont*font_height+7;_sy<(nfont*font_height+font_height-20);_sy+=1){
					for(_sx=font_start-1;_sx<font_end;_sx+=1){
				        rgb=gdImageGetPixel(font, _sx, _sy);
				        opacity=rgb>>24;
						if(opacity<127){
							left=_sx-font_start+x;
							py=_sy+y;
							if(py>ysize) break;
							for(px=MIN(left,xsize-1);px>left-12 && px>=0;px-=1){
						        color=gdImageGetPixel(image, px, py) & 0xff;
								if(color+opacity<190){
									if(shift>left-px){
										shift=left-px;
									}
									break;
								}
							}
							break;
						}
					}
				}
				if(shift==1000){
					shift=my_rand(4,6);
				}
			}
		}
		gdImageCopy(image,font,x-shift,y,font_start,nfont*font_height+1,font_end-font_start,font_height-1);
		x+=font_end-font_start-shift;
	}
	center=x/2;

	// Work out random wavelike purtubations (1-4=purtubations, 5-8=phases, 9-10=amplitudes)
	rand1=my_rand(750000,1200000)/10000000.0;
	rand2=my_rand(750000,1200000)/10000000.0;
	rand3=my_rand(750000,1200000)/10000000.0;
	rand4=my_rand(750000,1200000)/10000000.0;
	rand5=my_rand(0,3141592)/500000.0;
	rand6=my_rand(0,3141592)/500000.0;
	rand7=my_rand(0,3141592)/500000.0;
	rand8=my_rand(0,3141592)/500000.0;
	rand9=my_rand(330,420)/110.0;
	rand10=my_rand(330,450)/110.0;

	// Print to second image
	image2 = gdImageCreateTrueColor(xsize, ysize);
	gdImageAlphaBlending(image2, true);

	foreground_color[0]=my_rand(0,100); foreground_color[1]=my_rand(0,100); foreground_color[2]=my_rand(0,100);
	background_color[0]=255; background_color[1]=255; background_color[2]=255;

	//	for each pixel in second image work out distorted pixel value
	for(x=0;x<xsize;x++){

		col_vfytext[0]=vfyimg_colfn(foreground_color[0]+x/3,COL_RANGE/3*0,27,135); 
		col_vfytext[1]=vfyimg_colfn(foreground_color[1]+x/3,COL_RANGE/3*1,27,135); 
		col_vfytext[2]=vfyimg_colfn(foreground_color[2]+x/3,COL_RANGE/3*2,27,135);

		for(y=0;y<ysize;y++){
			sx=x+(sin(x*rand1+rand5)+sin(y*rand3+rand6))*rand9-xsize/2+center+1;
			sy=y+(sin(x*rand2+rand7)+sin(y*rand4+rand8))*rand10;

			// If any coords outside then all blank
			if(sx<0 || sy<0 || sx>=xsize-1 || sy>=ysize-1){
				color=255;
				color_x=255;
				color_y=255;
				color_xy=255;
			}else{
				color=gdImageGetPixel(image, (int) sx, (int) sy) & 0xFF;
				color_x=gdImageGetPixel(image, (int) sx+1, (int) sy) & 0xFF;
				color_y=gdImageGetPixel(image, (int) sx, (int) sy+1) & 0xFF;
				color_xy=gdImageGetPixel(image, (int) sx+1, (int) sy+1) & 0xFF;
			}
			// Now we have 4 valid colour values in source image to interpolate between

			// All black or white => optimise colour use
			if(color==0 && color_x==0 && color_y==0 && color_xy==0){
				newred=col_vfytext[0];
				newgreen=col_vfytext[1];
				newblue=col_vfytext[2];
			}else if(color==255 && color_x==255 && color_y==255 && color_xy==255){
				newred=background_color[0];
				newgreen=background_color[1];
				newblue=background_color[2];	
			}else{
				frsx=sx-floor(sx);
				frsy=sy-floor(sy);
				frsx1=1-frsx;
				frsy1=1-frsy;

				// Interpolate our grey scale value
				newcolor=(
					color*frsx1*frsy1+
					color_x*frsx*frsy1+
					color_y*frsx1*frsy+
					color_xy*frsx*frsy);

				if(newcolor>255) newcolor=255;
				newcolor=newcolor/255;
				newcolor0=1-newcolor;

				// and convert that based upon our colours
				newred=(int)(newcolor0*col_vfytext[0]+newcolor*background_color[0]);
				newgreen=(int)(newcolor0*col_vfytext[1]+newcolor*background_color[1]);
				newblue=(int)(newcolor0*col_vfytext[2]+newcolor*background_color[2]);
			}
			//Set the pixel
			mynewcolor=gdImageColorAllocate(image2, newred, newgreen, newblue);
			gdImageSetPixel(image2, x, y, mynewcolor);
			gdImageColorDeallocate(image2,mynewcolor);
		}
	}


	// Return the second image and free up stuff
	*pret=gdImagePngPtr(image2,size);
	//emsg("gdImagePngPtr returnd %p\n",*pret);
	
	xfile_gdImageDestroy(image);	
	xfile_gdImageDestroy(image2);	

	return TRUE;

error:
	emsg("new verify function failed, trying old style verification image instead\n");
orig:
	imsg("generating old style verification image\n");

	if (image!=NULL) xfile_gdImageDestroy(image);	
	if (image2!=NULL) xfile_gdImageDestroy(image2);	

	return vfyimg_imgcreate_old(text, pret, size);
}

int qr_create(char* text, void**pret, int *size)
{
	uint8_t qr0[qrcodegen_BUFFER_LEN_MAX];
	uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX];
	bool ok;
	int qsize;
	gdImagePtr image=NULL,image2=NULL;
	int length=lib_strlen(text);
	int white,black;
	int x,y;
	*pret = NULL;
	*size = 0;
	// Print it as a first image with random heights

	ok = qrcodegen_encodeText(text,   tempBuffer, qr0, qrcodegen_Ecc_MEDIUM,    qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX,    qrcodegen_Mask_AUTO, true);
	if (!ok) goto failed;

	qsize = qrcodegen_getSize(qr0);
	


	image = gdImageCreateTrueColor(qsize+2, qsize+2);
	gdImageAlphaBlending(image, true);
	
	white=gdImageColorAllocate(image, 255, 255, 255);
	black=gdImageColorAllocate(image, 0,0,0);
	gdImageFilledRectangle(image, 0, 0, qsize+1, qsize+1, white);

	for (y = 0; y < qsize; y++) {
	    for (x = 0; x < qsize; x++) {
			if (qrcodegen_getModule(qr0, x, y)) gdImageSetPixel(image,x+1,y+1,black);
	    }
	}
	*pret=gdImagePngPtr(image,size);

	return TRUE;
failed:
	xfile_gdImageDestroy(image);	
	return FALSE;
}
void qr_freemem(void *p)
{
	if (p) gdFree(p); p = NULL;
}
