
using System;
using System.IO;
using System.Collections.Generic;

namespace Animrig
{
    // key
    public abstract class AnimKey
    {
        // protected
        protected abstract bool LoadFromBinarySubClass(MemoryStream ms);
        // property
        public LayerType LayerType { get; protected set; } = LayerType.None;
        public int Frame = 0;   // AnimationSet has base frame rate.
        public float PrevTrackAlpha = 1.0f;
        public float CurrentTrackAlpha = 1.0f;
        public Easing TrackAlphaEasing = Easing.Linear;
        public float TrackAlphaPower = 1.0f;
        public bool IsPrevKey = false;

        public static readonly int CommonSaveVersion = 2;
        // public
        public static AnimKey CreateKeyFromLayerType(LayerType layertype, int frame)
        {
            AnimKey key = null;
            switch (layertype)
            {
                case LayerType.AnimBlend4: key = new AnimBlend4Key(); break;
                case LayerType.AnimBlend2: key = new AnimBlend2Key(); break;
                case LayerType.AnimBlend1: key = new AnimBlend1Key(); break;
                case LayerType.SeekBlend4: key = new SeekBlend4Key(); break;
                case LayerType.SeekBlend1: key = new SeekBlend1Key(); break;
                case LayerType.Transform: key = new TransformKey(); break;
                case LayerType.Event: key = new EventKey(); break;
                case LayerType.Priority: key = new PriorityKey(); break;
                case LayerType.State: key = new StateKey(); break;
                case LayerType.PhysicsForce: key = new PhysicsForceKey(); break;
            }
            if (key == null)
            {
                throw new Exception("CreateKeyFromLayerType : unsupport type.");
            }
            key.Frame = frame;
            return key;
        }
        public static AnimKey CreateKeyFromBinary(MemoryStream ms, string head, LayerType layertype)
        {
            int ver = 0;
            int f = 0;
            AnimKey key = null;
            try
            {
                Misc.ReadHead(ms, head);
                ver = Misc.ReadInteger(ms, 0, CommonSaveVersion); // ver
                // TAnimrigMisc.WriteString(ms,LayerType);  // * use parent layer
                f = Misc.ReadInteger(ms, 0, 0xffffff);

                key = AnimKey.CreateKeyFromLayerType(layertype, f);
            }
            catch
            {
                return null;
            }

            try
            {
                key.PrevTrackAlpha = Misc.ReadFloat(ms, 0.0f, 2.0f, true);
                key.CurrentTrackAlpha = Misc.ReadFloat(ms, 0.0f, 2.0f, true);
                if (ver >= 2)
                {
                    key.TrackAlphaEasing = Misc.ReadEasing(ms);
                    key.TrackAlphaPower = Misc.ReadFloat(ms, 0.0f, 2.0f, true);
                }
            }
            catch
            {
                // key = null;
                return null;
            }
            return key;
        }
        public AnimKey()
        {
            LayerType = LayerType.None;
            Frame = 0;
            IsPrevKey = false;
        }
        public virtual void Clear()
        {
            PrevTrackAlpha = 1.0f;
            CurrentTrackAlpha = 1.0f;
            TrackAlphaEasing = Easing.Linear;
            TrackAlphaPower = 1.0f;
        }
        public bool LoadFromBinary(MemoryStream ms)
        {
            // sub class
            if (!LoadFromBinarySubClass(ms)) return false;
            // end of data
            return true;
        }
    }
    public abstract class KeyHasTransform : AnimKey
    {
        public Vector3 Position;
        public Vector3 Scale;
        public Vector3 Rotate;
        public RotateOrder RotateOrder;

        public Spline TransSpline;
        public Easing TransformEasing;
        public float TransformPower;

        public override void Clear()
        {
            Misc.v3SetVector(out Position, 0.0f, 0.0f, 0.0f);
            Misc.v3SetVector(out Scale, 1.0f, 1.0f, 1.0f);
            Misc.v3SetVector(out Rotate, 0.0f, 0.0f, 0.0f);
            RotateOrder = RotateOrder.Pass;

            TransSpline = Spline.Linear;
            TransformEasing = Easing.Linear;
            TransformPower = 1.0f;
        }
    }
    public class AnimBlend4Key : AnimKey
    {
        // protected
        protected override bool LoadFromBinarySubClass(MemoryStream ms)
        {
            try
            {
                Misc.ReadHead(ms, "animblend4key");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion); // ver

                Animation = Misc.ReadString(ms);
                AnimationAllowAlways = Misc.ReadBool(ms);
                AnimationAdd = Misc.ReadBool(ms);
                AnimationMixBlend = (MixBlend)Misc.ReadInteger(ms, 0, 1);
                AnimationLoop = Misc.ReadBool(ms);
                if (ver >= 2) AnimationMixDuration = Misc.ReadFloat(ms, -1, 100, true);
                if (ver >= 3) AnimationLoopBegin = Misc.ReadFloat(ms, 0, 1000, true);
                if (ver >= 3) AnimationLoopEnd = Misc.ReadFloat(ms, 0, 1000, true);

                BlendAlphaX = Misc.ReadFloat(ms, -4.0f, +4.0f, true, true);
                BlendAlphaY = Misc.ReadFloat(ms, -4.0f, +4.0f, true, true);
                BlendAlphaSpline = Misc.ReadSpline(ms);
                BlendAlphaEasing = Misc.ReadEasing(ms);
                BlendAlphaPower = Misc.ReadFloat(ms, 0.0f, 2.0f, true);
            }
            catch
            {
                return false;
            }
            return true;
        }
        // public
        public string Animation = "";
        public bool AnimationAllowAlways;
        public bool AnimationAdd;
        public MixBlend AnimationMixBlend;
        public bool AnimationLoop;
        public float AnimationMixDuration;
        public float AnimationLoopBegin;
        public float AnimationLoopEnd;

        public float BlendAlphaX;
        public float BlendAlphaY;
        public Spline BlendAlphaSpline;
        public Easing BlendAlphaEasing;
        public float BlendAlphaPower;

        public static readonly int ClassSaveVersion = 3;

        public AnimBlend4Key()
            : base()     // inherited
        {
            LayerType = LayerType.AnimBlend4;
            Clear();
        }
        public override void Clear()
        {
            base.Clear();

            Animation = "none";
            AnimationAllowAlways = true;
            AnimationAdd = false;
            AnimationMixBlend = MixBlend.Add;
            AnimationMixDuration = -1.0f;
            AnimationLoopBegin = 0.0f;
            AnimationLoopEnd = 0.0f;

            BlendAlphaX = 0.0f;
            BlendAlphaY = 0.0f;
            BlendAlphaSpline = Spline.Linear;
            BlendAlphaEasing = Easing.Linear;
            BlendAlphaPower = 1.0f;
        }
    }
    public class AnimBlend1Key : AnimBlend4Key
    {
        public AnimBlend1Key()
            : base()
        {
            LayerType = LayerType.AnimBlend1;
            Clear();
        }
        public override void Clear()
        {
            base.Clear();
            AnimationMixBlend = MixBlend.Replace;
        }
    }
    public class AnimBlend2Key : AnimBlend4Key
    {
        public AnimBlend2Key()
            : base()
        {
            LayerType = LayerType.AnimBlend2;
            Clear();
        }
    }
    public class SeekBlend4Key : AnimKey
    {
        // protected
        protected override bool LoadFromBinarySubClass(MemoryStream ms)
        {
            try
            {
                Misc.ReadHead(ms, "seekblend4key");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion); // ver

                Animation = Misc.ReadString(ms);
                AnimationAllowAlways = Misc.ReadBool(ms);
                AnimationAdd = Misc.ReadBool(ms);
                AnimationMixBlend = (MixBlend)Misc.ReadInteger(ms, 0, 1);
                AnimationLoop = Misc.ReadBool(ms);
                if (ver >= 2) AnimationMixDuration = Misc.ReadFloat(ms, -1, 100, true);
                if (ver >= 3) AnimationLoopBegin = Misc.ReadFloat(ms, 0, 1000, true);
                if (ver >= 3) AnimationLoopEnd = Misc.ReadFloat(ms, 0, 1000, true);

                SeekTimeX = Misc.ReadFloat(ms, -4.0f, +4.0f, true, true);
                SeekTimeY = Misc.ReadFloat(ms, -4.0f, +4.0f, true, true);
                SeekTimeSpline = Misc.ReadSpline(ms);
                SeekTimeEasing = Misc.ReadEasing(ms);
                SeekTimePower = Misc.ReadFloat(ms, 0.0f, 2.0f, true);
            }
            catch
            {
                return false;
            }
            return true;
        }
        // property
        public string Animation = "";
        public bool AnimationAllowAlways;
        public bool AnimationAdd;
        public MixBlend AnimationMixBlend;
        public bool AnimationLoop;
        public float AnimationMixDuration;
        public float AnimationLoopBegin;
        public float AnimationLoopEnd;
        public float SeekTimeX;
        public float SeekTimeY;
        public Spline SeekTimeSpline;
        public Easing SeekTimeEasing;
        public float SeekTimePower;

        public static readonly int ClassSaveVersion = 3;
        // public
        public SeekBlend4Key()
            : base()
        {
            LayerType = LayerType.SeekBlend4;
            Clear();
        }
        public override void Clear()
        {
            base.Clear();

            Animation = "none";
            AnimationAllowAlways = true;
            AnimationAdd = false;
            AnimationMixBlend = MixBlend.Add;
            AnimationMixDuration = -1.0f;
            AnimationLoopBegin = 0.0f;
            AnimationLoopEnd = 0.0f;

            SeekTimeX = 0.0f;
            SeekTimeY = 0.0f;
            SeekTimeSpline = Spline.Linear;
            SeekTimeEasing = Easing.Linear;
            SeekTimePower = 1.0f;
        }
    }
    public class SeekBlend1Key : SeekBlend4Key
    {
        public SeekBlend1Key()
            : base()
        {
            LayerType = LayerType.SeekBlend1;
            Clear();
        }
        public override void Clear()
        {
            base.Clear();
            AnimationMixBlend = MixBlend.Replace;
        }
    }
    public class TransformKey : KeyHasTransform
    {
        // protected
        protected override bool LoadFromBinarySubClass(MemoryStream ms)
        {
            try {
                Misc.ReadHead(ms, "transformkey");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion); // ver

                Position.x = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                Position.y = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                Position.z = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);

                Scale.x = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                Scale.y = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                Scale.z = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);

                Rotate.x = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                Rotate.y = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                Rotate.z = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                RotateOrder = Misc.ReadRotateOrder(ms);

                TransSpline = Misc.ReadSpline(ms);
                TransformEasing = Misc.ReadEasing(ms);
                TransformPower = Misc.ReadFloat(ms, 0.0f, 2.0f, true);

                MulColor = Misc.ReadInteger(ms, 0x000000, 0xffffff, true, true);
                ColorAlpha = Misc.ReadFloat(ms, 0, 1.0f, true, true);
                if (ver >= 2) TintColor = Misc.ReadInteger(ms, 0x000000, 0xffffff, true, true);
                ColorEasing = Misc.ReadEasing(ms);
                ColorPower = Misc.ReadFloat(ms, 0.0f, 2.0f, true);
            }
            catch
            {
                return false;
            }
            return true;
        }
        // property
        public int MulColor;
        public float ColorAlpha;
        public int TintColor;
        public Easing ColorEasing;
        public float ColorPower;

        public static readonly int ClassSaveVersion = 2;
        // public
        public TransformKey()
            : base()
        {
            LayerType = LayerType.Transform;
            Clear();
        }
        public override void Clear()
        {
            base.Clear();

            MulColor = 0xffffff;
            ColorAlpha = 1.0f;
            TintColor = 0x000000;
            ColorEasing = Easing.Linear;
            ColorPower = 1.0f;
        }
    }
    public class EventKey : AnimKey
    {
        // protected 
        protected override bool LoadFromBinarySubClass(MemoryStream ms)
        {
            try
            {
                Misc.ReadHead(ms, "eventkey");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion); // ver

                ID = Misc.ReadString(ms);
                EventString[0] = Misc.ReadString(ms);
                EventString[1] = Misc.ReadString(ms);
                EventFloat[0] = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                EventFloat[1] = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);

                AllowAlways = Misc.ReadBool(ms);
                IgnitionAlways = Misc.ReadBool(ms);
            }
            catch
            {
                return false;
            }
            return true;
        }
        // property
        public string ID = "";
        public string[] EventString = new string[2];
        public float[] EventFloat = new float[2];
        public bool AllowAlways;     // ID compare
        public bool IgnitionAlways;
        public static readonly int ClassSaveVersion = 1;
        // public
        public EventKey()
            : base()
        {
            LayerType = LayerType.Event;
            Clear();
        }
        public override void Clear()
        {
            base.Clear();

            ID = "none";
            EventString[0] = "";
            EventString[1] = "";
            EventFloat[0] = 0.0f;
            EventFloat[1] = 0.0f;

            AllowAlways = true;
            IgnitionAlways = false;
        }
    }
    public class PriorityKey : AnimKey
    {
        // protected
        protected override bool LoadFromBinarySubClass(MemoryStream ms)
        {
            try
            {
                Misc.ReadHead(ms, "prioritykey");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion); // ver

                Priority = Misc.ReadFloat(ms, -0xffff, +0xffff, true, true);
                PriorityEasing = Misc.ReadEasing(ms);
                PriorityPower = Misc.ReadFloat(ms, 0.0f, 1.0f, true);
            }
            catch
            {
                return false;
            }
            return true;
        }
        // property
        public float Priority;
        public Easing PriorityEasing;
        public float PriorityPower;
        public static readonly int ClassSaveVersion = 1;
        // public
        public PriorityKey()
            : base()
        {
            LayerType = LayerType.Priority;
            Clear();
        }
        public override void Clear()
        {
            base.Clear();

            Priority = 0.0f;
            PriorityEasing = Easing.None;
            PriorityPower = 1.0f;
        }
    }
    public class StateKey : AnimKey
    {
        protected override bool LoadFromBinarySubClass(MemoryStream ms)
        {
            try
            {
                Misc.ReadHead(ms, "statekey");
                Misc.ReadInteger(ms, 0, ClassSaveVersion); // ver

                Enable = Misc.ReadBool(ms);
                Visible = Misc.ReadBool(ms);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public bool Enable;
        public bool Visible;
        public static readonly int ClassSaveVersion = 1;

        public StateKey()
            : base()
        {
            LayerType = LayerType.State;
            Clear();
        }

        public override void Clear()
        {
            base.Clear();
            Enable = true;
            Visible = true;
        }
    }
    public class PhysicsForceKey : AnimKey
    {
        protected override bool LoadFromBinarySubClass(MemoryStream ms)
        {
            try
            {
                Misc.ReadHead(ms, "physicsforcekey");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion); // ver

                if (ver >= 2) ObjectTarget = Misc.ReadString(ms);
                PhysicsTarget = Misc.ReadString(ms);
                ForceVector.x = Misc.ReadFloat(ms, -0xffff,0xffff, true, true);
                ForceVector.y = Misc.ReadFloat(ms, -0xffff,0xffff, true, true);
                ForceVector.z = Misc.ReadFloat(ms, -0xffff,0xffff, true, true);
                ForceEasing = Misc.ReadEasing(ms);
                ForcePower = Misc.ReadFloat(ms, 0.0f, 2.0f, true);
                ForceSpline = Misc.ReadSpline(ms);

                WiggleRatio = Misc.ReadFloat(ms, 0.0f, 1.0f, true);
                WiggleCycle = Misc.ReadFloat(ms, 0.1f, 100.0f, true);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public string ObjectTarget;
        public string PhysicsTarget;
        public Vector3 ForceVector;
        public Easing ForceEasing;
        public float ForcePower;
        public Spline ForceSpline;

        public float WiggleRatio;
        public float WiggleCycle;

        public static readonly int ClassSaveVersion = 2;

        public PhysicsForceKey()
            : base()
        {
            LayerType = LayerType.PhysicsForce;
            Clear();
        }
        public override void Clear()
        {
            base.Clear();

            ObjectTarget = "self";
            PhysicsTarget = "none";
            Misc.v3SetVector(out ForceVector, 0.0f, 0.0f, 0.0f);
            ForceEasing = Easing.Linear;
            ForcePower = 1.0f;
            ForceSpline = Spline.Linear;
            WiggleRatio = 0.0f;
            WiggleCycle = 5.0f;
        }
    }
   

    // layer
    // (May be integrated later ...)
    public class AnimLayer
    {
        // protected
        protected void CreateSetup()
        {
            Name = Misc._noname;
            Enable = true;

            Keys = new List<AnimKey>();
            PrevKey = AnimKey.CreateKeyFromLayerType(LayerType, 0);
            PrevKey.IsPrevKey = true;
        }
        // property
        public LayerType LayerType { get; protected set; } = LayerType.None;
        public AnimKey PrevKey { get; protected set; } = null;
        public string Name = "";
        public bool Enable;
        public static readonly int CommonSaveVersion = 1;
        public List<AnimKey> Keys { get; protected set; }
        public int KeyCount {get { return Keys.Count; } }

        // public
        public AnimLayer(LayerType layertype)
        {
            LayerType = layertype;
            CreateSetup();
        }
        public static AnimLayer CreateFromLayerType(LayerType layertype)
        {
            return new AnimLayer(layertype);
        }
        public static AnimLayer CreateFromBinary(MemoryStream ms, string head)
        {
            AnimLayer layer = null;
            try
            {
                Misc.ReadHead(ms, head);
                var ver = Misc.ReadInteger(ms, 0,CommonSaveVersion);  // ver
                var t = Misc.StringToLayerType(Misc.ReadString(ms));

                layer = AnimLayer.CreateFromLayerType(t);
            }
            catch
            {
                return null;
            }

            try
            {
                layer.Name = Misc.ReadString(ms);
                layer.Enable = Misc.ReadBool(ms);
            }
            catch
            {
                // layer = null;
                return null;
            }
            return layer;
        }
        public virtual void Clear()
        {
            Keys.Clear();
            PrevKey.Clear();
        }
        public bool LoadFromBinary(MemoryStream ms)
        {
            // prevkey
            var key = AnimKey.CreateKeyFromBinary(ms, "key", LayerType);
            if (key == null) return false;

            if (!key.LoadFromBinary(ms)) return false;
            key.IsPrevKey = true;
            PrevKey = key;

            // keys
            int count = 0;
            try
            {
                count = Misc.ReadInteger(ms);
            }
            catch
            {
                return false;
            }

            Keys.Clear();
            for (var i = 0; i < count; i++)
            {
                key = AnimKey.CreateKeyFromBinary(ms, "key", LayerType);
                if (key == null) return false;

                Keys.Add(key);
                if (!key.LoadFromBinary(ms)) return false;
            }
            return true;
        }
        public bool LoadReserveFromBinary(MemoryStream ms)
        {
            // sub class
            // * no data
            // keys
            // * no data
            return true;
        }
        public int GetKeyIndex(int frame)
        {
            var count = Keys.Count;
            for (var i = count - 1; i >= 0; i--)
            {
                if (frame >= Keys[i].Frame)
                {
                    return i;
                }
            }
            return -1;
        }
    }
    // object
    public class AnimObject
    {
        // protected
        protected void CreateSetup()
        {
            //Layers := TObjectList.Create;
            //InputPanelField:= TStringList.Create;
            Clear(true);
        }
        protected virtual bool LoadReserveFromBinarySubClass(MemoryStream ms)
        {
            try
            {
                // version
                var ver = Misc.ReadInteger(ms, 0, ReserveSaveVersion);
                // name
                if (ver >= 7) Name = Misc.ReadString(ms);
                if (ver >= 7) AliasName = Misc.ReadString(ms);
                // state
                Enable = Misc.ReadBool(ms);
                Visible = Misc.ReadBool(ms);
                // offset / scale
                if (ver >= 2)
                {
                    Offset.x = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
                    Offset.y = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
                    Offset.z = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
                    Scale.x = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
                    Scale.y = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
                    Scale.z = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
                }
                if (ver >= 3) ParentObjectIndex = Misc.ReadInteger(ms, -1, 0xff, true);
                if (ver >= 6) ParentBoneName = Misc.ReadString(ms);
                if (ver >= 4) DrawPriority = Misc.ReadFloat(ms, -0xffff, 0xffff, true);
                // setting field
                if (ver >= 5) Misc.ReadStringList(ms, SettingField);
                // input field
                InputPanelField.Clear();
                var n = Misc.ReadInteger(ms, 0, 256);
                for (var i = 0; i < n; i++)
                {
                    InputPanelField.Add(Misc.ReadString(ms));
                }
            }
            catch
            {
                return false;
            }
            return true;
        }
        // property
        public static readonly int CommonSaveVersion = 3;
        public static readonly int ReserveSaveVersion = 7;
        public ObjectType ObjectType { get; protected set; } = ObjectType.Any;
        public string Name;
        public string AliasName;
        public bool Enable;
        public bool Visible;
        public Vector3 Offset;
        public Vector3 Scale;
        public int ParentObjectIndex; // -1 : none
        public string ParentBoneName;
        public float DrawPriority;
        public List<string> InputPanelField { get; protected set; } = new List<string>();
        public List<string> SettingField { get; protected set; } = new List<string>();

        public List<AnimLayer> Layers { get; protected set; } = new List<AnimLayer>(); 
        public int LayerCount { get { return Layers.Count; } }

        // public
        public static AnimObject CreateFromObjectType(ObjectType objecttype)
        {
            AnimObject obj = null;
            switch (objecttype)
            {
                case ObjectType.Null: obj = new NullObject(); break;
                case ObjectType.Spine: obj = new SpineObject(); break;
                case ObjectType.Camera: obj = new CameraObject(); break;
            }
            if (obj == null)
            {
                throw new Exception("AddObject : unsupport object.");
            }
            return obj;
        }
        public static AnimObject CreateFromBinary(MemoryStream ms, string head)
        {
            AnimObject obj = null;
            string n = "";

            try
            {
                // base head
                Misc.ReadHead(ms, head);
                var ver = Misc.ReadInteger(ms, 0, CommonSaveVersion);
                var t = Misc.StringToObjectType(Misc.ReadString(ms));
                // name : move to ReserveObject savedata
                if (ver < 3) n = Misc.ReadString(ms);

                obj = AnimObject.CreateFromObjectType(t);
                obj.Name = n;
            }
            catch
            {
                return null;
            }
            return obj;
        }

        public void Clear(bool keeplayer = true)
        {
            if (keeplayer) {
                foreach (var l in Layers)
                {
                    l.Clear();
                }
            }
            else
            {
                Layers.Clear();
                InputPanelField.Clear();
            }

            SettingField.Clear();

            Name = Misc._noname;
            AliasName = "";
            Enable = true;
            Visible = true;
            Misc.v3SetVector(out Offset, 0.0f, 0.0f, 0.0f);
            Misc.v3SetVector(out Scale, 1.0f, 1.0f, 1.0f);
            ParentObjectIndex = -1;
            ParentBoneName = "none";
            DrawPriority = 0.0f;
        }
        public bool LoadFromBinary(MemoryStream ms)
        {
            int count = 0;
            try
            {
                count = Misc.ReadInteger(ms, 0, 255);
            }
            catch
            {
                return false;
            }

            Layers.Clear();
            for (var i = 0; i < count; i++)
            {
                var layer = AnimLayer.CreateFromBinary(ms, "layer");
                if (layer == null) return false;

                Layers.Add(layer);
                if (!layer.LoadFromBinary(ms)) return false;
            }
            return true;
        }
        public bool LoadReserveFromBinary(MemoryStream ms)
        {
            if (!LoadReserveFromBinarySubClass(ms)) return false;
            // layers
            int count = 0;
            try
            {
                count = Misc.ReadInteger(ms, 0, 255);
            }
            catch
            {
                return false;
            }

            Layers.Clear();
            for (var i = 0; i < count; i++)
            {
                var layer = AnimLayer.CreateFromBinary(ms, "reservelayer");
                if (layer == null) return false;

                Layers.Add(layer);
                if (!layer.LoadReserveFromBinary(ms)) return false;
            }
            return true;
        }
    }
    public class NullObject : AnimObject
    {
        // protected
        protected override bool LoadReserveFromBinarySubClass(MemoryStream ms)
        {
            // inherited
            if (!base.LoadReserveFromBinarySubClass(ms)) return false;

            try
            {
                Misc.ReadHead(ms,"nullobject");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion);
                ImageFilename = Misc.ReadString(ms);
            }
            catch
            {
                return false;
            }
            return true;
        }
        // property
        public static readonly int ClassSaveVersion = 1;
        public string ImageFilename;
        // public
        public NullObject()
        {
            ObjectType = ObjectType.Null;
            CreateSetup();

            ImageFilename = "";
        }
    }
    public class SpineObject : AnimObject
    {
        // protected
        protected override bool LoadReserveFromBinarySubClass(MemoryStream ms)
        {
            // inherited
            if (!base.LoadReserveFromBinarySubClass(ms)) return false;

            try
            {
                Misc.ReadHead(ms, "spineobject");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion);
                SpineFilename = Misc.ReadString(ms);
                MixDuration = Misc.ReadFloat(ms, 0,0xffff);
                BaseScale = Misc.ReadFloat(ms, 0,0xffff);
                if (ver >= 2) PreMultiplyAlpha = Misc.ReadBool(ms);
                if (ver >= 3) DefaultSkin = Misc.ReadString(ms);
            }
            catch
            {
                return false;
            }
            return true;
        }
        // property
        public static readonly int ClassSaveVersion = 3;
        public string SpineFilename;
        public float MixDuration;
        public float BaseScale;
        public bool PreMultiplyAlpha;
        public string DefaultSkin;
        // public
        public SpineObject()
        {
            ObjectType = ObjectType.Spine;
            CreateSetup();

            SpineFilename = "";
            MixDuration = 0.25f;
            BaseScale = 1.0f;
            PreMultiplyAlpha = false;
            DefaultSkin = "default";
        }
    }
    public class CameraObject : AnimObject
    {
        protected override bool LoadReserveFromBinarySubClass(MemoryStream ms)
        {
            // inherited
            if (!base.LoadReserveFromBinarySubClass(ms)) return false;

            try
            {
                Misc.ReadHead(ms, "cameraobject");
                var ver = Misc.ReadInteger(ms, 0, ClassSaveVersion);

                RotateOffset.x = Misc.ReadFloat(ms, -0xffff, 0xffff, true);
                RotateOffset.y = Misc.ReadFloat(ms, -0xffff, 0xffff, true);
                RotateOffset.z = Misc.ReadFloat(ms, -0xffff, 0xffff, true);

                if (ver >= 2) AreaBegin = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
                if (ver >= 2) AreaEnd = Misc.ReadFloat(ms, -0xffff, +0xffff, true);
            }
            catch
            {
                return false;
            }

            return true;
        }
        // property
        public Vector3 RotateOffset;
        public float AreaBegin;
        public float AreaEnd;

        public static readonly int ClassSaveVersion = 2;

        public CameraObject()
        {
            ObjectType = ObjectType.Camera;
            CreateSetup();

            Misc.v3SetVector(out RotateOffset, 0, 0, 0);
            AreaBegin = 0.0f;
            AreaEnd = 0.0f;
        }
    }

    // animation
    public class Animation
    {
        // protected
        // property
        public static readonly int CommonSaveVersion = 1;
        public string Name = "";
        public string PreviewBaseAnimation = "";
        public bool LoopEnable = true;
        public int LoopBeginFrame = 0;
        public int LoopEndFrame = 0;

        public List<AnimObject> Objects { get; protected set; } = new List<AnimObject>();
        public int ObjectCount { get { return Objects.Count; } }
        // public
        public Animation()
        {
            // Objects = new List<AnimObject>();
            // Name = Misc._noname;
        }
        public void Clear(bool keepobject = true)
        {
            if (keepobject)
            {
                var count = ObjectCount;
                foreach (var o in Objects)
                {
                    o.Clear();
                }
            }
            else
            {
                Objects.Clear();
                LoopEnable = false;
                LoopBeginFrame = 0;
                LoopEndFrame = 0;
            }
        }
        public double GetLoopedFrame(double frame)
        {
            if (!LoopEnable) return frame;
            if (frame < LoopEndFrame) return frame;

            var len = LoopEndFrame - LoopBeginFrame;
            if (len < 1) return frame;

            frame = frame - LoopEndFrame;
            frame = frame - (Math.Floor(frame / len)*len);

            return Math.Max(LoopBeginFrame + frame, LoopBeginFrame);
        }
        public bool LoadFromBinary(MemoryStream ms)
        {
            Clear(false);

            try
            {
                Misc.ReadHead(ms, "animation");
                var ver = Misc.ReadInteger(ms, 0, CommonSaveVersion);
                Name = Misc.ReadString(ms);
                LoopEnable = Misc.ReadBool(ms);
                LoopBeginFrame = Misc.ReadInteger(ms, 0,0xffff);
                LoopEndFrame = Misc.ReadInteger(ms, 0,0xffff);
                PreviewBaseAnimation = Misc.ReadString(ms, 256);
            }
            catch
            {
                return false;
            }

            // objects
            int count = 0;
            try
            {
                count = Misc.ReadInteger(ms, 0,255);
            }
            catch
            {
                return false;
            }
            Objects.Clear();
            for (var i = 0; i < count; i++)
            {
                var obj = AnimObject.CreateFromBinary(ms, "object");
                if (obj == null) return false;

                Objects.Add(obj);
                if (!obj.LoadFromBinary(ms)) return false;
            }
            return true;
        }
    }
    public class Animationrig
    {
        // protected 
        protected static readonly int CommonSaveVersion = 7;
        protected static readonly int GlobalChunkSaveVersion = 2;
        // property
        public static readonly int FileSaveVersion =
            AnimKey.CommonSaveVersion +               // keys
            AnimBlend4Key.ClassSaveVersion +
            SeekBlend4Key.ClassSaveVersion +
            TransformKey.ClassSaveVersion +
            EventKey.ClassSaveVersion +
            PriorityKey.ClassSaveVersion +
            StateKey.ClassSaveVersion +
            PhysicsForceKey.ClassSaveVersion +
            AnimLayer.CommonSaveVersion +             // layer
            AnimObject.CommonSaveVersion + AnimObject.ReserveSaveVersion + // object
            SpineObject.ClassSaveVersion +
            NullObject.ClassSaveVersion +
            CameraObject.ClassSaveVersion +
            Animation.CommonSaveVersion +             // animation
            CommonSaveVersion +                 // common change version
            GlobalChunkSaveVersion;             // save data chunk version

        public int Framerate = 30;
        public List<string> UserMemo { get; protected set; } = new List<string>();
        public int LoadVersion { get; protected set; }
        public string LastError { get; protected set; }
        public Vector3 Offset;
        public Vector3 Size;
        public Vector3 CameraOffset;

        public List<Animation> Animations { get; protected set; } = new List<Animation>();
        public int AnimationCount { get { return Animations.Count; } }

        public List<AnimObject> Objects { get; protected set; } = new List<AnimObject>();
        public int ObjectCount { get { return Objects.Count; } }
        // public
        public Animationrig()
        {
            // Objects = new 
            // Animations = new
            // UserMemo = new
            Framerate = 30;

            Clear();
        }
        public void Clear(bool keepobject = true)
        {
            Animations.Clear();
            UserMemo.Clear();

            if (!keepobject)
            {
                Framerate = 30;
                Objects.Clear();
                Misc.v3SetVector(out Offset, 0.0f, 0.0f, 0.0f);
                Misc.v3SetVector(out Size, 0.0f, 0.0f, 0.0f);
                Misc.v3SetVector(out CameraOffset, 0.0f, 0.0f, 0.0f);
            }

            LoadVersion = FileSaveVersion;
        }
        public bool LoadFromBinary(MemoryStream ms)
        {
            LastError = "";
            int loadver = 0;

            // head
            try
            {
                byte[] hed = new byte[8];
                if (ms.Read(hed, 0, 8) != 8)
                {
                    LastError = "Head error : can not read stream.";
                    return false;
                }
                if (hed[0] != (byte)'a') throw new Exception(); // goto :Q
                if (hed[1] != (byte)'n') throw new Exception();
                if (hed[2] != (byte)'i') throw new Exception();
                if (hed[3] != (byte)'m') throw new Exception();
                if (hed[4] != (byte)'r') throw new Exception();
                if (hed[5] != (byte)'i') throw new Exception();
                if (hed[6] != (byte)'g') throw new Exception();
                if (hed[7] != 0) throw new Exception();
            }
            catch
            {
                LastError = "Head error : char code error.";
                return false;
            }

            // version
            try
            {
                loadver = Misc.ReadInteger(ms);
                if ((loadver < 0) || (loadver > FileSaveVersion))
                {
                    LastError = $"Load version error : {loadver}";
                    return false;
                }
            }
            catch
            {
                LastError = "Header error : version chank error.";
                return false;
            }

            // clear
            Clear(false);

            // global
            try
            {
                Misc.ReadHead(ms, "global");
                var ver = Misc.ReadInteger(ms, 0, GlobalChunkSaveVersion);
                Framerate = Misc.ReadInteger(ms, 5, 120);
                Offset.x = Misc.ReadFloat(ms, -0xffff, +0xffff);
                Offset.y = Misc.ReadFloat(ms, -0xffff, +0xffff);
                Offset.z = Misc.ReadFloat(ms, -0xffff, +0xffff);
                Size.x = Misc.ReadFloat(ms, 0, 8192);
                Size.y = Misc.ReadFloat(ms, 0, 8192);
                Size.z = Misc.ReadFloat(ms, 0, 8192);
                if (ver >= 7)
                {
                    CameraOffset.x = Misc.ReadFloat(ms, -0xffff, +0xffff);
                    CameraOffset.y = Misc.ReadFloat(ms, -0xffff, +0xffff);
                    CameraOffset.z = Misc.ReadFloat(ms, -0xffff, +0xffff);
                }
                if (ver >= 2)
                {
                    Misc.ReadStringList(ms, UserMemo);
                }
            }
            catch
            {
                LastError = "Global chunk error.";
                return false;
            }

            // load version
            LoadVersion = loadver;

            // reserve object
            try
            {
                Misc.ReadHead(ms, "reserveobjects");
                var ver = Misc.ReadInteger(ms, 0, 1);
                var count = Misc.ReadInteger(ms, 0, 255);

                Objects.Clear();
                for (var i = 0; i < count; i++)
                {
                    var obj = AnimObject.CreateFromBinary(ms, "reserveobject");
                    if (obj == null)
                    {
                        LastError = "Reserve object head Error";
                        return false;
                    }

                    Objects.Add(obj);
                    if (!obj.LoadReserveFromBinary(ms))
                    {
                        LastError = "Reserve object load Error";
                        return false;
                    }
                }
            }
            catch
            {
                LastError = "Reserve chunk error.";
                return false;
            }

            // animation
            try
            {
                Misc.ReadHead(ms, "animations");
                var ver = Misc.ReadInteger(ms, 0, 1);
                var count = Misc.ReadInteger(ms, 0, 255);

                Animations.Clear();
                for (var i = 0; i < count; i++)
                {
                    var anim = new Animation();
                    Animations.Add(anim);
                    if (!anim.LoadFromBinary(ms))
                    {
                        LastError = "Animation load error.";
                        return false;
                    }
                }
            }
            catch
            {
                LastError = "Animation chunk error.";
                return false;
            }
            return true;
        }
        public int Search(string animationname)
        {
            var ls = animationname.ToLower();
            var count = AnimationCount;
            for (var i = 0; i < count; i++)
            {
                if (Animations[i].Name.ToLower() == ls)
                {
                    return i;
                }
            }
            return -1;
        }
    }
}