//===========================================================================//
// File:	AnimFormat.cpp                                                 
// Project: MechWarrior 4
// Contents: 
//			This is the base animation player source file.
//			This file defines Keyframes and Animation Player classes
//			PLEASE NOTE!!!!  THIS FILE IS USED BY THE PLUGIN :
//				3dsplugins/AnimationExport
//			If you change this you must also change the plugin!!!!
//			
//			THIS IS A THREAT!  
//			All the animations that have been exported rely on this format
//			If you change this format, you better know what you are doing!
//			If you don't support multiple versions when you change this
//			Then you HAVE TO EXPORT ALL THE ANIMATIONS AGAIN!!!!!
//			The max plugin environment allows for this.
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 07/01/98  JSE  Initial coding, 
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive, Inc.
// All Rights reserved worldwide
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL
//===========================================================================//




#include <MW4\MW4AnimationSystem.hpp>
#include <Adept\Resource.hpp>

using namespace MW4Animation;


//
//#############################################################################
//#############################################################################
//

AnimData::AnimData()
{

	startOfBlocks = NULL;
	animHeaderBlock = NULL;
	animJointBlock = NULL;
	animNameBlock = NULL;
	animKeyTimeBlock = NULL;
	animKeyBlock = NULL;
	animChannelBlock = NULL;


	animHeaderBlockSize = 0;
	animJointBlockSize = 0;
	animKeyTimeBlockSize = 0;
	animKeyBlockSize = 0;
	animNameBlockSize = 0;
	animChannelBlockSize = 0;

}


AnimData::~AnimData()
{

	// I don't actually delete any of the extra arrays.
	// The reason is that it is allocated as one LARGE piece
	// of memory.  I do, however register the memory as a 
	// "precaution"

	
	if (animKeyBlock != NULL)
	{
		Unregister_Pointer(animKeyBlock);
//		delete[] animKeyBlock;	
	}

	if (animKeyTimeBlock != NULL)
	{
		Unregister_Pointer(animKeyTimeBlock);
//		delete[] animKeyTimeBlock;	
	}


	if (animChannelBlock != NULL)
	{		
		Unregister_Pointer(animChannelBlock);
//		delete[] animChannelBlock;
	}	

	if (animNameBlock != NULL)
	{
		Unregister_Pointer(animNameBlock);
//		delete[] animNameBlock;
	}

	if (animJointBlock != NULL)
	{
		Unregister_Pointer(animJointBlock);
//		delete[] animJointBlock;
	}

	if (animHeaderBlock != NULL)
	{
		Unregister_Pointer(animHeaderBlock);
//		delete animHeaderBlock;
	}

	if (startOfBlocks != NULL)
	{
		Unregister_Pointer(startOfBlocks);
		delete[] startOfBlocks;
	}
}



//
//#############################################################################
//#############################################################################
//


bool AnimData::Save(const char *file_name)
{
	Stuff::FileStream file_stream(file_name, Stuff::FileStream::WriteOnly);
	Save(&file_stream);
	return true;
}
//
//#############################################################################
//#############################################################################
//


bool AnimData::Load(const char *file_name)
{
	Stuff::FileStream file_stream(file_name);
	if (!Load(&file_stream))
	{
		SPEW((0, "Anim is corrupt or wrong format : "));
		SPEW((0, file_name));
		return false;
	}
	return true;
}

//
//#############################################################################
//#############################################################################
//

bool AnimData::Load(Adept::Resource *resource)
{
	resource->LoadData();


	resource->AdvancePointer(sizeof(BYTE));

	AnimHeader temp_header_block;
	
	resource->ReadBytes(&temp_header_block, sizeof(AnimHeader));
	
	resource->RewindPointer(sizeof(BYTE) + sizeof(AnimHeader));


	if (temp_header_block.majorVersion == MAJOR_ANIM_VERSION_1_0 && temp_header_block.minorVersion == MINOR_ANIM_VERSION_1_0)
	{
		return Load_1_0((Stuff::MemoryStream*)resource);
	}

	if (temp_header_block.majorVersion == MAJOR_ANIM_VERSION_2_0 && temp_header_block.minorVersion == MINOR_ANIM_VERSION_2_0)
	{
		return Load_2_0((Stuff::MemoryStream*)resource);
	}

	if (temp_header_block.majorVersion == MAJOR_ANIM_VERSION_2_1 && temp_header_block.minorVersion == MINOR_ANIM_VERSION_2_1)
	{
		return Load_2_1((Stuff::MemoryStream*)resource);
	}

	return false;
}


//
//#############################################################################
//#############################################################################
//

bool AnimData::Save(Stuff::FileStream *stream)
{
	return Save((Stuff::MemoryStream*)stream);
}

//
//#############################################################################
//#############################################################################
//

bool AnimData::Load(Stuff::FileStream *stream)
{
	return CopyAndLoad(stream);
}

//
//#############################################################################
//#############################################################################
//

bool AnimData::CopyAndLoad(Stuff::MemoryStream *stream)
{
	// copy the file into a memory chunk and make a stream out of it.
	// then pass it to the load function.


	// skip the null byte and get the header
	// we are getting the header since this format can 
	// be munged together inside of a bigger file
	
	// still try to verify this is a good file
	
	stream->AdvancePointer(sizeof(BYTE));

	AnimHeader temp_header_block;
	
	stream->ReadBytes(&temp_header_block, sizeof(AnimHeader));
	
	stream->RewindPointer(sizeof(BYTE) + sizeof(AnimHeader));


	if (temp_header_block.majorVersion == MAJOR_ANIM_VERSION_1_0 && temp_header_block.minorVersion == MINOR_ANIM_VERSION_1_0)
	{
		
		// since it is a file and it will be closed we are going to buffer it
		// to memory

		gos_PushCurrentHeap(g_AnimDataHeap);

		startOfBlocks = new BYTE[temp_header_block.totalSizeOfAnimation + 1];
		Register_Pointer(startOfBlocks);

		gos_PopCurrentHeap();

		Stuff::MemoryStream mem_stream(startOfBlocks, temp_header_block.totalSizeOfAnimation + 1);

		stream->ReadBytes(mem_stream.GetPointer(), temp_header_block.totalSizeOfAnimation);


		return Load_1_0(&mem_stream);
	}

	if (temp_header_block.majorVersion == MAJOR_ANIM_VERSION_2_0 && temp_header_block.minorVersion == MINOR_ANIM_VERSION_2_0)
	{
		// since it is a file and it will be closed we are going to buffer it
		// to memory

		gos_PushCurrentHeap(g_AnimDataHeap);

		startOfBlocks = new BYTE[temp_header_block.totalSizeOfAnimation + 1];
		Register_Pointer(startOfBlocks);

		gos_PopCurrentHeap();

		Stuff::MemoryStream mem_stream(startOfBlocks, temp_header_block.totalSizeOfAnimation + 1);

		stream->ReadBytes(mem_stream.GetPointer(), temp_header_block.totalSizeOfAnimation);


		return Load_2_0(&mem_stream);
	}

	if (temp_header_block.majorVersion == MAJOR_ANIM_VERSION_2_1 && temp_header_block.minorVersion == MINOR_ANIM_VERSION_2_1)
	{
		// since it is a file and it will be closed we are going to buffer it
		// to memory

		gos_PushCurrentHeap(g_AnimDataHeap);

		startOfBlocks = new BYTE[temp_header_block.totalSizeOfAnimation + 1];
		Register_Pointer(startOfBlocks);

		gos_PopCurrentHeap();

		Stuff::MemoryStream mem_stream(startOfBlocks, temp_header_block.totalSizeOfAnimation + 1);

		stream->ReadBytes(mem_stream.GetPointer(), temp_header_block.totalSizeOfAnimation);


		return Load_2_1(&mem_stream);
	}

	return false;


	

}

//
//#############################################################################
//#############################################################################
//


bool AnimData::Save(Stuff::MemoryStream *stream)
{
	BYTE null_byte = NULL;

	stream->WriteBytes(&null_byte, sizeof(BYTE));

	// write the rest of the data out
	Verify(animHeaderBlock->offsetToHeaderBlock == stream->GetBytesUsed()-1);
	stream->WriteBytes(animHeaderBlock,animHeaderBlockSize);
	Verify(animHeaderBlock->offsetToJointBlock == stream->GetBytesUsed()-1);
	stream->WriteBytes(animJointBlock,animJointBlockSize);
	Verify(animHeaderBlock->offsetToNameBlock == stream->GetBytesUsed()-1);
	stream->WriteBytes(animNameBlock,animNameBlockSize);
	Verify(animHeaderBlock->offsetToKeyBlock == stream->GetBytesUsed()-1);
	stream->WriteBytes(animKeyBlock,animKeyBlockSize);
	Verify(animHeaderBlock->offsetToKeyTimeBlock == stream->GetBytesUsed()-1);
	stream->WriteBytes(animKeyTimeBlock, animKeyTimeBlockSize);
	Verify(animHeaderBlock->offsetToChannelBlock == stream->GetBytesUsed()-1);
	stream->WriteBytes(animChannelBlock,animChannelBlockSize);
	
	return true;
}


//#############################################################################
//#############################################################################
//


bool AnimData::Load_2_1(Stuff::MemoryStream *stream)
{


	// skip the null byte
	BYTE *null_byte;
	null_byte = (BYTE *)stream->GetPointer();
	
	if (*null_byte != NULL)
	{
		return false;
	}
	
	Verify(*null_byte == NULL);
	stream->AdvancePointer(sizeof(BYTE));

	size_t start_index = stream->GetIndex();

	// load the header
	animHeaderBlock = (AnimHeader *)stream->GetPointer();
	Register_Pointer(animHeaderBlock);


	if (animHeaderBlock->majorVersion != MAJOR_ANIM_VERSION_2_1)
	{
		return false;
	}

	if (animHeaderBlock->minorVersion != MINOR_ANIM_VERSION_2_1)
	{
		return false;
	}


	Verify(
		(animHeaderBlock->offsetToJointBlock -  animHeaderBlock->offsetToHeaderBlock)
		== sizeof(AnimHeader)
	);

	Verify(animHeaderBlock->majorVersion == MAJOR_ANIM_VERSION_2_1);
	Verify(animHeaderBlock->minorVersion == MINOR_ANIM_VERSION_2_1);

	// register the rest of the arrays
	stream->SetPointer(animHeaderBlock->offsetToJointBlock + start_index);
	animJointBlock = (JointAnimData *)stream->GetPointer();
	Register_Pointer(animJointBlock);
	
	stream->SetPointer(animHeaderBlock->offsetToNameBlock + start_index);
	animNameBlock = (char *)stream->GetPointer();
	Register_Pointer(animNameBlock);
	
	stream->SetPointer(animHeaderBlock->offsetToKeyBlock + start_index);
	animKeyBlock = (void *)stream->GetPointer();
	Register_Pointer(animKeyBlock);
	
	stream->SetPointer(animHeaderBlock->offsetToKeyTimeBlock + start_index);
	animKeyTimeBlock = (void *)stream->GetPointer();
	Register_Pointer(animKeyTimeBlock);

	stream->SetPointer(animHeaderBlock->offsetToChannelBlock + start_index);
	animChannelBlock = (ChannelAnimData *)stream->GetPointer();
	Register_Pointer(animChannelBlock);
	
	stream->SetPointer(animHeaderBlock->totalSizeOfAnimation + start_index);

	animHeaderBlockSize = animHeaderBlock->offsetToJointBlock - start_index;
	animJointBlockSize = animHeaderBlock->offsetToNameBlock - animHeaderBlock->offsetToJointBlock;
	animNameBlockSize = animHeaderBlock->offsetToKeyBlock - animHeaderBlock->offsetToNameBlock;
	animKeyBlockSize = animHeaderBlock->offsetToKeyTimeBlock - animHeaderBlock->offsetToKeyBlock;
	animKeyTimeBlockSize = animHeaderBlock->offsetToChannelBlock - animHeaderBlock->offsetToKeyTimeBlock;
	animChannelBlockSize = animHeaderBlock->totalSizeOfAnimation - animHeaderBlock->offsetToChannelBlock;


	// check that keyframe counts are lower than 256

#ifdef _ARMOR
	int count = GetChannelCount();

	for (int i = 0; i < count; ++i)
	{
		Verify(animChannelBlock[i].keyframeCount < AnimIterator::MaxKeyFrame);
	}
#endif


	return true;
	
}


//
//#############################################################################
//#############################################################################
//

bool AnimData::Load_1_0(Stuff::MemoryStream *stream)
{


	// skip the null byte
	BYTE *null_byte;
	null_byte = (BYTE *)stream->GetPointer();
	
	if (*null_byte != NULL)
	{
		return false;
	}
	
	Verify(*null_byte == NULL);
	stream->AdvancePointer(sizeof(BYTE));


	// load the header
	animHeaderBlock = (AnimHeader *)stream->GetPointer();
	Register_Pointer(animHeaderBlock);


	if (animHeaderBlock->majorVersion != MAJOR_ANIM_VERSION_1_0)
	{
		return false;
	}

	if (animHeaderBlock->minorVersion != MINOR_ANIM_VERSION_1_0)
	{
		return false;
	}


	STOP (("Animformat 1.0 Not Supported"));

	return false;
	
}

//
//#############################################################################
//#############################################################################
//

bool AnimData::Load_2_0(Stuff::MemoryStream *stream)
{


	// skip the null byte
	BYTE *null_byte;
	null_byte = (BYTE *)stream->GetPointer();
	
	if (*null_byte != NULL)
	{
		return false;
	}
	
	Verify(*null_byte == NULL);
	stream->AdvancePointer(sizeof(BYTE));


	// load the header
	animHeaderBlock = (AnimHeader *)stream->GetPointer();
	Register_Pointer(animHeaderBlock);


	if (animHeaderBlock->majorVersion != MAJOR_ANIM_VERSION_1_0)
	{
		return false;
	}

	if (animHeaderBlock->minorVersion != MINOR_ANIM_VERSION_1_0)
	{
		return false;
	}


	STOP (("Animformat 2.0 Not Supported"));

	return false;
	
}
//
//#############################################################################
//#############################################################################
//



char *Joint_Name_Compression_Table[COMPRESS_JOINT_COUNT] =
{
"joint_cage",
"joint_centertorsofront",
"joint_centertorsorear",
"joint_head",
"joint_hip",
"joint_hipabove",
"joint_hipbelow",
"joint_lankle",
"joint_lbelowankle",
"joint_lbtoe",
"joint_ldleg",
"joint_lefttorsofront",
"joint_lfoot",
"joint_lftoe",
"joint_lgun",
"joint_lgunabove",
"joint_litoe",
"joint_lmissile",
"joint_lmleg",
"joint_lotoe",
"joint_ltoe",
"joint_luarm",
"joint_luleg",
"joint_rankle",
"joint_rbelowankle",
"joint_rbtoe",
"joint_rdleg",
"joint_rfoot",
"joint_rftoe",
"joint_rgun",
"joint_rgunabove",
"joint_righttorsofront",
"joint_ritoe",
"joint_rmissile",
"joint_rmleg",
"joint_root",
"joint_rotoe",
"joint_rtoe",
"joint_ruarm",
"joint_ruleg",
"joint_specialone",
"joint_specialtwo",
"joint_torso",
"joint_torsoabove",
"joint_torsobelow",
"joint_vel",
"joint_world"
};


int MW4Animation::CompressName(const char *source, Stuff::MString &dest)
{
	for (BYTE i = 0; i < COMPRESS_JOINT_COUNT; ++i)
	{
		if (!stricmp(source, Joint_Name_Compression_Table[i]))
		{
			BYTE index = (BYTE)(128 + i);
			dest = "#";
			dest += (char)index;
			dest += "\0";

			return i;
		}
	}

	dest = source;
	return -1;
}


int MW4Animation::UncompressName(const char *source, Stuff::MString &dest)
{

	const char *test = source;
	if (*test == '#')
	{
		++test;
		BYTE index = (BYTE)*test;
		index -= (BYTE)128;
		dest = Joint_Name_Compression_Table[index];

		return index;
	}
	
	dest = source;
	return -1;

}