context: using System;
  using System.Collections.Generic;
  using System.Linq;
  using HouraiTeahouse.Editor;
  using UnityEditor;
  using UnityEditor.Animations;
  using UnityEngine;
  using UnityEngine.Assertions;
  using Object = UnityEngine.Object;
  
  namespace HouraiTeahouse.SmashBrew.Editor {
  
      public class EventsEditorWindow : LockableEditorWindow {
  
          enum DragStart {
  
              None,
              SeekBar,
              EditArea,
              Keyframe,
              Hitbox
  
          }
  
          struct Edit {
  
              public int index;
              public int keyframe;
              public Hitbox.Type type;
  
          }
  
          const float elementHeight = 17;
  
          static readonly Color KeyframeLineColor = Color.black;
          static readonly Color SeekLineColor = Color.red;
          static readonly Color SelectedColor = Color.blue;
  
          Hitbox.Type state = Hitbox.Type.Offensive;
          DragStart dragStart = DragStart.None;
          HitboxKeyframe selectedKeyframe;
          AnimatorState _state;
          CharacterStateEvents _behaviour;
          List<Hitbox> _hitboxes;
          List<Edit> changes;
          float _seekTime;
          Rect seekArea;
  
          int hitboxIndex,
              keyframeIndex;
  
          EventListener DragListener;
          EventListener SeekListener;
          EventListener SetListener;
          EventListener GeneralEventListener;
          EventListener KeyframeListener;
          EventListener HitboxListener;
          bool play;
  
          public float SeekTime {
              get { return _seekTime; }
              set {
                  if (_seekTime == value)
                      return;
                  _seekTime = Mathf.Clamp01(value);
                  if (State == null || Animator == null)
                      return;
                  // TODO: Figure out a layer-agnostic way to do thisGUILayout.ExpandHeight(true)
                  Animator.Play(State.nameHash, 0, _seekTime);
                  Animator.Update(0f);
                  UpdateSpawn();
              }
          }
  
          public Animator Animator {
              get {
                  if (Spawn == null)
                      return null;
                  return Spawn.GetComponentInChildren<Animator>();
              }
          }
  
          ObjectSelector<CharacterData> Characters { get; set; }
  
          CharacterStateEvents Behaviour {
              get { return _behaviour; }
              set {
                  bool changed = _behaviour != value;
                  _behaviour = value;
                  if (_behaviour != null && _behaviour.Data != null)
                      Events = _behaviour.Data;
                  if (changed && Events != null)
                      Characters.SetSelected(Events.FindBestHitboxMatch(Characters.Selections));
              }
          }
  
          EventData Events { get; set; }
  
          GameObject Spawn { get; set; }
  
          GameObject Target {
              get {
                  GameObject target = Spawn;
                  if (target != null)
                      return target;
                  if (Characters.Selected && Characters.Selected.Prefab.Load())
                      return Characters.Selected.Prefab.Load();
                  return null;
              }
          }
  
          List<int> IDs {
              get { return Events ? Events.IDs : null; }
          }
  
          List<HitboxKeyframe> Keyframes {
              get { return Events ? Events.Keyframes : null; }
          }
  
          List<AnimationEvent> OtherEvents {
              get { return Events ? Events.OtherEvents : null; }
          }
  
          public List<Hitbox> Hitboxes {
              get {
                  if (Target == null)
                      return new List<Hitbox>();
                  return new List<Hitbox>(Target.GetComponentsInChildren<Hitbox>());
              }
          }
  
          public AnimatorState State {
              get { return _state; }
              set {
                  _state = value;
                  Behaviour = _state ? _state.GetBehaviour<CharacterStateEvents>() : null;
                  OnEdit();
              }
          }
  
          public static EventsEditorWindow GetWindow() { return GetWindow<EventsEditorWindow>("Events"); }
  
          [MenuItem("Window/Animator Behaviour Editor")]
          static void CreateWindow() {
              EventsEditorWindow window = GetWindow();
              window.Show();
              window.Repaint();
          }
  
          public void UpdateSpawn() {
              if (Spawn == null)
                  return;
              HitboxKeyframe frame = Keyframes.FindLast(h => h.Time < SeekTime);
              for (var i = 0; i < IDs.Count; i++) {
                  Hitbox hitbox = GetHitbox(IDs[i]);
                  if (hitbox == null)
                      continue;
                  hitbox.CurrentType = frame != null ? frame.States[i] : hitbox.DefaultType;
              }
              SceneView.RepaintAll();
          }
  
          event Action OnAssetEdit;
  
          Hitbox GetHitbox(int id) {
              if (Hitboxes == null)
                  return null;
              return Hitboxes.Find(h => h.ID == id);
          }
  
          Hitbox.Type GetType(int id) {
              Hitbox hitbox = GetHitbox(id);
              return hitbox != null ? hitbox.DefaultType : Hitbox.Type.Inactive;
          }
  
          void OnEdit() {
              if (Keyframes != null)
                  Keyframes.Sort((k1, k2) => k1.Time.CompareTo(k2.Time));
              OnAssetEdit.SafeInvoke();
          }
  
          Hitbox.Type GetTypeOrDefault(int index) {
              Hitbox hitbox = GetHitbox(IDs[index]);
              return hitbox != null ? hitbox.DefaultType : Hitbox.Type.Inactive;
          }
  
          public IEnumerable<KeyValuePair<float, Hitbox.Type>> GetProgression(int index) {
              if (Keyframes == null)
                  throw new InvalidOperationException();
              Argument.Check(Check.Range(index, IDs));
              Hitbox.Type type = GetTypeOrDefault(index);
              foreach (HitboxKeyframe hitboxKeyframe in Keyframes) {
                  yield return new KeyValuePair<float, Hitbox.Type>(hitboxKeyframe.Time, type);
                  type = hitboxKeyframe.States[index];
              }
              yield return new KeyValuePair<float, Hitbox.Type>(1.0f, type);
          }
  
          // Loads all of the Characters from Resources.
          static CharacterData[] LoadAllCharacters() { return Resources.LoadAll<CharacterData>(string.Empty); }
  
          void Refresh() {
              CharacterData selected = Characters.Selected;
              Characters.Selections = LoadAllCharacters();
              UpdateCharacter(selected, Characters.Selected);
              Repaint();
          }
  
          void UpdateCharacter(CharacterData oldCharacter, CharacterData data) {
              if (oldCharacter == data)
                  return;
              if (oldCharacter != null)
                  oldCharacter.Prefab.Unload();
              if (Spawn != null) {
                  GameObject newInstance = SpawnPrefab();
                  newInstance.transform.Copy(Spawn.transform);
                  Undo.DestroyObjectImmediate(Spawn);
                  Spawn = newInstance;
              }
              _hitboxes =
                  new List<Hitbox>(Target == null ? Enumerable.Empty<Hitbox>() : Target.GetComponentsInChildren<Hitbox>());
              _hitboxes.Sort((h1, h2) => string.Compare(h1.name, h2.name, StringComparison.Ordinal));
          }
  
          void ToggleSpawn(bool toggle) {
              if (!toggle && Spawn)
                  Undo.DestroyObjectImmediate(Spawn);
              if (toggle && !Spawn)
                  Spawn = SpawnPrefab();
          }
  
          GameObject SpawnPrefab() {
              GameObject prefab = Characters.Selected.Prefab.Load();
  
              var instance = (GameObject) PrefabUtility.InstantiatePrefab(prefab);
              Undo.RegisterCreatedObjectUndo(instance, "Spawn {0}".With(prefab.name));
              return instance;
          }
  
          void SetSeekTime(Event evt) {
              Vector2 pos = evt.mousePosition;
              SeekTime = Mathf.Clamp01((pos.x - seekArea.x) / seekArea.width);
              Repaint();
          }
  
          void SetKeyframeTime(Event evt, HitboxKeyframe keyframe) {
              Assert.IsNotNull(Events);
              Assert.IsNotNull(keyframe);
              Vector2 pos = evt.mousePosition;
              keyframe.Time = Mathf.Clamp01((pos.x - seekArea.x) / seekArea.width);
              Events.Sortkeyframes();
              Repaint();
          }
  
          IEnumerable<Hitbox> GetObjectHitboxes(IEnumerable<Object> objects) {
              return objects.OfType<GameObject>().GetComponents<Hitbox>();
          }
  
          #region Unity Callbacks
  
          /// <summary> Unity callback. Called when the EditorWindow is created. </summary>
          void OnEnable() {
              changes = new List<Edit>();
              DragListener =
                  new EventListener().AddListener(EventType.DragUpdated,
                      delegate {
                          DragAndDrop.visualMode = Behaviour && GetObjectHitboxes(DragAndDrop.objectReferences).Any()
                              ? DragAndDropVisualMode.Generic
                              : DragAndDropVisualMode.None;
                      })
                      .AddListeners(new[] {EventType.DragExited, EventType.DragPerform},
                          delegate {
                              if (Events == null)
                                  return;
                              DragAndDrop.AcceptDrag();
                              foreach (Hitbox hb in GetObjectHitboxes(DragAndDrop.objectReferences))
                                  Events.AddHitbox(hb);
                          });
  
              GeneralEventListener = new EventListener().AddListener(EventType.MouseDrag,
                  delegate(Event evt) {
                      if (dragStart == DragStart.SeekBar)
                          SetSeekTime(evt);
                      if (dragStart == DragStart.Keyframe)
                          SetKeyframeTime(evt, selectedKeyframe);
                  })
                  .AddListener(EventType.MouseUp, delegate { dragStart = DragStart.None; })
                  .AddListener(EventType.KeyDown,
                      delegate(Event evt) {
                          if (evt.keyCode == KeyCode.Delete && selectedKeyframe != null) {
                              Events.DeleteKeyframe(selectedKeyframe);
                              Repaint();
                          }
                      });
  
              SeekListener = new EventListener().AddListeners(new[] {EventType.MouseDown, EventType.MouseDrag},
                  delegate(Event evt) {
  
gt:                     if (evt.type == EventType.MouseDown)