//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

//
//	File Name: animFileExport.cpp
//
//	Description:
//		Utility classes to write .anim files.
//
#include <maya/MGlobal.h>

#include <maya/MIOStream.h>
#include <maya/MFStream.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include "animFileExport.h"
#include <maya/MString.h>
#include <maya/MFnAnimCurve.h>

//-------------------------------------------------------------------------
//	Class animUnitNames
//-------------------------------------------------------------------------

//	String names for units.
//
const char *kMmString = 		"mm";
const char *kCmString =			"cm";
const char *kMString =			"m";
const char *kKmString =			"km";
const char *kInString =			"in";
const char *kFtString =			"ft";
const char *kYdString =			"yd";
const char *kMiString =			"mi";

const char *kMmLString =		"millimeter";
const char *kCmLString =		"centimeter";
const char *kMLString =			"meter";
const char *kKmLString =		"kilometer";
const char *kInLString =		"inch";
const char *kFtLString =		"foot";
const char *kYdLString =		"yard";
const char *kMiLString =		"mile";

const char *kRadString =		"rad";
const char *kDegString =		"deg";
const char *kMinString =		"min";
const char *kSecString =		"sec";

const char *kRadLString =		"radian";
const char *kDegLString =		"degree";
const char *kMinLString =		"minute";
const char *kSecLString =		"second";

const char *kHourTString =		"hour";
const char *kMinTString =		"min";
const char *kSecTString =		"sec";
const char *kMillisecTString =	"millisec";

const char *kGameTString =		"game";
const char *kFileTString =		"film";
const char *kPalTString =		"pal";
const char *kNtscTString =		"ntsc";
const char *kShowTString =		"show";
const char *kPalFTString =		"palf";
const char *kNtscFTString =		"ntscf";

const char *kUnitlessString = "unitless";
const char *kUnknownTimeString =	"Unknown Time Unit";
const char *kUnknownAngularString =	"Unknown Angular Unit";
const char *kUnknownLinearString = 	"Unknown Linear Unit";

animUnitNames::animUnitNames()
//
//	Description:
//		Class constructor.
//
{
}

animUnitNames::~animUnitNames()
//
//	Description:
//		Class destructor.
//
{
}

/* static */
void animUnitNames::setToLongName(const MAngle::Unit& unit, MString& name)
//
//	Description:
//		Sets the string with the long text name of the angle unit.
//
{
	switch(unit) {
		case MAngle::kDegrees:
			name.set(kDegLString);
			break;
		case MAngle::kRadians:
			name.set(kRadLString);
			break;
		case MAngle::kAngMinutes:
			name.set(kMinLString);
			break;
		case MAngle::kAngSeconds:
			name.set(kSecLString);
			break;
		default:
			name.set(kUnknownAngularString);
			break;
	}
}

/* static */
void animUnitNames::setToShortName(const MAngle::Unit& unit, MString& name)
//
//	Description:
//		Sets the string with the short text name of the angle unit.
//
{
	switch(unit) {
		case MAngle::kDegrees:
			name.set(kDegString);
			break;
		case MAngle::kRadians:
			name.set(kRadString);
			break;
		case MAngle::kAngMinutes:
			name.set(kMinString);
			break;
		case MAngle::kAngSeconds:
			name.set(kSecString);
			break;
		default:
			name.set(kUnknownAngularString);
			break;
	}
}

/* static */
void animUnitNames::setToLongName(const MDistance::Unit& unit, MString& name)
//
//	Description:
//		Sets the string with the long text name of the distance unit.
//
{
	switch(unit) {
		case MDistance::kInches:
			name.set(kInLString);
			break;
		case MDistance::kFeet:
			name.set(kFtLString);
			break;
		case MDistance::kYards:
			name.set(kYdLString);
			break;
		case MDistance::kMiles:
			name.set(kMiLString);
			break;
		case MDistance::kMillimeters:
			name.set(kMmLString);
			break;
		case MDistance::kCentimeters:
			name.set(kCmLString);
			break;
		case MDistance::kKilometers:
			name.set(kKmLString);
			break;
		case MDistance::kMeters:
			name.set(kMLString);
			break;
		default:
			name.set(kUnknownLinearString);
			break;
	}
}

/* static */
void animUnitNames::setToShortName(const MDistance::Unit& unit, MString& name)
//
//	Description:
//		Sets the string with the short text name of the distance unit.
//
{
	switch(unit) {
		case MDistance::kInches:
			name.set(kInString);
			break;
		case MDistance::kFeet:
			name.set(kFtString);
			break;
		case MDistance::kYards:
			name.set(kYdString);
			break;
		case MDistance::kMiles:
			name.set(kMiString);
			break;
		case MDistance::kMillimeters:
			name.set(kMmString);
			break;
		case MDistance::kCentimeters:
			name.set(kCmString);
			break;
		case MDistance::kKilometers:
			name.set(kKmString);
			break;
		case MDistance::kMeters:
			name.set(kMString);
			break;
		default:
			name.set(kUnknownLinearString);
			break;
	}
}

/* static */
void animUnitNames::setToLongName(const MTime::Unit &unit, MString &name)
//
//	Description:
//		Sets the string with the long text name of the time unit.
//
{
	switch(unit) {
		case MTime::kHours:
			name.set(kHourTString);
			break;
		case MTime::kMinutes:
			name.set(kMinTString);
			break;
		case MTime::kSeconds:
			name.set(kSecTString);
			break;
		case MTime::kMilliseconds:
			name.set(kMillisecTString);
			break;
		case MTime::kGames:
			name.set(kGameTString);
			break;
		case MTime::kFilm:
			name.set(kFileTString);
			break;
		case MTime::kPALFrame:
			name.set(kPalTString);
			break;
		case MTime::kNTSCFrame:
			name.set(kNtscTString);
			break;
		case MTime::kShowScan:
			name.set(kShowTString);
			break;
		case MTime::kPALField:
			name.set(kPalFTString);
			break;
		case MTime::kNTSCField:
			name.set(kNtscFTString);
			break;
		default:
			name.set(kUnknownTimeString);
			break;
	}
}

/* static */
void animUnitNames::setToShortName(const MTime::Unit &unit, MString &name)
//
//	Description:
//		Sets the string with the short text name of the time unit.
//
{
	setToLongName(unit, name);
}

//-------------------------------------------------------------------------
//	Class animBase
//-------------------------------------------------------------------------

// Tangent type words
//
const char *kWordTangentGlobal = "global";
const char *kWordTangentFixed = "fixed";
const char *kWordTangentLinear = "linear";
const char *kWordTangentFlat = "flat";
const char *kWordTangentSmooth = "spline";
const char *kWordTangentStep = "step";	// Maintains current value until next key
const char *kWordTangentStepNext = "stepnext";	// Immediately steps to next key's value
const char *kWordTangentSlow = "slow";
const char *kWordTangentFast = "fast";
const char *kWordTangentClamped = "clamped";
const char *kWordTangentPlateau = "plateau";
const char *kWordTangentAuto = "auto";

// Infinity type words
//
const char *kWordConstant = "constant";
const char *kWordLinear = "linear";
const char *kWordCycle = "cycle";
const char *kWordCycleRelative = "cycleRelative";
const char *kWordOscillate = "oscillate";

//	Param Curve types
//
const char *kWordTypeUnknown = "unknown";
const char *kWordTypeLinear = "linear";
const char *kWordTypeAngular = "angular";
const char *kWordTypeTime = "time";
const char *kWordTypeUnitless = "unitless";

//	Keywords
//
const char *kAnim = "anim";
const char *kAnimData = "animData";
const char *kMovData = "movData";
const char *kMayaVersion = "mayaVersion";
const char *kAnimVersion = "animVersion";

const char *kTimeUnit = "timeUnit";
const char *kLinearUnit = "linearUnit";
const char *kAngularUnit = "angularUnit";
const char *kStartTime = "startTime";
const char *kEndTime = "endTime";
const char *kStartUnitless = "startUnitless";
const char *kEndUnitless = "endUnitless";

// animVersions:
//
const char *kAnimVersionString = "1.1";

const char *kTwoSpace = "  ";

//	animData keywords
//
const char *kInputString = "input";
const char *kOutputString = "output";
const char *kWeightedString = "weighted";
const char *kPreInfinityString = "preInfinity";
const char *kPostInfinityString = "postInfinity";
const char *kInputUnitString = "inputUnit";
const char *kOutputUnitString = "outputUnit";
const char *kTanAngleUnitString = "tangentAngleUnit";
const char *kKeysString = "keys";

//	special characters
//
const char kSemiColonChar	= ';';
const char kSpaceChar		= ' ';
const char kBraceLeftChar	= '{';
const char kBraceRightChar	= '}';

animBase::animBase ()
//
//	Description:
//		The constructor.
//
{
	resetUnits();
}
	
animBase::~animBase()
//
//	Description:
//		The destructor.
//
{
}

void animBase::resetUnits()
//
//	Description:
//		Reset the units used by this class to the ui units.
//
{
	timeUnit = MTime::uiUnit();
	linearUnit = MDistance::uiUnit();
	angularUnit = MAngle::uiUnit();
}

const char *
animBase::tangentTypeAsWord(MFnAnimCurve::TangentType type)
//
//	Description:
//		Returns a string with a test based desription of the passed
//		MFnAnimCurve::TangentType. 
//
{
	switch (type) {
		case MFnAnimCurve::kTangentGlobal:
			return (kWordTangentGlobal);
		case MFnAnimCurve::kTangentFixed:
			return (kWordTangentFixed);
		case MFnAnimCurve::kTangentLinear:
			return (kWordTangentLinear);
		case MFnAnimCurve::kTangentFlat:
			return (kWordTangentFlat);
		case MFnAnimCurve::kTangentSmooth:
			return (kWordTangentSmooth);
		case MFnAnimCurve::kTangentStep:
			return (kWordTangentStep);
		case MFnAnimCurve::kTangentSlow:
			return (kWordTangentSlow);
		case MFnAnimCurve::kTangentFast:
			return (kWordTangentFast);
		case MFnAnimCurve::kTangentClamped:
			return (kWordTangentClamped);
		case MFnAnimCurve::kTangentPlateau:
			return (kWordTangentPlateau);
		case MFnAnimCurve::kTangentStepNext:
			return (kWordTangentStepNext);
		case MFnAnimCurve::kTangentAuto:
			return (kWordTangentAuto);
		default:
			break;
	}
	return (kWordTangentGlobal);
}

const char *
animBase::infinityTypeAsWord(MFnAnimCurve::InfinityType type)
//	
//	Description:
//		Returns a string containing the name of the passed 
//		MFnAnimCurve::InfinityType type.
//
{
	switch (type) {
		case MFnAnimCurve::kConstant:
			return (kWordConstant);
		case MFnAnimCurve::kLinear:
			return (kWordLinear);
		case MFnAnimCurve::kCycle:
			return (kWordCycle);
		case MFnAnimCurve::kCycleRelative:
			return (kWordCycleRelative);
		case MFnAnimCurve::kOscillate:
			return (kWordOscillate);
		default:
			break;
	}
	return (kWordConstant);
}

const char *
animBase::outputTypeAsWord (MFnAnimCurve::AnimCurveType type)
//
//	Description:
//		Returns a string identifying the output type of the
//		passed MFnAnimCurve::AnimCurveType.
//
{
	switch (type) {
		case MFnAnimCurve::kAnimCurveTL:
		case MFnAnimCurve::kAnimCurveUL:
			return (kWordTypeLinear);
		case MFnAnimCurve::kAnimCurveTA:
		case MFnAnimCurve::kAnimCurveUA:
			return (kWordTypeAngular);
		case MFnAnimCurve::kAnimCurveTT:
		case MFnAnimCurve::kAnimCurveUT:
			return (kWordTypeTime);
		case MFnAnimCurve::kAnimCurveTU:
		case MFnAnimCurve::kAnimCurveUU:
			return (kWordTypeUnitless);
		case MFnAnimCurve::kAnimCurveUnknown:
			return (kWordTypeUnitless);
	}
	return (kWordTypeUnknown);
}

const char *
animBase::boolInputTypeAsWord(bool isUnitless) 
//
//	Description:
//		Returns a string based on a bool. 
//
{
	if (isUnitless) {
		return (kWordTypeUnitless);
	} else {
		return (kWordTypeTime);
	}
}

//-------------------------------------------------------------------------
//	Class animWriter
//-------------------------------------------------------------------------

animWriter::animWriter()
//
//	Description:
//		Class constructor.
//
{
}

animWriter::~animWriter()
//
//	Description:
//		Class destructor.
//
{
}

bool animWriter::writeHeader(ofstream& clip,
							double startTime, double endTime,
							double startUnitless, double endUnitless
)
//
//	Description:
//		Writes the header for the file. The header contains the clipboard
//		specific data. 
//
{
	if (!clip) {
		return false;
	}

	clip << kAnimVersion << kSpaceChar << kAnimVersionString << kSemiColonChar << endl;
	clip << kMayaVersion << kSpaceChar << MGlobal::mayaVersion() << kSemiColonChar << endl;

	if (startTime != endTime) {
		MString unit;
		animUnitNames::setToShortName(timeUnit, unit);
		clip << kTimeUnit << kSpaceChar << unit << kSemiColonChar << endl;
		animUnitNames::setToShortName(linearUnit, unit);
		clip << kLinearUnit << kSpaceChar << unit << kSemiColonChar << endl;
		animUnitNames::setToShortName(angularUnit, unit);
		clip << kAngularUnit << kSpaceChar << unit << kSemiColonChar << endl;
		clip << kStartTime << kSpaceChar << startTime << kSemiColonChar << endl;
		clip << kEndTime << kSpaceChar << endTime << kSemiColonChar << endl;
	}

	if (startUnitless != endUnitless) {
		clip << kStartUnitless << kSpaceChar << startUnitless << 
				kSemiColonChar << endl;
		clip << kEndUnitless << kSpaceChar << endUnitless << 
				kSemiColonChar << endl;
	}

	return true;
}

bool animWriter::writeAnimCurve(ofstream &clip, 
								const MObject *animCurveObj,
								bool verboseUnits /* false */)
//
//	Description:
//		Write out the anim curve from the clipboard item into the
//		ofstream. The actual anim curve data is written out.
//
//		This method returns true if the write was successful.
//
{
	if (NULL == animCurveObj || animCurveObj->isNull() || !clip) {
		return true;
	}

	MStatus status = MS::kSuccess;
	MFnAnimCurve animCurve(*animCurveObj, &status);
	if (MS::kSuccess != status) {
		cerr << "Error: Could not read the anim curve for export." << endl;
		return false;
	}

	clip << kAnimData << kSpaceChar << kBraceLeftChar << endl;

	clip << kTwoSpace << kInputString << kSpaceChar <<
			boolInputTypeAsWord(animCurve.isUnitlessInput()) << 
			kSemiColonChar << endl;

	clip << kTwoSpace << kOutputString << kSpaceChar <<
			outputTypeAsWord(animCurve.animCurveType()) << kSemiColonChar << endl;

	clip << kTwoSpace << kWeightedString << kSpaceChar <<
			(animCurve.isWeighted() ? 1 : 0) << kSemiColonChar << endl;

	//	These units default to the units in the header of the file.
	//	
	if (verboseUnits) {
		clip << kTwoSpace << kInputUnitString << kSpaceChar;
		if (animCurve.isTimeInput()) {
			MString unitName;
			animUnitNames::setToShortName(timeUnit, unitName);
			clip << unitName;
		} else {
			//	The anim curve has unitless input.
			//
			clip << kUnitlessString;
		}
		clip << kSemiColonChar << endl;

		clip << kTwoSpace << kOutputUnitString << kSpaceChar;
	}

	double conversion = 1.0;
	MString unitName;
	switch (animCurve.animCurveType()) {
		case MFnAnimCurve::kAnimCurveTA:
		case MFnAnimCurve::kAnimCurveUA:
			animUnitNames::setToShortName(angularUnit, unitName);
			if (verboseUnits) clip << unitName;
			{
				MAngle angle(1.0);
				conversion = angle.as(angularUnit);
			}
			break;
		case MFnAnimCurve::kAnimCurveTL:
		case MFnAnimCurve::kAnimCurveUL:
			animUnitNames::setToShortName(linearUnit, unitName);
			if (verboseUnits) clip << unitName;
			{
				MDistance distance(1.0);
				conversion = distance.as(linearUnit);
			}
			break;
		case MFnAnimCurve::kAnimCurveTT:
		case MFnAnimCurve::kAnimCurveUT:
			animUnitNames::setToShortName(timeUnit, unitName);
			if (verboseUnits) clip << unitName;
			break;
		default:
			if (verboseUnits) clip << kUnitlessString;
			break;
	}
	if (verboseUnits) clip << kSemiColonChar << endl;

	if (verboseUnits) {
		MString angleUnitName;
		animUnitNames::setToShortName(angularUnit, angleUnitName);
		clip << kTwoSpace << kTanAngleUnitString << 
				kSpaceChar << angleUnitName << kSemiColonChar << endl;
	}

	clip << kTwoSpace << kPreInfinityString << kSpaceChar <<
			infinityTypeAsWord(animCurve.preInfinityType()) << 
			kSemiColonChar << endl;

	clip << kTwoSpace << kPostInfinityString << kSpaceChar <<
			infinityTypeAsWord(animCurve.postInfinityType()) << 
			kSemiColonChar << endl;

	clip << kTwoSpace << kKeysString << kSpaceChar << kBraceLeftChar << endl;

	// And then write out each keyframe
	//
	unsigned numKeys = animCurve.numKeyframes();
	for (unsigned i = 0; i < numKeys; i++) {
		clip << kTwoSpace << kTwoSpace;
		if (animCurve.isUnitlessInput()) {
			clip << animCurve.unitlessInput(i);
		}
		else {
			clip << animCurve.time(i).value();
		}

		clip << kSpaceChar << (conversion*animCurve.value(i));

		clip << kSpaceChar << tangentTypeAsWord(animCurve.inTangentType(i));
		clip << kSpaceChar << tangentTypeAsWord(animCurve.outTangentType(i));

		clip << kSpaceChar << (animCurve.tangentsLocked(i) ? 1 : 0);
		clip << kSpaceChar << (animCurve.weightsLocked(i) ? 1 : 0);
		clip << kSpaceChar << (animCurve.isBreakdown(i) ? 1 : 0);

		if (animCurve.inTangentType(i) == MFnAnimCurve::kTangentFixed) {
			MAngle angle;
			double weight;
			animCurve.getTangent(i, angle, weight, true);

			clip << kSpaceChar << angle.as(angularUnit);
			clip << kSpaceChar << weight;
		}
		if (animCurve.outTangentType(i) == MFnAnimCurve::kTangentFixed) {
			MAngle angle;
			double weight;
			animCurve.getTangent(i, angle, weight, false);

			clip << kSpaceChar << angle.as(angularUnit);
			clip << kSpaceChar << weight;
		}

		clip << kSemiColonChar << endl;
	}
	clip << kTwoSpace << kBraceRightChar << endl;

	clip << kBraceRightChar << endl;

	return true;
}
