using UnityEngine;

#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
#endif

#if UNITY_EDITOR
/// <summary>
/// Draws the property field for any field marked with ExpandableAttribute.
/// </summary>
[CustomPropertyDrawer(typeof(ExpandableAttribute), true)]
public class ExpandableAttributeDrawer : PropertyDrawer {
  // Use the following area to change the style of the expandable ScriptableObject drawers;
  #region Style Setup
  private enum BackgroundStyles {
    None,
    HelpBox,
    Darken,
    Lighten
  }

  /// <summary>
  /// Whether the default editor Script field should be shown.
  /// </summary>
  private static bool SHOW_SCRIPT_FIELD = false;

  /// <summary>
  /// The spacing on the inside of the background rect.
  /// </summary>
  private static float INNER_SPACING = 6.0f;

  /// <summary>
  /// The spacing on the outside of the background rect.
  /// </summary>
  private static float OUTER_SPACING = 4.0f;

  /// <summary>
  /// The style the background uses.
  /// </summary>
  private static BackgroundStyles BACKGROUND_STYLE = BackgroundStyles.HelpBox;

  /// <summary>
  /// The colour that is used to darken the background.
  /// </summary>
  private static Color DARKEN_COLOUR = new Color(0.0f, 0.0f, 0.0f, 0.2f);

  /// <summary>
  /// The colour that is used to lighten the background.
  /// </summary>
  private static Color LIGHTEN_COLOUR = new Color(1.0f, 1.0f, 1.0f, 0.2f);
  #endregion

  private bool saveTargetDirty = false;

  public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    float totalHeight = 0.0f;

    totalHeight += EditorGUIUtility.singleLineHeight;

    if (property.objectReferenceValue == null)
      return totalHeight;

    if (!property.isExpanded)
      return totalHeight;

    SerializedObject targetObject = new SerializedObject(property.objectReferenceValue);

    if (targetObject == null)
      return totalHeight;

    SerializedProperty field = targetObject.GetIterator();

    field.NextVisible(true);

    if (SHOW_SCRIPT_FIELD) {
      totalHeight += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
    }

    while (field.NextVisible(false)) {
      totalHeight += EditorGUI.GetPropertyHeight(field, true) + EditorGUIUtility.standardVerticalSpacing;
    }

    totalHeight += INNER_SPACING * 2;
    totalHeight += OUTER_SPACING * 2;

    return totalHeight;
  }

  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    Rect fieldRect = new Rect(position);
    fieldRect.height = EditorGUIUtility.singleLineHeight;

    EditorGUI.PropertyField(fieldRect, property, label, true);

    if (property.objectReferenceValue == null)
      return;

    property.isExpanded = EditorGUI.Foldout(fieldRect, property.isExpanded, GUIContent.none, true);

    if (!property.isExpanded)
      return;

    SerializedObject targetObject = new SerializedObject(property.objectReferenceValue);

    if (targetObject == null)
      return;


    #region Format Field Rects
    List<Rect> propertyRects = new List<Rect>();
    Rect marchingRect = new Rect(fieldRect);

    Rect bodyRect = new Rect(fieldRect);
    bodyRect.xMin += EditorGUI.indentLevel * 14;
    bodyRect.yMin += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing
        + OUTER_SPACING;

    SerializedProperty field = targetObject.GetIterator();
    field.NextVisible(true);

    marchingRect.y += INNER_SPACING + OUTER_SPACING;

    if (SHOW_SCRIPT_FIELD) {
      propertyRects.Add(marchingRect);
      marchingRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
    }

    while (field.NextVisible(false)) {
      marchingRect.y += marchingRect.height + EditorGUIUtility.standardVerticalSpacing;
      marchingRect.height = EditorGUI.GetPropertyHeight(field, true);
      propertyRects.Add(marchingRect);
    }

    marchingRect.y += INNER_SPACING;

    bodyRect.yMax = marchingRect.yMax;
    #endregion

    DrawBackground(bodyRect);

    #region Draw Fields
    EditorGUI.indentLevel++;

    int index = 0;
    field = targetObject.GetIterator();
    field.NextVisible(true);

    if (SHOW_SCRIPT_FIELD) {
      //Show the disabled script field
      EditorGUI.BeginDisabledGroup(true);
      EditorGUI.PropertyField(propertyRects[index], field, true);
      EditorGUI.EndDisabledGroup();
      index++;
    }

    //Replacement for "editor.OnInspectorGUI ();" so we have more control on how we draw the editor
    while (field.NextVisible(false)) {
      try {
        EditorGUI.PropertyField(propertyRects[index], field, true);
      } catch (StackOverflowException) {
        field.objectReferenceValue = null;
        Debug.LogError("Detected self-nesting cauisng a StackOverflowException, avoid using the same " +
            "object iside a nested structure.");
      }

      index++;
    }
    bool wasModified = targetObject.ApplyModifiedProperties();

    // Show save/revert buttons if setup for that 
    var expandableAttribute = attribute as ExpandableAttribute;
    var serializedObject = property.serializedObject;
    if (wasModified && expandableAttribute.TargetPropertyPath != null) {
      var targetProperty = serializedObject.FindProperty(expandableAttribute.TargetPropertyPath);
      if (targetProperty.objectReferenceValue != null) {
        saveTargetDirty = true;
      }
    }
    if (saveTargetDirty) {
      var targetProperty = serializedObject.FindProperty(expandableAttribute.TargetPropertyPath);
      GUILayout.BeginHorizontal();
      if (GUILayout.Button("Save")) {
        CopyScriptableObjectReference(property, targetProperty);
        serializedObject.ApplyModifiedProperties();
        saveTargetDirty = false;
      } else if (GUILayout.Button("Revert")) {
        CopyScriptableObjectReference(targetProperty, property);
        serializedObject.ApplyModifiedProperties();
        saveTargetDirty = false;
      }
      GUILayout.EndHorizontal();
    }

    EditorGUI.indentLevel--;
    #endregion
  }

  /// <summary>
  /// Draws the Background
  /// </summary>
  /// <param name="rect">The Rect where the background is drawn.</param>
  private void DrawBackground(Rect rect) {
    switch (BACKGROUND_STYLE) {

      case BackgroundStyles.HelpBox:
        EditorGUI.HelpBox(rect, "", MessageType.None);
        break;

      case BackgroundStyles.Darken:
        EditorGUI.DrawRect(rect, DARKEN_COLOUR);
        break;

      case BackgroundStyles.Lighten:
        EditorGUI.DrawRect(rect, LIGHTEN_COLOUR);
        break;
    }
  }

  private static void CopyScriptableObjectReference(SerializedProperty src, SerializedProperty dst) {
    var sourceSO = src.objectReferenceValue as ScriptableObject;
    var destSO = dst.objectReferenceValue as ScriptableObject;
    EditorUtility.CopySerialized(sourceSO, destSO);
    EditorUtility.SetDirty(destSO);
  }
}
#endif
