/* Copyright (C) Itseez3D, Inc. - All Rights Reserved
* You may not use this file except in compliance with an authorized license
* Unauthorized copying of this file, via any medium is strictly prohibited
* Proprietary and confidential
* UNLESS REQUIRED BY APPLICABLE LAW OR AGREED BY ITSEEZ3D, INC. IN WRITING, SOFTWARE DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED
* See the License for the specific language governing permissions and limitations under the License.
* Written by Itseez3D, Inc. <support@avatarsdk.com>, August 2026
*/

using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;

namespace AvatarSDKMove
{
	public class AnimationGenerator : MonoBehaviour
	{
		private const string AnimationFormat = "glb";

		public HumanoidRetargeter humanoidRetargeter;

		public UnityEvent<string> onProgress;
		public UnityEvent onCompleted;
		public UnityEvent<string> onFailed;

		private readonly Connection connection = new Connection();
		private Animation sourceAnimation;
		private AnimationClip currentClip;
		private bool isBusy = false;

		public bool IsBusy { get { return isBusy; } }

		public async Task Generate(GameObject targetModel, string prompt, float duration)
		{
			if (isBusy)
				return;

			isBusy = true;
			try
			{
				GeneratedAnimation animation = await connection.GenerateAnimation(prompt, duration, AnimationFormat, ReportProgress);

				ReportProgress("Loading animation...");
				AnimationClip clip = GlbAnimationParser.Parse(ExtractGlb(animation.data));

				humanoidRetargeter.targetModel = targetModel;
				humanoidRetargeter.Initialize();

				PlayOnSourceModel(clip);

				ReportProgress(string.Empty);
				onCompleted.Invoke();
			}
			catch (Exception ex)
			{
				Debug.LogException(ex);
				onFailed.Invoke(ex.Message);
			}
			finally
			{
				isBusy = false;
			}
		}

		private static byte[] ExtractGlb(byte[] data)
		{
			if (data == null || data.Length < 2 || data[0] != 'P' || data[1] != 'K')
				return data;

			using (var stream = new MemoryStream(data))
			using (var archive = new ZipArchive(stream, ZipArchiveMode.Read))
			{
				ZipArchiveEntry entry = archive.Entries.FirstOrDefault(e => e.FullName.EndsWith(".glb", StringComparison.OrdinalIgnoreCase));
				if (entry == null)
					throw new Exception("The generated animation archive doesn't contain a glb file.");

				using (var entryStream = entry.Open())
				using (var extracted = new MemoryStream())
				{
					entryStream.CopyTo(extracted);
					return extracted.ToArray();
				}
			}
		}

		private void PlayOnSourceModel(AnimationClip clip)
		{
			GameObject sourceModel = humanoidRetargeter.sourceModel;

			if (sourceAnimation == null)
			{
				sourceAnimation = sourceModel.GetComponent<Animation>();
				if (sourceAnimation == null)
					sourceAnimation = sourceModel.AddComponent<Animation>();
			}

			sourceAnimation.Stop();
			if (currentClip != null)
				sourceAnimation.RemoveClip(currentClip);

			currentClip = clip;
			sourceAnimation.AddClip(clip, clip.name);
			sourceAnimation.clip = clip;
			sourceAnimation.Play();
		}

		private void ReportProgress(string message)
		{
			onProgress.Invoke(message);
		}
	}
}
