/*
copyright : Yasushi Emoto
Created by 2024-10-22
version1.03
*/
using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Collections;
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;

public class FacialReaderFromFileWindow : EditorWindow {

	private float oneFrameTime = 1.0f / 60.0f;
    private float[] recordTimetArray = new float[0];
    public float startTime = 0.0f;

    private string facialFileString = "";

	//object names
	public string faceObjectGroupName = "";
	public string headBoneName = "";
	public string rightEyeBoneName = "";
	public string leftEyeBoneName = "";
	public string headPositionObjectName = "";

    private SkinnedMeshRenderer meshTarget;
	private List<SkinnedMeshRenderer> meshTargetList = new List<SkinnedMeshRenderer>();
	private List<GameObject> meshTargetObjectArray = new List<GameObject>();
	private List<GameObject> headObjectArray = new List<GameObject>();
	private List<GameObject> rightEyeObjectArray = new List<GameObject>();
	private List<GameObject> leftEyeObjectArray = new List<GameObject>();
	private List<GameObject> headPositionObjectArray = new List<GameObject>();

    private Dictionary<string,List<float>> BlendShapeNameAndValueList;
	private List<string> blendShapeNameList = new List<string>();

	private List<float> HeadRotationXList = new List<float>();
	private List<float> HeadRotationYList = new List<float>();
	private List<float> HeadRotationZList = new List<float>();
	private List<float> RightEyeRotationXList = new List<float>();
	private List<float> RightEyeRotationYList = new List<float>();
	private List<float> RightEyeRotationZList = new List<float>();
	private List<float> LeftEyeRotationXList = new List<float>();
	private List<float> LeftEyeRotationYList = new List<float>();
	private List<float> LeftEyeRotationZList = new List<float>();
	private List<float> HeadPositionXList = new List<float>();
	private List<float> HeadPositionYList = new List<float>();
	private List<float> HeadPositionZList = new List<float>();

    [MenuItem("Window/FacialReaderFromFile")]
    public static void ShowWindow ()
    {
        GetWindow<FacialReaderFromFileWindow>("FacialReaderFromFile");
    }

	private void FindGameObjectsInsideUnitySettings()
	{
		//Find BlendShape Objects
		meshTargetList = new List<SkinnedMeshRenderer>();
		meshTargetObjectArray = new List<GameObject>();

		BlendShapeNameAndValueList = new Dictionary<string,List<float>>();
		blendShapeNameList = new List<string>();
		HeadRotationXList = new List<float>();
		HeadRotationYList = new List<float>();
		HeadRotationZList = new List<float>();
		RightEyeRotationXList = new List<float>();
		RightEyeRotationYList = new List<float>();
		RightEyeRotationZList = new List<float>();
		LeftEyeRotationXList = new List<float>();
		LeftEyeRotationYList = new List<float>();
		LeftEyeRotationZList = new List<float>();
		HeadPositionXList = new List<float>();
		HeadPositionYList = new List<float>();
		HeadPositionZList = new List<float>();

		GameObject faceObjGrp = GameObject.Find(faceObjectGroupName);
		if (faceObjGrp != null)
		{
			List<GameObject> list = FacialReaderFromFileWindow_GetAllChildren.GetAll(faceObjGrp);

			foreach (GameObject obj in list)
			{
				meshTarget = obj.GetComponent<SkinnedMeshRenderer>();
				if (meshTarget != null)
				{
					if (HasBlendShapes(meshTarget) == true)
					{
						meshTargetList.Add(meshTarget);
						meshTargetObjectArray.Add(obj);
					}
				}
			}
		}

		//Find Bone Objects
		headObjectArray = new List<GameObject>();
		foreach (string headString in headBoneName.Split(','))
		{
			GameObject headObject = GameObject.Find(headString);
			if (headObject != null)
			{
				headObjectArray.Add(headObject);
			}
		}

		rightEyeObjectArray = new List<GameObject>();
		foreach (string rightEyeString in rightEyeBoneName.Split(','))
		{
			GameObject rightEyeObject = GameObject.Find(rightEyeString);
			if (rightEyeObject != null)
			{
				rightEyeObjectArray.Add(rightEyeObject);
			}
		}

		leftEyeObjectArray = new List<GameObject>();
		foreach (string leftEyeString in leftEyeBoneName.Split(','))
		{
			GameObject leftEyeObject = GameObject.Find(leftEyeString);
			if (leftEyeObject != null)
			{
				leftEyeObjectArray.Add(leftEyeObject);
			}
		}

		headPositionObjectArray = new List<GameObject>();
		foreach (string headPositionString in headPositionObjectName.Split(','))
		{
			GameObject headPositionObject = GameObject.Find(headPositionString);
			if (headPositionObject != null)
			{
				headPositionObjectArray.Add(headPositionObject);
			}
		}
	}

    void OnGUI ()
    {
        GUILayout.Label("Apply facial movement from file", EditorStyles.boldLabel);

        faceObjectGroupName = EditorGUILayout.TextField("Face Object Group Name: ", faceObjectGroupName);
        headBoneName = EditorGUILayout.TextField("Head Bone Name: ", headBoneName);
        rightEyeBoneName = EditorGUILayout.TextField("Right Eye Bone Name: ", rightEyeBoneName);
        leftEyeBoneName = EditorGUILayout.TextField("Left Eye Bone Name: ", leftEyeBoneName);
        headPositionObjectName = EditorGUILayout.TextField("Head Position Object Name: ", headPositionObjectName);
        startTime = EditorGUILayout.FloatField("Start Time(s):", startTime);

        if (GUILayout.Button("Read from file"))
        {
            FindGameObjectsInsideUnitySettings();
            ReadFromFile();
        }
    }

    void ReadFromFile ()
    {
        string path = EditorUtility.OpenFilePanel("Read from txt file", "", "txt");
        if (path.Length != 0)
        {
            StreamReader reader = new StreamReader(path);
            facialFileString = reader.ReadToEnd();
            reader.Close(); // StreamReaderを閉じることを忘れないでください

            string[] txtArray = new string[0];

            try
            {
                using (StreamReader streamReader = new StreamReader(path)) // 変数名を変更
                {
                    string line;
                    int lineNumber = 0;

                    while ((line = streamReader.ReadLine()) != null)
                    {
                        lineNumber++;

                        if (lineNumber == 3) // line3
                        {
                            txtArray = line.Split(new[] { "\",\"" }, StringSplitOptions.None);
                            break;
                        }
                        if (lineNumber == 4) // line4
                        {
                            recordTimetArray = ConvertStringToFloatArray(line);
                            break;
                        }
                    }
                }
            }
            catch (IOException e)
            {
                Debug.LogError("File reading error: " + e.Message);
            }



            foreach (var txt in txtArray)
            {
				try
				{
                    string txt_remove = txt.Replace("\"", "");
					string[] strArray1 = txt.Split('=');

					if (strArray1.Length == 2)
					{
						//blendShapes
						foreach (string message in strArray1[0].Split('|'))
						{
                            try
				            {
                                string[] strArray2 = message.Split('&');
                                if (strArray2.Length == 2)
                                {
                                    var bs_name = strArray2[0].Replace("_L","Left").Replace("_R","Right");
                                    //if key exists
                                    if ( BlendShapeNameAndValueList.ContainsKey(bs_name) )
                                    {
                                        List<float> blendShape_values = new List<float>();
                                        blendShape_values = BlendShapeNameAndValueList[bs_name];
                                        blendShape_values.Add(float.Parse(strArray2[1], CultureInfo.InvariantCulture));
                                        BlendShapeNameAndValueList[bs_name] = blendShape_values;
                                    }
                                    else
                                    {
                                        BlendShapeNameAndValueList.Add(bs_name,new List<float>(){float.Parse(strArray2[1], CultureInfo.InvariantCulture)});
                                        blendShapeNameList.Add(bs_name);
                                    }
                                }
                            }
                            catch(System.NullReferenceException e) 
                            {
                                Debug.Log(e);
                            }
						}

						foreach (string message in strArray1[1].Split('|'))
						{
                            try
				            {
                                string[] strArray2 = message.Split('#');
                                
                                if (strArray2.Length == 2)
                                {
                                    string[] commaList = strArray2[1].Split(',');
                                    if (commaList.Length > 1)
                                    {
                                        if (strArray2[0] == "head")
                                        {
                                            HeadRotationXList.Add(float.Parse(commaList[0], CultureInfo.InvariantCulture));
                                            HeadRotationYList.Add(-float.Parse(commaList[1], CultureInfo.InvariantCulture));
                                            HeadRotationZList.Add(-float.Parse(commaList[2], CultureInfo.InvariantCulture));

                                            HeadPositionXList.Add(-float.Parse(commaList[3], CultureInfo.InvariantCulture));
                                            HeadPositionYList.Add(float.Parse(commaList[4], CultureInfo.InvariantCulture));
                                            HeadPositionZList.Add(float.Parse(commaList[5], CultureInfo.InvariantCulture));
                                        }
                                        else if (strArray2[0] == "rightEye")
                                        {
                                            RightEyeRotationXList.Add(float.Parse(commaList[0], CultureInfo.InvariantCulture));
                                            RightEyeRotationYList.Add(-float.Parse(commaList[1], CultureInfo.InvariantCulture));
                                            RightEyeRotationZList.Add(0.0f);
                                        }
                                        else if (strArray2[0] == "leftEye")
                                        {
                                            LeftEyeRotationXList.Add(float.Parse(commaList[0], CultureInfo.InvariantCulture));
                                            LeftEyeRotationYList.Add(-float.Parse(commaList[1], CultureInfo.InvariantCulture));
                                            LeftEyeRotationZList.Add(0.0f);
                                        }
                                    }
                                }
                            }
                            catch(System.NullReferenceException e) 
                            {
                                Debug.Log(e);
                            }
						}
					}
				}
                catch(System.NullReferenceException e) 
                {
                    Debug.Log(e);
                }
			}

            try
            {
                foreach (var meshTargetObj in meshTargetObjectArray)
                {
                    AnimationCurve curve;
                    Keyframe[] keys;
                    
                    var _animation = meshTargetObj.GetComponent<Animation>();

                    if (!_animation) _animation = meshTargetObj.AddComponent<Animation>();

                    var clip = _animation.clip;
                    var clip_exists_flag = true;
                    if (!clip)
                    {
                        clip_exists_flag = false;
                        clip = new AnimationClip();
                    }

                    meshTarget = meshTargetObj.GetComponent<SkinnedMeshRenderer>();
                    if (meshTarget != null)
                    {
                        if (HasBlendShapes(meshTarget) == true)
                        {
                            var shared_mesh = meshTarget.sharedMesh;

                            for (int b_count = 0; b_count < shared_mesh.blendShapeCount; b_count++)
                            {
                                string sharedMeshBlendShapeName = shared_mesh.GetBlendShapeName(b_count);
                                foreach (var mappedShapeName in BlendShapeNameAndValueList.Keys)
                                {
                                    if (sharedMeshBlendShapeName.Contains(mappedShapeName))
                                    {
                                        List<float> blendShape_values = new List<float>();
                                        blendShape_values = BlendShapeNameAndValueList[mappedShapeName];

                                        keys = new Keyframe[blendShape_values.Count];

                                        for ( int i  = 0 ; i < blendShape_values.Count ; i++ )
                                        {
                                            keys[i] = new Keyframe(startTime + oneFrameTime * i, blendShape_values[i]);
                                        }
                                        
                                        curve = new AnimationCurve(keys);

                                        clip.SetCurve("", typeof(SkinnedMeshRenderer), "blendShape."+sharedMeshBlendShapeName, curve);
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    if(clip_exists_flag == false)
                    {
                        clip.name = meshTargetObj.name+"_AnimClip"; // set name
                        clip.legacy = true; // change to legacy

                        _animation.clip = clip; // set default clip
                        _animation.AddClip(clip, clip.name); // add clip to animation component

                        AssetDatabase.CreateAsset(clip, "Assets/"+clip.name+".anim"); // to create asset
                    }
                }
            }
            catch(System.NullReferenceException e) 
            {
                Debug.Log(e);
            }

            try
            {
                foreach (var headObject in headObjectArray)
                {
                    var _animation = headObject.GetComponent<Animation>();

                    if (!_animation) _animation = headObject.AddComponent<Animation>();

                    var clip = _animation.clip;
                    var clip_exists_flag = true;
                    if (!clip)
                    {
                        clip_exists_flag = false;
                        clip = new AnimationClip();
                    }

                    AnimationCurve curve_x;
                    Keyframe[] keys;
                    keys = new Keyframe[HeadRotationXList.Count];

                    for ( int i  = 0 ; i < HeadRotationXList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, HeadRotationXList[i]);
                    }
                    curve_x = new AnimationCurve(keys);

                    AnimationCurve curve_y;
                    keys = new Keyframe[HeadRotationYList.Count];
                    for ( int i  = 0 ; i < HeadRotationYList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, HeadRotationYList[i]);
                    }
                    curve_y = new AnimationCurve(keys);

                    AnimationCurve curve_z;
                    keys = new Keyframe[HeadRotationZList.Count];
                    for ( int i  = 0 ; i < HeadRotationZList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime+ oneFrameTime * i, HeadRotationZList[i]);
                    }
                    curve_z = new AnimationCurve(keys);

                    clip.SetCurve("", typeof(Transform), "localEulerAngles.x", curve_x);
                    clip.SetCurve("", typeof(Transform), "localEulerAngles.y", curve_y);
                    clip.SetCurve("", typeof(Transform), "localEulerAngles.z", curve_z);

                    if(clip_exists_flag == false)
                    {
                        clip.name = headObject.name+"_AnimClip"; // set name
                        clip.legacy = true; // change to legacy

                        _animation.clip = clip; // set default clip
                        _animation.AddClip(clip, clip.name); // add clip to animation component

                        AssetDatabase.CreateAsset(clip, "Assets/"+clip.name+".anim"); // to create asset
                    }
                }

                foreach (var rightEyeObject in rightEyeObjectArray)
                {
                    var _animation = rightEyeObject.GetComponent<Animation>();

                    if (!_animation) _animation = rightEyeObject.AddComponent<Animation>();

                    var clip = _animation.clip;
                    var clip_exists_flag = true;
                    if (!clip)
                    {
                        clip_exists_flag = false;
                        clip = new AnimationClip();
                    }

                    AnimationCurve curve_x;
                    Keyframe[] keys;
                    keys = new Keyframe[RightEyeRotationXList.Count];

                    for ( int i  = 0 ; i < RightEyeRotationXList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, RightEyeRotationXList[i]);
                    }
                    curve_x = new AnimationCurve(keys);

                    AnimationCurve curve_y;
                    keys = new Keyframe[RightEyeRotationYList.Count];
                    for ( int i  = 0 ; i < RightEyeRotationYList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, RightEyeRotationYList[i]);
                    }
                    curve_y = new AnimationCurve(keys);

                    AnimationCurve curve_z;
                    keys = new Keyframe[RightEyeRotationZList.Count];
                    for ( int i  = 0 ; i < RightEyeRotationZList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, RightEyeRotationZList[i]);
                    }
                    curve_z = new AnimationCurve(keys);

                    clip.SetCurve("", typeof(Transform), "localEulerAngles.x", curve_x);
                    clip.SetCurve("", typeof(Transform), "localEulerAngles.y", curve_y);
                    clip.SetCurve("", typeof(Transform), "localEulerAngles.z", curve_z);

                    if(clip_exists_flag == false)
                    {
                        clip.name = rightEyeObject.name+"_AnimClip"; // set name
                        clip.legacy = true; // change to legacy

                        _animation.clip = clip; // set default clip
                        _animation.AddClip(clip, clip.name); // add clip to animation component

                        AssetDatabase.CreateAsset(clip, "Assets/"+clip.name+".anim"); // to create asset
                    }
                }

                foreach (var leftEyeObject in leftEyeObjectArray)
                {
                    var _animation = leftEyeObject.GetComponent<Animation>();

                    if (!_animation) _animation = leftEyeObject.AddComponent<Animation>();

                    var clip = _animation.clip;
                    var clip_exists_flag = true;
                    if (!clip)
                    {
                        clip_exists_flag = false;
                        clip = new AnimationClip();
                    }

                    AnimationCurve curve_x;
                    Keyframe[] keys;
                    keys = new Keyframe[LeftEyeRotationXList.Count];

                    for ( int i  = 0 ; i < LeftEyeRotationXList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, LeftEyeRotationXList[i]);
                    }
                    curve_x = new AnimationCurve(keys);

                    AnimationCurve curve_y;
                    keys = new Keyframe[LeftEyeRotationYList.Count];
                    for ( int i  = 0 ; i < LeftEyeRotationYList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, LeftEyeRotationYList[i]);
                    }
                    curve_y = new AnimationCurve(keys);

                    AnimationCurve curve_z;
                    keys = new Keyframe[LeftEyeRotationZList.Count];
                    for ( int i  = 0 ; i < LeftEyeRotationZList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, LeftEyeRotationZList[i]);
                    }
                    curve_z = new AnimationCurve(keys);

                    clip.SetCurve("", typeof(Transform), "localEulerAngles.x", curve_x);
                    clip.SetCurve("", typeof(Transform), "localEulerAngles.y", curve_y);
                    clip.SetCurve("", typeof(Transform), "localEulerAngles.z", curve_z);

                    if(clip_exists_flag == false)
                    {
                        clip.name = leftEyeObject.name+"_AnimClip"; // set name
                        clip.legacy = true; // change to legacy

                        _animation.clip = clip; // set default clip
                        _animation.AddClip(clip, clip.name); // add clip to animation component

                        AssetDatabase.CreateAsset(clip, "Assets/"+clip.name+".anim"); // to create asset
                    }
                }

                foreach (var headPositionObject in headPositionObjectArray)
                {
                    var _animation = headPositionObject.GetComponent<Animation>();

                    if (!_animation) _animation = headPositionObject.AddComponent<Animation>();

                    var clip = _animation.clip;
                    var clip_exists_flag = true;
                    if (!clip)
                    {
                        clip_exists_flag = false;
                        clip = new AnimationClip();
                    }

                    AnimationCurve curve_x;
                    Keyframe[] keys;
                    keys = new Keyframe[HeadPositionXList.Count];

                    for ( int i  = 0 ; i < HeadPositionXList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, HeadPositionXList[i]);
                    }
                    curve_x = new AnimationCurve(keys);

                    AnimationCurve curve_y;
                    keys = new Keyframe[HeadPositionYList.Count];
                    for ( int i  = 0 ; i < HeadPositionYList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, HeadPositionYList[i]);
                    }
                    curve_y = new AnimationCurve(keys);

                    AnimationCurve curve_z;
                    keys = new Keyframe[HeadPositionZList.Count];
                    for ( int i  = 0 ; i < HeadPositionZList.Count ; i++ )
                    {
                        keys[i] = new Keyframe(startTime + oneFrameTime * i, HeadPositionZList[i]);
                    }
                    curve_z = new AnimationCurve(keys);

                    clip.SetCurve("", typeof(Transform), "localPosition.x", curve_x);
                    clip.SetCurve("", typeof(Transform), "localPosition.y", curve_y);
                    clip.SetCurve("", typeof(Transform), "localPosition.z", curve_z);

                    if(clip_exists_flag == false)
                    {
                        clip.name = headPositionObject.name+"_AnimClip"; // set name
                        clip.legacy = true; // change to legacy

                        _animation.clip = clip; // set default clip
                        _animation.AddClip(clip, clip.name); // add clip to animation component

                        AssetDatabase.CreateAsset(clip, "Assets/"+clip.name+".anim"); // to create asset
                    }
                }

            }
            catch(System.NullReferenceException e) 
            {
                Debug.Log(e);
            }
        }
    }
    
    private static float[] ConvertStringToFloatArray(string jsonString)
    {
        string pattern = @"-?\d+(\.\d+)?(e[+-]?\d+)?";
        MatchCollection matches = Regex.Matches(jsonString, pattern);

        float[] floatArray = matches.OfType<Match>() 
                                    .Select(m => float.Parse(m.Value))
                                    .ToArray();

        return floatArray;
    }

	private bool HasBlendShapes(SkinnedMeshRenderer skin)
	{
		if (!skin.sharedMesh)
		{
			return false;
		}

		if (skin.sharedMesh.blendShapeCount <= 0)
		{
			return false;
		}

		return true;
	}
}

public static class FacialReaderFromFileWindow_GetAllChildren
{
	public static List<GameObject> GetAll(this GameObject obj)
	{
		List<GameObject> allChildren = new List<GameObject>();
		allChildren.Add(obj);
		GetChildren(obj, ref allChildren);
		return allChildren;
	}

	public static void GetChildren(GameObject obj, ref List<GameObject> allChildren)
	{
		Transform children = obj.GetComponentInChildren<Transform>();
		if (children.childCount == 0)
		{
			return;
		}
		foreach (Transform ob in children)
		{
			allChildren.Add(ob.gameObject);
			GetChildren(ob.gameObject, ref allChildren);
		}
	}
}