// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.Collections.Generic;
using UnityEngine;

namespace HoloToolkit.Unity.Collections
{
    /// <summary>
    /// A utility that stores transform information for objects in a collection
    /// This info can then be used for non-destructive real-time manipulation
    /// Or to restore an earlier configuration gathered on startup
    /// </summary>
    public class ObjectCollectionDynamic : MonoBehaviour
    {
        public enum BehaviorEnum
        {
            StoreEveryTime,
            StoreOnceOnStartup,
            StoreManually,
        }

        /// <summary>
        /// Extends collection node class to include stored local position / rotation data
        /// </summary>
        [Serializable]
        public class CollectionNodeDynamic : CollectionNode
        {
            public CollectionNodeDynamic(CollectionNode node)
            {
                transform = node.transform;
                Name = node.Name;
                Offset = node.Offset;
                Radius = node.Radius;

                localPositionOnStartup = transform.localPosition;
                localEulerAnglesOnStartup = transform.localEulerAngles;
            }

            public CollectionNodeDynamic (Transform nodeTransform)
            {
                transform = nodeTransform;
                Name = nodeTransform.name;
                Offset = Vector2.zero;
                Radius = 0f;

                localPositionOnStartup = transform.localPosition;
                localEulerAnglesOnStartup = transform.localEulerAngles;
            }

            public Vector3 localPositionOnStartup;
            public Vector3 localEulerAnglesOnStartup;
        }

        /// <summary>
        /// When to gather the dynamic node information
        /// </summary>
        public BehaviorEnum Behavior = BehaviorEnum.StoreEveryTime;

        /// <summary>
        /// List of dynamic nodes in the collection
        /// </summary>
        public List<CollectionNodeDynamic> DynamicNodeList;

        [SerializeField]
        private ObjectCollection collection;

        /// <summary>
        /// Sets each node in the collection to its last stored arrangement
        /// Automatically prunes destroyed or removed nodes
        /// Does not handle nodes added since last stored arrangement
        /// </summary>
        public virtual void RestoreArrangement()
        {
            if (DynamicNodeList == null)
                return;

            for (int i = DynamicNodeList.Count - 1; i >= 0; i--)
            {
                if (DynamicNodeList[i].transform == null || DynamicNodeList[i].transform.parent != collection.transform)
                {
                    DynamicNodeList.RemoveAt(i);
                } else
                {
                    DynamicNodeList[i].transform.localPosition = DynamicNodeList[i].localPositionOnStartup;
                    DynamicNodeList[i].transform.localEulerAngles = DynamicNodeList[i].localEulerAnglesOnStartup;
                }
            }
        }

        /// <summary>
        /// Manually store collection arrangement
        /// </summary>
        public void StoreArrangement ()
        {
            if (collection == null)
                return;

            DynamicNodeList = new List<CollectionNodeDynamic>();
            if (collection.NodeList == null || collection.NodeList.Count != transform.childCount)
            {
                foreach (Transform child in transform)
                {
                    DynamicNodeList.Add(new CollectionNodeDynamic(child));
                }
            }
            else
            {
                foreach (CollectionNode node in collection.NodeList)
                {
                    DynamicNodeList.Add(new CollectionNodeDynamic(node));
                }
            }
        }

        void Awake()
        {
            if (collection == null)
            {
                collection = gameObject.GetComponent<ObjectCollection>();
            }
        }

        void Start()
        {
            switch (Behavior)
            {
                case BehaviorEnum.StoreManually:
                default:
                    // Don't do anything
                    break;

                case BehaviorEnum.StoreEveryTime:
                    // Subscribe to the collection's update events
                    collection.OnCollectionUpdated += OnCollectionUpdated;
                    break;

                case BehaviorEnum.StoreOnceOnStartup:
                    // If we're only supposed to update once
                    // Gather our list of nodes now
                    StoreArrangement();
                    break;
            }
        }

        void OnCollectionUpdated (ObjectCollection collection)
        {
            StoreArrangement();
        }
    }
}
