//#pragma once
#ifndef AnimationrigSpine_H
#define AnimationrigSpine_H

#include <CommonMemoryStream.h>	// unicode
#include <AnimationrigMisc.h>
#include <Animationrig.h>
#include <AnimationrigState.h>

#include <spine/Animation.h>
#include <spine/AnimationState.h>
#include <spine/AnimationStateData.h>
#include <spine/Skeleton.h>
#include <spine/SkeletonData.h>

#include <math.h>

using namespace std;

namespace Animrig {
	class SpineRenderer : public StateRenderer {
	public :
		// property
		const int SaveVersion = 1;
		spine::AnimationState *state;
		spine::Skeleton *skeleton;
		// public
		SpineRenderer()
		{
			fObjectType = ObjectType::Spine;

			state = nullptr;
			skeleton = nullptr;
		}
		// common 
		void update(double deltatime) override
		{
			if (state == nullptr) return;
			if (skeleton == nullptr) return;

			state->update((float)deltatime);
			state->apply(*skeleton);
			skeleton->updateWorldTransform();
		}
		// spine
		void setAnimation(int trackindex, int usetrack, const wstring &basename, bool loop, bool addanim, MixBlend trackmix, float duration, bool seekmode) override
		{
			struct LocalFunction {
				static void inSetEmpty(spine::AnimationState *state, int index, float duration, bool addanim)
				{
					float d = state->getData()->getDefaultMix();
					if (duration >= 0.0) d = duration;

					if (addanim)
						state->addEmptyAnimation(index, d, 0.0f);
					else
						state->setEmptyAnimation(index, d);
				}
				static void inSetAnimation(spine::AnimationState *state, int index, const wstring &name1, const wstring &name2, bool loop, bool addanim, MixBlend trackmix, float duration, bool seekmode)
				{
					// search
					spine::Animation *anim;
					{
						spine::String ss1(UnicodeConverter::utf16to8(name1).c_str());
						anim = state->getData()->getSkeletonData()->findAnimation(ss1);
						if (anim == nullptr) {
							spine::String ss2(UnicodeConverter::utf16to8(name2).c_str());
							anim = state->getData()->getSkeletonData()->findAnimation(ss2);
						}
					}
					if (anim == nullptr)
					{
						LocalFunction::inSetEmpty(state, index, duration, addanim);
					}
					else
					{
						// set animation
						spine::TrackEntry *track;
						if (addanim)
							track = state->addAnimation(index, anim, loop, 0.0f);
						else
							track = state->setAnimation(index, anim, loop);
						if (track != nullptr)
						{
							// mix blend
							if ((track->getMixBlend() == spine::MixBlend_Replace) || (track->getMixBlend() == spine::MixBlend_Add))     // safe code
							{
								switch (trackmix)
								{
								case MixBlend::Add:
									track->setMixBlend(spine::MixBlend_Add);
									break;
								case MixBlend::Replace:
									track->setMixBlend(spine::MixBlend_Replace);
									break;
								}
							}
							// duration
							if (duration < 0.0f)
							{
								// use default duration
							}
							else
							{
								track->setMixDuration(max(duration, 0.0f));
							}
							// seek mode
							if (seekmode)
								track->setTimeScale(0.0f);
							else
								track->setTimeScale(1.0f);
						}
					}
				}
			};

			if (state == nullptr) return;

			// layertype AnimBlend1
			if (usetrack == 1)
			{
				if (basename == L"empty")
				{
					LocalFunction::inSetEmpty(state, trackindex + 0, duration, addanim);
				}
				else
				{
					LocalFunction::inSetAnimation(state, trackindex + 0, basename, basename + L"@up", loop, addanim, trackmix, duration, seekmode);
				}
				return;
			}

			// layertype AnimBlend4
			if (usetrack == 4)
			{
				if (basename == L"empty")
				{
					LocalFunction::inSetEmpty(state,trackindex + 0, duration, addanim);
					LocalFunction::inSetEmpty(state,trackindex + 1, duration, addanim);
					LocalFunction::inSetEmpty(state,trackindex + 2, duration, addanim);
					LocalFunction::inSetEmpty(state,trackindex + 3, duration, addanim);
				}
				else
				{
					wstring s;
					LocalFunction::inSetAnimation(state, trackindex + 0, basename, basename + L"@up", loop, addanim, trackmix, duration, seekmode);
					LocalFunction::inSetAnimation(state, trackindex + 0, basename, basename + L"@right", loop, addanim, trackmix, duration, seekmode);
					LocalFunction::inSetAnimation(state, trackindex + 0, basename, basename + L"@down", loop, addanim, trackmix, duration, seekmode);
					LocalFunction::inSetAnimation(state, trackindex + 0, basename, basename + L"@left", loop, addanim, trackmix, duration, seekmode);
				}
			}
		}
		void setTrackBlendAlpha(int trackindex, int usetrack, float alpha1, float alpha2, float alpha3, float alpha4, bool exists) override
		{
			struct LocalFunction {
				static void inSet(spine::AnimationState *state, int index, float alpha) {
					auto track = state->getCurrent(index);
					if (track == nullptr) return;

					track->setAlpha(alpha);
				}
			};
			
			if (state == nullptr) return;

			if (usetrack == 1)
			{
				LocalFunction::inSet(state, trackindex + 0, alpha1);
			}
			else
			{
				LocalFunction::inSet(state, trackindex + 0, alpha1);
				LocalFunction::inSet(state, trackindex + 1, alpha2);
				LocalFunction::inSet(state, trackindex + 2, alpha3);
				LocalFunction::inSet(state, trackindex + 3, alpha4);
			}
		}
		void setTrackSeekTime(int trackindex, int usetrack, float time1, float time2, float time3, float time4, bool exists) override
		{
			struct LocalFunction {
				static void inSet(spine::AnimationState *state, int index, float time) {
					auto track = state->getCurrent(index);
					if (track == nullptr) return;

					track->setTrackTime(time);
				}
			};

			if (state == nullptr) return;

			if (usetrack == 1)
			{
				LocalFunction::inSet(state, trackindex + 0, time1);
			}
			else
			{
				LocalFunction::inSet(state, trackindex + 0, time1);
				LocalFunction::inSet(state, trackindex + 1, time2);
				LocalFunction::inSet(state, trackindex + 2, time3);
				LocalFunction::inSet(state, trackindex + 3, time4);
			}
		}
		void setMixDuration(float duration) override
		{
			if (state == nullptr) return;

			state->getData()->setDefaultMix(duration);
		}
	};
}

#endif

