using Cryville.Common;
using Cryville.Crtr.Event;
using System;
using System.Collections.Generic;
using System.Globalization;
using UnityEngine;

namespace Cryville.Crtr.Skin {
	public class EffectInstance : ISkinnableGroup, IComparable<EffectInstance> {
		readonly EffectDefinition _def;
		readonly SkinContainer _skinContainer;
		public Transform RootTransform { get; private set; }
		readonly SkinComponent[] _comps;
		public EffectInstance(EffectDefinition def) {
			_def = def;
			_skinContainer = new SkinContainer(this, _def.elements);
			RootTransform = new GameObject("effect:" + GetHashCode().ToString(CultureInfo.InvariantCulture)).transform;
			SkinContext = new SkinContext(RootTransform);
			PdtEvaluator.Instance.ContextCascadeInsertBlock();
			_skinContainer.MatchStatic();
			PdtEvaluator.Instance.ContextCascadeDiscardBlock();
			_comps = RootTransform.GetComponentsInChildren<SkinComponent>();
			foreach (var i in _comps) i.Init();
			_durationOp = new PropOp.Float(v => _duration = v);
		}
		public void Rewind(double time, Transform target) {
			_startTime = time;
			foreach (var i in _comps) i.Rewind(time, target);
		}
		Transform _currentTarget;
		Identifier _currentStateName = Identifier.Empty;
		EffectState _currentState;
		ChartEvent _ctxev;
		ContainerState _ctxstate;
		internal static readonly int _VAR_EFFECT_INDEX = IdentifierManager.Shared.Request("effect_index");
		readonly PropStores.Float _indexst = new PropStores.Float();
		public float Index {
			get { return _indexst.Value; }
			set { _indexst.Value = value; }
		}
		double _startTime;
		float _duration;
		readonly PropOp _durationOp;
		public double EndTime { get { return _startTime + _duration; } }
		public void Tick(double time) {
			foreach (var i in _comps) i.Tick(_skinContainer, time);
		}
		public bool CanEmit() {
			return _currentStateName.Key == 0 || _currentState.rewind.Key != 0;
		}
		public void OnEmit(double time, Transform target) {
			_currentTarget = target;
			_ctxev = PdtEvaluator.Instance.ContextEvent;
			_ctxstate = PdtEvaluator.Instance.ContextState;
			_skinContainer.MatchDynamic(0, true);
			if (_currentStateName.Key == 0) {
				EnterState(_def.init, time, _currentTarget, true);
			}
			else {
				if (_currentState.rewind.Key == 0) throw new InvalidOperationException("Cannot rewind");
				EnterState(_currentState.rewind, time, _currentTarget, true);
			}
		}
		void EnterState(Identifier name, double time, Transform target, bool emitting) {
			_currentStateName = name;
			_currentState = _def.states[name];
			Rewind(time, target);
			RootTransform.gameObject.SetActive(true);
			PdtEvaluator.Instance.ContextCascadeInsert();
			PdtEvaluator.Instance.ContextCascadeUpdate(_VAR_EFFECT_INDEX, _indexst.Source);
			PdtEvaluator.Instance.Evaluate(_durationOp, _currentState.duration);
			_skinContainer.MatchDynamic(1, emitting);
			PdtEvaluator.Instance.ContextCascadeDiscard();
		}
		public bool OnStateDone() {
			if (_currentState.next.Key == 0) {
				RootTransform.gameObject.SetActive(false);
				_currentStateName = Identifier.Empty;
				_currentState = null;
				return false;
			}
			else {
				PdtEvaluator.Instance.ContextEvent = _ctxev;
				PdtEvaluator.Instance.ContextState = _ctxstate;
				EnterState(_currentState.next, EndTime, _currentTarget, false);
				PdtEvaluator.Instance.ContextEvent = null;
				PdtEvaluator.Instance.ContextState = null;
				return true;
			}
		}
		public void Dispose() {
			GameObject.Destroy(RootTransform.gameObject);
		}

		public string TypeName { get { throw new InvalidOperationException("Type name undefined"); } }
		public SkinContext SkinContext { get; private set; }
		public int AtAnchorDynamicLevel { get { return 1; } }
		public int OpenedAnchorName { get { return _currentStateName.Key; } }
		public void PushAnchorEvent(double time, int name) {
			throw new InvalidOperationException("Anchor not supported");
		}
		public void RegisterAnchor(int name) {
			throw new InvalidOperationException("Anchor not supported");
		}
		public bool TryGetAnchorsByName(int name, out IReadOnlyCollection<Anchor> result) {
			throw new InvalidOperationException("Anchor not supported");
		}

		public int CompareTo(EffectInstance other) {
			int r = EndTime.CompareTo(other.EndTime);
			if (r != 0) return r;
			return GetHashCode().CompareTo(other.GetHashCode());
		}
	}
}
