using UnityEngine;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using System;
using System.Collections;
using System.Globalization;

public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
{
	// broadcast address
	public bool gameStartWithConnect = true;
	public string iOS_IPAddress = "255.255.255.255";
	private UdpClient client;
	private bool StartFlag = true;

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

	private UdpClient udp;
	private Thread thread;
	private SkinnedMeshRenderer meshTarget;
	private List<SkinnedMeshRenderer> meshTargetList;
	private List<GameObject> headObjectArray;
	private List<GameObject> rightEyeObjectArray;
	private List<GameObject> leftEyeObjectArray;
	private List<GameObject> headPositionObjectArray;

	private string messageString = "";
	public int LOCAL_PORT = 49983;

	// Start is called
	void StartFunction()
	{
		if (StartFlag == true)
		{
			StartFlag = false;

			FindGameObjectsInsideUnitySettings();

			//Send to iOS
			if (gameStartWithConnect == true)
			{
				Connect_to_iOS_App();
			}

			//Recieve udp from iOS
			CreateUdpServer();
		}
	}

	void Start()
	{
		StartFunction();
	}

	void CreateUdpServer()
	{
		udp = new UdpClient(LOCAL_PORT);
		udp.Client.ReceiveTimeout = 5;

		thread = new Thread(new ThreadStart(ThreadMethod));
		thread.Start();
	}

	IEnumerator WaitProcess(float WaitTime)
	{
		yield return new WaitForSeconds(WaitTime);
	}

	void Connect_to_iOS_App()
	{
		//iFacialMocap
		SendMessage_to_iOSapp("iFacialMocap_sahuasouryya9218sauhuiayeta91555dy3719", 49983);

		//Facemotion3d
		SendMessage_to_iOSapp("FACEMOTION3D_OtherStreaming", 49993);
	}

	void StopStreaming_iOS_App()
	{
		SendMessage_to_iOSapp("StopStreaming_FACEMOTION3D", 49993);
	}

	//iOSアプリに通信開始のメッセージを送信
	//Send a message to the iOS application to start streaming
	void SendMessage_to_iOSapp(string sendMessage,int send_port)
	{
		try
		{
			client = new UdpClient();
			client.Connect(iOS_IPAddress, send_port);
			byte[] dgram = Encoding.UTF8.GetBytes(sendMessage);
			client.Send(dgram, dgram.Length);
			client.Send(dgram, dgram.Length);
			client.Send(dgram, dgram.Length);
			client.Send(dgram, dgram.Length);
			client.Send(dgram, dgram.Length);
		}
		catch { }
	}

	// Update is called once per frame
	void Update()
	{
		try
		{
			SetAnimation_inside_Unity_settings();
		}
		catch
		{ }
	}

	//BlendShapeの設定
	//set blendshapes
	void SetBlendShapeWeightFromStrArray(string[] strArray2)
	{
		string mappedShapeName = strArray2[0].Replace("_R", "Right").Replace("_L", "Left");
		
		float weight = float.Parse(strArray2[1], CultureInfo.InvariantCulture);


		foreach (SkinnedMeshRenderer meshTarget in meshTargetList)
		{
			var shared_mesh = meshTarget.sharedMesh;
			int index = shared_mesh.GetBlendShapeIndex(mappedShapeName);
			if (index > -1)
			{
				meshTarget.SetBlendShapeWeight(index, weight);
			}
		}
	}

	
	//BlendShapeとボーンの回転の設定
	//set blendshapes & bone rotation
	void SetAnimation_inside_Unity_settings()
	{
		try
		{
			string[] strArray1 = messageString.Split('=');

			if (strArray1.Length >= 2)
			{
				//blendShapes
				foreach (string message in strArray1[0].Split('|'))
				{
					string[] strArray2 = new string[3];
					if (message.Contains("&"))
					{
						strArray2 = message.Split('&');
					}
					else
					{
						strArray2 = message.Split('-');
					}

					if (strArray2.Length == 2)
					{
						SetBlendShapeWeightFromStrArray(strArray2);
					}
				}

				foreach (string message in strArray1[1].Split('|'))
				{
					string[] strArray2 = message.Split('#');

					if (strArray2.Length == 2)
					{
						string[] commaList = strArray2[1].Split(',');
						if (strArray2[0] == "head")
						{
							foreach (GameObject headObject in headObjectArray)
							{
								headObject.transform.localRotation = Quaternion.Euler(float.Parse(commaList[0], CultureInfo.InvariantCulture), -float.Parse(commaList[1], CultureInfo.InvariantCulture), -float.Parse(commaList[2], CultureInfo.InvariantCulture));
							}
							
							foreach (GameObject headPositionObject in headPositionObjectArray)
							{
								headPositionObject.transform.localPosition = new Vector3(-float.Parse(commaList[3], CultureInfo.InvariantCulture), float.Parse(commaList[4], CultureInfo.InvariantCulture), float.Parse(commaList[5], CultureInfo.InvariantCulture));
							}
						}
						else if (strArray2[0] == "rightEye")
						{
							foreach (GameObject rightEyeObject in rightEyeObjectArray)
							{
								rightEyeObject.transform.localRotation = Quaternion.Euler(float.Parse(commaList[0], CultureInfo.InvariantCulture), -float.Parse(commaList[1], CultureInfo.InvariantCulture), float.Parse(commaList[2], CultureInfo.InvariantCulture));
							}
						}
						else if (strArray2[0] == "leftEye")
						{
							foreach (GameObject leftEyeObject in leftEyeObjectArray)
							{
								leftEyeObject.transform.localRotation = Quaternion.Euler(float.Parse(commaList[0], CultureInfo.InvariantCulture), -float.Parse(commaList[1], CultureInfo.InvariantCulture), float.Parse(commaList[2], CultureInfo.InvariantCulture));
							}
						}
					}
				}
			}
		}
		catch
		{
		}
	}

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

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

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

		//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 ThreadMethod()
	{
		//Process once every 5ms
		long next = DateTime.Now.Ticks + 50000;
		long now;

		while (true)
		{
			try
			{
				IPEndPoint remoteEP = null;
				byte[] data = udp.Receive(ref remoteEP);
				messageString = Encoding.ASCII.GetString(data);
			}
			catch
			{
			}

			do
			{
				now = DateTime.Now.Ticks;
			}
			while (now < next);
			next += 50000;
		}
	}


	public string GetMessageString()
	{
		return messageString;
	}

	void OnEnable()
	{
		StartFunction();
	}

	void OnDisable()
	{
		try
		{
			OnApplicationQuit();
		}
		catch
		{
		}
	}

	void OnApplicationQuit()
	{
		if (StartFlag == false)
		{
			StartFlag = true;
			StopUDP();
		}
	}


	public void StopUDP()
	{
		if (gameStartWithConnect == true)
		{
			StopStreaming_iOS_App();
		}
		udp.Dispose();
		thread.Abort();
	}

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

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

		return true;
	}

}
public static class FM3D_and_iFacialMocap_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);
		}
	}
}