/*ScianVisTraces.c
  Eric Pepke
  September 9, 1991
  Trace visualization object in SciAn
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianArrays.h"
#include "ScianWindows.h"
#include "ScianTextBoxes.h"
#include "ScianObjWindows.h"
#include "ScianIcons.h"
#include "ScianColors.h"
#include "ScianControls.h"
#include "ScianLists.h"
#include "ScianSpaces.h"
#include "ScianButtons.h"
#include "ScianSliders.h"
#include "ScianIDs.h"
#include "ScianDatasets.h"
#include "ScianErrors.h"
#include "ScianVisObjects.h"
#include "ScianVisWindows.h"
#include "ScianStyle.h"
#include "ScianPictures.h"
#include "ScianTimers.h"

#define NSEGMENTS	10		/*Number of segments in a trace to show*/

ObjPtr traceClass;			/*Class for trace*/

static ObjPtr MakeTraceBounds(object)
ObjPtr object;
/*Makes bounds for a trace*/
{
    ObjPtr objBounds;
    real bounds[6];
	ObjPtr dataObj;
	ObjPtr var;
	int component;
	long index, *dims;
	real sample;

	/*Go through all data expanding bounds*/
	bounds[0] = 1E12;
	bounds[1] = -1E12;
	bounds[2] = 1E12;
	bounds[3] = -1E12;
	bounds[4] = 1E12;
	bounds[5] = -1E12;

	/*X bounds*/
	dataObj = GetObjectVar("TraceBounds", object, XFIELD);
	var = GetIntVar("TraceBounds", object, XTOPDIM);
	if (!dataObj || !var)
	{
	    return ObjFalse;
	}
	component = GetInt(var);

	SetCurField(FIELD1, dataObj);
	if (GetNComponents(FIELD1) < component)
	{
	    component = GetNComponents(FIELD1);
	}

	if (CountComponentDims(FIELD1, component) != 1)
	{
	    ReportError("TraceBounds", "Only one-dimensional components are valid in a trace");
	    return ObjFalse;
	}

	dims = GetComponentDims(FIELD1, component);

	index = 0;
	sample = SelectFieldComponent(FIELD1, component, &index);
	bounds[0] = sample;
	bounds[1] = sample;
	for (index = 1; index < dims[0]; ++index)
	{
	    sample = SelectFieldComponent(FIELD1, component, &index);
	    if (sample < bounds[0]) bounds[0] = sample;
	    if (sample > bounds[1]) bounds[1] = sample;
	}

	/*Y bounds*/
	dataObj = GetObjectVar("TraceBounds", object, YFIELD);
	var = GetIntVar("TraceBounds", object, YTOPDIM);
	if (!dataObj || !var)
	{
	    return ObjFalse;
	}
	component = GetInt(var);

	SetCurField(FIELD1, dataObj);
	if (GetNComponents(FIELD1) < component)
	{
	    component = GetNComponents(FIELD1);
	    printf("Bumping component down to %d", component);
	}

	if (CountComponentDims(FIELD1, component) != 1)
	{
	    ReportError("TraceBounds", "Only one-dimensional components are valid in a trace");
	    return ObjFalse;
	}

	dims = GetComponentDims(FIELD1, component);

	index = 0;
	sample = SelectFieldComponent(FIELD1, component, &index);
	bounds[2] = sample;
	bounds[3] = sample;
	for (index = 1; index < dims[0]; ++index)
	{
	    sample = SelectFieldComponent(FIELD1, component, &index);
	    if (sample < bounds[2]) bounds[2] = sample;
	    if (sample > bounds[3]) bounds[3] = sample;
	}

	/*Z bounds*/
	dataObj = GetObjectVar("TraceBounds", object, ZFIELD);
	var = GetIntVar("TraceBounds", object, ZTOPDIM);
	if (!dataObj || !var)
	{
	    return ObjFalse;
	}
	component = GetInt(var);

	SetCurField(FIELD1, dataObj);
	if (GetNComponents(FIELD1) < component)
	{
	    component = GetNComponents(FIELD1);
	}

	if (CountComponentDims(FIELD1, component) != 1)
	{
	    ReportError("TraceBounds", "Only one-dimensional components are valid in a trace");
	    return ObjFalse;
	}

	dims = GetComponentDims(FIELD1, component);

	index = 0;
	sample = SelectFieldComponent(FIELD1, component, &index);
	bounds[4] = sample;
	bounds[5] = sample;
	for (index = 0; index < dims[0]; ++index)
	{
	    sample = SelectFieldComponent(FIELD1, component, &index);
	    if (sample < bounds[4]) bounds[4] = sample;
	    if (sample > bounds[5]) bounds[5] = sample;
	}

	objBounds = NewRealArray(1, (long) 6);
	CArray2Array(objBounds, bounds);
	SetVar(object, BOUNDS, objBounds);
    return ObjTrue;
}

static ObjPtr ChangeMovingTrace(object)
ObjPtr object;
/*Changed value for a moving trace*/
{
    int movingTrace;
    ObjPtr val;
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeMovingTrace", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetVar(object, VALUE);
    if (val)
    {
	movingTrace = GetInt(val);
    }
    else
    {
	movingTrace = false;
    }

    DoNotDisturb(repObj, WAKEUP);
    if (movingTrace)
    {
	SetVar(repObj, HILITESEGMENT, NewInt(1));
	WakeMe(repObj, WAKEUP, Clock());
    }
    else
    {
	SetVar(repObj, HILITESEGMENT, NULLOBJ);
    }
    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr ChangeSmoothTrace(object)
ObjPtr object;
/*Changed value for the smooth trace*/
{
    int smoothTrace;
    ObjPtr val;
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeSmoothTrace", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetVar(object, VALUE);
    if (val)
    {
	smoothTrace = GetInt(val);
    }
    else
    {
	smoothTrace = false;
    }

    SetVar(repObj, SMOOTH, NewInt(smoothTrace));
    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr AddTraceControls(object, panelContents)
ObjPtr object, panelContents;
/*Adds controls for a trace object*/
{
    int width;
    int left, top;
    ObjPtr checkBox;
    int smooth;

    width = CWINWIDTH - 2 * CORRALBORDER - CWINCORRALWIDTH;
    left = MAJORBORDER;
    top = CWINHEIGHT - MAJORBORDER;
/****UPDATE*** moving trace is not the best idea*/
    /*Create the check box for moving trace*/
    checkBox = NewCheckBox(left, left + width,
	top - CHECKBOXHEIGHT, top, 
	"Moving Trace", GetVar(object, HILITESEGMENT) ? 1 : 0);
    if (!checkBox)
    {
	return ObjFalse;
    }
    PrefixList(panelContents, checkBox);
    SetVar(checkBox, HELPSTRING,
	NewString("If this box is checked, a moving red trace will follow \
the trace through time.\n"));
    SetVar(checkBox, PARENT, panelContents);
    SetVar(checkBox, REPOBJ, object);
    SetMethod(checkBox, CHANGEDVALUE, ChangeMovingTrace);
    top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

    /*Create the check box for smooth trace*/
    smooth = GetPredicate(object, SMOOTH);
    checkBox = NewCheckBox(left, left + width,
	top - CHECKBOXHEIGHT, top, "Smooth Trace", smooth);
    if (!checkBox)
    {
	return ObjFalse;
    }
    PrefixList(panelContents, checkBox);
    SetVar(checkBox, HELPSTRING, 
	NewString("If this box is checked, the trace will be drawn smoothly \
using a Cardinal spline.  If not, the trace will be drawn with a series of \
line segments."));
    SetVar(checkBox, PARENT, panelContents);
    SetVar(checkBox, REPOBJ, object);
    SetMethod(checkBox, CHANGEDVALUE, ChangeSmoothTrace);
    top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

    return ObjTrue;
}

static ObjPtr WakeTrace(object)
ObjPtr object;
/*Wakes up a trace*/
{
    ObjPtr space;
    WinInfoPtr window;
    ObjPtr hiliteObj;
    ObjPtr dataField;
    long nSamples;
    int xComponent;
    ObjPtr var;
    long *dims;
    int hiliteSegment;

    /*Set up X coordinate*/
    dataField = GetObjectVar("WakeTrace", object, XFIELD);
    var = GetIntVar("WakeTrace", object, XTOPDIM);
    if (!dataField || !var)
    {
	return ObjFalse;
    }
    xComponent = GetInt(var);

    SetCurField(FIELD1, dataField);
    if (GetNComponents(FIELD1) < xComponent)
    {
	xComponent = GetNComponents(FIELD1);
    }

    if (CountComponentDims(FIELD1, xComponent) != 1)
    {
	ReportError("WakeTrace", "Only one-dimensional components are valid in a trace");
    }

    dims = GetComponentDims(FIELD1, xComponent);
    nSamples = *dims;

    /*Next highlighted segment*/
    hiliteObj = GetVar(object, HILITESEGMENT);
    if (hiliteObj)
    {
	hiliteSegment = GetInt(hiliteObj);
	hiliteSegment += 1;
	if (hiliteSegment > nSamples)
	{
	    SetVar(object, HILITESEGMENT, NewInt(0));
	}
	else
	{
	    SetVar(object, HILITESEGMENT, NewInt(hiliteSegment));
	}
	WakeMe(object, WAKEUP, Clock() + 1.0 / 30.0);
    }

    ImInvalid(object);

    return ObjTrue;
}

static ObjPtr SetTraceMainDataset(visObj, dataSet)
ObjPtr visObj, dataSet;
/*Sets the main data set of visObj to dataSet*/
{
    SetVar(visObj, REPOBJ, dataSet);
    SetVar(visObj, XFIELD, dataSet);
    SetVar(visObj, YFIELD, dataSet);
    SetVar(visObj, ZFIELD, dataSet);
    SetVar(visObj, MAINDATASET, dataSet);
    return ObjTrue;
}

static ObjPtr InitTrace(visObj)
ObjPtr visObj;
/*Initializes a trace*/
{
    SetVar(visObj, XTOPDIM, NewInt(0));
    SetVar(visObj, YTOPDIM, NewInt(1));
    SetVar(visObj, ZTOPDIM, NewInt(2));
}

static ObjPtr DrawTrace(object)
ObjPtr object;
/*Draw a trace*/
{
#ifdef GRAPHICS
    long k;
    int xComponent, yComponent, zComponent;
    int x, y, z;
    real xScale, yScale, zScale;
    ObjPtr dataField, var;
    Bool smooth;
    long *dims;
    int hiliteSegment;
    Coord curveSeg[4][3];
    long nSamples;		/*The number of samples*/

    /*Get x, y, z, scale*/
    var = GetRealVar("DrawTrace", object, XSCALE);
    if (var)
    {
	xScale = GetReal(var);
    }
    else 
    {
	xScale = 1.0;
    }
    var = GetRealVar("DrawTrace", object, YSCALE);
    if (var)
    {
	yScale = GetReal(var);
    }
    else 
    {
	yScale = 1.0;
    }
    var = GetRealVar("DrawTrace", object, ZSCALE);
    if (var)
    {
	zScale = GetReal(var);
    }
    else 
    {
	zScale = 1.0;
    }

    /*Set up X coordinate*/
    dataField = GetObjectVar("DrawTrace", object, XFIELD);
    var = GetIntVar("DrawTrace", object, XTOPDIM);
    if (!dataField || !var)
    {
	return ObjFalse;
    }
    xComponent = GetInt(var);

    SetCurField(FIELD1, dataField);
    if (GetNComponents(FIELD1) < xComponent)
    {
	xComponent = GetNComponents(FIELD1);
    }

    if (CountComponentDims(FIELD1, xComponent) != 1)
    {
	ReportError("DrawTrace", "Only one-dimensional components are valid in a trace");
    }

    dims = GetComponentDims(FIELD1, xComponent);
    nSamples = *dims;

    /*Set up Y coordinate*/
    dataField = GetObjectVar("DrawTrace", object, YFIELD);
    var = GetIntVar("DrawTrace", object, YTOPDIM);
    if (!dataField || !var)
    {
	return ObjFalse;
    }
    yComponent = GetInt(var);

    SetCurField(FIELD2, dataField);
    if (GetNComponents(FIELD2) < yComponent)
    {
	yComponent = GetNComponents(FIELD2);
    }

    if (CountComponentDims(FIELD2, yComponent) != 1)
    {
	ReportError("DrawTrace", "Only one-dimensional components are valid in a trace");
    }

    dims = GetComponentDims(FIELD2, yComponent);
    if (*dims != nSamples)
    {
	/****UPDATE*** do something else*/
	nSamples = MIN(*dims, nSamples);
    }

    /*Set up Z coordinate*/
    dataField = GetObjectVar("DrawTrace", object, ZFIELD);
    var = GetIntVar("DrawTrace", object, ZTOPDIM);
    if (!dataField || !var)
    {
	return ObjFalse;
    }
    zComponent = GetInt(var);

    SetCurField(FIELD3, dataField);
    if (GetNComponents(FIELD3) < zComponent)
    {
	zComponent = GetNComponents(FIELD3);
    }

    if (CountComponentDims(FIELD3, zComponent) != 1)
    {
	ReportError("DrawTrace", "Only one-dimensional components are valid in a trace");
    }

    dims = GetComponentDims(FIELD3, zComponent);
    if (*dims != nSamples)
    {
	/****UPDATE*** do something else*/
	nSamples = MIN(*dims, nSamples);
    }

    /*Get smooth predicate*/
    smooth = GetPredicate(object, SMOOTH);

    /*Determine which segment to hilite, if any*/
    var = GetVar(object, HILITESEGMENT);
    if (var && IsInt(var))
    {
	hiliteSegment = GetInt(var);
    }
    else
    {
	hiliteSegment = -50;
    }


    SetLineWidth(1);

    /*Make z-buffer read-only*/
    zwritemask(0);

    if (smooth)
    {
	int b, e;
	curvebasis(CARDBASIS);

	SetUIColor(WHITE);
	/*Draw the unhilited segments*/
	k = 0;
	x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
	curveSeg[0][0] = x;
	curveSeg[0][1] = y;
	curveSeg[0][2] = z;
	curveSeg[1][0] = x;
	curveSeg[1][1] = y;
	curveSeg[1][2] = z;

	k = 1;
	x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
	curveSeg[2][0] = x;
	curveSeg[2][1] = y;
	curveSeg[2][2] = z;

	k = 2;
	x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
	curveSeg[3][0] = x;
	curveSeg[3][1] = y;
	curveSeg[3][2] = z;

	for (k = 1; k < nSamples; ++k)
	{
	    crv(curveSeg);

	    /*Shift everything down and add new point*/
	    curveSeg[0][0] = curveSeg[1][0];
	    curveSeg[0][1] = curveSeg[1][1];
	    curveSeg[0][2] = curveSeg[1][2];
	    curveSeg[1][0] = curveSeg[2][0];
	    curveSeg[1][1] = curveSeg[2][1];
	    curveSeg[1][2] = curveSeg[2][2];
	    curveSeg[2][0] = curveSeg[3][0];
	    curveSeg[2][1] = curveSeg[3][1];
	    curveSeg[2][2] = curveSeg[3][2];

	    if (k < nSamples - 1)
	    {
		curveSeg[3][0] = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
		curveSeg[3][1] = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
		curveSeg[3][2] = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
	    }
	}

	/*Draw the hilited segments*/
	SetUIColor(UIRED);
	SetLineWidth(3);

	b = hiliteSegment - NSEGMENTS;
	e = hiliteSegment;
	if (b < 0) b = 0;
	if (e >= nSamples) e = nSamples - 1;
		
	if (b > 0)
	{
	    k = b - 1;

	    x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	    y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	    z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);

	    curveSeg[0][0] = x;
	    curveSeg[0][1] = y;
	    curveSeg[0][2] = z;
	    ++k;
	}
	else
	{
	    k = b;
	    x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	    y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	    z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);

	    curveSeg[0][0] = x;
	    curveSeg[0][1] = y;
	    curveSeg[0][2] = z;
	}
	x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
	curveSeg[1][0] = x;
	curveSeg[1][1] = y;
	curveSeg[1][2] = z;
	
	++k;
	x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
	curveSeg[2][0] = x;
	curveSeg[2][1] = y;
	curveSeg[2][2] = z;

	++k;
	x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
	curveSeg[3][0] = x;
	curveSeg[3][1] = y;
	curveSeg[3][2] = z;
	for (k = b + 1; k < e; ++k)
	{
	    crv(curveSeg);

	    /*Shift everything down and add new point*/
	    curveSeg[0][0] = curveSeg[1][0];
	    curveSeg[0][1] = curveSeg[1][1];
	    curveSeg[0][2] = curveSeg[1][2];
	    curveSeg[1][0] = curveSeg[2][0];
	    curveSeg[1][1] = curveSeg[2][1];
	    curveSeg[1][2] = curveSeg[2][2];
	    curveSeg[2][0] = curveSeg[3][0];
	    curveSeg[2][1] = curveSeg[3][1];
	    curveSeg[2][2] = curveSeg[3][2];

	    if (k < nSamples - 1)
	    {
		x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
		y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
		z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
		curveSeg[3][0] = x;
		curveSeg[3][1] = y;
		curveSeg[3][2] = z;
	    }
	}
    }
    else
    {
	/*Draw the unhilited segments*/
	long b, e;

	SetUIColor(WHITE);
	SetLineWidth(1);
	k = 0;
	x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	y = yScale * SelectFieldComponent(FIELD2, yComponent, &k);
	z = zScale * SelectFieldComponent(FIELD3, zComponent, &k);
	move(x, y, z);

	for (k = 1; k < nSamples; ++k)
	{
	    x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	    y = yScale * SelectFieldComponent(FIELD1, yComponent, &k);
	    z = zScale * SelectFieldComponent(FIELD1, zComponent, &k);
	    draw(x, y, z);
	}

	SetUIColor(UIRED);
	SetLineWidth(3);

	/*Draw the hilited segments*/
	b = hiliteSegment - NSEGMENTS;
	e = hiliteSegment;
	if (b < 0) b = 0;
	if (e >= nSamples) e = nSamples - 1;

	x = xScale * SelectFieldComponent(FIELD1, xComponent, &b);
	y = yScale * SelectFieldComponent(FIELD2, yComponent, &b);
	z = zScale * SelectFieldComponent(FIELD3, zComponent, &b);
	move(x, y, z);
		
	for (k = b + 1; k <= e; ++k)
	{
	    x = xScale * SelectFieldComponent(FIELD1, xComponent, &k);
	    y = yScale * SelectFieldComponent(FIELD1, yComponent, &k);
	    z = zScale * SelectFieldComponent(FIELD1, zComponent, &k);
	    draw(x, y, z);
	}
    }
    SetLineWidth(1);
    SetUIColor(UIGRAY50);
    zwritemask(0xffffffff);
#endif
    return ObjTrue;
}

void InitTraces()
/*Initializes the traces*/
{

    ObjPtr icon;

    /*Class for a trace displayer*/
    traceClass = NewObject(visColored, 0);
    AddToReferenceList(traceClass);
    SetVar(traceClass, NAME, NewString("Trace"));
    SetMethod(traceClass, BOUNDS, MakeTraceBounds);
    SetMethod(traceClass, ADDCONTROLS, AddTraceControls);
    SetMethod(traceClass, SETMAINDATASET, SetTraceMainDataset);
    SetMethod(traceClass, INITIALIZE, InitTrace);
    icon = NewIcon(0, 0, ICONTRACE , "Trace");
    SetVar(icon, HELPSTRING,
	NewString("Select this icon to show controls for a trace, such \
as whether to smooth the trace and show a moving trace."));
    SetVar(traceClass, CONTROLICON, icon);
    icon = NewObject(visIcon, 0);
    SetVar(icon, WHICHICON, NewInt(ICONTRACE));
    SetVar(icon, NAME, NewString("Trace"));
    SetVar(icon, HELPSTRING,
	NewString("This icon represents a trace object.  A trace object shows \
a 1-D vector field as a trace in 3-space, taking the x, y, and z positions \
from components of the vector."));

    SetVar(traceClass, DEFAULTICON, icon);
    SetMethod(traceClass, DRAW, DrawTrace);
    SetMethod(traceClass, WAKEUP, WakeTrace);

/*    DefineVisMapping(DS_HASFORM | DS_HASFIELD, 1, -1, -1, traceClass);
    DefineVisMapping(DS_HASFIELD, 1, -1, -1, traceClass);
*/
    DefineVisMapping(DS_HASFORM | DS_HASFIELD | DS_VECTOR, 1, -1, -1, traceClass);
    DefineVisMapping(DS_HASFIELD | DS_VECTOR, 1, -1, -1, traceClass);
}

void KillTraces()
/*Kills the traces*/
{
    DeleteThing(traceClass);
}
