#include "stdafx.h"
#include "project.h"
#include "graphView.h"
#include "gimbalDefs.h"
#include "gimbalDef.h"
#include <math.h>
#include <sys/timeb.h>

//	----------------------------------------------------------------------------
//	globals
//	----------------------------------------------------------------------------

	float tscale = 60;
	float xscale = 1.0f/tscale;
	float tmin;
	float tmax;
	int imin = 0;
	int imax = 0;

	int nslot = 4;		// number of horiz slots
	int tx0 = 5;		// label text x offset
	int ty0 = 1;		// label text y offset

	extern controls_t controls;
	extern state_t state;

//	----------------------------------------------------------------------------
//	gimLogs
//	----------------------------------------------------------------------------

	extern log_t gimLogs[NUMLOGS];
	extern int gimLogCount;

//	----------------------------------------------------------------------------
//	gyro buffer
//	----------------------------------------------------------------------------

	const int gbsiz = TMULT*NSAMP;

	extern int tmulx;
	extern float gbufx[gbsiz];
	extern int64_t gbufxTime[gbsiz];

//	----------------------------------------------------------------------------
//	local gyro buffer
//	----------------------------------------------------------------------------

	int tmul;
	float gbuf[gbsiz];
	int64_t gbufTime[gbsiz];

//	----------------------------------------------------------------------------
//	stateValue
//	----------------------------------------------------------------------------

	extern state_t stateS[NUMSTATES];
	extern int64_t stateTimeS[NUMSTATES];
	extern int stateLogCount;

//	----------------------------------------------------------------------------
//	graphView
//	----------------------------------------------------------------------------

graphView::graphView(void)
{
	greybrush.CreateSolidBrush(0xd0d0d0);
}

//	----------------------------------------------------------------------------
//	~graphView
//	----------------------------------------------------------------------------

graphView::~graphView(void)
{
}

//	----------------------------------------------------------------------------
//	stateValue
//	----------------------------------------------------------------------------

	enum{
		VAR_angle0 = 0,
		VAR_angle1,
		VAR_time,
		VAR_geoInt0,
		VAR_geoInt1,
		VAR_targetRate0,
		VAR_targetRate1,
		VAR_tach0,
		VAR_tach1,
		VAR_rateCommand0,
		VAR_rateCommand1,
		VAR_rate0,
		VAR_rate1,
		VAR_losEr20,
		VAR_losEr21,
		VAR_losErrRate20,
		VAR_losErrRate21,
		VAR_profile0,
		VAR_profile1,
		VAR_settled0,
		VAR_settled1,
		VAR_vprofile0,
		VAR_vprofile1,
		VAR_enc0,
		VAR_enc1,
	};

//	----------------------------------------------------------------------------
//	stateValue
//	----------------------------------------------------------------------------

float stateValue(int ivar,int isample,int64_t ts)
{
	switch(ivar){
		case VAR_angle0:		return stateS[isample].axis[0].angle.val;
		case VAR_angle1:		return stateS[isample].axis[1].angle.val;
		case VAR_time:			return (float)((stateTimeS[isample]-ts)*kSecPerTick);
		case VAR_geoInt0:		return -stateS[isample].axis[0].geoInt;
		case VAR_geoInt1:		return -stateS[isample].axis[1].geoInt;
		case VAR_targetRate0:	return stateS[isample].geo.targetRate[0];
		case VAR_targetRate1:	return stateS[isample].geo.targetRate[1];
		case VAR_tach0:			return stateS[isample].axis[0].tach.val;
		case VAR_tach1:			return stateS[isample].axis[1].tach.val;
		case VAR_rateCommand0:	return stateS[isample].axis[0].rateCommand;
		case VAR_rateCommand1:	return stateS[isample].axis[1].rateCommand;
		case VAR_rate0:			return stateS[isample].axis[0].r;
		case VAR_rate1:			return stateS[isample].axis[1].r;
		case VAR_losEr20:		return stateS[isample].geo.losError2[0];
		case VAR_losEr21:		return stateS[isample].geo.losError2[1];
		case VAR_losErrRate20:	return stateS[isample].geo.losErrorRate2[0];
		case VAR_losErrRate21:	return stateS[isample].geo.losErrorRate2[1];
		case VAR_profile0:		return min(2.0f,stateS[isample].axis[0].profile);
		case VAR_profile1:		return min(2.0f,stateS[isample].axis[1].profile)+0.1f;
		case VAR_settled0:		return stateS[isample].axis[0].settled;
		case VAR_settled1:		return stateS[isample].axis[1].settled+0.1f;
		case VAR_vprofile0:		return stateS[isample].geo.vprofile[0];
		case VAR_vprofile1:		return stateS[isample].geo.vprofile[1];
		case VAR_enc0:			return stateS[isample].axis[0].enc.val;
		case VAR_enc1:			return stateS[isample].axis[1].enc.val;
	}
	return 0;
}

//	----------------------------------------------------------------------------
//	scx
//	----------------------------------------------------------------------------

int graphView::scx(float t){

	return (int)(npixx*(t-tmin)*xscale);
}

//	----------------------------------------------------------------------------
//	scy
//	----------------------------------------------------------------------------

int graphView::scy(float y,float ymin, float ymax,int slot){
	int t = slot*npixy/nslot;
	int b = t + (npixy/nslot);
	return (int)( b + (t-b)*(y-ymin)/(ymax-ymin) );
}

//	----------------------------------------------------------------------------
//	ti
//	----------------------------------------------------------------------------

int ti(int t){
	int i = tmul*NSAMP + t;
	if(i>=TMULT*NSAMP){
		i -= TMULT*NSAMP;
	}
	return i;
}

//	----------------------------------------------------------------------------
//	getis
//	----------------------------------------------------------------------------

int getis(int64_t ts)
{
	int is = 0;
	for(int i=1;i<NUMSTATES;i++){
		if(stateTimeS[i-1]==0)break;
		if(stateTimeS[i]==0)break;
		if((stateTimeS[i-1]<=ts)&&(stateTimeS[i]>ts)){
			is = i;
			break;
		}
	}
	return is;
}

//	----------------------------------------------------------------------------
//	timex
//	----------------------------------------------------------------------------

double timex()
{
	struct _timeb pcTime;
	int t1;
	static int t0 = -1;
	_ftime_s( &pcTime );
	t1 = pcTime.millitm;
	if(t0<0)t0=t1;
	double dT = 0.001*(t1-t0);
	if(dT<0)dT+=1;
	t0=t1;
	return dT;
}

//	----------------------------------------------------------------------------
//	tsec
//	----------------------------------------------------------------------------

inline float tsec()
{
	struct _timeb pcTime;
	_ftime_s( &pcTime );
	return 0.001f*pcTime.millitm;
}

	float tscroll;
	float tgyrobuf;
	int gyrobusy;
	int displaybusy;

void dts()
{
	tscroll = tsec()-tgyrobuf;
}

//	----------------------------------------------------------------------------
//	render
//	----------------------------------------------------------------------------

void graphView::render(void)
{

	if(mdcOK==0)return;
	if(gyrobusy)return;
	displaybusy = 1;

	mdc.SelectStockObject(BLACK_PEN);
	mdc.SelectStockObject(WHITE_BRUSH);
	mdc.Rectangle(rect);
	memcpy(gbuf,gbufx,sizeof(gbuf));
	memcpy(gbufTime,gbufxTime,sizeof(gbufTime));
	tmul = tmulx;
	dts();

//	-------------------------------------------------------------------
//	Horizontal Gridlines
//	-------------------------------------------------------------------

	for(int i=0;i<nslot;i++){
		mdc.MoveTo(      1,i*npixy/nslot);
		mdc.LineTo(npixx-1,i*npixy/nslot);
	}
	mdc.SelectObject(greypen);
	mdc.SelectObject(redpen);
	for(int i=0;i<nslot;i++){
		mdc.MoveTo(      1,(i*npixy/nslot)+npixy/(nslot*2));
		mdc.LineTo(npixx-1,(i*npixy/nslot)+npixy/(nslot*2));
	}

//	-------------------------------------------------------------------
//	Plot
//	-------------------------------------------------------------------

	float dT = 1.0f/kFreq;
	int ilin;
	int islot;

	tmax = gbsiz*dT;
	tscale = tmax;
	xscale = 1.0f/tscale;
	tmin = 0;
	imin = 0;
	imax = (int)(tmax/dT);

	switch(controls.plotType){

	case PLOT_Set1:
		nslot = 4;
		drawSeq(3,6);
		drawImaging(3);
		drawLog(0);

		islot = 0;
		ilin = 0;
		drawSet(islot,ilin++, 0, 90, "geoOuter (90)");
		drawSet(islot,ilin++, 2, 90, "encoder (90)");

		islot++;
		ilin = 0;
		drawSet(islot,ilin++, 3, 20, "outerPosErr (20)");

		islot++;
		ilin = 0;
		drawSet(islot,ilin++, 4, 20, "outerDemandedRate (20)");
		drawSet(islot,ilin++, 7, 20, "tach (20)");

		islot++;
		ilin = 0;
		drawSet(islot,ilin++, 5, 10, "innerAngle (10)");
		drawSet(islot,ilin++, 1,  2, "settled (2)");

		break;

	case PLOT_Set2:
		nslot = 4;
		drawSet(0,0,3,30,"gyro");
		drawSet(1,0,0,30,"rateCommand");
		drawSet(1,1,1,30,"losTach");
		drawSet(1,2,5,30,"tach");
		drawSet(1,3,4,30,"outerDemV");
		drawSet(2,0,6, 1,"outerDrive");
		drawSet(2,1,7,10,"deadband");
		drawSet(3,1,2,90,"angle");
		break;

	case PLOT_Set3:
	{
		nslot = 4;
		drawSeq(3,7);
		drawImaging(3);
		drawLog(0);

		islot = 0;
		ilin = 0;
		float klos = 30;
		drawSet(  islot ,ilin++, 0,   klos, "los.err.Y");
		drawSet(  islot ,ilin++, 1,   klos, "los.err.P");
		drawSet(  islot ,ilin++, 4,   klos, "outer.err.Y");
		drawSet(  islot ,ilin++, 5,   klos, "outer.err.P");

		islot++;
		ilin = 0;
		drawSet(  islot ,ilin++, 2,   10, "los.errRate.Y");
		drawSet(  islot ,ilin++, 3,   10, "los.errRate.P");
		drawSet(  islot ,ilin++, 6,   10, "rangeRate");

		islot++;
		ilin = 0;
		const float kloe = 5;
		drawSet(  islot ,ilin++, 0,   kloe, "los.err.Y");
		drawSet(  islot ,ilin++, 1,   kloe, "los.err.P");
		drawSet(  islot ,ilin++, 4,   kloe, "outer.err.Y");
		drawSet(  islot ,ilin++, 5,   kloe, "outer.err.P");

		islot++;
		ilin = 0;
		drawSet(  islot ,ilin++, 7,   10, "seq");

/*		drawSet(  islot ,ilin++, 4,   10, "los.err.rate2.Y");
		drawSet(  islot ,ilin++, 5,   10, "los.err.rate2.P");
//		drawState(islot ,ilin++, VAR_losErrRate20,	10, "losErrRate2.Y");
//		drawState(islot ,ilin++, VAR_losErrRate21,	10, "losErrRate2.P");
		drawProfile(islot ,0);
		drawProfile(islot ,1);

		islot++;
		ilin = 0;
		drawState(islot ,ilin++, VAR_targetRate0,	20, "targRate.Y");
		drawState(islot ,ilin++, VAR_targetRate1,	20, "targRate.P");
		drawState(islot ,ilin++, VAR_rate0,			20, "rate.Y");
		drawState(islot ,ilin++, VAR_rate1,			20, "rate.P");
		drawState(islot ,ilin++, VAR_geoInt0,		20, "geoInt.Y");
		drawState(islot ,ilin++, VAR_geoInt1,		20, "geoInt.P");
		drawState(islot ,ilin++, VAR_vprofile0,		20, "vprofile.Y");
		drawState(islot ,ilin++, VAR_vprofile1,		20, "vprofile.P");
		drawSet(  islot ,ilin++, 7,   10, "seq");
		drawState(islot ,ilin++, VAR_enc0,			180, "encoder.Y");
		drawState(islot ,ilin++, VAR_enc1,			180, "encoder.P");

		islot++;
		ilin = 0;

//		drawState(islot ,ilin++, VAR_targetRate0,	 80, "targRate.Y");
//		drawState(islot ,ilin++, VAR_targetRate1,	 80, "targRate.P");
//		drawState(islot ,ilin++, VAR_rate0,			200, "rate.Y");
//		drawState(islot ,ilin++, VAR_rate1,			200, "rate.P");
//		drawState(islot ,ilin++, VAR_vprofile0,		 90, "vprofile.Y");
//		drawState(islot ,ilin++, VAR_vprofile1,		 90, "vprofile.P");
		drawState(islot ,ilin++, VAR_tach0,			200, "tach.Y");
		drawState(islot ,ilin++, VAR_tach1,			200, "tach.P");
//		drawState(islot ,ilin++, VAR_profile0,		  3, "profile.Y");
//		drawState(islot ,ilin++, VAR_profile1,		  3, "profile.P");
//		drawState(islot ,ilin++, VAR_rateCommand0,	 80, "rateCmd.Y");
//		drawState(islot ,ilin++, VAR_rateCommand1,	 80, "rateCmd.P");
//		drawState(islot ,ilin++, VAR_settled0,		  3, "settled.Y");
//		drawState(islot ,ilin++, VAR_settled1,		  3, "settled.P");
//		drawState(islot ,ilin++, VAR_rate0,			 80, "rate.Y");
//		drawState(islot ,ilin++, VAR_rate1,			 80, "rateCmd.P");
//		drawState(islot ,ilin++, VAR_angle0,		 5, "angle.Y");
//		drawState(islot ,ilin++, VAR_angle1,		 5, "angle.P");*/

		break;
	}

	case PLOT_Set4:
	{
		nslot = 4;
		drawSeq(1,7);
		drawImaging(1);
		drawLog(0);

		islot = 0;
		ilin = 0;
		const float klos = 5;
		drawSet(  islot ,ilin++, 0,   90, "los.err.Y");
		drawSet(  islot ,ilin++, 1,   90, "los.err.P");
		drawSet(  islot ,ilin++, 0,    klos, "los.err.Y");
		drawSet(  islot ,ilin++, 1,    klos, "los.err.P");
		drawState(islot ,ilin++, VAR_losEr20,	klos, "losErr2.Y");
		drawState(islot ,ilin++, VAR_losEr21,	klos, "losErr2.P");

		islot++;
		ilin = 0;
//		drawSet(  islot ,ilin++, 6,     2, "camPulse");
//		drawSet(  islot ,ilin++, 2,    25, "profile.Y");
//		drawSet(  islot ,ilin++, 3,    25, "profile.P");
		drawSet(  islot ,ilin++, 3, 20, "outerCmd.focus");
		drawSet(  islot ,ilin++, 4, 20, "outerFb.focus");
		drawSet(  islot ,ilin++, 5, 20, "focus err");

		islot++;
		ilin = 0;
		float ratescale = 20;
		drawSet(  islot ,ilin++, 7,   10, "seq");
//		drawSet(  islot ,ilin++, 8,    2, "time");
		drawState(islot ,ilin++, VAR_time,			 2, "time");
		drawState(islot ,ilin++, VAR_geoInt0,		ratescale, "geoInt.Y");
		drawState(islot ,ilin++, VAR_geoInt1,		ratescale, "geoInt.P");
		drawState(islot ,ilin++, VAR_targetRate0,	ratescale, "targRate.Y");
		drawState(islot ,ilin++, VAR_targetRate1,	ratescale, "targRate.P");
		drawState(islot ,ilin++, VAR_rateCommand0,	ratescale, "rateCmd.Y");

		islot++;
		ilin = 0;

		ratescale = 300;

		drawState(islot ,ilin++, VAR_tach0,			ratescale, "tach.Y");
//		drawState(islot ,ilin++, VAR_tach1,			ratescale, "tach.P");
		drawState(islot ,ilin++, VAR_rateCommand0,	ratescale, "rateCmd.Y");
//		drawState(islot ,ilin++, VAR_rateCommand1,	ratescale, "rateCmd.P");
		drawState(islot ,ilin++, VAR_rate0,			ratescale, "rate.Y");
//		drawState(islot ,ilin++, VAR_rate1,			ratescale, "rateCmd.P");
		drawState(islot ,ilin++, VAR_angle0,		10, "angle.Y");
//		drawState(islot ,ilin++, VAR_angle1,		10, "angle.P");

		break;
	}

	default:
		nslot = 1;
		drawGyro(0,60);
		break;
	}
	displaybusy = 0;
}

//	----------------------------------------------------------------------------
//	setState
//	----------------------------------------------------------------------------

void graphView::setState(void)
{
	InvalidateRect(rect);
}

//	----------------------------------------------------------------------------
//	drawGyro
//	----------------------------------------------------------------------------

void graphView::drawGyro(int slot, float scale)
{
	mdc.SetTextColor(0xc0c0c0);
	mdc.TextOutA(tx0,ty0+(slot*npixy)/nslot,"Gyro (60 deg/s)");

	float t = -tscroll;
	float dT = 1.0f/kFreq;

	mdc.SelectObject(bluepen);
	float y = gbuf[ti(0)];
	mdc.MoveTo(scx(t),scy(y,-scale,scale,slot));
	for(int i=1;i<=gbsiz;i++){
		y = gbuf[ti(i)];
		mdc.LineTo(scx(t),scy(y,-scale,scale,slot));
		t += dT;
	}
}

//	----------------------------------------------------------------------------
//	drawSeq
//	----------------------------------------------------------------------------

void graphView::drawSeq(int slot, int ivar)
{
	const int nvar = 8;
	const float dT = 1.0f/kFreq;

	float s = 1;
	float t = 0;
	int im = gbsiz/nvar;
	for(int i=0;i<im;i++){
		int targSeqIndex = (int)(gbuf[ti(i*nvar)+ivar]+0.5f);

		int res = 0;
		if(targSeqIndex==OSEQ_FIRE_TRANSIT){
			mdc.SelectObject(yellowpen);
			res = 1;
		}else if(targSeqIndex==OSEQ_STARE_TRANSIT){
			mdc.SelectObject(greenpen);
			res = 1;
		}else if(targSeqIndex==OSEQ_FIRE_IMAGING){
			mdc.SelectObject(cyanpen);
			res = 1;
		}

		if(res){
			if(state.gyro.misc0){
				mdc.MoveTo(scx(t),scy( 0.0f*s,-s,s,slot));
				mdc.LineTo(scx(t),scy(-0.9f*s,-s,s,slot));
			}else{
				mdc.MoveTo(scx(t),scy( 1.0f*s,-s,s,slot));
				mdc.LineTo(scx(t),scy( 0.0f*s,-s,s,slot));
			}
		}

		t += nvar*dT;
	}
}

//	----------------------------------------------------------------------------
//	drawImaging
//	----------------------------------------------------------------------------

//	const float xtweak = -0.075f;
	const float xtweak = -0.150f;
	int qqt;
	char qqtString[100];

void graphView::drawImaging(int slot)
{
	return;

	int64_t ts = gbufTime[ti(0)];
	int64_t t0 = ts;
	mdc.SelectObject(redpen);
	float s = 1;
	int yt = scy( 1.0f*s,-s,s,slot);
	int yb = scy(-0.9f*s,-s,s,slot);
	int ym = scy( 0.0f*s,-s,s,slot);

	int im = gbsiz;
	int imaging = 0;
	for(int i=1;i<im;i++){
		int64_t t1 = gbufTime[ti(i)];
		for(int j=0;j<gimLogCount;j++){
			if((gimLogs[j].time >= t0)&&(gimLogs[j].time < t1)){
				static int64_t imtim = t0;
				switch(gimLogs[j].type){
					case LOG_fireCamera:
						imaging = 1;
						imtim = gimLogs[j].time;
						break;
					case LOG_imageComplete:
						imaging = 0;
						break;
					case LOG_settle_timeout:
						qqt = gimLogs[j].type;
						strcpy_s(qqtString,sizeof(qqtString),gimLogs[j].s);
						imaging = 0;
						break;
					default:
						if(imaging){
							qqt = gimLogs[j].type;
							strcpy_s(qqtString,sizeof(qqtString),gimLogs[j].s);
							qqt = gimLogs[j].type;
						}
						break;
				}
				if(imaging){
					if((gimLogs[j].time - imtim) > 1000){
						imaging = 0;
					}
				}
			}
			if(imaging){
				float t = (float)((t0-ts)*kSecPerTick);
				int ix = scx(t+xtweak);
				if(state.gyro.misc0){
					mdc.MoveTo(ix,ym);
					mdc.LineTo(ix,yb);
				}else{
					mdc.MoveTo(ix,yt);
					mdc.LineTo(ix,ym);
				}
			}
		}
		t0 = t1;
	}
}
//	----------------------------------------------------------------------------
//	gimLogOK
//	----------------------------------------------------------------------------

int gimLogOK(int i)
{
	if(0==(strncmp(gimLogs[i].s,"LENS",4))){
		return 0;
	}
	return 1;
}

//	----------------------------------------------------------------------------
//	drawLog
//	----------------------------------------------------------------------------

void graphView::drawLog(int slot)
{
	int dy = 16;
	int iy = scy( 0,-1,1,slot);
	int64_t ts = gbufTime[ti(0)];
	int64_t t0 = ts;
	int im = gbsiz;
	int cy = dy;
	for(int i=1;i<im;i++){
		int64_t t1 = gbufTime[ti(i)];
		for(int j=0;j<gimLogCount;j++){
			if( (gimLogs[j].time >= t0) && (gimLogs[j].time < t1) && (gimLogOK(j)) ){
				float t = (float)((t0-ts)*kSecPerTick);
				int ix = scx(t+xtweak);
				mdc.SetTextColor(0);
				mdc.TextOutA(ix,iy,"o");
				mdc.SetTextColor(0xc0c0c0);
				mdc.TextOutA(ix,iy+cy,gimLogs[j].s);
				cy += dy;
				if(cy>(npixy-4*dy))cy=dy;
			}
		}
		t0 = t1;
	}
	char s[40];
	sprintf_s(s,sizeof(s),"%1.3f",ts*kSecPerTick);
	mdc.TextOutA(10,iy,s);
}

//	----------------------------------------------------------------------------
//	drawState
//	----------------------------------------------------------------------------
#if 0
void graphView::drawState(int slot, int line, int ivar, float yscale, char *name)
{
	float s =  yscale;
	float tfirst = 0;
	float tlast = 0;
	float tfl = 0;

	drawGrid(slot);
	drawLegend(slot,line,ivar,yscale,name);
	linePen(line);

	int64_t ts = gbufTime[ti(0)];
	int is = getis(ts);
	if(is==0)return;

	int64_t t1 = stateTimeS[is];
	float x = (float)((t1-ts)*kSecPerTick);
	float y = stateValue(ivar,is,t1);
//	int iy = scy(y,-s,s,slot);
//	mdc.MoveTo(scx(x),iy);
	mdc.MoveTo(scx(x + xtweak),scy(y,-s,s,slot));
	for(int i=0;i<NUMSTATES;i++){
		bool inrange = fabs(y)<s;
		t1 = stateTimeS[is];
		x = (float)((t1-ts)*kSecPerTick) ;
		y = stateValue(ivar,is,ts);
		inrange &= fabs(y)<s;
		linePen(line,inrange);
		if(x<0)break;
		is++;
		if(is>(NUMSTATES-1))is=0;
	//	mdc.LineTo(scx(x),iy);
	//	iy = scy(y,-s,s,slot);
	//	mdc.LineTo(scx(x),iy);
		mdc.LineTo(scx(x + xtweak),scy(y,-s,s,slot));
	}
}
#elif 0
//	----------------------------------------------------------------------------
//	drawState
//	----------------------------------------------------------------------------

void graphView::drawState(int slot, int line, int ivar, float yscale, char *name)
{
	float s =  yscale;
	float tfirst = 0;
	float tlast = 0;
	float tfl = 0;

	drawGrid(slot);
	drawLegend(slot,line,ivar,yscale,name);
	linePen(line);

	int64_t ts = gbufTime[ti(0)];
	int is = 0;
//	if(stateLogCount>1){
//		int64_t tx = stateTimeS[stateLogCount-1];
//		tlast = (float)((tx)*kSecPerTick);
//		tfirst = (float)((ts)*kSecPerTick);
//		tfl = (float)((tx-ts)*kSecPerTick);
//	}

	for(int i=1;i<NUMSTATES;i++){
		if(stateTimeS[i-1]==0)break;
		if(stateTimeS[i]==0)break;
		if((stateTimeS[i-1]<=ts)&&(stateTimeS[i]>ts)){
			is = i;
			break;
		}
	}
	if(is==0)return;

	int64_t t1 = stateTimeS[is];
	float x = (float)((t1-ts)*kSecPerTick);
	float y = stateS[is].axis[0].angle.val;
	int iy = scy(y,-s,s,slot);
	mdc.MoveTo(scx(x),iy);
	for(int i=0;i<NUMSTATES;i++){
		t1 = stateTimeS[is];
		x = (float)((t1-ts)*kSecPerTick);
		y = stateValue(ivar,is,ts);
		if(x<0)break;
		is++;
		if(is>(NUMSTATES-1))is=0;
		mdc.LineTo(scx(x),iy);
		iy = scy(y,-s,s,slot);
		mdc.LineTo(scx(x),iy);
	}
}
#else

void graphView::drawState(int slot, int line, int ivar, float yscale, char *name)
{
	float s =  yscale;
	float tfirst = 0;
	float tlast = 0;
	float tfl = 0;

	drawGrid(slot);
	drawLegend(slot,line,ivar,yscale,name);
	linePen(line);

	int64_t ts = gbufTime[ti(0)];
	int is = 0;
//	if(stateLogCount>1){
//		int64_t tx = stateTimeS[stateLogCount-1];
//		tlast = (float)((tx)*kSecPerTick);
//		tfirst = (float)((ts)*kSecPerTick);
//		tfl = (float)((tx-ts)*kSecPerTick);
//	}

	for(int i=1;i<NUMSTATES;i++){
		if(stateTimeS[i-1]==0)break;
		if(stateTimeS[i]==0)break;
		if((stateTimeS[i-1]<=ts)&&(stateTimeS[i]>ts)){
			is = i;
			break;
		}
	}
	if(is==0)return;

	int64_t t1 = stateTimeS[is];
	float x = (float)((t1-ts)*kSecPerTick);
	float y = stateS[is].axis[0].angle.val;
	int first = 1;
	for(int i=0;i<NUMSTATES;i++){
		t1 = stateTimeS[is];
		x = (float)((t1-ts)*kSecPerTick);
		y = stateValue(ivar,is,ts);
		if(x>=0){
			int ix = scx(x);
			int iy = scy(y,-s,s,slot);
			if(first){
				first = 0;
				mdc.MoveTo(ix,iy);
//				char s[128];
//				sprintf_s(s,sizeof(s),"tfirst=%1.3f tlast=%1.3f tfl%1.3f",tfirst,tlast,tfl);
//				mdc.TextOutA(ix,iy,s);
			}else{
				mdc.LineTo(ix,iy);
			}
		}
		//		if(x<0)break;
		is++;
		if(is>(NUMSTATES-1))is=0;
	}
}
#endif

//	----------------------------------------------------------------------------
//	drawProfile
//	----------------------------------------------------------------------------

void graphView::drawProfile(int slot, int axis)
{
	int64_t ts = gbufTime[ti(0)];
	int is = getis(ts);
	if(is==0)return;

	int64_t t1 = stateTimeS[is];
	float x = (float)((t1-ts)*kSecPerTick);
	int y0 = scy(-1.0f,-1,1,slot) - (int)(6*axis);
	mdc.MoveTo(scx(x + xtweak),y0);
	for(int i=0;i<NUMSTATES;i++){
		t1 = stateTimeS[is];
		x = (float)((t1-ts)*kSecPerTick);
		profilePen(axis,is);
		if(x<0)break;
		is++;
		if(is>(NUMSTATES-1))is=0;
		mdc.LineTo(scx(x + xtweak),y0);
	}
}

//	----------------------------------------------------------------------------
//	drawGrid
//	----------------------------------------------------------------------------

void graphView::drawGrid(int slot)
{
	float s =  1;
	int yt = scy( s,-s,s,slot);
	int yb = scy(-s,-s,s,slot);

	float t = 0;
	float dT = 1.0f/kFreq;
	mdc.SelectObject(lightgreypen);			// vertical gridlines
	while(t<tmax){
		t+=0.1f;
		int x = scx(t);
		mdc.MoveTo(x,yt);
		mdc.LineTo(x,yb);
	}
	t = 0;
	mdc.SelectObject(greypen);				// vertical gridlines
	while(t<tmax){
		t+=1;
		int x = scx(t);
		mdc.MoveTo(x,yt);
		mdc.LineTo(x,yb);
	}
}

//	----------------------------------------------------------------------------
//	linePen
//	----------------------------------------------------------------------------

void graphView::linePen(int line,int inrange)
{
	const int nlin = 10;
	CPen *pens[nlin]={
		&bluepen,
		&darkgreenpen,
		&redpen,
		&magentapen,
		&cyanpen,
		&greenpen,
		&blackpen,
		&bluepen,
		&bluepen,
		&bluepen
	};
	if(inrange){
		int linex = max(0,min(line,nlin-1));
		mdc.SelectObject(pens[linex]);
	}else{
		mdc.SelectObject(lightgreypen);
	}
}

//	----------------------------------------------------------------------------
//	profilePen
//	----------------------------------------------------------------------------

enum {
		ODIN_MODE_UNKNOWN,			// Unknown Mode
		ODIN_MODE_INIT,				// Startup
		ODIN_MODE_BIT,				// Built in Test
		ODIN_MODE_RATE,				// Rate Mode
		ODIN_MODE_POS,				// Position Mode
		ODIN_MODE_STOW,				// Stow
		ODIN_MODE_GEO,				// Geo Pointing Mode
		ODIN_MODE_FAULT,			// Fault Condition
		ODIN_MODE_CUSTOM,			// An Extended Mode specific to type of Gimbal
		ODIN_MODE_OUTER,			// Outer
		NUM_ODIN_MODES
	};

//	----------------------------------------------------------------------------
//	profilePen
//	----------------------------------------------------------------------------

void graphView::profilePen(int axis,int isample)
{
	int settled = stateS[isample].axis[axis].settled;
	int profile = stateS[isample].axis[axis].profile;
	int geoMode = (stateS[isample].odin.gimbalMode == ODIN_MODE_GEO);

	if(!geoMode){
		mdc.SelectObject(lightgreypen2);
	}else if(profile){
		mdc.SelectObject(yellowpen2);
	}else if(settled){
		mdc.SelectObject(greenpen2);
	}else{
		mdc.SelectObject(redpen2);
	}
}

//	----------------------------------------------------------------------------
//	drawLegend
//	----------------------------------------------------------------------------

void graphView::drawLegend(int slot, int line, int ivar, float yscale, char *name)
{
	char s[40];
	mdc.SetTextColor(0xc0c0c0);
	int tx = tx0+250*line;
	int ty = ty0+(slot*npixy)/nslot;
	linePen(line);
	for(int i=0;i<10;i++){
		mdc.MoveTo(tx,ty+3+i);mdc.LineTo(tx+10,ty+3+i);
	}
	sprintf_s(s,sizeof(s),"%s(%1.0f)",name,yscale);
	mdc.TextOutA(tx+13,ty,s);
}

//	----------------------------------------------------------------------------
//	drawSet
//	----------------------------------------------------------------------------

const int nvar = 8;

void graphView::drawSet(int slot, int line, int ivar, float yscale, char *name)
{

	float s =  yscale;
	float dT = 1.0f/kFreq;
	
	drawGrid(slot);
	drawLegend(slot,line,ivar,yscale,name);

	float t = 0;
	float y = gbuf[ti(0)+ivar];
	int64_t ts = gbufTime[ti(0)];
	mdc.MoveTo(scx(t),scy(y,-s,s,slot));
	int im = gbsiz/nvar;
	for(int i=1;i<im;i++){
		bool inrange = fabs(y)<s;
		y = gbuf[ti(i*nvar)+ivar];
		if(ivar==8){
			int64_t t1 = gbufTime[ti(i*nvar)];
			y = (float)((t1-ts)*kSecPerTick);
		}
		inrange &= fabs(y)<s;
		linePen(line,inrange);
		mdc.LineTo(scx(t),scy(y,-s,s,slot));
		t += nvar*dT;
	}
	linePen(0,0);
}