/*ScianAnimation.c
  Eric Pepke
  21 May 1993

  Advanced keyframe animation for SciAn
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianIDs.h"
#include "ScianNames.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianDialogs.h"
#include "ScianScripts.h"
#include "ScianErrors.h"
#include "ScianSymbols.h"
#include "ScianEvents.h"
#include "ScianLists.h"
#include "ScianArrays.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianTextBoxes.h"
#include "ScianTitleBoxes.h"
#include "ScianIcons.h"
#include "ScianStyle.h"
#include "ScianObjFunctions.h"
#include "ScianDatabase.h"
#include "ScianColors.h"
#include "ScianObjFunctions.h"
#include "ScianTemplates.h"
#include "ScianTemplateHelper.h"
#include "ScianSnap.h"
#include "ScianAnimation.h"
#include "ScianTrackControls.h"

ObjPtr trackClass;		/*Class of a track within a sequence*/
ObjPtr subTrackClass;		/*Subtracks*/
ObjPtr sequenceClass;		/*Class of a sequence*/

#ifdef PROTO
static ObjPtr NewTrack(ObjPtr repObj)
#else
static ObjPtr NewTrack(repObj)
ObjPtr repObj;
#endif
/*Creates a new track to control repObj*/
{
    ObjPtr retVal;

    retVal = NewObject(trackClass, 0L);
    SetVar(retVal, REPOBJ, repObj);
    MakeVar(repObj, NAME);
    SetVar(retVal, NAME, GetVar(repObj, NAME));
    return retVal;
}

#ifdef PROTO
static ObjPtr NewSubTrack(ObjPtr repObj, char *name)
#else
static ObjPtr NewSubTrack(repObj, name)
ObjPtr repObj;
char *name;
#endif
/*Creates a new subtrack to control repObj's variable group named name*/
{
    ObjPtr retVal;

    retVal = NewObject(subTrackClass, 0L);
    SetVar(retVal, REPOBJ, repObj);
    SetVar(retVal, NAME, NewString(name));
    return retVal;
}

#ifdef PROTO
ObjPtr TrackObjectWithinSequence(ObjPtr sequence, ObjPtr object)
#else
ObjPtr TrackObjectWithinSequence(sequence, object)
ObjPtr sequence;
ObjPtr object;
#endif
/*Returns the track for object within sequence or adds one*/
{
    ObjPtr tracks, track, var;
    ObjPtr *elements;
    long k, dim;

    /*First find out if there is a track with that object*/
    MakeVar(sequence, TRACKS);
    tracks = GetVar(sequence, TRACKS);
    if (tracks)
    {
	for (k = 0; k < DIMS(tracks)[0]; ++k)
	{
	    var = GetObjectElement(tracks, &k);
	    if (GetVar(var, REPOBJ) == object)
	    {
		return var;
	    }
	}
    }

    /*Fell through, object not found.  Create a new track*/
    track = NewTrack(object);

    if (tracks)
    {
	/*Insert track in sorted place*/
	var = GetStringVar("TrackObjectWithinSequence", object, NAME);
	if (!var) return NULLOBJ;
	k = SearchStringVar(tracks, NAME, GetString(var));
	if (k < 0) return NULLOBJ;
	tracks = InsertInArray(tracks, track, k);
    }
    else
    {
	/*Create single array to hold tracks*/
	dim = 1;
	tracks = NewArray(AT_OBJECT, 1, &dim);
	elements = (ObjPtr *) ELEMENTS(tracks);
	elements[0] = track;
    }
    SetVar(track, PARENT, sequence);

    SetVar(sequence, TRACKS, tracks);
    return track;
}

#ifdef PROTO
ObjPtr TrackVarGroupWithinSequence(ObjPtr sequence, ObjPtr object, char *group)
#else
ObjPtr TrackVarGroupWithinSequence(sequence, object, group)
ObjPtr sequence;
ObjPtr object;
char *group;
#endif
/*Returns the track for vargroup group of object within sequence
  or adds one*/
{
    ObjPtr track, var, name;
    ObjPtr subTracks, subTrack;
    ObjPtr *elements;
    long k, dim;

    track = TrackObjectWithinSequence(sequence, object);

    /*First find out if there is a subtrack for that group*/
    MakeVar(track, SUBTRACKS);
    subTracks = GetVar(track, SUBTRACKS);
    if (subTracks)
    {
	for (k = 0; k < DIMS(subTracks)[0]; ++k)
	{
	    var = GetObjectElement(subTracks, &k);
	    name = GetVar(var, NAME);
	    if (name && (0 == strcmp2(GetString(name), group)))
	    {
		return var;
	    }
	}
    }

    /*Fell through, object not found.  Create a new track*/
    subTrack = NewSubTrack(object, group);

    if (IsSelected(track)) Select(subTrack, true);

    if (subTracks)
    {
	/*Insert track in sorted place*/
	k = SearchStringVar(subTracks, NAME, group);
	if (k < 0) return NULLOBJ;
	subTracks = InsertInArray(subTracks, subTrack, k);
    }
    else
    {
	/*Create single array to hold tracks*/
	dim = 1;
	subTracks = NewArray(AT_OBJECT, 1, &dim);
	elements = (ObjPtr *) ELEMENTS(subTracks);
	elements[0] = subTrack;
    }
    SetVar(subTrack, PARENT, track);
    SetVar(track, SUBTRACKS, subTracks);

    /*Also need to touch TRACKS of sequence*/
    SetVar(sequence, TRACKS, GetVar(sequence, TRACKS));
    return subTrack;
}

#if 1
static void toado(ObjPtr sequence, char *name)
{
    ObjPtr temp;
    temp = NewObject(NULLOBJ, 0L);
    SetVar(temp, NAME, NewString(name));
    TrackVarGroupWithinSequence(sequence, temp, "Guano");
    TrackVarGroupWithinSequence(sequence, temp, "Bat");
}
#endif

#ifdef PROTO
ObjPtr NewSequence(char *name)
#else
ObjPtr NewSequence(name)
char *name;
#endif
/*Returns a new sequence with name, also shows controls*/
{
    ObjPtr retVal;

    /*UPDATE log command to make sequence*/
    retVal = NewObject(sequenceClass, 0L);
    SetVar(retVal, NAME, NewString(name));
    AddObjToDatabase(retVal);

#if 1
    /*DEBUG give it some tracks by default*/
    {
	toado(retVal, "And");
	toado(retVal, "Did");
	toado(retVal, "Those");
	toado(retVal, "Teeth");
	toado(retVal, "In");
	toado(retVal, "Ancient");
	toado(retVal, "Times");
	toado(retVal, "Walk");
	toado(retVal, "Upon");
	toado(retVal, "England's");
	toado(retVal, "Mountains");
	toado(retVal, "Green?");
    }
#endif

    DeferMessage(retVal, SHOWCONTROLS);

    return retVal;
}

ObjPtr TryNewSequence(object, name)
ObjPtr object;
char *name;
/*Trys to make a new sequence with name.  Ignores object*/
{
    ObjPtr keylist, result;

    keylist = NewList();
    PostfixList(keylist, NewSymbol(NAME));
    PostfixList(keylist, NewString(name));
    PostfixList(keylist, NewSymbol(CLASSID));
    PostfixList(keylist, NewInt(CLASS_SEQUENCE));
    result = SearchDatabase(keylist);
    if (result && LISTOF(result))
    {
	WinInfoPtr alert;

	alert = AskUser((WinInfoPtr) 0, "That name is already taken.  Please try another:",
	    TryNewSequence, name);
	SetVar((ObjPtr) alert, INHIBITLOGGING, ObjTrue);
	return ObjFalse;
    }
    NewSequence(name);
    return ObjTrue;
}

#ifdef PROTO
void DoNewSequence(void)
#else
void DoNewSequence()
#endif
/*Asks the user for a name and makes the new sequence*/
{
    WinInfoPtr alert;

    alert = AskUser((WinInfoPtr) 0, "Please name the new animation sequence:",
	TryNewSequence, "sequence");
    SetVar((ObjPtr) alert, INHIBITLOGGING, ObjTrue);
}

static ObjPtr ShowSequenceControls(object, windowName)
ObjPtr object;
char *windowName;
/*Makes a new control window to control sequence object*/
{
    WinInfoPtr controlWindow;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr contents;
    ObjPtr control;
    int left, right, bottom, top, width;
    WinInfoPtr dialogExists;

    GetTemplateBounds(SequenceTemplate, "Panel", &left, &right, &bottom, &top);


    dialogExists = DialogExists((WinInfoPtr) object, NewString("Controls"));
    controlWindow = GetDialog((WinInfoPtr) object, NewString("Controls"), windowName, 
	(right - left), (top - bottom), 1280, 1024, WINDBUF);
    
    if (!dialogExists)
    {
	SetVar((ObjPtr) controlWindow, REPOBJ, object);

	/*Put in a help string*/
	SetVar((ObjPtr) controlWindow, HELPSTRING,
	    NewString("This window shows controls for a an animation sequence.  Using the controls in this \
window, you can define keyframes and produce mini-movies."));

	/*Add in a panel*/
	panel = TemplatePanel(SequenceTemplate, "Panel");
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);

	contents = GetVar(panel, CONTENTS);

	/*Make a new track control to hold the tracks*/
	control = TemplateTrackControl(SequenceTemplate, "Animation Tracks",
				  0.0, 1.0);
	SetVar(control, PARENT, panel);
	SetVar(control, REPOBJ, object);
	PrefixList(contents, control);
	SetVar(control, HELPSTRING,
	    NewString("This control allows you to do lots of neat stuff with \
animation."));
	SetVar(control, STICKINESS, NewInt(STICKYLEFT + STICKYRIGHT + STICKYBOTTOM + STICKYTOP));

	control = TemplateCheckBox(SequenceTemplate, "Recording", false);
	if (control)
	{
	    SetVar(control, PARENT, panel);
	    PrefixList(contents, control);
	    SetVar(control, HELPSTRING,
		NewString("This check box sets up the sequence to record user \
interface actions.  When the box is on, everything you do to visualization objects \
will be recorded in the sequence at the current time."));
	    SetVar(control, INHIBITRECORDING, ObjTrue);
	    AssocDirectControlWithVar(control, object, RECORDING);
	    SetVar(control, STICKINESS, NewInt(STICKYLEFT + STICKYBOTTOM));
	}
    }
    return (ObjPtr) controlWindow;
}

static ObjPtr DrawTrackName(track, l, r, b, t)
ObjPtr track;
int l, r, b, t;
/*Draws a track name within l, r, b, t*/
{
    int texty;
    ObjPtr name;
    ObjPtr subTracks;
    int k;
    ObjPtr *elements;

    MakeVar(track, APPEARANCE);

    texty = t - TC_CELLHEIGHT / 2 - TCFONTSIZE / 2;

    FillUIRect(l, r, t - TC_CELLHEIGHT, t, IsSelected(track) ? UIYELLOW : UIGRAY62);

    SetUIColor(UIBLACK);
    name = GetVar(track,  NAME);
    if (name)
    {
	DrawAString(LEFTALIGN, l + 1 + TCTEXTLOFF,
		    texty, GetString(name)); 
    }
    else
    {
	DrawAString(LEFTALIGN, l + 1 + TCTEXTLOFF,
		    texty, "?");
    }

    if (GetPredicate(track, EXPANDEDP))
    {
	/*Draw the subtracks*/
	ObjPtr var;
	FuncTyp method;

	MakeVar(track, SUBTRACKS);
	subTracks = GetVar(track, SUBTRACKS);
	if (subTracks)
	{
	    t -= TC_CELLHEIGHT;
	    elements = ELEMENTS(subTracks);
	    for (k = 0; k < DIMS(subTracks)[0]; ++k)
	    {

		MakeVar(elements[k], TRACKHEIGHT);
		var = GetVar(elements[k], TRACKHEIGHT);
		if (var)
		{
		    b = t - GetInt(var);

		    method = GetMethod(elements[k], DRAWNAME);
		    if (method)
		    {
			(*method)(elements[k], l, r, b, t);
		    }
		    t = b;
		}
	    }
	}
    }

    return ObjTrue;
}

static ObjPtr DrawTrackLine(track, l, r, b, t, minTime, maxTime)
ObjPtr track;
int l, r, b, t;
real minTime, maxTime;
/*Draws a track name within l, r, b, t between minTime and maxTime*/
{
    int middle;
    ObjPtr name;
    ObjPtr subTracks;
    int k;
    ObjPtr *elements;
    MakeVar(track, APPEARANCE);

    middle = t - TC_CELLHEIGHT / 2;

    DrawUILine(l, middle + 1, r, middle + 1, UIYELLOW);
    DrawUILine(l, middle, r, middle, UIYELLOW);

    if (GetPredicate(track, EXPANDEDP))
    {
	/*Draw the subtracks*/
	ObjPtr var;
	FuncTyp method;
	MakeVar(track, SUBTRACKS);
	subTracks = GetVar(track, SUBTRACKS);
	if (subTracks)
	{
	    t -= TC_CELLHEIGHT;
	    elements = ELEMENTS(subTracks);
	    for (k = 0; k < DIMS(subTracks)[0]; ++k)
	    {

		MakeVar(elements[k], TRACKHEIGHT);
		var = GetVar(elements[k], TRACKHEIGHT);
		if (var)
		{
		    b = t - GetInt(var);

		    method = GetMethod(elements[k], DRAWTRACK);
		    if (method)
		    {
			(*method)(elements[k], l, r, b, t, minTime, maxTime);
		    }
		    t = b;
		}
	    }
	}
    }

    return ObjTrue;
}

static ObjPtr DrawSubTrackName(track, l, r, b, t)
ObjPtr track;
int l, r, b, t;
/*Draws a subtrack name within l, r, b, t*/
{
    int texty;
    ObjPtr name;
    ObjPtr subTracks;
    int k;
    ObjPtr *elements;

    MakeVar(track, APPEARANCE);

    texty = t - TC_CELLHEIGHT / 2 - TCFONTSIZE / 2;

    FillUIRect(l, r, t - TC_CELLHEIGHT, t, IsSelected(track) ? UIYELLOW : UIGRAY62);

    SetUIColor(UIBLACK);
    name = GetVar(track,  NAME);
    if (name)
    {
	DrawAString(LEFTALIGN, l + 1 + TCSUBTEXTLOFF,
		    texty, GetString(name)); 
    }
    else
    {
	DrawAString(LEFTALIGN, l + 1 + TCSUBTEXTLOFF,
		    texty, "?");
    }

    return ObjTrue;
}

static ObjPtr MakeTrackAppearance(track)
ObjPtr track;
/*Makes a track's appearance*/
{
    SetVar(track, APPEARANCE, ObjTrue);
    return ObjTrue;
}

static ObjPtr PressTrackName(track, x, y, flags)
ObjPtr track;
int x, y;
long flags;
/*Press in a track control name section.  y is negative from top*/
{
    ObjPtr parent;

    if (y > -TC_CELLHEIGHT)
    {
	/*It's a press in the main name*/
	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(track);
	    return ObjTrue;
	}

	if (TOOL(flags) == T_ROTATE)
	{
	    flags |= F_EXTEND;
	}

	if (flags & F_EXTEND)
	{
	    Select(track, IsSelected(track) ? false : true);
	}
	else
	{
	    if (!IsSelected(track))
	    {
		DeselectAll();
		Select(track, true);
	    }
	    if (flags & F_DOUBLECLICK)
	    {
		/*Expand or contract*/
		SetVar(track, EXPANDEDP, GetPredicate(track, EXPANDEDP) ? ObjFalse : ObjTrue);

		/*Have to touch parent's TRACKS*/
		parent = GetObjectVar("PressTrackName", track, PARENT);
		if (parent)
		{
		    SetVar(parent, TRACKS, GetVar(parent, TRACKS));
		}

		/*DIKEO log*/
	    }
	}
    }
    else if (GetPredicate(track, EXPANDEDP))
    {
	/*It may be a press in a subtrack*/
	int ty;
	ObjPtr var;
	ObjPtr subTracks;
	int height;
	long k;
	ObjPtr *elements;

	ty = - TC_CELLHEIGHT;

	subTracks = GetVar(track, SUBTRACKS);
	if (subTracks)
	{
	    elements = ELEMENTS(subTracks);
	    for (k = 0; k < DIMS(subTracks)[0]; ++k)
	    {
		MakeVar(elements[k], TRACKHEIGHT);
		var = GetVar(elements[k], TRACKHEIGHT);
		if (var)
		{
		    height = GetInt(var);
		    if ((ty - height) < y &&
			 (ty >= y)) break;
		    ty -= height;
		}
	    }

	    if (k < DIMS(subTracks)[0])
	    {
		/*It is a press in a subtrack*/
		FuncTyp method;

		method = GetMethod(elements[k], PRESSNAME);
		if (method)
		{
		    (*method)(elements[k], x, y - ty, flags);
		}
	    }
	}
    }

    return ObjTrue;
}

static ObjPtr PressSubTrackName(track, x, y, flags)
ObjPtr track;
int x, y;
long flags;
/*Press in a subtrack control name section.  y is negative from top*/
{
    ObjPtr parent;

	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(track);
	    return ObjTrue;
	}

	if (TOOL(flags) == T_ROTATE)
	{
	    flags |= F_EXTEND;
	}

	if (flags & F_EXTEND)
	{
	    Select(track, IsSelected(track) ? false : true);
	}
	else
	{
	    if (!IsSelected(track))
	    {
		DeselectAll();
		Select(track, true);
	    }
	}

    /*Make its parent invalid*/
    parent = GetVar(track, PARENT);
    SetVar(parent, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeTrackHeight(track)
ObjPtr track;
/*Makes a track's TRACKHEIGHT*/
{
    int height;

    height = TC_CELLHEIGHT;

    if (GetPredicate(track, EXPANDEDP))
    {
	ObjPtr subTracks;

	MakeVar(track, SUBTRACKS);
	subTracks = GetVar(track, SUBTRACKS);
	if (subTracks)
	{
	    ObjPtr *elements;
	    ObjPtr var;
	    int k;

	    elements = ELEMENTS(subTracks);

	    for (k = 0; k < DIMS(subTracks)[0]; ++k)
	    {
		MakeVar(elements[k], TRACKHEIGHT);
		var = GetVar(elements[k], TRACKHEIGHT);
		if (var)
		{
		    height += GetInt(var);
		}
	    }
	}
    }

    SetVar(track, TRACKHEIGHT, NewInt(height));

    return ObjTrue;
}

static ObjPtr MakeSubTrackHeight(track)
ObjPtr track;
/*Makes a subtrack's TRACKHEIGHT*/
{
    SetVar(track, TRACKHEIGHT, NewInt(TC_CELLHEIGHT));
    return ObjTrue;
}

static ObjPtr SelectTrack(track, whether)
ObjPtr track;
Bool whether;
/*Extra stuff for selecting a track; selects all its subtracks*/
{
    ObjPtr subTracks;
    ObjPtr *elements;
    long k;

    subTracks = GetVar(track, SUBTRACKS);
    if (subTracks)
    {
	elements = ELEMENTS(subTracks);
	for (k = 0; k < DIMS(subTracks)[0]; ++k)
	{
	    Select(elements[k], whether);
	}
    }
}

#ifdef PROTO
char *GetGoodVarName(NameTyp var)
#else
char *GetGoodVarName(var)
NameTyp var;
#endif
/*Gets a good var name from var*/
{
    char *name;
    static char processedName[300];
    int k;

    name = GetInternalString(var);
    strcpy(processedName, name);

    for (k = 1; processedName[k]; ++k)
    {
	processedName[k] = tolower(processedName[k]);
    }
    return processedName;
}

#ifdef PROTO
void RecordObjectVar(ObjPtr object, NameTyp whichVar)
#else
void RecordObjectVar(object, whichVar)
ObjPtr object;
NameTyp whichVar;
#endif
/*Records the current value of an object var in recording sequences*/
{
    ObjPtr keyList, result;

    keyList = NewList();
    PostfixList(keyList, NewSymbol(CLASSID));
    PostfixList(keyList, NewInt(CLASS_SEQUENCE));
    PostfixList(keyList, NewSymbol(RECORDING));
    PostfixList(keyList, ObjTrue);

    result = SearchDatabase(keyList);
    if (result && LISTOF(result))
    {
	ThingListPtr runner;
	char *name;

	runner = LISTOF(result);
	while (runner)
	{
	    name = GetGoodVarName(whichVar);
	    TrackVarGroupWithinSequence(runner -> thing, object, name);
	    runner = runner -> next;
	}
    }
}

#ifdef PROTO
void InitAnimation(void)
#else
void InitAnimation()
#endif
/*Initializes the animations*/
{
    sequenceClass = NewObject(NULLOBJ, 0L);
    SetVar(sequenceClass, CLASSID, NewInt(CLASS_SEQUENCE));
    SetMethod(sequenceClass, SHOWCONTROLS, NewControlWindow);
    SetMethod(sequenceClass, NEWCTLWINDOW, ShowSequenceControls);
    SetVar(sequenceClass, RECORDING, ObjFalse);
    AddToReferenceList(sequenceClass);

    trackClass = NewObject(NULLOBJ, 0L);
    AddToReferenceList(trackClass);
    DeclareDependency(trackClass, TRACKHEIGHT, EXPANDEDP);
    DeclareDependency(trackClass, TRACKHEIGHT, SUBTRACKS);
    SetMethod(trackClass, TRACKHEIGHT, MakeTrackHeight);
    SetMethod(trackClass, DRAWNAME, DrawTrackName);
    SetMethod(trackClass, DRAWTRACK, DrawTrackLine);
    DeclareDependency(trackClass, APPEARANCE, SELECTED);
    DeclareDependency(trackClass, APPEARANCE, TRACKHEIGHT);
    DeclareDependency(trackClass, APPEARANCE, EXPANDEDP);
    SetMethod(trackClass, APPEARANCE, MakeTrackAppearance);
    SetMethod(trackClass, PRESSNAME, PressTrackName);
    SetMethod(trackClass, SELECT, SelectTrack);

    subTrackClass = NewObject(NULLOBJ, 0L);
    AddToReferenceList(subTrackClass);
    SetMethod(subTrackClass, TRACKHEIGHT, MakeSubTrackHeight);
    SetMethod(subTrackClass, DRAWNAME, DrawSubTrackName);
    SetMethod(subTrackClass, PRESSNAME, PressSubTrackName);
}

#ifdef PROTO
void KillAnimation(void)
#else
void KillAnimation()
#endif
/*Kills animation*/
{
    RemoveFromReferenceList(subTrackClass);
    RemoveFromReferenceList(trackClass);
    RemoveFromReferenceList(sequenceClass);
}
