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

#ifdef DOPERFLOG
//////////////////////////////////////////////////////////////////
// Graphing of multiscale fixed size long term rotating log file
// Author: Marijn Weehuizen (roughly based on mrtg code)
//
// Note: Requires the use of gd graphics library
//       (which in turn requires libpng and zlib)
//////////////////////////////////////////////////////////////////

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

#ifndef GFORM_GD
#define GFORM_GD gdImagePng
#endif

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

#include "lib.h"
#include "mlog.h"
#include "mlog_graph.h"
#include "../dftp/mail/xfile2.h"

#define DMID 23
#include "debug.h"

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


TIMEBASE xtimebase[]={
	"filler", 	1,
	"s", 		1,
	"min",		60,
	"hr",		60*60,
	"day",		24*60*60,
	"week",		7*24*60*60,
	"month",	30*24*60*60,
	NULL,		0
};	// only add entries ad the end!! (line numbers used in perflog.c)


char *short_si[] = {"","k","M","G","T"};
char *longup = NULL;
char *shortup = NULL;
char *rtimezone = NULL;
char *program,*router,*routerpath;
char weekformat = 'W';			/* strftime() fmt char for week #  */

int col_in[3], col_out[3], col_inm[3], col_outm[3], col_outp[3];


// Complete graph image outer dimensions
#define XSIZE (long)((xpix*xscale)+100)
#define YSIZE (long)((ypix*yscale)+35)

// Translate any graph points to image coordinates
#define ytr(y) (long)(ypix*yscale+14-((y)*yscale))
#define xtr(x) (long)((growright) ? (xpix*xscale+81-((x)*xscale)) : (81+((x)*xscale)))


int mlog_graph(char *file, MLOG_IMAGE *pimage, MLOG *log, int scale, time_t now, int logbase, int timebase)
{
	return mlog_graph2(file, pimage, log, scale, now, logbase, timebase, 1, 0);
}

int mlog_graph2(char *file, MLOG_IMAGE *pimage, MLOG *log, int scale, time_t now, int logbase, int timebase, double yfactor, int cutoff)
{
	struct tm tm2, *tm=&tm2;
	struct tm tm3, *tmtz=&tm3;
	FILE *fo;

	/* GD LIB declarations */
	gdImagePtr graph,brush_out,brush_outm,brush_outp;
	int i_light,i_dark,i_blank, i_major, i_in, i_out, i_grid, i_inm, i_outm;
	int i_outp, i_outpg;
	int styleDotted[3];

	HISTORY *lhist;
	char bf[BFSZ];
	// variables related to scaling
	int ytics=4,i;
	char *short_si_out="";
	unsigned long maxv;
	double stepsize;
	long digits;
	time_t graphtime;
	char *plabel;

	// Keep a local copy of some of these for ease of use :-)
	long xpix, ypix;
	short options;
	long growright;
	double xscale, yscale;
	int peak;
	int x;

	xpix = pimage->xpix;
	ypix = pimage->ypix;
	xscale = pimage->xscale;
	yscale = pimage->yscale;
	options = pimage->options;
	peak = pimage->peak;
	plabel = (pimage->ylabel[0]=='\0') ? "Bytes/s" : pimage->ylabel;

	growright = options & OPTION_GROWRIGHT;

	sscanf("#00cc00","#%2x%2x%2x", &col_in[0], &col_in[1],&col_in[2]);
	sscanf("#0000ff","#%2x%2x%2x", &col_out[0], &col_out[1],&col_out[2]);
	sscanf("#006600","#%2x%2x%2x", &col_inm[0], &col_inm[1],&col_inm[2]);
	sscanf("#ff00ff","#%2x%2x%2x", &col_outm[0], &col_outm[1],&col_outm[2]);

	if ((lhist = malloc(sizeof(HISTORY) * xpix)) == NULL) {
		emsg("perflog: graph failed to allocate memory (%s)\n",file);
		return FALSE;
	}
	
	graphtime = now/SAMPLE_PERIOD[scale]*SAMPLE_PERIOD[scale];

	// Make hour graphs look slightly nicer
	if (scale==MLOG_HOUR) 
		graphtime=graphtime-SAMPLE_PERIOD[scale];

	if (pimage->maxy==0) {
		// If have automatic rescaling need to work out max value first
		mlog_fillgraphbufer(log, (int)graphtime, scale, lhist, xpix, ypix, 1.0*((double)xtimebase[logbase].time/xtimebase[timebase].time)/yfactor, cutoff);
		maxv=0;
		for(x=xpix-1;x>=0;x--){
			maxv = MAX(maxv,lhist[x].in);
			if (peak){
				maxv = MAX(maxv,lhist[x].inmax);
			}
		}
		// Make sure we appear to have have some "room to move" on the top of the graph :-)
		maxv = TRUNC_ULONG(maxv*1.2);
		if (pimage->miny && (pimage->miny > maxv))
			maxv=pimage->miny;
	} else {
		maxv=pimage->maxy;
	}

	// Work out everything required for scaling :-)
	stepsize = ceil((double)maxv / (double) ytics); /* int */
	if (stepsize==0) stepsize=1;	// To get around ceil command returning 0(out) on 0(in) on unix
	digits = 0;
	while (stepsize >= (double)1000 ) {
		stepsize /= (double) 1000;
		digits += 3;
	}

	if(digits/3>4){
		short_si_out="?";
		emsg("perflog: number out of range digits=%d (%d)",digits,digits/3);
	}else{
		short_si_out = short_si[digits/3];
	}
	if (maxv<500) {
		maxv=(long)stepsize*ytics;
	}

	// Get us our real graph data
	mlog_fillgraphbufer(log, (int)graphtime, scale, lhist, xpix, ypix, ((double)maxv/ypix)*((double)xtimebase[logbase].time/xtimebase[timebase].time)/yfactor, 0);

#ifdef __MLOG_DEBUG__
	if (fo = fopen("rawgraphbuffer.txt","w")) {
		for (x=0;x<xpix;x++){
			fprintf(fo,"Point %d  %d\n", x, lhist[x].in);
		}
		fclose(fo);
	}
#endif

	/* the graph is made ten pixels higher to acomodate the x labels */
	graph = gdImageCreate(XSIZE, YSIZE);
	brush_out = gdImageCreate(1,2);
	brush_outm = gdImageCreate(1,2);
	brush_outp = gdImageCreate(1,2);

	/* the first color allocated will be the background color. */
	i_blank = gdImageColorAllocate(graph,c_blank);
	i_light = gdImageColorAllocate(graph,c_light);
	i_dark = gdImageColorAllocate(graph,c_dark);

	if (options & OPTION_TRANSPARENT) {
		gdImageColorTransparent(graph, i_blank);
	}
	gdImageInterlace(graph, 1);

	/* do NOT delete the out variables. they are dummies, but the actual color
	   allocation for the brush is essential */
	i_major = gdImageColorAllocate(graph,c_major);
	i_in = gdImageColorAllocate(graph,col_in[0], col_in[1], col_in[2]);
	i_out = gdImageColorAllocate(brush_out,col_out[0], col_out[1], col_out[2]);
	i_grid = gdImageColorAllocate(graph,c_grid );
	i_inm = gdImageColorAllocate(graph,col_inm[0], col_inm[1], col_inm[2]);
	i_outm = gdImageColorAllocate(brush_outm,col_outm[0], col_outm[1], col_outm[2]);
	i_outp = gdImageColorAllocate(brush_outp,col_outp[0], col_outp[1], col_outp[2]);
	i_outpg = gdImageColorAllocate(graph,col_outp[0], col_outp[1], col_outp[2]);

	/* draw the image border */
	if (!(options & OPTION_NOBORDER)) {
		gdImageLine(graph,0,0,XSIZE-1,0,i_light);
		gdImageLine(graph,1,1,XSIZE-2,1,i_light);
		gdImageLine(graph,0,0,0,YSIZE-1,i_light);
		gdImageLine(graph,1,1,1,YSIZE-2,i_light);
		gdImageLine(graph,XSIZE-1,0,XSIZE-1,YSIZE-1,i_dark);
		gdImageLine(graph,0,YSIZE-1,XSIZE-1,YSIZE-1,i_dark);
		gdImageLine(graph,XSIZE-2,1,XSIZE-2,YSIZE-2,i_dark);
		gdImageLine(graph,1,YSIZE-2,XSIZE-2,YSIZE-2,i_dark);
	}

	/* draw the incoming traffic */
	if (options & OPTION_I) {
		for (x=0; x<xpix; x++) {
			/* peak is always above average, we therefore only draw the upper part */
			if (peak) gdImageLine(graph,xtr(x),ytr(lhist[x].in), xtr(x),ytr(lhist[x].inmax),i_inm);
#ifdef INCOMING_UNFILLED
			gdImageLine(graph,xtr(x),ytr(lhist[x].in),xtr(x),ytr(lhist[x+1].in),i_in);
#else
			gdImageLine(graph,xtr(x),ytr(0),xtr(x),ytr(lhist[x].in),i_in);
#endif
		}
	}

	/* draw the percentage */
	if (options & OPTION_DORELPERCENT) {
		gdImageSetBrush(graph, brush_outp);
		for (x=0; x<xpix-1; x++)
			gdImageLine(graph,xtr(x), ytr(lhist[x].percent), xtr(x+1), ytr(lhist[x+1].percent), gdBrushed);
	}

	/* draw the outgoing traffic */
	if (options & OPTION_O) {
		gdImageSetBrush(graph, brush_outm);
		if (peak) for (x=0; x<xpix-1; x++)
			gdImageLine(graph,xtr(x), ytr(lhist[x].outmax),	xtr(x+1), ytr(lhist[x+1].outmax), gdBrushed);
		gdImageSetBrush(graph, brush_out);
		for (x=0; x<xpix-1; x++)
			gdImageLine(graph,xtr(x),ytr(lhist[x].out),	xtr(x+1),ytr(lhist[x+1].out), gdBrushed);
	}

	/* print the graph title */
	if ( pimage->pngtitle != NULL ) {
		gdImageString(graph, gdFontSmall,81, 1, (unsigned char*)pimage->pngtitle, i_grid);
	}

	/* print the date & time */
	if ( options & OPTION_PNGDATE  ) {
		safe_localtime(&now,tmtz);
		// QQQ not right yet
		if ( rtimezone == NULL ) {
			strftime(bf, 18, "%b %d %Y %H:%M", tmtz);
		} else  {
			strftime(bf, 19, "%b %d %Y %H:%M ", tmtz);
			strcat(bf, rtimezone);
		}
		gdImageString(graph, gdFontSmall, (int)(81+xpix*xscale)-gdFontSmall->w*lib_strlen(bf), 1, (unsigned char*)bf, i_grid);
	}

	/* draw the graph border */
	gdImageRectangle(graph,xtr(0),ytr(0),xtr(xpix),ytr(ypix),i_grid);

	/*create a dotted style for the grid lines*/
	styleDotted[0] = i_grid;
	styleDotted[1] = gdTransparent;
	styleDotted[2] = gdTransparent;
	gdImageSetStyle(graph, styleDotted, 3);

	/* Vertical scaling info and associated horizontal grid */
	sprintf(bf,"%s%s%s",plabel,timebase?"/":"", timebase?xtimebase[timebase].name:"");
	gdImageStringUp(graph, gdFontSmall,8, ytr((ypix-gdFontSmall->w*lib_strlen(bf))/2), (unsigned char*)bf, i_grid);

	for(i=0;i<=ytics;i++){
		gdImageLine(graph,xtr(-2),ytr(i*ypix/ytics),
		xtr(1),ytr(i*ypix/ytics),i_grid);

		gdImageLine(graph,xtr(xpix+2),ytr(i*ypix/ytics),
		xtr(xpix-1),ytr(i*ypix/ytics),i_grid);

		gdImageLine(graph,xtr(0),ytr(i*ypix/ytics),
		xtr(xpix),ytr(i*ypix/ytics),gdStyled);

		sprintf(bf,"%6.1f %s",stepsize*i,short_si_out);

		gdImageString(graph, gdFontSmall,23, ytr(i*ypix/ytics+gdFontSmall->h/2),	(unsigned char*)bf, i_grid);
	}

	/* draw vertical grid and horizontal labels */
	for (x=0; x<xpix; x++) {
		time_t t;	// same as long
		struct tm tm;
		int grid;

		lib_mail_time(bf,graphtime);

		t=graphtime-x*SAMPLE_PERIOD[scale];

		lib_mail_time(bf,t);

		bf[0] = '\0';
		grid=0;

		if (!safe_localtime(&t,&tm)){
			emsg("perflog: safe_localtime failed for t=[%d]\n",t);
		}
		switch(scale){
			case MLOG_HOUR:
				if ((tm.tm_sec==0) && (tm.tm_min%5==0)) {
					grid |= 1;
					if (tm.tm_min == 0)
						grid |= 2;
				}
				if ((tm.tm_sec==10) && (tm.tm_min%15==0))
					sprintf(bf,"%d:%.2d",tm.tm_hour,tm.tm_min);
				break;
			default:
			case MLOG_DAY:
				if (tm.tm_min < 5) {
					grid |= 1;
					if (tm.tm_hour == 0)
						grid |= 2;
				}
				if ((tm.tm_min<5) && (tm.tm_hour%2==0))
					sprintf(bf,"%i",tm.tm_hour);
				break;
			case MLOG_WEEK:
				if (tm.tm_min < 30 && tm.tm_hour == 0) {
					grid |= 1;
					if (tm.tm_wday == 1)
						grid |= 2;
				}
				if ((tm.tm_min<30) && (tm.tm_hour==12))
					strftime(bf,4,"%a",&tm);
				break;
			case MLOG_MONTH:
				if (tm.tm_hour < 2) {
					if (tm.tm_wday == 1)
						grid |= 1;
					if (tm.tm_mday == 1)
						grid |= 2;
				}
				if ((tm.tm_hour>10) && (tm.tm_hour<=12) && (tm.tm_wday==4))  // label goes to thursday noon
					strftime(bf,8,"Week %W", &tm);
				break;
			case MLOG_YEAR:
				if (tm.tm_mday == 1)
					grid |= 1;
				if (tm.tm_yday == 0)
					grid |= 2;
				if (tm.tm_mday==15)
					strftime(bf,4,"%b",&tm);
				break;
		}
		if (bf[0] != '\0')
			gdImageString(graph, gdFontSmall, (xtr(x) - (lib_strlen(bf) * gdFontSmall->w / 2)),	ytr(-4), bf, i_grid);
		if (grid&1) {
			gdImageLine(graph,xtr(x),ytr(-2),xtr(x),ytr(1),i_grid);
			gdImageLine(graph,xtr(x),ytr(0),xtr(x),ytr(ypix),gdStyled);
		}
		if (grid&2)
			gdImageLine(graph,xtr(x),ytr(0),xtr(x),ytr(ypix),i_major);
	}

	/* draw line at peak In value in i_major color */
#ifdef __JUNK__
	if ( ytr( ypix ) < ytr(((double)origmaxvi/nmax)*ypix)) {
		styleDotted[0] = i_major;
		gdImageSetStyle(graph, styleDotted, 3);
		gdImageLine(graph, xtr(0),ytr(((double)origmaxvi/nmax)*ypix), xtr(xpix),ytr(((double)origmaxvi/nmax)*ypix), gdStyled);
	}

	/* draw line at peak Out value in i_major color */
	if ( ytr( ypix ) < ytr(((double)origmaxvo/nmax)*ypix)) {
		styleDotted[0] = i_major;
		gdImageSetStyle(graph, styleDotted, 3);
		gdImageLine(graph, xtr(0),ytr(((double)origmaxvo/nmax)*ypix), xtr(xpix),ytr(((double)origmaxvo/nmax)*ypix), gdStyled);
	}
#endif

	remove(file);
	if ((fo = fopen(file,"wb")) != NULL) {
		GFORM_GD (graph, fo);
		fclose(fo);
	}else{
		emsg("perflog: cannot open image files (%s)\n",file);
	}
	xfile_gdImageDestroy(graph);
	xfile_gdImageDestroy(brush_out);
	xfile_gdImageDestroy(brush_outm);
	xfile_gdImageDestroy(brush_outp);
	free(lhist);

	return TRUE;
}

#endif //DOPERFLOG
