//Created by Neodrop
//neodrop@unity3d.ru
//Created by PAX
//pax62rus@yandex.ru
using System;
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

//#pragma warning disable

public class WizardAntaresMultiComponentsSetting : ScriptableWizard
{
    class Container : IDisposable
    {
        public GameObject obj = null;
        private List<Container> _childrens;
        private bool _selectChildren;
        private int _stepRight = 0;

        public Container(GameObject _obj, bool findChildrens, int stepRight)
        {
            obj = _obj;
            _stepRight = stepRight;
            _selectChildren = findChildrens;
            if (obj == null || !findChildrens) return;
            _childrens = new List<Container>();
            foreach (Transform t in obj.GetComponentsInChildren<Transform>())
            {
                if (t.gameObject != obj) _childrens.Add(new Container(t.gameObject, true, stepRight + 10));
            }
        }

        public void ProcessComponent(Component component, bool add)
        {
            if (component == null || obj == null) return;
            Component objComponent = obj.GetComponent(component.GetType());

            if (!add)
            {
                DestroyImmediate(obj.GetComponent(component.GetType()));
                if (_selectChildren) foreach (Container con in _childrens) con.ProcessComponent(component, add);
                return;
            }

            if (objComponent == null)
            {
                objComponent = obj.AddComponent(component.GetType());
            }

            SerializedObject so = new SerializedObject(objComponent);
            so.Update();

            foreach (KeyValuePair<FieldInfo, bool> field in Fields)
            {
                if (field.Value)
                {
                    try
                    {
                        field.Key.SetValue(objComponent, field.Key.GetValue(component));
                    }
                    catch (Exception ex)
                    {
                        Debug.Log("Faild to set value for Field " +
                            field.Key.Name + " of object " + obj.name + "\n" +
                            ex.Message + "\n" + ex.StackTrace);
                    }
                }
            }

            foreach (KeyValuePair<PropertyInfo, bool> property in Properties)
            {
                if (property.Value)
                {
                    try
                    {
                        property.Key.SetValue(objComponent, property.Key.GetValue(component, null), null);
                    }
                    catch (Exception ex)
                    {
                        Debug.Log("Faild to set value for Proprty " +
                            property.Key.Name + " of object " + obj.name + "\n" +
                            ex.Message + "\n" + ex.StackTrace);
                    }
                }
            }
            so.ApplyModifiedProperties();
            EditorUtility.SetDirty(obj);
            if (_selectChildren) foreach (Container con in _childrens) con.ProcessComponent(component, add);
        }

        public void Draw()
        {
            if (obj == null) return;
            GUILayout.BeginHorizontal();
            if (_stepRight != 0) GUILayout.Space(_stepRight);
            EditorGUILayout.ObjectField(obj, typeof(GameObject), GUILayout.Width(200 - _stepRight));
            if (GUILayout.Button("Remove", EditorStyles.miniButton, GUILayout.Width(64)))
            {
                Dispose();
                return;
            }
            GUILayout.EndHorizontal();
            if (!_selectChildren) return;
            foreach (Container con in _childrens) con.Draw();
        }

        #region IDisposable Members

        private bool disposed;
        public void Dispose()
        {
            if (disposed) return;
            if (_selectChildren)
            {
                foreach (Container con in _childrens) if (con != null) con.Dispose();
                _childrens.Clear();
            }
            obj = null;
            disposed = true;
        }

        #endregion
    }

    internal static Dictionary<PropertyInfo, bool> Properties = new Dictionary<PropertyInfo, bool>();
    internal static Dictionary<FieldInfo, bool> Fields = new Dictionary<FieldInfo, bool>();

    private static WizardAntaresMultiComponentsSetting wizard;
    private Component component;
    private Vector2 _scroll, _scrollParameters;
    private List<Container> _objects = new List<Container>();
    private bool _selectChildrens = false;

    [MenuItem("Antares/DUBLICATOR &d")]
    public static void CreateWizard()
    {
        wizard = ScriptableWizard.DisplayWizard("ANTARES : DUBLICATOR", typeof(WizardAntaresMultiComponentsSetting)) as WizardAntaresMultiComponentsSetting;
        wizard.minSize = new Vector2(512, 512);
        wizard.wantsMouseMove = true;
    }

    void OnGUI()
    {
        if (EditorApplication.isCompiling) return;
        try
        {
            AntaresGameObjectCustom.Routines.DrawBackground(new Rect(wizard.position.width - 512,
                                                                     wizard.position.height - 512, 512, 512), true);
            AntaresGameObjectCustom.Routines.ShowCredits(new Rect(wizard.position.width - 120,
                                                                  wizard.position.height - 60,
                                                                  100, 30));

            if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform)
            {
                DragAndDrop.visualMode = DragAndDropVisualMode.Link;  

                if (Event.current.type == EventType.DragPerform)
                    DragAndDrop.AcceptDrag();

                wizard.Focus();
                Event.current.Use();
            }

            if (Event.current.type == EventType.DragExited)
            {
                if (DragAndDrop.objectReferences[0] as Component != null)
                {
                    component = DragAndDrop.objectReferences[0] as Component;
                    ShowNotification(new GUIContent("Received Component : " + ObjectNames.GetInspectorTitle(component), AntaresGameObjectCustom.Routines.imageIconSmall));

                    FieldInfo[] finfo = component.GetType().GetFields(
                        BindingFlags.Public | BindingFlags.Instance);

                    PropertyInfo[] pinfo = component.GetType().GetProperties(
                        BindingFlags.Public | BindingFlags.Instance);

                    List<PropertyInfo> pinfoList = new List<PropertyInfo>();

                    foreach (PropertyInfo propertyInfo in pinfo)
                    {
                        if (propertyInfo.CanRead && propertyInfo.CanWrite)
                        {
                            pinfoList.Add(propertyInfo);
                        }
                    }

                    Fields.Clear();
                    Properties.Clear();

                    foreach (FieldInfo fieldInfo in finfo)
                    {
                        Fields.Add(fieldInfo, true);
                    }

                    foreach (PropertyInfo propertyInfo in pinfoList)
                    {
                        Properties.Add(propertyInfo, false);
                    }
                }
                else
                {
                    foreach (UnityEngine.Object o in DragAndDrop.objectReferences)
                    {
                        GameObject go = o as GameObject;
                        if (go != null) _objects.Add(new Container(go, _selectChildrens, 0));
                    }
                }
                return;
            }

            ProcessComponent();

            if (component == null)
            {
                ShowSteps(1, "Drag and Drop any component which you wish to add or recustomize on \n   desirable objects.");
                return;
            }

            bool toggle = 
            EditorGUILayout.InspectorTitlebar(true, component);

            if (!toggle) EditorGUIUtility.PingObject(component.gameObject);

            EditorGUILayout.Separator();
            ShowSteps(2, "Drag and Drop GameObjects, for component addition to them.");

            ShowParameters();

            GUILayout.BeginHorizontal();
            GUILayout.Space(10);
            _selectChildrens = GUILayout.Toggle(_selectChildrens, "Select childrens");
            GUILayout.EndHorizontal();

            GUILayout.BeginArea(new Rect(215, 80, wizard.position.width - 220, wizard.position.height - 130));
            _scroll = GUILayout.BeginScrollView(_scroll, EditorStyles.textField);

            int count = _objects.Count;

            GUI.enabled = false;
            GUILayout.Label("Objects to which the component and its settings\n will be added");
            GUI.enabled = true;

            for (int i = 0; i < count; i++)
            {
                if (_objects[i] == null)
                {
                    _objects.Remove(_objects[i]);
                    i--;
                    count--;
                }
                else _objects[i].Draw();
            }

            GUILayout.EndScrollView();
            GUILayout.EndArea();
        }
        catch (Exception ex)
        {
            Debug.Log(ex);
        }
    }

    private void ShowParameters()
    {
        GUILayout.BeginArea(new Rect(10, 80, 200, wizard.position.height - 130));
        _scrollParameters = GUILayout.BeginScrollView(_scrollParameters, EditorStyles.textField);

        GUI.enabled = false;
        GUILayout.Label("Select parametres which you wish\n to copy");
        GUI.enabled = true;

        KeyValuePair<FieldInfo, bool> changedField = new KeyValuePair<FieldInfo,bool>();
        KeyValuePair<PropertyInfo, bool> changedProperty = new KeyValuePair<PropertyInfo,bool>();

        foreach (KeyValuePair<FieldInfo, bool> field in Fields)
        {
            GUILayout.BeginHorizontal();
            GUILayout.Space(10);
            bool before = GUILayout.Toggle(field.Value, FormatString(field.Key.Name));
            if(before != field.Value)changedField = field;
            GUILayout.EndHorizontal();
        }

        foreach (KeyValuePair<PropertyInfo, bool> property in Properties)
        {
            GUILayout.BeginHorizontal();
            GUILayout.Space(10);
            bool before = GUILayout.Toggle(property.Value, FormatString(property.Key.Name));
            if (before != property.Value)changedProperty = property;
            GUILayout.EndHorizontal();
        }

        if (changedField.Key != null) Fields[changedField.Key] = !Fields[changedField.Key];
        if (changedProperty.Key != null) Properties[changedProperty.Key] = !Properties[changedProperty.Key];

        GUILayout.EndScrollView();
        GUILayout.EndArea();
    }

    string FormatString(string str)
    {
        string s = char.ToUpper(str[0]) + str.Substring(1);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.Length; i++)
        {
            if (char.IsUpper( s[i]))sb.Append(" ");
            sb.Append(s[i]);
        }
        return sb.ToString();
    }

    private void ProcessComponent()
    {
        GUILayout.BeginArea(new Rect(10, wizard.position.height - 25, wizard.position.width - 120, 30));
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Add component", EditorStyles.miniButtonLeft, GUILayout.Width(120)))
        {
            foreach (Container con in _objects) con.ProcessComponent(component, true);
            ShowNotification(new GUIContent("Done", AntaresGameObjectCustom.Routines.imageIconSmall));
        }
        if (GUILayout.Button("Remove component", EditorStyles.miniButtonMid, GUILayout.Width(120)))
        {
            foreach (Container con in _objects) con.ProcessComponent(component, false);
            ShowNotification(new GUIContent("Done", AntaresGameObjectCustom.Routines.imageIconSmall));
        }
        if (GUILayout.Button("Reset", EditorStyles.miniButtonMid, GUILayout.Width(64)))
        {
            ResetAll();   
        }
        if (GUILayout.Button("Close", EditorStyles.miniButtonRight, GUILayout.Width(64)))
        {
            wizard.Close();
        }
        GUILayout.EndHorizontal();
        GUILayout.EndArea();
    }

    void ResetAll()
    {
        foreach (Container con in _objects) con.Dispose();
        _objects.Clear();
        component = null;
        ShowNotification(new GUIContent("Clear", AntaresGameObjectCustom.Routines.imageIconSmall));
    }

    void ShowSteps(int step, string message)
    {
        message = "   STEP " + step + " : " + message;
        GUI.enabled = false;
        GUILayout.Label(message);
        GUI.enabled = true;
    }
}

