

#ifndef __skelanim_h__
#include "skelanim.h"
#endif

#ifndef __tags_h__
#include "tags.h"
#endif

#ifndef PandaWave_XMESSAGES
#include <XMessages.h>
#endif

#ifndef PandaWave_TTREEVIEW
#include "TTreeView.h"
#endif

#ifndef PandaWave_XWINDOW
#include <XWindow.h>
#endif

#ifndef PandaWave_XERROR
#include <XError.h>
#endif

#ifndef PandaWave_XPTRSET
#include <XPtrSet.h>
#endif

#ifndef PandaWave_XFILE
#include <XFile.h>
#endif

#ifndef __read_skl_h__
#include "read_skl.h"
#endif

#ifndef PandaWave_XDIALOGS
#include <XDialog.h>
#endif

#ifndef PandaWave_XLIST
#include <XList.h>
#endif

#ifndef PandaWave_XBUTTONS
#include <XButtons.h>
#endif

#ifndef SS_STRINGUTILS
#include <XStringUtil.h>
#endif

#include "formatters.h"

#include <XPopup.h>
#include "tagpick.h"


#include "RevengeResources.h"

byte_swap_code bs_skan_bone_frame_pos[] = {
	_begin_bs_array, 1,
	_2byte, _2byte, _2byte,	// offset
	_2byte, _2byte, _2byte, _2byte, // quat
	_end_bs_array
};

byte_swap_code bs_skan_frame[] = {
	_begin_bs_array, 1,
	_4byte, // distance delta
	_2byte,	// attach_bone
	2,	// run-time index
	_4byte, // attach_id
	_end_bs_array
};

skan_frame *import_skan_frame( skan_frame *f, int bone_count )
{
	byte_swap_data_be( "skan frame", f, sizeof(skan_frame), 1, bs_skan_frame );
	byte_swap_data_be( "skan_bone_frame_pos", f + 1, sizeof(skan_bone_frame_pos), bone_count, bs_skan_bone_frame_pos );
	int size = sizeof( skan_frame ) + bone_count * sizeof(skan_bone_frame_pos);
	size = (size + 3) & ~3;
	return (skan_frame *)((char *)f + size);
}

byte_swap_code bs_skan_animation_v1[] = {
	_begin_bs_array, 1,
	MAX_SKAN_ANIM_NAME+1,	// name
	_2byte, // keyframe
	_2byte,	// frame_count
	_2byte, // keybone
	_2byte, // flags
	_4byte, // fps
	_4byte, // offset_scale
	_4byte, // crossfade in
	_4byte, // crossfade interrupt
	_4byte, // crossfade finish
	_end_bs_array
};

byte_swap_code bs_skan_animation[] = {
	_begin_bs_array, 1,
	MAX_SKAN_ANIM_NAME+1,	// name
	_2byte,	// frame_count
	_2byte, // flags
	_2byte, // fixed_fps
	_2byte, // offset_shift
	_4byte, // crossfade in
	_4byte, // crossfade interrupt
	_4byte, // crossfade finish
	_end_bs_array
};

skan_animation *import_skan_animation( skan_animation *a, int bone_count )
{
	byte_swap_data_be( "skan animation", a, sizeof(*a), 1, bs_skan_animation );
	skan_frame *f = (skan_frame *)(a + 1);
	for (int i = 0; i < a->frame_count; ++i) {
		f = import_skan_frame( f, bone_count );
	}
	return (skan_animation *)f;
}


byte_swap_code bs_skan_state[] = {
	_begin_bs_array, 1,
	MAX_SKAN_STATE_NAME+1,	// name
	_2byte,	// anim_count
	_2byte, // flags
	_4byte, // total_probability
	// _2byte * 2 * anim_count
	_end_bs_array
};

skan_state *import_skan_state( skan_state *s )
{
	byte_swap_data_be( "skan state", s, sizeof(*s) - sizeof(s->animations), 1, bs_skan_state );
	byte_swap_memory_be( "skan state anim list", s->animations, s->anim_count * 2, _2byte );
	return (skan_state *)(s->animations + s->anim_count);
}


byte_swap_code bs_skan_bone_base[] = {
	_begin_bs_array, 1,
	MAX_SKAN_BONE_NAME+1,	// name
	_4byte, _4byte, _4byte,		// offset[3]
	_4byte, _4byte, _4byte, _4byte,		// quat[4]
	_4byte,	// parent
	_end_bs_array
};


byte_swap_code bs_skan_tag_header[] = {
	_begin_bs_array, 1,
	_4byte,	// bone_count
	_4byte, // anim_count
	_4byte,	// state_count
	4, 4, 4,	// pointers
	_end_bs_array
};

void *setup_skan_ptrs( skan_tag_header *skan )
{
	skan->baseframe = (skan_bone_base *)(skan + 1);
	skan->animations = (skan_animation **)(skan->baseframe + skan->bone_count);
	skan->states = (skan_state **)(skan->animations + skan->anim_count);
	return skan->states + skan->state_count;
}


void *import_skan( skan_tag_header *skan )
{
	int i;
	void *ptr;
	
	byte_swap_data_be( "skan header", skan, sizeof(*skan), 1, bs_skan_tag_header );
	ptr = setup_skan_ptrs( skan );
	byte_swap_data_be( "skan bone base", skan->baseframe, sizeof(skan_bone_base), skan->bone_count, bs_skan_bone_base );
	for (i = 0; i < skan->anim_count; ++i) {
		skan->animations[i] = (skan_animation *)ptr;
		ptr = import_skan_animation( (skan_animation *)ptr, skan->bone_count );
	}
	for (i = 0; i < skan->state_count; ++i) {
		skan->states[i] = (skan_state *)ptr;
		ptr = import_skan_state( (skan_state *)ptr );
	}
	return ptr;
}

#pragma mark -

void defaultnew_skelanim( tag_entry *e )
{
	skan_tag_header head;
	memset( &head, 0, sizeof(head) );
	byte_swap_data_be( "skan header", &head, sizeof(head), 1, bs_skan_tag_header );
	set_entry_data( e, &head, sizeof(head), false );
}

// only private structure ripped from TskanEditForm
struct statedata
{
	skan_state header;
	XGSDynArray<int> probabilities;
	XGPDynArray<skan_animation> animations;
};

void dependency_skelanim( tag_entry *e )
{
	XGSDynArray<skan_bone_base> basebones;
	XGPDynArray<statedata> states;
	XGPDynArray<skan_animation> animations;
	const void *tagdata;
	unsigned taglength;
	
	int frame_size;
	
	int i, j;
	
	// most of this ripped from EditAttachments and ReloadTag
	if( !get_entry_data( e, &tagdata, &taglength ) )
		return;
		
	byte *buf = (byte *)malloc( taglength );
	skan_tag_header *skan = (skan_tag_header *)buf;
	memcpy( buf, tagdata, taglength );
	
	void *end = import_skan( (skan_tag_header *)buf );
	if( end != (void *)(buf + taglength) )
	{
		Assert( 0 );
		free( buf );
		return;
	}
	
	for( i = 0; i < skan->bone_count; ++i )
	{
		basebones += skan->baseframe[i];
	}
	
	frame_size = sizeof(skan_frame);
	frame_size += basebones.Length() * sizeof(skan_bone_frame_pos);
	frame_size = (frame_size + 3) & ~3;
	
	for( i = 0; i < skan->anim_count; ++i )
	{
		int anim_size = sizeof(skan_animation) + frame_size * skan->animations[i]->frame_count;
		skan_animation *anim = (skan_animation *)malloc( anim_size );
		memcpy( anim, skan->animations[i], anim_size );
		animations += anim;
	}
	
	for( i = 0; i < skan->state_count; ++i )
	{
		statedata *state = new statedata;
		state->header = *(skan->states[i]);
		for( j = 0; j < skan->states[i]->anim_count; ++j )
		{
			state->animations += animations[skan->states[i]->animations[j][0]];
			state->probabilities += skan->states[i]->animations[j][1];
		}
		states += state;
	}

	for( i = 0; i < skan->anim_count; ++i )
	{
		int frame_size = (sizeof(skan_frame) + basebones.Length() * sizeof(skan_bone_frame_pos) + 3) & ~3;
		skan_frame *frame = (skan_frame *)(animations[i] + 1);

		for( j = 0; j < animations[i]->frame_count; ++j )
		{
			if( frame->frame_action != (word)-1 )
			{
				int kind = (frame->frame_action & _skan_frame_action_kind_mask) >> _skan_frame_action_first_kind_bit;
				tag type;
				
				switch( kind )
				{
				case _skan_frame_action_kind_sound:
					type = 'soun';
					break;
				case _skan_frame_action_kind_skmd:
					type = 'skmd';
					break;
				case _skan_frame_action_kind_loli:
					type = 'loli';
					break;
				case _skan_frame_action_kind_lpgr:
					type = 'lpgr';
					break;
				case _skan_frame_action_kind_project:
					type = 'proj';
					break;
				default:
					type = '????';
					break;
				}
					
				if( type != '????' )
				{
					add_tag_dependency( type, frame->action_tag_id );
				}
			}
			frame = (skan_frame *)((byte *)frame + frame_size);
		}
	}
	
	
	free( buf );
	
	// duplication of free_data();
	basebones.DeleteAll();
	
	for( i = 0; i < states.Length(); ++i )
	{
		delete states[i];
	}
	states.DeleteAll();
	
	for( i = 0; i < animations.Length(); ++i )
	{
		free( animations[i] );
	}
	animations.DeleteAll();
}

void edit_skelanim( tag_entry *e )
{
	TskanEditForm *form = new TskanEditForm( e );
	form->AttachWindow( KWindowSkelAnim );
}

void update_skelanim( tag_entry *e )
{
	int i, j, k;
	
	for (;;)
	{
		int version = get_entry_version( e );
		const void *tagdata;
		unsigned taglength;
		byte *in_buf;
		byte *out_buf;
		void *ptr;
		switch (version)
		{
			case 1: {
			
				// first load in the old data
				
					if (!get_entry_data( e, &tagdata, &taglength )) return;
					in_buf = (byte *)malloc( taglength );
					skan_tag_header_v1 *v1header = (skan_tag_header_v1 *)in_buf;
					skan_animation_v1 *v1anim;
					skan_frame_v1 *v1frame;
					skan_state *v1state;
					skan_bone_frame_pos *v1pos;
					
					memcpy( in_buf, tagdata, taglength );
					byte_swap_data_be( "skan v1 header", v1header, sizeof(*v1header), 1, bs_skan_tag_header );
					v1header->baseframe = (skan_bone_base *)(v1header + 1);
					v1header->animations = (skan_animation_v1 **)(v1header->baseframe + v1header->bone_count);
					v1header->states = (skan_state **)(v1header->animations + v1header->anim_count);
					ptr = v1header->states + v1header->state_count;
					byte_swap_data_be( "skan bone base v1", v1header->baseframe, sizeof(skan_bone_base), v1header->bone_count, bs_skan_bone_base );
					for (i = 0; i < v1header->anim_count; ++i)
					{
						v1header->animations[i] = (skan_animation_v1 *)ptr;
						v1anim = (skan_animation_v1 *)ptr;
						byte_swap_data_be( "skan animation v1", v1anim, sizeof(*v1anim), 1, bs_skan_animation_v1 );
						v1frame = (skan_frame_v1 *)(v1anim + 1);
						for (j = 0; j < v1anim->frame_count; ++j)
						{
							byte_swap_data_be( "skan frame v1", v1frame, sizeof(*v1frame), 1, bs_skan_frame );
							byte_swap_data_be( "skan bone frame pos v1", v1frame+1, sizeof(skan_bone_frame_pos), v1header->bone_count, bs_skan_bone_frame_pos );
							int size = sizeof( *v1frame ) + v1header->bone_count * sizeof(skan_bone_frame_pos);
							size = (size + 3) & ~3;
							v1frame = (skan_frame_v1 *)((byte *)v1frame + size);
						}
						ptr = v1frame;
					}
					for (i = 0; i < v1header->state_count; ++i)
					{
						v1header->states[i] = v1state = (skan_state *)ptr;
						byte_swap_data_be( "skan state v1", v1state, sizeof(*v1state) - sizeof(v1state->animations), 1, bs_skan_state );
						byte_swap_memory_be( "skan state anim list v1", v1state->animations, v1state->anim_count * 2, _2byte );
						ptr = v1state->animations + v1state->anim_count;
					}
					
				// we've got the old one, now make the new one
				
					skan_animation *v2anim;
					skan_frame *v2frame;
					skan_state *v2state;
					skan_bone_frame_pos *v2pos;
					
					out_buf = (byte *)malloc( taglength * 2 );
					skan_tag_header *v2header = (skan_tag_header *)out_buf;
					v2header->bone_count = v1header->bone_count;
					v2header->anim_count = v1header->anim_count;
					v2header->state_count = v1header->state_count;
					
					v2header->baseframe = (skan_bone_base *)(v2header + 1);
					v2header->animations = (skan_animation **)(v2header->baseframe + v2header->bone_count);
					v2header->states = (skan_state **)(v2header->animations + v2header->anim_count);
					
					ptr = v2header->states + v2header->state_count;
					
					memcpy( v2header->baseframe, v1header->baseframe, v2header->bone_count * sizeof(skan_bone_base) );
					
					for (i = 0; i < v2header->anim_count; ++i)
					{
						v1anim = v1header->animations[i];
						v2header->animations[i] = v2anim = (skan_animation *)ptr;
						float max_offset = 32000.0f * v1anim->offset_scale;
						float rescale = 1.0f;
						int shift = 0;
						while (max_offset * rescale < 16383.0f)
						{
							rescale *= 2.0f;
							shift += 1;
						}
						while (max_offset * rescale > 32767.0f)
						{
							rescale *= 0.5f;
							shift -= 1;
						}
						memcpy( v2anim->name, v1anim->name, sizeof(v2anim->name) );
						v2anim->name[MAX_SKAN_ANIM_NAME] = 0;
						v2anim->frame_count = v1anim->frame_count;
						v2anim->flags = v1anim->flags;
						v2anim->fixed_fps = float_to_short_fixed( v1anim->fps );
						v2anim->offset_shift = shift;
						v2anim->crossfade_in_time = v1anim->crossfade_in_time;
						v2anim->crossfade_interrupted_time = v1anim->crossfade_interrupted_time;
						v2anim->crossfade_finished_time = v1anim->crossfade_finished_time;
						
						v1frame = (skan_frame_v1 *)(v1anim + 1);
						v2frame = (skan_frame *)(v2anim + 1);
						for (j = 0; j < v2anim->frame_count; ++j)
						{
							if (v2anim->flags & _skelanim_turn_driven_flag)
							{
								v2frame->fixed_distance_delta = (int)round( v1frame->distance_delta * FULL_CIRCLE / 360.0 );
							}
							else
							{
								v2frame->fixed_distance_delta = float_to_fixed( v1frame->distance_delta );
							}
							v2frame->frame_action = v1frame->frame_action;
							v2frame->action_tag_index = v1frame->action_tag_index;
							v2frame->action_tag_id = v1frame->action_tag_id;
							
							if (j == v1anim->keyframe)
							{
								v2frame->frame_action |= _skan_frame_action_keyframe_flag;
								v2frame->frame_action = (v2frame->frame_action & ~_skan_frame_action_bone_mask) | (v1anim->keybone << _skan_frame_action_first_bone_bit);
							}
							
							v1pos = (skan_bone_frame_pos *)(v1frame + 1);
							v2pos = (skan_bone_frame_pos *)(v2frame + 1);
							for (k = 0; k < v2header->bone_count; ++k)
							{
								v2pos->shortOffset[0] = float_to_clamped_short( v1pos->shortOffset[0] * v1anim->offset_scale * rescale );
								v2pos->shortOffset[1] = float_to_clamped_short( v1pos->shortOffset[1] * v1anim->offset_scale * rescale );
								v2pos->shortOffset[2] = float_to_clamped_short( v1pos->shortOffset[2] * v1anim->offset_scale * rescale );
								
								v2pos->shortQuat[0] = float_to_clamped_short( v1pos->shortQuat[0] * 32767.0 / 32000.0 );
								v2pos->shortQuat[1] = float_to_clamped_short( v1pos->shortQuat[1] * 32767.0 / 32000.0 );
								v2pos->shortQuat[2] = float_to_clamped_short( v1pos->shortQuat[2] * 32767.0 / 32000.0 );
								v2pos->shortQuat[3] = float_to_clamped_short( v1pos->shortQuat[3] * 32767.0 / 32000.0 );
								
								++v1pos;
								++v2pos;
							}
							
							int v1size = sizeof( *v1frame ) + v1header->bone_count * sizeof(skan_bone_frame_pos);
							v1size = (v1size + 3) & ~3;
							v1frame = (skan_frame_v1 *)((byte *)v1frame + v1size);
							
							int v2size = sizeof( *v2frame ) + v2header->bone_count * sizeof(skan_bone_frame_pos);
							v2size = (v2size + 3) & ~3;
							v2frame = (skan_frame *)((byte *)v2frame + v2size);
						}
						
						ptr = v2frame;
					}
					
					for (i = 0; i < v2header->state_count; ++i)
					{
						v1state = v1header->states[i];
						v2header->states[i] = v2state = (skan_state *)ptr;
						memcpy( v2state, v1state, sizeof(skan_state) + (v1state->anim_count - 1) * 2 * sizeof(short) );
						ptr = v2state->animations + v2state->anim_count;
					}
					
					Assert( (byte *)ptr - out_buf <= taglength );
					
				// finally byte-swap and export the output
					
					byte_swap_data_be( "skan bone base", v2header->baseframe, sizeof(skan_bone_base), v2header->bone_count, bs_skan_bone_base );
					
					for (i = 0; i < v2header->anim_count; ++i)
					{
						v2anim = v2header->animations[i];
						v2frame = (skan_frame *)(v2anim + 1);
						for (j = 0; j < v2anim->frame_count; ++j)
						{
							byte_swap_data_be( "skan frame pos", v2frame+1, sizeof(skan_bone_frame_pos), v2header->bone_count, bs_skan_bone_frame_pos );
							byte_swap_data_be( "skan frame", v2frame, sizeof(*v2frame), 1, bs_skan_frame );
							
							int v2size = sizeof( *v2frame ) + v2header->bone_count * sizeof(skan_bone_frame_pos);
							v2size = (v2size + 3) & ~3;
							v2frame = (skan_frame *)((byte *)v2frame + v2size);
						}
						byte_swap_data_be( "skan animation", v2anim, sizeof(*v2anim), 1, bs_skan_animation );
					}
					
					for (i = 0; i < v2header->state_count; ++i)
					{
						v2state = v2header->states[i];
						byte_swap_memory_be( "skan state anim list", v2state->animations, v2state->anim_count * 2, _2byte );
						byte_swap_data_be( "skan state", v2state, sizeof(*v2state) - sizeof(v2state->animations), 1, bs_skan_state );
					}
					
					byte_swap_data_be( "skan header", v2header, sizeof(*v2header), 1, bs_skan_tag_header );
					
					set_entry_data( e, out_buf, (byte *)ptr - out_buf, false );
					set_entry_version( e, 2 );
					free( out_buf );
				} break;
				
			case 2:
				return;
			default:
				Assert(0);
				return;
		}
	}
}

#pragma mark -

BEGINDISPATCH(TskanEditForm,TTagEditForm)
	DISPATCHTABLE(KXGControlClick,TskanEditForm::DoControlClick)
	DISPATCHTABLE(KEventTreeClick,TskanEditForm::DoTreeClick)
	DISPATCHTABLE(KEventTreeCanDrop,TskanEditForm::DoTreeCanDrop)
	DISPATCHTABLE(KEventTreeDoDrop,TskanEditForm::DoTreeDrop)
	DISPATCHTABLE(KXGKeyClick,TskanEditForm::DoKeyClick)
	DISPATCHTABLE(KXGPopValue,TskanEditForm::DoPopValue)
	//DISPATCHTABLE(KXGListValue,TskanEditForm::DoListValue)
	//DISPATCHTABLE(KXGSliderValue,TskanEditForm::DoSliderValue)
ENDDISPATCH


TskanEditForm::TskanEditForm( tag_entry *e ):
	TTagEditForm( e )
{
	selectedState = 0;
	selectedAnimation = 0;
}

TskanEditForm::~TskanEditForm()
{
}


long TskanEditForm::DoControlClick( long arg, void * /* ptr */ )
{
	int origin;
	OriginPickData pd;
	int i;
	int new_val;
	
	switch (arg) {
		case 1: {	// specify base
				XGFileSpecifier *fs = GetSkl();
				if (fs) {
					char name[256];
					fs->GetBaseName( name );
					pd.want_offset_settings = false;
					origin = LoadAndPickOrigin( fs, &pd );
					delete fs;
					if (origin >= -1) {
						SetEnabled( 1, false );
						SetEnabled( 2, true );
						SetVisible( 3, false );
						SetVisible( 4, true );
						SetEnabled( 201, true );
						
						compact_mesh();
						compact_bones();
						make_skan_base( origin, this->basebones, 0 );
						//RebuildBonePop();
						fModified = true;
					}
				}
			} return 0;
		case 2: {	// change base
			} return 0;
		case 101: {	// new state
				statedata *newstate = NewState();
				strcpy( newstate->header.name, "Unnamed State" );
				reload_states();
				SetSelect( newstate, 0 );
				fModified = true;
			} return 0;
		case 102: {	// delete from state
				if (selectedState) DeleteSelected();
			} return 0;
		case 201: {	// import animation
				XGFileSpecifier *fs = GetSkl();
				if (fs) {
					char name[256];
					fs->GetBaseName(name);
					pd.want_offset_settings = true;
					pd.loopable = false;
					origin = LoadAndPickOrigin( fs, &pd );
					delete fs;
					if (origin >= -1) {
						skan_animation *anim = make_skan_animation( origin, this->basebones, pd.offset_setting, pd.loopable );
						strncpy( anim->name, name, sizeof(anim->name) );
						anim->name[sizeof(anim->name)-1] = 0;
						animations += anim;
						reload_animations();
						SetSelect( 0, anim );
						fModified = true;
					}
				}
			} return 0;
		case 202: {	// delete animation
				if (selectedAnimation && !selectedState) DeleteSelected();
			} return 0;
		case 313: {
				// no la toque!
			} return 0;
/*		case 314:
			if (selectedAnimation) {
				int new_val = GetFlag( arg ) ? 0 : _skelanim_distance_driven_flag;
				SetFlag( arg, new_val );
				selectedAnimation->flags = (selectedAnimation->flags & ~_skelanim_distance_driven_flag) | new_val;
			} return 0; */
		case 315:
			if (selectedAnimation) {
				new_val = GetFlag( arg ) ? 0 : _skelanim_fast_interrupt_flag;
				SetFlag( arg, new_val );
				selectedAnimation->flags = (selectedAnimation->flags & ~_skelanim_fast_interrupt_flag) | new_val;
				fModified = true;
			} return 0;
		case 316: {
				if (selectedAnimation) {
					EditAttachments( selectedAnimation );
				}
			} return 0;
		case 320:
		case 321:
		case 322:
			if (selectedAnimation) {
				for (i = 320; i <= 322; ++i) {
					SetFlag( i, i == arg );
				}
				selectedAnimation->flags &= ~(_skelanim_distance_driven_flag|_skelanim_turn_driven_flag);
				if (arg == 321) {
					selectedAnimation->flags |= _skelanim_distance_driven_flag;
				}
				if (arg == 322) {
					selectedAnimation->flags |= _skelanim_turn_driven_flag;
				}
				fModified = true;
			} return 0;
		case 323:
			if (selectedState) {
				new_val = GetFlag( arg ) ? 0 : _skelstate_pauses_at_end_flag;
				SetFlag( arg, new_val );
				selectedState->header.flags = (selectedState->header.flags & ~_skelstate_pauses_at_end_flag) | new_val;
				fModified = true;
			} return 0;
		case 324:
			if (selectedState) {
				new_val = GetFlag( arg ) ? 0 : _skelstate_keeps_animation_when_cycling_flag;
				SetFlag( arg, new_val );
				selectedState->header.flags = (selectedState->header.flags & ~_skelstate_keeps_animation_when_cycling_flag) | new_val;
				fModified = true;
			} return 0;
	}
	
	return -1;
}


long TskanEditForm::DoKeyClick( long arg, void * /* ptr */ )
{
	switch (arg) {
		case 301:
			if (selectedState) {
				GetText( arg, selectedState->header.name, sizeof(selectedState->header.name) );
				reload_states();
				fModified = true;
				return 0;
			} else return 1;
		case 303:
			if (selectedAnimation) {
				GetText( arg, selectedAnimation->name, sizeof(selectedAnimation->name) );
				reload_states();
				reload_animations();
				fModified = true;
				return 0;
			} else return 1;
		case 304:
			if (selectedState && selectedAnimation) {
				for (int i = 0; i < selectedState->animations.Length(); ++i) {
					if (selectedState->animations[i] == selectedAnimation) {
						selectedState->probabilities[i] = GetInt( arg );
					}
				}
				reload_states();
				fModified = true;
				return 0;
			} else return 1;
		case 308:
			if (selectedAnimation) {
				selectedAnimation->fixed_fps = GetShortFixed( arg );
				if (selectedAnimation->fixed_fps > 0.0f) {
					SetFloat( 309, selectedAnimation->frame_count / short_fixed_to_float(selectedAnimation->fixed_fps) );
				}
				fModified = true;
				return 0;
			} else return 1;
		case 310:
			if (selectedAnimation) {
				selectedAnimation->crossfade_in_time = GetFloat( arg );
				fModified = true;
				return 0;
			} else return 1;
		case 311:
			if (selectedAnimation) {
				selectedAnimation->crossfade_finished_time = GetFloat( arg );
				fModified = true;
				return 0;
			} else return 1;
		case 312:
			if (selectedAnimation) {
				selectedAnimation->crossfade_interrupted_time = GetFloat( arg );
				fModified = true;
				return 0;
			} else return 1;
	}
	return -1;
}


long TskanEditForm::DoPopValue( long arg, void * )
{
	switch (arg) {
#if 0
		case 318:
			if (selectedAnimation) {
				selectedAnimation->keybone = GetInt( arg );
				fModified = true;
			} else return 1;
#endif
	}
	return -1;
}


void TskanEditForm::DeleteSelected()
{
	int i, j;
	
	if (selectedState) {
		if (selectedAnimation) {
			XGSDynArray<int> probabilities;
			XGPDynArray<skan_animation> animations;
			for (i = 0; i < selectedState->animations.Length(); ++i) {
				if (selectedState->animations[i] != selectedAnimation) {
					probabilities += selectedState->probabilities[i];
					animations += selectedState->animations[i];
				}
			}
			selectedState->probabilities = probabilities;
			selectedState->animations = animations;
		} else {
			XGPDynArray<statedata> tempstates;
			for (i = 0; i < states.Length(); ++i) {
				if (states[i] != selectedState) {
					tempstates += states[i];
				}
			}
			delete selectedState;
			states = tempstates;
		}
		reload_states();
		fModified = true;
	} else if (selectedAnimation) {
		for (i = 0; i < states.Length(); ++i) {
			XGSDynArray<int> probabilities;
			XGPDynArray<skan_animation> animations;
			for (j = 0; j < states[i]->animations.Length(); ++j) {
				if (states[i]->animations[j] != selectedAnimation) {
					probabilities += states[i]->probabilities[j];
					animations += states[i]->animations[j];
				}
			}
			states[i]->probabilities = probabilities;
			states[i]->animations = animations;
		}
		reload_states();
		XGPDynArray<skan_animation> tempanim;
		for (i = 0; i < animations.Length(); ++i) {
			if (animations[i] != selectedAnimation) {
				tempanim += animations[i];
			}
		}
		animations = tempanim;
		free( selectedAnimation );
		reload_animations();
		fModified = true;
	}
	SetSelect( 0, 0, true );
}


long TskanEditForm::DoTreeCanDrop( long arg, void *ptr )
{
	TreeDropRecord *d = (TreeDropRecord *)ptr;
	if (!IsEnabled(201)) return 0;
	if (d->dropID == KDragFlavorFile) return 1;
	if (arg == 100 && d->dropID == 'sani') return 1;
	return 0;
}


TskanEditForm::statedata *TskanEditForm::NewState()
{
	statedata *data = new statedata;
	memset( &data->header, 0, sizeof(data->header) );
	states += data;
	return data;
}


long TskanEditForm::DoTreeDrop( long arg, void *ptr )
{
	TreeDropRecord d = *(TreeDropRecord *)ptr;
	skanTreeData temptd;
	int i;
	
	if (!IsEnabled( 201 )) return 0;
	if (d.dropID == KDragFlavorFile) {
		XGFileSpecifier fs;
		XGDropper::MakeFileSpecFromDropData( fs, d.data, d.len );
		char name[256];
		fs.GetBaseName( name );
		OriginPickData pd;
		pd.want_offset_settings = true;
		pd.loopable = false;
		int origin = LoadAndPickOrigin( &fs, &pd );
		if (origin >= -1) {
			skan_animation *anim = make_skan_animation( origin, this->basebones, pd.offset_setting, pd.loopable );
			strncpy( anim->name, name, sizeof(anim->name) );
			anim->name[sizeof(anim->name)-1] = 0;
			animations += anim;
			reload_animations();
			SetSelect( 0, anim );
			temptd.state = 0;
			temptd.anim = anim;
			d.dropID = 'sani';
			d.data = &temptd;
			d.len = sizeof(temptd);
			fModified = true;
		}
	}
	if (arg == 100 && d.dropID == 'sani' && d.len == sizeof(skanTreeData)) {
		skanTreeData td = *(skanTreeData *)(d.data);
		if ((d.modifiers & KKeyDragCopyMask) == KKeyDragCopy) {	
			td.state = 0;
		}
		for (i = 0; i < animations.Length(); ++i) {
			if (animations[i] == td.anim) break;
		}
		if (i == animations.Length()) return 0;
		statedata *dest = 0;
		if (d.parent == 0) {
			dest = NewState();
			strcpy( dest->header.name, td.anim->name );
		} else {
			skanTreeData std;
			if (stateView->GetExtraData( d.parent, &std, sizeof(std) ) == sizeof(std)) {
				dest = std.state;
			}
		}
		if (!dest) {
			dest = NewState();
			strcpy( dest->header.name, "error state" );
		}
		for (i = 0; i < dest->animations.Length(); ++i) {
			if (td.anim == dest->animations[i]) return 0;
		}
		int oldprob = 60;
		if (td.state) {
			XGSDynArray<int> prob;
			XGPDynArray<skan_animation> ani;
			for (i = 0; i < td.state->animations.Length(); ++i) {
				if (td.state->animations[i] != td.anim) {
					prob += td.state->probabilities[i];
					ani += td.state->animations[i];
				} else {
					oldprob = td.state->probabilities[i];
				}
			}
			td.state->probabilities = prob;
			td.state->animations = ani;
		}
		dest->animations += td.anim;
		dest->probabilities += oldprob;
		reload_states();
		SetSelect( dest, td.anim );
		fModified = true;
	}
	return 0;
}


long TskanEditForm::DoTreeClick( long arg, void *ptr )
{
	TreeEventRecord *ev = (TreeEventRecord *)ptr;
	skanTreeData td;
	td.state = 0;
	td.anim = 0;
	skanTreeData td2;
	if (ev->child) {
		if (arg == 100) {
			if (stateView->GetExtraData( ev->child, &td2, sizeof(td2) ) == sizeof(td2)) {
				td = td2;
			}
		} else if (arg == 200) {
			if (animView->GetExtraData( ev->child, &td2, sizeof(td2) ) == sizeof(td2)) {
				td = td2;
			}
		}
	}
	SetSelect( td.state, td.anim );
	return -1;
}


void TskanEditForm::DoFormInitialize()
{
	TTagEditForm::DoFormInitialize();
	stateView = dynamic_cast<TTreeView *>(GetWindow()->FindViewByID(100));
	animView = dynamic_cast<TTreeView *>(GetWindow()->FindViewByID(200));
}


void TskanEditForm::CommitToTag()
{
	int i, j, k;
	
	int total_size = sizeof(skan_tag_header);
	total_size += basebones.Length() * sizeof(skan_bone_base);
	total_size += animations.Length() * 4;
	total_size += states.Length() * 4;
	
	int frame_size = sizeof(skan_frame);
	frame_size += basebones.Length() * sizeof(skan_bone_frame_pos);
	frame_size = (frame_size + 3) & ~3;
	
	int anim_size;
	int state_size;
	
	for (i = 0; i < animations.Length(); ++i) {
		anim_size = sizeof(skan_animation);
		anim_size += animations[i]->frame_count * frame_size;
		
		total_size += anim_size;
	}
	
	for (i = 0; i < states.Length(); ++i) {
		state_size = sizeof(skan_state);
		state_size += 2 * sizeof(word) * (states[i]->animations.Length() - 1);
		
		total_size += state_size;
	}
	
	void *tagdata = malloc( total_size );
	if (!tagdata) return;
	skan_tag_header *header = (skan_tag_header *)tagdata;
	header->bone_count = basebones.Length();
	header->anim_count = animations.Length();
	header->state_count = states.Length();
	byte *ptr = (byte *)setup_skan_ptrs( header );
	byte_swap_data_be( "skan header", header, sizeof(*header), 1, bs_skan_tag_header );
	byte_swap_move_be( "skan bone base", header->baseframe, &(basebones[0]), sizeof(skan_bone_base), basebones.Length(), bs_skan_bone_base );
	
	for (i = 0; i < animations.Length(); ++i) {
		anim_size = sizeof(skan_animation);
		anim_size += animations[i]->frame_count * frame_size;
		memcpy( ptr, animations[i], anim_size );
		skan_animation *anim = (skan_animation *)ptr;
		header->animations[i] = 0;
		byte_swap_data_be( "skan animation", anim, sizeof(*anim), 1, bs_skan_animation );
		ptr += sizeof(skan_animation);
		for (j = 0; j < animations[i]->frame_count; ++j) {
			byte_swap_data_be( "skan frame", ptr, sizeof(skan_frame), 1, bs_skan_frame );
			ptr += sizeof(skan_frame);
			byte_swap_data_be( "skan frame pos", ptr, sizeof(skan_bone_frame_pos), basebones.Length(), bs_skan_bone_frame_pos );
			ptr += sizeof(skan_bone_frame_pos) * basebones.Length();
			if (basebones.Length() & 1) {
				*ptr++ = 0;
				*ptr++ = 0;
			}
		}
		Assert( ptr == (byte *)anim + anim_size );
	}
	
	for (i = 0; i < states.Length(); ++i) {
		state_size = sizeof(skan_state);
		state_size += 2 * sizeof(word) * (states[i]->animations.Length() - 1);
		skan_state *state = (skan_state *)ptr;
		header->states[i] = 0;
		memcpy( state->name, states[i]->header.name, sizeof(state->name) );
		state->anim_count = states[i]->animations.Length();
		state->flags = states[i]->header.flags;
		state->total_probability = 0;
		for (j = 0; j < states[i]->probabilities.Length(); ++j) {
			state->animations[j][0] = 0;
			for (k = 0; k < animations.Length(); ++k) {
				if (states[i]->animations[j] == animations[k]) {
					state->animations[j][0] = k;
					break;
				}
			}
			state->animations[j][1] = states[i]->probabilities[j];
			state->total_probability += states[i]->probabilities[j];
		}
		
		byte_swap_data_be( "skan state", state, sizeof(skan_state) - sizeof(state->animations), 1, bs_skan_state );
		byte_swap_memory_be( "skan state anim list", state->animations, states[i]->animations.Length() * 2, _2byte );
		ptr += state_size;
	}
	
	Assert( ptr == (byte *)tagdata + total_size );
	set_entry_data( fEntry, tagdata, total_size, true );
	fModified = false;
}


void TskanEditForm::ReloadTag()
{
	free_data();
	
	int i, j;
	
	const void *tagdata;
	unsigned taglength;
	if (!get_entry_data( fEntry, &tagdata, &taglength )) return;
	byte *buf = (byte *)malloc( taglength );
	skan_tag_header *skan = (skan_tag_header *)buf;
	memcpy( buf, tagdata, taglength );
	void *end = import_skan( (skan_tag_header *)buf );
	if (end != (void *)(buf + taglength)) {
		Assert(0);
		free( buf );
		return;
	}
	
	for (i = 0; i < skan->bone_count; ++i) {
		basebones += skan->baseframe[i];
	}
	
	int frame_size = sizeof(skan_frame);
	frame_size += basebones.Length() * sizeof(skan_bone_frame_pos);
	frame_size = (frame_size + 3) & ~3;
	
	for (i = 0; i < skan->anim_count; ++i) {
		int anim_size = sizeof(skan_animation) + frame_size * skan->animations[i]->frame_count;
		skan_animation *anim = (skan_animation *)malloc( anim_size );
		memcpy( anim, skan->animations[i], anim_size );
		animations += anim;
	}
	
	for (i = 0; i < skan->state_count; ++i) {
		statedata *state = new statedata;
		state->header = *(skan->states[i]);
		for (j = 0; j < skan->states[i]->anim_count; ++j) {
			state->animations += animations[skan->states[i]->animations[j][0]];
			state->probabilities += skan->states[i]->animations[j][1];
		}
		states += state;
	}
	
	free( buf );
	
	if (basebones.Length() < 1) {
		SetEnabled( 1, true );
		SetEnabled( 2, false );
		SetVisible( 3, true );
		SetVisible( 4, false );
		
		SetEnabled( 201, false );
	} else {
		SetEnabled( 1, false );
		SetEnabled( 2, true );
		SetVisible( 3, false );
		SetVisible( 4, true );
		
		SetEnabled( 201, true );
	}
	
	//RebuildBonePop();
	reload_animations();
	reload_states();
	SetSelect( 0, 0, true );
	fModified = false;
}


bool TskanEditForm::NeedCommit()
{
	return fModified;
}

#if 0
void TskanEditForm::RebuildBonePop()
{
	int i;
	XGPopControl *pop = dynamic_cast<XGPopControl *>(GetWindow()->FindViewByID(318));
	pop->DeleteAll();
	for (i = 0; i < basebones.Length(); ++i) {
		pop->Insert( i, basebones[i].name );
	}
}
#endif

	
void TskanEditForm::free_data()
{
	int i;
	basebones.DeleteAll();
	for (i = 0; i < states.Length(); ++i) {
		delete states[i];
	}
	states.DeleteAll();
	for (i = 0; i < animations.Length(); ++i) {
		free( animations[i] );
	}
	animations.DeleteAll();
}


void TskanEditForm::reload_states()
{
	int i, j;
	if (!stateView) return;
	
	skanTreeData td;
	XGPtrSet<statedata> open;
	
	for (TableEntryRecord *r = (TableEntryRecord *)stateView->GetItemHandle( 0, 0 ); r ; r = r->next) {
		if (r->fOpen) {
			if (stateView->GetExtraData( (uint32)r, &td, sizeof(td) ) == sizeof(td) && td.state && !td.anim) {
				open += td.state;
			}
		}
	}
	
	stateView->DisableDrawing();
	stateView->DeleteAll();
	uint32 par = 0;
	for (i = 0; i < states.Length(); ++i) {
		par = stateView->AppendItem( 0, par, states[i]->header.name, 135 );
		td.state = states[i];
		td.anim = 0;
		stateView->SetExtraData( par, &td, sizeof(td) );
		stateView->SetDragSource( par, 0, 0, 0, false );
		states[i]->header.total_probability = 0;
		for (j = 0; j < states[i]->probabilities.Length(); ++j) {
			states[i]->header.total_probability += states[i]->probabilities[j];
		}
		uint32 child = 0;
		for (j = 0; j < states[i]->animations.Length(); ++j) {
			char buf[256];
			sprintf( buf, "%s (%d/%d)",
					states[i]->animations[j]->name,
					states[i]->probabilities[j],
					states[i]->header.total_probability );
			child = stateView->AppendItem( par, child, buf, 130 );
			td.anim = states[i]->animations[j];
			stateView->SetExtraData( child, &td, sizeof(td) );
			stateView->SetDragSource( child, 'sani', &td, sizeof(td), false );
		}
		if (open.HasMember(td.state)) {
			stateView->OpenItem( par, true );
		}
		stateView->SortChildrenOf( par );
	}
	stateView->SortChildrenOf( 0 );
	stateView->EnableDrawing();
	SetSelect( selectedState, selectedAnimation );
}


void TskanEditForm::reload_animations()
{
	if (!animView) return;
	
	skanTreeData td;
	animView->DisableDrawing();
	animView->DeleteAll();
	uint32 id = 0;
	td.state = 0;
	for (int i = 0; i < animations.Length(); ++i) {
		id = animView->AppendItem( 0, id, animations[i]->name, 130 );
		td.anim = animations[i];
		animView->SetExtraData( id, &td, sizeof(td) );
		animView->SetDragSource( id, 'sani', &td, sizeof(td), false );
	}
	animView->SortChildrenOf( 0 );
	animView->EnableDrawing();
	SetSelect( selectedState, selectedAnimation );
}

long TskanEditForm::BonePickProc( XGDialog *dlog, void *data, long msg, long arg, void * /*parg*/ )
{
	XGListControl *list = dynamic_cast< XGListControl * >(dlog->FindViewByID(1));
	OriginPickData *pd = (OriginPickData *)data;
	if (!list) return -1;
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog: {
						list->DeleteAll();
						int i = 0;
						int picked = -1;
						const char *name;
						while ((name = get_bone_name(i)) != 0) {
							list->Insert( i, name[0] ? name : "(unnamed bone)" );
							if (XGCompareStrings( name, "tag_origin_standard" ) == 0) {
								picked = i;
							}
							++i;
						}
						list->SetValue( picked );
						dlog->FindViewByID( 2 )->SetEnabled( picked >= 0 );
						if (!pd->want_offset_settings) {
							for (int i = 0; i < 3; ++i) {
								for (int j = 3; j >= 0; --j) {
									int id = i * 100 + j + 100;
									XGView *v = dlog->FindViewByID( id );
									if (v) {
										v->SetEnabled( false );
										if (j) {
											dlog->SetValue( id, 0 );
										}
									}
								}
							}
							dlog->SetEnabled( 501, false );
						} else {
							dlog->SetValue( 501, pd->loopable );
						}
						return 0;
					} break;
				default:
					break;
			} break;
		case KXGListValue:
			switch (arg) {
				case 1:	// bone list
					dlog->FindViewByID( 2 )->SetEnabled( list->GetValue() >= 0 );
					return 0;
				default:
					break;
			} break;
		case KXGControlClick:
			switch (arg) {
				case 2:		// select
					pd->bone_num = list->GetValue();
					for (int i = 0; i < 4; ++i) {
						pd->offset_setting[i] = dlog->FindViewByID( (i + 1) * 100 )->GetRefNum() % 100 - 1;
					}
					pd->loopable = dlog->GetValue( 501 );
					dlog->EndDialog(1);
					return 0;
				case 3:		// none
					pd->bone_num = -1;
					dlog->EndDialog(1);
					return 0;
				case 4:		// cancel
					pd->bone_num = -2;
					dlog->EndDialog(0);
					return 0;
				case 101: case 102: case 103:
				case 201: case 202: case 203:
				case 301: case 302: case 303:
				case 401: case 402:
					{
						XGView *view = dlog->FindViewByID( arg );
						XGRadioButton *radio = dynamic_cast< XGRadioButton * >(view);
						if (radio) {
							for (XGView *scan = radio->GetParent()->GetChild(); scan; scan = scan->GetSibling()) {
								XGRadioButton *sib = dynamic_cast< XGRadioButton * >(scan);
								if (sib) {
									sib->SetValue( sib == radio );
								}
							}
							radio->GetParent()->SetRefNum( arg );
						}
					} return 0;
				case 501:
					dlog->SetValue( arg, !dlog->GetValue( arg ) );
					return 0;
			} break;
		default:
			break;
	}
	
	return -1;
}


XGFileSpecifier *TskanEditForm::GetSkl()
{
	XGFileSpecifier *fs = 0;
	
	#if OPT_MACOS
		fs = XGFileSpecifier::OpenDialog( 130 );
	#endif
	
	#if OPT_WINOS
		fs = XGFileSpecifier::OpenDialog( "Tiki Skeleton Export|*.skl|All Files|*.*","skl" );
	#endif
	
	return fs;
}

int TskanEditForm::LoadAndPickOrigin( XGFileSpecifier *fs, OriginPickData *pd )
{
	if (!fs) return -2;
	
	FILE *f = fs->OpenFile( "r" );
	if (!f) return -2;
	
	read_skl( f );
	fclose( f );
	
	bool ok = XGDialog::ModalDialog( KWindowPickBone, BonePickProc, pd );
	if (ok) {
		return pd->bone_num;
	}
	return -2;
}


void TskanEditForm::SetSelect( statedata *state, skan_animation *anim, bool force_update )
{
	skanTreeData td;
	TableEntryRecord *r;
	if (state) {
		animView->ClearSelection();
		for (r = (TableEntryRecord *)stateView->GetItemHandle( 0, 0 ); r ; r = r->next) {
			if (stateView->GetExtraData( (uint32)r, &td, sizeof(td) ) == sizeof(td) && td.state == state) {
				if (anim) {
					TableEntryRecord *r2;
					for (r2 = r->child; r2; r2 = r2->next) {
						if (stateView->GetExtraData( (uint32)r2, &td, sizeof(td) ) == sizeof(td) && td.anim == anim) {
							stateView->OpenItem( (uint32)r, true );
							stateView->SetSelection( (uint32)r2 );
							goto done;
						}
					}
					state = 0;
					anim = 0;
					stateView->ClearSelection();
				} else {
					stateView->SetSelection( (uint32)r );
				}
				goto done;
			}
		}
		state = 0;
		anim = 0;
		stateView->ClearSelection();
	} else {
		stateView->ClearSelection();
		if (anim) {
			for (r = (TableEntryRecord *)animView->GetItemHandle( 0, 0 ); r ; r = r->next) {
				if (animView->GetExtraData( (uint32)r, &td, sizeof(td) ) == sizeof(td) && td.anim == anim) {
					animView->SetSelection( (uint32)r );
					break;
				}
			}
			if (!r) {
				state = 0;
				anim = 0;
				animView->ClearSelection();
			}
		} else {
			animView->ClearSelection();
		}
	}
done:
	if (selectedState == state && selectedAnimation == anim && !force_update) return;
	
	selectedState = state;
	selectedAnimation = anim;
	if (state) {
		SetEnabled( 301, true );
		SetText( 301, state->header.name );
		SetEnabled( 323, true );
		SetFlag( 323, state->header.flags & _skelstate_pauses_at_end_flag );
		SetEnabled( 324, true );
		SetFlag( 324, state->header.flags & _skelstate_keeps_animation_when_cycling_flag );
		if (anim) {
			SetEnabled( 304, true );
			for (int i = 0; i < state->animations.Length(); ++i) {
				if (state->animations[i] == anim) {
					SetInt( 304, state->probabilities[i] );
					break;
				}
			}
		} else {
			SetText( 304, "" );
			SetEnabled( 304, false );
		}
	} else {
		SetEnabled( 301, false );
		SetText( 301, "" );
		SetText( 304, "" );
		SetEnabled( 304, false );
		SetEnabled( 323, false );
		SetFlag( 323, false );
		SetEnabled( 324, false );
		SetFlag( 324, false );
	}
	if (anim) {
		SetEnabled( 303, true );
		SetText( 303, anim->name );
		SetInt( 305, anim->frame_count );
		int frame_size = sizeof(skan_frame) + basebones.Length() * sizeof(skan_bone_frame_pos);
		frame_size = (frame_size + 3) & ~3;
		SetInt( 306, sizeof(skan_animation) + anim->frame_count * frame_size );
		float dist = 0.0f;
		for (int i = 0; i < anim->frame_count; ++i) {
			skan_frame *f = (skan_frame *)(((char *)(anim + 1)) + i * frame_size);
			dist += fixed_to_float(f->fixed_distance_delta);
		}
		SetFloat( 307, dist );
		
		SetEnabled( 308, true );
		SetShortFixed( 308, anim->fixed_fps );
		SetFloat( 309, anim->frame_count / short_fixed_to_float(anim->fixed_fps) );
		SetEnabled( 310, true );
		SetFloat( 310, anim->crossfade_in_time );
		SetEnabled( 311, true );
		SetFloat( 311, anim->crossfade_finished_time );
		SetEnabled( 312, true );
		SetFloat( 312, anim->crossfade_interrupted_time );
		SetEnabled( 313, false );	// yes, this should be false. looping is only settable on import
		SetFlag( 313, anim->flags & _skelanim_loopable_flag );
//		SetEnabled( 314, true );
//		SetFlag( 314, anim->flags & _skelanim_distance_driven_flag );
		SetEnabled( 315, true );
		SetFlag( 315, anim->flags & _skelanim_fast_interrupt_flag );
		SetEnabled( 316, true );
		
		SetEnabled( 320, true );
		SetFlag( 320, dist <= 0.0f || !(anim->flags & (_skelanim_distance_driven_flag|_skelanim_turn_driven_flag)) );
		SetEnabled( 321, dist > 0.0f );
		SetFlag( 321, dist > 0.0f && (anim->flags & _skelanim_distance_driven_flag) );
		SetEnabled( 322, dist > 0.0f );
		SetFlag( 322, dist > 0.0f && (anim->flags & _skelanim_turn_driven_flag) );
	} else {
		SetEnabled( 303, false );
		SetText( 303, "" );
		SetText( 305, "--" );
		SetText( 306, "--" );
		SetText( 307, "--" );
		
		SetEnabled( 308, false );
		SetText( 308, "" );
		SetText( 309, "--" );
		SetEnabled( 310, false );
		SetText( 310, "" );
		SetEnabled( 311, false );
		SetText( 311, "" );
		SetEnabled( 312, false );
		SetText( 312, "" );
		
		SetEnabled( 313, false );
		SetFlag( 313, false );
		SetEnabled( 315, false );
		SetFlag( 315, false );
		SetEnabled( 316, false );
		
		SetEnabled( 320, false );
		SetFlag( 320, false );
		SetEnabled( 321, false );
		SetFlag( 321, false );
		SetEnabled( 322, false );
		SetFlag( 322, false );
	}
	SetEnabled( 202, anim && !state );
	SetEnabled( 102, state );
}


bool ExtractSKANNames( tag_entry *e, XGStringList *out )
{
	out->DeleteAll();
	if (!e) {
		out->Insert( 0, "(skan missing)" );
		return false;
	}
	const void *tagdata;
	unsigned taglen;
	if (!get_entry_data( e, &tagdata, &taglen )) {
		out->Insert( 0, "(skan damaged)" );
		return false;
	}
	
	void *temp = malloc( taglen );
	memcpy( temp, tagdata, taglen );
	skan_tag_header *skan = (skan_tag_header *)temp;
	import_skan( skan );
	for (int i = 0; i < skan->state_count; ++i) {
		out->Insert( i, skan->states[i]->name );
	}
	
	return true;
}


const char *TskanEditForm::FrameActionText( int frame, AttachEdit *ae )
{
	static char buf[256];
	if (frame < 0 || frame >= ae->frame_count) {
		sprintf( buf, "bad frame %d", frame );
		return buf;
	}
	
	FrameAttachEdit *f = ae->frames + frame;
	sprintf( buf, "%d. ", frame );
	bool need_bone = false;
	tag type = -1;
	switch (f->kind) {
		case _skan_frame_action_kind_none:
			break;
		case _skan_frame_action_kind_sound:
			sprintf( buf+strlen(buf), "sound " );
			type = 'soun';
			break;
		case _skan_frame_action_kind_skmd:
			sprintf( buf+strlen(buf), "attach model " );
			type = 'skmd';
			need_bone = true;
			break;
		case _skan_frame_action_kind_loli:
			sprintf( buf+strlen(buf), "attach light " );
			type = 'loli';
			need_bone = true;
			break;
		case _skan_frame_action_kind_lpgr:
			sprintf( buf+strlen(buf), "attach particles " );
			type = 'lpgr';
			need_bone = true;
			break;
		case _skan_frame_action_kind_delete:
			sprintf( buf+strlen(buf), "delete attachment " );
			need_bone = true;
			break;
		case _skan_frame_action_kind_project:
			sprintf( buf+strlen(buf), "emit as " );
			type = 'proj';
			need_bone = true;
			break;
		case _skan_frame_action_kind_footprint:
			strcat( buf, "normal footprint " );
			need_bone = true;
			break;
		case _skan_frame_action_kind_giant_footprint:
			strcat( buf, "giant footprint " );
			need_bone = true;
			break;
		default:
			sprintf( buf+strlen(buf), "invalid %d ", f->kind );
			type = '????';
			need_bone = true;
			break;
	}
	if (f->keyframe)
	{
		strcat( buf, "ACTION KEYFRAME " );
		need_bone = true;
	}
	
	if (type != (tag)-1) {
		tag_entry *e = get_entry( type, f->id );
		if (e) {
			strcat( buf, get_entry_name(e) );
		} else if (f->id == (tag)-1) {
			strcat( buf, "(unassigned tag)" );
		} else {
			sprintf( buf+strlen(buf), "(missing %s/%s)", tagstr(f->id), tagstr(type) );
		}
	}
	
	if (need_bone) {
		if (f->bone >= 0 && f->bone < ae->bones->Length()) {
			sprintf( buf+strlen(buf), " at %s", (*ae->bones)[f->bone].name );
		} else {
			sprintf( buf+strlen(buf), " at invalid %d", f->bone );
		}
	}
	
	return buf;
}

void TskanEditForm::SetActionControls( XGDialog *dlog, FrameAttachEdit *f )
{
	for (int i = 101; i <= 109; ++i) {
		dlog->SetValue( i, (i - 101 == f->kind) );
	}
	
	bool need_bone = false;
	tag type = -1;
	switch (f->kind) {
		case _skan_frame_action_kind_none:
			break;
		case _skan_frame_action_kind_sound:
			type = 'soun';
			break;
		case _skan_frame_action_kind_skmd:
			type = 'skmd';
			need_bone = true;
			break;
		case _skan_frame_action_kind_loli:
			type = 'loli';
			need_bone = true;
			break;
		case _skan_frame_action_kind_lpgr:
			type = 'lpgr';
			need_bone = true;
			break;
		case _skan_frame_action_kind_delete:
			need_bone = true;
			break;
		case _skan_frame_action_kind_project:
			type = 'proj';
			need_bone = true;
			break;
		case _skan_frame_action_kind_footprint:
		case _skan_frame_action_kind_giant_footprint:
			need_bone = true;
			break;
		default:
			type = '????';
			need_bone = true;
			break;
	}
	
	dlog->SetValue( 110, f->keyframe );
	if (f->keyframe)
	{
		need_bone = true;
	}
	
	if (type == (tag)-1) {
		dlog->FindViewByID(120)->HideView();
	} else {
		dlog->FindViewByID(120)->ShowView();
		char buf[256];
		tag_entry *e = get_entry( type, f->id );
		if (e) {
			sprintf( buf, "%s '%s'", get_entry_name( e ), tagstr( f->id ) );
		} else if (f->id == (tag)-1) {
			strcpy( buf, "(tag unassigned)" );
		} else {
			sprintf( buf, "(missing %s/%s)", tagstr( f->id ), tagstr( type ) );
		}
		dlog->SetText( 120, buf );
		dlog->FindViewByID( 120 )->SetRefNum( type );
	}
	
	if (!need_bone) {
		dlog->FindViewByID( 130 )->HideView();
	} else {
		dlog->FindViewByID( 130 )->ShowView();
		dlog->SetValue( 130, f->bone );
	}
}


long TskanEditForm::AttachProc( XGDialog *dlog, void *data, long msg, long arg, void * /*parg*/ )
{
	XGListControl *list = dynamic_cast< XGListControl * >(dlog->FindViewByID(1));
	int val = list->GetValue();
	AttachEdit *ae = (AttachEdit *)data;
	int i;
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog: {
						XGPopControl *pop = dynamic_cast<XGPopControl *>(dlog->FindViewByID(130));
						pop->DeleteAll();
						for (i = 0; i < ae->bones->Length(); ++i) {
							pop->Insert( i, (*(ae->bones))[i].name );
						}
						list->DeleteAll();
						for (i = 0; i < ae->frame_count; ++i) {
							list->Insert( i, FrameActionText( i, ae ) );
						}
						list->SetValue( 0 );
						SetActionControls( dlog, ae->frames + 0 );
						return 0;
					} break;
				default:
					break;
			} break;
		case KXGListValue:
			switch (arg) {
				case 1:	// frame list
					if (val >= 0 && val < ae->frame_count) {
						SetActionControls( dlog, ae->frames + val );
					}
					return 0;
				default:
					break;
			} break;
		case KXGPopValue:
			switch (arg) {
				case 130: // bone list
					if (val >= 0 && val < ae->frame_count) {
						ae->frames[val].bone = dlog->GetValue(arg);
						list->SetItem( val, FrameActionText( val, ae ) );
					}
					return 0;
			} break;
		case KXGControlClick:
			if (arg >= 101 && arg <= 109) { // radio
				if (val >= 0 && val < ae->frame_count) {
					ae->frames[val].kind = arg - 101;
					list->SetItem( val, FrameActionText( val, ae ) );
					SetActionControls( dlog, ae->frames + val );
				}
				return 0;
			} else if (arg == 110) { // keyframe
				if (val >= 0 && val < ae->frame_count) {
					ae->frames[val].keyframe = !dlog->GetValue( arg );
					list->SetItem( val, FrameActionText( val, ae ) );
					SetActionControls( dlog, ae->frames + val );
				}
				return 0;
			} else if (arg == 120) { // tag type
				if (val >= 0 && val < ae->frame_count) {
					tag new_id = pick_tag_of_type( dlog->FindViewByID(arg)->GetRefNum(), ae->frames[val].id, ae->source );
					if (new_id) {
						ae->frames[val].id = new_id;
					}
					list->SetItem( val, FrameActionText( val, ae ) );
					SetActionControls( dlog, ae->frames + val );
				}
				return 0;
			} else if (arg == 2) {	// cancel
				dlog->EndDialog( 0 );
				return 0;
			} else if (arg == 3) { // ok
				dlog->EndDialog( 1 );
				return 0;
			}
		default:
			break;
	}
	
	return -1;
}


void TskanEditForm::EditAttachments( skan_animation *anim )
{
	AttachEdit ae;
	ae.source = get_entry_file( fEntry );
	ae.bones = &this->basebones;
	ae.frame_count = anim->frame_count;
	ae.frames = new FrameAttachEdit[ae.frame_count];
	int frame_size = (sizeof(skan_frame) + basebones.Length() * sizeof(skan_bone_frame_pos) + 3) & ~3;
	skan_frame *frame;
	
	frame = (skan_frame *)(anim + 1);
	for (int i = 0; i < ae.frame_count; ++i) {
		if (frame->frame_action == (word)-1) {
			ae.frames[i].kind = 0;
			ae.frames[i].bone = 0;
			ae.frames[i].id = -1;
		} else {
			ae.frames[i].kind = (frame->frame_action & _skan_frame_action_kind_mask) >> _skan_frame_action_first_kind_bit;
			ae.frames[i].bone = (frame->frame_action & _skan_frame_action_bone_mask) >> _skan_frame_action_first_bone_bit;
			ae.frames[i].keyframe = (frame->frame_action & _skan_frame_action_keyframe_flag) != 0;
			ae.frames[i].id = frame->action_tag_id;
		}
		frame = (skan_frame *)((byte *)frame + frame_size);
	}
	
	bool ok = XGDialog::ModalDialog( KWindowAnimationActions, AttachProc, &ae );
	if (ok) {
		frame = (skan_frame *)(anim + 1);
		for (int i = 0; i < ae.frame_count; ++i) {
			frame->frame_action = ((ae.frames[i].kind << _skan_frame_action_first_kind_bit) & _skan_frame_action_kind_mask) |
								((ae.frames[i].bone << _skan_frame_action_first_bone_bit) & _skan_frame_action_bone_mask);
			if (ae.frames[i].keyframe)
			{
				frame->frame_action |= _skan_frame_action_keyframe_flag;
			}
			frame->action_tag_id = ae.frames[i].id;
			frame = (skan_frame *)((byte *)frame + frame_size);
		}
		fModified = true;
	}
	
	delete [] ae.frames;
}


