using System;
using System.IO;
using System.Collections.Generic;
using TC = System.ComponentModel.TypeConverterAttribute;
using EXP = System.ComponentModel.ExpandableObjectConverter;

namespace CodeWalker.GameFiles
{
    // Unused node indexes by GTAV: 11, 12, 14, 16
    // Exist in GTAV but not used in MRFs: 4, 8, 10, 17, 21, 22, 28, 29, 31, 32
    public enum MrfNodeType : ushort
    {
        None = 0,
        StateMachineClass = 1,
        Tail = 2,
        InlinedStateMachine = 3,
        Unk4 = 4,
        Blend = 5,
        AddSubtract = 6,
        Filter = 7,
        Unk8 = 8,
        Frame = 9,
        Unk10 = 10,
        BlendN = 13,
        Clip = 15,
        Unk17 = 17,
        Unk18 = 18,
        Expression = 19,
        Unk20 = 20,
        Proxy = 21,
        AddN = 22,
        Identity = 23,
        Unk24 = 24,
        Unk25 = 25,
        MergeN = 26,
        State = 27,
        Invalid = 28,
        Unk29 = 29,
        SubNetworkClass = 30,
        Unk31 = 31,
        Max = 32
    }

    #region mrf node abstractions
    
    [TC(typeof(EXP))] public abstract class MrfNode
    {
        public MrfNodeType NodeType { get; set; }
        public ushort NodeIndex { get; set; } //index in the parent node

        public int FileIndex { get; set; } //index in the file
        public int FileOffset { get; set; } //offset in the file
        public int FileDataSize { get; set; } //number of bytes read from the file (this node only)

        public virtual void Read(DataReader r)
        {
            NodeType = (MrfNodeType)r.ReadUInt16();
            NodeIndex = r.ReadUInt16();
        }

        public virtual void Write(DataWriter w)
        {
            w.Write((ushort)NodeType);
            w.Write(NodeIndex);
        }

        public override string ToString()
        {
            return /* FileIndex.ToString() + ":" + FileOffset.ToString() + "+" + FileDataSize.ToString() + ": " +  */
                NodeType.ToString() + " - " + NodeIndex.ToString();
        }
    }

    [TC(typeof(EXP))] public abstract class MrfNodeNameFlagsBase : MrfNode
    {
        public MetaHash NameHash { get; set; }
        public uint Flags { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);
            NameHash = new MetaHash(r.ReadUInt32());
            Flags = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);
            w.Write(NameHash);
            w.Write(Flags);
        }

        public override string ToString()
        {
            return base.ToString() + " - " + NameHash.ToString() + " - " + Flags.ToString();
        }
    }

    [TC(typeof(EXP))] public abstract class MrfNodeStateBase : MrfNode
    {
        public MetaHash NameHash { get; set; } // Used as an identifier for transitions
        public uint StateByteCount { get; set; }
        public uint StateUnk3 { get; set; }
        public byte StateUnk4 { get; set; }
        public byte StateUnk5 { get; set; }
        public byte StateChildCount { get; set; }
        public byte StateSectionCount { get; set; }
        public uint StateUnk8 { get; set; }
        public uint StateUnk9 { get; set; }
        public uint StateFlags { get; set; }

        public MrfNode[] ChildNodes { get; set; }
        public MrfNode TailNode { get; set; }


        public override void Read(DataReader r)
        {
            base.Read(r);
            NameHash = new MetaHash(r.ReadUInt32());
            StateByteCount = r.ReadUInt32();
            StateUnk3 = r.ReadUInt32();
            StateUnk4 = r.ReadByte();
            StateUnk5 = r.ReadByte();
            StateChildCount = r.ReadByte();
            StateSectionCount = r.ReadByte();
            StateUnk8 = r.ReadUInt32();
            StateUnk9 = r.ReadUInt32();
            StateFlags = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);
            w.Write(NameHash);
            w.Write(StateByteCount);
            w.Write(StateUnk3);
            w.Write(StateUnk4);
            w.Write(StateUnk5);
            w.Write(StateChildCount);
            w.Write(StateSectionCount);
            w.Write(StateUnk8);
            w.Write(StateUnk9);
            w.Write(StateFlags);
        }

        public override string ToString()
        {
            return base.ToString() + " - " + NameHash.ToString()
                + " - BC:" + StateByteCount.ToString()
                + " - CC:" + StateChildCount.ToString()
                + " - SC:" + StateSectionCount.ToString()
                + " - " + StateUnk3.ToString()
                + " - " + StateUnk4.ToString()
                + " - " + StateUnk5.ToString()
                + " - " + StateUnk8.ToString()
                + " - " + StateUnk9.ToString()
                + " - F:" + StateFlags.ToString();
        }
    }



    [TC(typeof(EXP))] public abstract class MrfNodeValueBase : MrfNode
    {
        public MetaHash Value { get; set; }//maybe not an actual hash

        public override void Read(DataReader r)
        {
            base.Read(r);
            Value = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);
            w.Write(Value);
        }
    }

    [TC(typeof(EXP))] public abstract class MrfNodeBlendAddSubtractBase : MrfNodeNameFlagsBase
    {
        public int Unk1 { get; set; }
        public int Unk2 { get; set; }
        public uint Unk3 { get; set; }
        public MetaHash Unk4 { get; set; }
        public MetaHash Unk5 { get; set; }
        public MetaHash Unk6 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadInt32();
            Unk2 = r.ReadInt32();

            if ((Flags & 0x180000) == 0x80000)
                Unk3 = r.ReadUInt32();

            if ((Flags & 3) != 0)
                Unk4 = new MetaHash(r.ReadUInt32());

            switch ((Flags >> 2) & 3)
            {
                case 1:
                    Unk5 = new MetaHash(r.ReadUInt32());
                    Unk6 = new MetaHash(r.ReadUInt32());
                    break;
                case 2:
                    Unk6 = new MetaHash(r.ReadUInt32());
                    break;
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);
            w.Write(Unk2);

            if ((Flags & 0x180000) == 0x80000)
                w.Write(Unk3);

            if ((Flags & 3) != 0)
                w.Write(Unk4);

            switch ((Flags >> 2) & 3)
            {
                case 1:
                    w.Write(Unk5);
                    w.Write(Unk6);
                    break;
                case 2:
                    w.Write(Unk6);
                    break;
            }
        }
    }

    [TC(typeof(EXP))] public abstract class MrfNodeFilterUnkBase : MrfNodeNameFlagsBase
    {
        public uint Unk1 { get; set; }
        public MetaHash Unk2 { get; set; }
        public MetaHash Unk3 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadUInt32();

            switch (Flags & 3)
            {
                case 1:
                    Unk2 = new MetaHash(r.ReadUInt32()); // Filter Frame dict hash
                    Unk3 = new MetaHash(r.ReadUInt32()); // Filter Frame name hash
                    break;
                case 2:
                    Unk3 = new MetaHash(r.ReadUInt32());
                    break;
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);

            switch (Flags & 3)
            {
                case 1:
                    w.Write(Unk2);
                    w.Write(Unk3);
                    break;
                case 2:
                    w.Write(Unk3);
                    break;
            }
        }
    }

    [TC(typeof(EXP))] public abstract class MrfNodeNegativeBase : MrfNodeNameFlagsBase
    {
        public uint Unk1 { get; set; }
        public byte[] Unk2 { get; set; }
        public uint Unk3 { get; set; }
        public uint Unk4 { get; set; }
        public uint Unk5 { get; set; }
        public uint Unk6 { get; set; }
        public int[] Unk7 { get; set; }
        public MrfStructNegativeDataUnk7[] Unk7_Items { get; set; }
        public uint[] Unk8 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            var unkTypeFlag1 = Flags & 3;
            var unkTypeFlag2 = (Flags >> 2) & 3;
            var unk7Count = Flags >> 26;

            if ((Flags & 0x180000) == 0x80000)
                Unk1 = r.ReadUInt32();

            if (unkTypeFlag1 == 1)
                Unk2 = r.ReadBytes(76); // Unused?
            else if (unkTypeFlag1 == 2)
                Unk3 = r.ReadUInt32();

            if (unkTypeFlag2 == 1)
            {
                Unk4 = r.ReadUInt32();
                Unk5 = r.ReadUInt32();
            }
            else if (unkTypeFlag2 == 2)
                Unk6 = r.ReadUInt32();

            if (unk7Count != 0)
            {
                Unk7 = new int[unk7Count];

                for (int i = 0; i < unk7Count; i++)
                    Unk7[i] = r.ReadInt32();
            }

            var unk8Count = (((2 * unk7Count) | 7) + 1) >> 3;

            if (unk8Count != 0)
            {
                Unk8 = new uint[unk8Count];

                for (int i = 0; i < unk8Count; i++)
                    Unk8[i] = r.ReadUInt32();
            }

            if (unk7Count == 0)
                return;

            Unk7_Items = new MrfStructNegativeDataUnk7[unk7Count];
            int iteration = 0;

            for (int i = 0; i < unk7Count; i++)
            {
                var unk8Value = Unk8[iteration >> 3];
                var unk7Flag = unk8Value >> (4 * (iteration & 7));
                var unkTypeFlag3 = (unk7Flag >> 4) & 3;

                var item = new MrfStructNegativeDataUnk7();

                if ((unk7Flag & 3) != 0)
                    item.Unk1 = r.ReadUInt32();

                if (unkTypeFlag3 == 1)
                {
                    item.Unk2 = r.ReadUInt32();
                    item.Unk3 = r.ReadUInt32();
                }
                else if (unkTypeFlag3 == 2)
                    item.Unk4 = r.ReadUInt32();

                Unk7_Items[i] = item;

                iteration += 2;
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            var unkTypeFlag1 = Flags & 3;
            var unkTypeFlag2 = (Flags >> 2) & 3;
            var unk7Count = Flags >> 26;

            if ((Flags & 0x180000) == 0x80000)
                w.Write(Unk1);

            if (unkTypeFlag1 == 1)
                w.Write(Unk2);
            else if (unkTypeFlag1 == 2)
                w.Write(Unk3);

            if (unkTypeFlag2 == 1)
            {
                w.Write(Unk4);
                w.Write(Unk5);
            }
            else if (unkTypeFlag2 == 2)
                w.Write(Unk6);

            if (unk7Count > 0)
            {
                foreach (var value in Unk7)
                    w.Write(value);
            }

            var unk8Count = (((2 * unk7Count) | 7) + 1) >> 3;

            if (unk8Count > 0)
            {
                foreach (var value in Unk8)
                    w.Write(value);
            }

            if (unk7Count == 0)
                return;

            int iteration = 0;

            foreach (var item in Unk7_Items)
            {
                var unk8Value = Unk8[iteration >> 3];
                var unk7Flag = unk8Value >> (4 * (iteration & 7));
                var unkTypeFlag3 = (unk7Flag >> 4) & 3;

                if ((unk7Flag & 3) != 0)
                    w.Write(item.Unk1);

                if (unkTypeFlag3 == 1)
                {
                    w.Write(item.Unk2);
                    w.Write(item.Unk3);
                }
                else if (unkTypeFlag3 == 2)
                    w.Write(item.Unk4);

                iteration += 2;
            }
        }
    }
    


    #endregion

    #region mrf node structs

    public abstract class MrfStruct
    {
        public abstract void Write(DataWriter w);
    }

    [TC(typeof(EXP))] public class MrfStructHeaderUnk1 : MrfStruct
    {
        public uint Size { get; set; }
        public byte[] Bytes { get; set; }

        public MrfStructHeaderUnk1(DataReader r)
        {
            Size = r.ReadUInt32();
            Bytes = r.ReadBytes((int)Size);
        }

        public override void Write(DataWriter w)
        {
            w.Write(Size);
            w.Write(Bytes);
        }

        public override string ToString()
        {
            return Size.ToString() + " bytes";
        }
    }

    [TC(typeof(EXP))] public class MrfStructHeaderUnk2 : MrfStruct
    {
        public uint Unk1 { get; set; }
        public uint Unk2 { get; set; }

        public MrfStructHeaderUnk2(DataReader r)
        {
            Unk1 = r.ReadUInt32();
            Unk2 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            w.Write(Unk1);
            w.Write(Unk2);
        }

        public override string ToString()
        {
            return Unk1.ToString() + " - " + Unk2.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfStructHeaderUnk3 : MrfStruct
    {
        public uint Unk1 { get; set; }
        public uint Unk2 { get; set; }

        public MrfStructHeaderUnk3(DataReader r)
        {
            Unk1 = r.ReadUInt32();
            Unk2 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            w.Write(Unk1);
            w.Write(Unk2);
        }

        public override string ToString()
        {
            return Unk1.ToString() + " - " + Unk2.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfStructStateMainSection : MrfStruct
    {
        //maybe Transition ..?

        public uint Unk1 { get; set; }
        public int Unk2 { get; set; }
        public float Unk3 { get; set; }
        public uint Unk4 { get; set; }
        public uint Unk5 { get; set; }
        public uint Unk6 { get; set; }
        public uint Unk7 { get; set; }
        public uint Unk8 { get; set; }
        public MrfStructStateCondition[] Conditions { get; set; }

        public MrfStructStateMainSection(DataReader r)
        {
            Unk1 = r.ReadUInt32();
            Unk2 = r.ReadInt32();
            Unk3 = r.ReadSingle();
            Unk4 = r.ReadUInt32();
            Unk5 = r.ReadUInt32();
            Unk6 = r.ReadUInt32();

            uint flags = Unk1 & 0xFFFBFFFF;
            var numconds = (flags >> 20) & 0xF;

            if (numconds > 0)
            {
                Conditions = new MrfStructStateCondition[numconds];
                for (int i = 0; i < numconds; i++)
                {
                    Conditions[i] = new MrfStructStateCondition(r);
                }
            }

            if ((flags & 0x40000000) != 0)
            {
                Unk7 = r.ReadUInt32();
                Unk8 = r.ReadUInt32();
            }
            else
            {
                Unk7 = 0;
                Unk8 = 0;
            }
        }

        public override void Write(DataWriter w)
        {
            w.Write(Unk1);
            w.Write(Unk2);
            w.Write(Unk3);
            w.Write(Unk4);
            w.Write(Unk5);
            w.Write(Unk6);

            // FIXME: might be broken if changed without flags, see "numconds"
            if (Conditions != null)
                for (int i = 0; i < Conditions.Length; i++)
                    Conditions[i].Write(w);

            // FIXME: might be broken if changed without flags
            uint flags = Unk1 & 0xFFFBFFFF;

            if ((flags & 0x40000000) != 0)
            {
                w.Write(Unk7);
                w.Write(Unk8);
            }
        }

        public override string ToString()
        {
            return Unk1.ToString() + " - " + Unk2.ToString() + " - " + FloatUtil.ToString(Unk3)
                + " - " + Unk4.ToString()
                + " - " + Unk5.ToString()
                + " - " + Unk6.ToString()
                + " - " + Unk7.ToString()
                + " - " + Unk8.ToString()
                + " - " + (Conditions?.Length ?? 0).ToString() + " conditions";
        }
    }

    [TC(typeof(EXP))] public class MrfStructStateCondition : MrfStruct
    {
        public short UnkType { get; }
        public short UnkIndex { get; }
        public uint Unk1_1 { get; }
        public uint Unk1_2 { get; }
        public uint Unk1_3 { get; }
        public uint Unk2_1 { get; }
        public uint Unk3_1 { get; }
        public uint Unk3_2 { get; }

        public MrfStructStateCondition(DataReader r)
        {
            Unk1_1 = 0;
            Unk1_2 = 0;
            Unk1_3 = 0;
            Unk2_1 = 0;
            Unk3_1 = 0;
            Unk3_2 = 0;

            UnkType = r.ReadInt16();
            UnkIndex = r.ReadInt16();

            switch (UnkType)
            {
                case 0:
                case 1:
                    {
                        Unk1_1 = r.ReadUInt32();
                        Unk1_2 = r.ReadUInt32();
                        Unk1_3 = r.ReadUInt32();
                        break;
                    }
                case 9:
                case 10:
                    {
                        Unk2_1 = r.ReadUInt32();
                        break;
                    }
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                case 8:
                case 11:
                case 12:
                    {
                        Unk3_1 = r.ReadUInt32();
                        Unk3_2 = r.ReadUInt32();
                        break;
                    }
            }
        }

        public override void Write(DataWriter w)
        {
            w.Write(UnkType);
            w.Write(UnkIndex);

            // FIXME: might be broken if changed outside
            switch (UnkType)
            {
                case 0:
                case 1:
                    {
                        w.Write(Unk1_1);
                        w.Write(Unk1_2);
                        w.Write(Unk1_3);
                        break;
                    }
                case 9:
                case 10:
                    {
                        w.Write(Unk2_1);
                        break;
                    }
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                case 8:
                case 11:
                case 12:
                    {
                        w.Write(Unk3_1);
                        w.Write(Unk3_2);
                        break;
                    }
            }
        }

        public override string ToString()
        {
            return UnkType.ToString() + " - " + UnkIndex.ToString()
                + " - " + Unk1_1.ToString()
                + " - " + Unk1_2.ToString()
                + " - " + Unk1_3.ToString()
                + " - " + Unk2_1.ToString()
                + " - " + Unk3_1.ToString()
                + " - " + Unk3_2.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfStructStateVariable : MrfStruct
    {
        public MetaHash VariableName { get; }
        public ushort Unk2 { get; }
        public ushort Unk3 { get; }
        public uint Unk4 { get; }

        public MrfStructStateVariable(DataReader r)
        {
            VariableName = new MetaHash(r.ReadUInt32());
            Unk2 = r.ReadUInt16();
            Unk3 = r.ReadUInt16();
            Unk4 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            w.Write(VariableName);
            w.Write(Unk2);
            w.Write(Unk3);
            w.Write(Unk4);
        }

        public override string ToString()
        {
            return VariableName.ToString() + " - " + Unk2.ToString() + " - " + Unk3.ToString() + " - " + Unk4.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfStructStateEvent : MrfStruct
    {
        public ushort Unk1 { get; }
        public ushort Unk2 { get; }
        public MetaHash NameHash { get; }

        public MrfStructStateEvent(DataReader r)
        {
            Unk1 = r.ReadUInt16();
            Unk2 = r.ReadUInt16();
            NameHash = new MetaHash(r.ReadUInt32());
        }

        public override void Write(DataWriter w)
        {
            w.Write(Unk1);
            w.Write(Unk2);
            w.Write(NameHash);
        }

        public override string ToString()
        {
            return Unk1.ToString() + " - " + Unk2.ToString() + " - " + NameHash.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfStructStateUnk6 : MrfStruct
    {
        public MetaHash Unk1 { get; }
        public ushort Unk2 { get; }
        public ushort Unk3 { get; }
        public uint Unk4 { get; }

        public MrfStructStateUnk6(DataReader r)
        {
            Unk1 = new MetaHash(r.ReadUInt32());
            Unk2 = r.ReadUInt16();
            Unk3 = r.ReadUInt16();
            Unk4 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            w.Write(Unk1);
            w.Write(Unk2);
            w.Write(Unk3);
            w.Write(Unk4);
        }

        public override string ToString()
        {
            return Unk1.ToString() + " - " + Unk2.ToString() + " - " + Unk3.ToString() + " - " + Unk4.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfStructStateSignalDataUnk3 : MrfStruct
    {
        public uint UnkValue { get; }
        public uint UnkDefault { get; }
        public ulong UnkRange { get; }

        public MrfStructStateSignalDataUnk3(DataReader r)
        {
            UnkValue = r.ReadUInt32();
            UnkDefault = r.ReadUInt32();
            UnkRange = r.ReadUInt64(); // 2 merged 32 bit values?
        }

        public override void Write(DataWriter w)
        {
            w.Write(UnkValue);
            w.Write(UnkDefault);
            w.Write(UnkRange);
        }

        public override string ToString()
        {
            return UnkValue.ToString() + " - " + UnkDefault.ToString() + " - " + UnkRange.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfStructStateMachineState : MrfStruct
    {
        public MetaHash StateName { get; }
        public uint UnkValue { get; }

        public MrfStructStateMachineState(DataReader r)
        {
            StateName = new MetaHash(r.ReadUInt32());
            UnkValue = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            w.Write(StateName);
            w.Write(UnkValue);
        }

        public override string ToString()
        {
            return StateName.ToString() + " - " + UnkValue.ToString();
        }
    }

    
    // FIXME: most likely broken

    [TC(typeof(EXP))] public class MrfStructStateSignalData : MrfStruct
    {
        public uint UnkType { get; }
        public uint NameHash { get; }
        public uint Unk1 { get; }
        public uint Unk2 { get; }
        public uint Unk3_Count { get; }
        public uint Unk4 { get; }

        public MrfStructStateSignalDataUnk3[] Unk3_Items { get; }

        public MrfStructStateSignalData(DataReader r)
        {
            UnkType = r.ReadUInt32();
            NameHash = r.ReadUInt32();

            if (UnkType != 5)
                return;

            Unk1 = r.ReadUInt32();
            Unk2 = r.ReadUInt32(); // Default value too?
            Unk3_Count = r.ReadUInt32();
            Unk4 = r.ReadUInt32();

            Unk3_Items = new MrfStructStateSignalDataUnk3[Unk3_Count];

            for (int i = 0; i < Unk3_Count; i++)
                Unk3_Items[i] = new MrfStructStateSignalDataUnk3(r);
        }

        public override void Write(DataWriter w)
        {
            w.Write(UnkType);
            w.Write(NameHash);

            if (UnkType != 5)
                return;

            w.Write(Unk1);
            w.Write(Unk2);
            w.Write(Unk3_Count);
            w.Write(Unk4);

            // FIXME: might be broken if changed outside
            foreach (var item in Unk3_Items)
                item.Write(w);
        }

        public override string ToString()
        {
            return UnkType.ToString() + " - " + NameHash.ToString() + " - " + 
                Unk1.ToString() + " - " + Unk2.ToString() + " - C:" + Unk3_Count.ToString() + " - " + Unk4.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfStructStateSignal : MrfStruct
    {
        public ushort Unk1 { get; }
        public ushort Unk2 { get; }
        public ushort Unk3 { get; }
        public ushort Unk4 { get; }
        public MrfStructStateSignalData[] Items { get; }

        public MrfStructStateSignal(DataReader r)
        {
            Unk1 = r.ReadUInt16();
            Unk2 = r.ReadUInt16();
            Unk3 = r.ReadUInt16();
            Unk4 = r.ReadUInt16();

            uint shouldContinue;
            var itemsList = new List<MrfStructStateSignalData>();

            // FIXME: those loops looks weird
            do
            {
                while (true)
                {
                    var data = new MrfStructStateSignalData(r);
                    itemsList.Add(data);

                    shouldContinue = data.UnkType;

                    if (data.UnkType != 5)
                        break;
                }
            }
            while (shouldContinue != 0);

            Items = itemsList.ToArray();
        }

        public override void Write(DataWriter w)
        {
            w.Write(Unk1);
            w.Write(Unk2);
            w.Write(Unk3);
            w.Write(Unk4);

            foreach (var item in Items)
                item.Write(w);
        }

        public override string ToString()
        {
            return Unk1.ToString() + " - " + Unk2.ToString() + " - " + Unk3.ToString() + " - " + Unk4.ToString() + " - " +
                (Items?.Length ?? 0).ToString() + " items";
        }
    }

    [TC(typeof(EXP))] public class MrfStructNegativeDataUnk7 : MrfStruct
    {
        public uint Unk1 { get; set; }
        public uint Unk2 { get; set; }
        public uint Unk3 { get; set; }
        public uint Unk4 { get; set; }

        public override void Write(DataWriter w)
        {
            w.Write(Unk1);
            w.Write(Unk2);
            w.Write(Unk3);
            w.Write(Unk4);
        }

        public override string ToString()
        {
            return Unk1.ToString() + " - " + Unk2.ToString() + " - " + Unk3.ToString() + " - " + Unk4.ToString();
        }
    }
    
    #endregion

    #region mrf node classes
    
    [TC(typeof(EXP))] public class MrfNodeStateMachineClass : MrfNodeStateBase
    {
        // rage__mvNodeStateMachineClass (1)

        public MrfStructStateMachineState[] States { get; set; }
        public MrfStructStateMainSection[] Sections { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);


            if (StateChildCount > 0)
            {
                States = new MrfStructStateMachineState[StateChildCount];
                for (int i = 0; i < StateChildCount; i++)
                    States[i] = new MrfStructStateMachineState(r);
            }

            if (StateSectionCount > 0)
            {
                Sections = new MrfStructStateMainSection[StateSectionCount];
                for (int i = 0; i < StateSectionCount; i++)
                    Sections[i] = new MrfStructStateMainSection(r);
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            if (States != null)
                foreach (var state in States)
                    state.Write(w);

            if (Sections != null)
                foreach(var section in Sections)
                    section.Write(w);
        }

    }

    [TC(typeof(EXP))] public class MrfNodeTail : MrfNodeValueBase
    {
        // rage__mvNodeTail (2)

    }

    [TC(typeof(EXP))] public class MrfNodeInlinedStateMachine : MrfNodeStateBase
    {
        // rage__mvNodeInlinedStateMachine (3)

        public int Unk { get; set; } //usually(always?) negative, seems to be a byte offset maybe to parent node
        public MrfStructStateMachineState[] States { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk = r.ReadInt32();

            if (StateChildCount > 0)
            {
                States = new MrfStructStateMachineState[StateChildCount];
                for (int i = 0; i < StateChildCount; i++)
                    States[i] = new MrfStructStateMachineState(r);
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk);

            if (States != null)
                foreach (var item in States)
                    item.Write(w);
        }

        public override string ToString()
        {
            return base.ToString() + " - " + Unk.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfNodeUnk4 : MrfNodeNameFlagsBase
    {
        // rage__mvNode* (4) not used in final game

        public uint Unk1 { get; set; }
        public uint Unk2_Count { get; set; }
        public byte[] Unk2_Bytes { get; set; }
        public uint Unk3 { get; set; }
        public uint Unk4 { get; set; }
        public uint Unk5 { get; set; }
        public uint Unk6 { get; set; }
        public uint Unk7 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            switch (Flags & 3)
            {
                case 1:
                    {
                        Unk2_Count = r.ReadUInt32();
                        Unk2_Bytes = r.ReadBytes((int)Unk2_Count);
                        break;
                    }
                case 2:
                    Unk1 = r.ReadUInt32();
                    break;
            }

            if (((Flags >> 2) & 3) != 0)
                Unk3 = r.ReadUInt32();

            if (((Flags >> 4) & 3) != 0)
                Unk4 = r.ReadUInt32();

            if (((Flags >> 6) & 3) != 0)
                Unk5 = r.ReadUInt32();

            if (((Flags >> 8) & 3) != 0)
                Unk6 = r.ReadUInt32();

            if (((Flags >> 10) & 3) != 0)
                Unk7 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            switch (Flags & 3)
            {
                case 1:
                    {
                        w.Write(Unk2_Count);
                        w.Write(Unk2_Bytes);
                        break;
                    }
                case 2:
                    w.Write(Unk1);
                    break;
            }

            if (((Flags >> 2) & 3) != 0)
                w.Write(Unk3);

            if (((Flags >> 4) & 3) != 0)
                w.Write(Unk4);

            if (((Flags >> 6) & 3) != 0)
                w.Write(Unk5);

            if (((Flags >> 8) & 3) != 0)
                w.Write(Unk6);

            if (((Flags >> 10) & 3) != 0)
                w.Write(Unk7);
        }

    }

    [TC(typeof(EXP))] public class MrfNodeBlend : MrfNodeBlendAddSubtractBase
    {
        // rage__mvNodeBlend (5)

    }

    [TC(typeof(EXP))] public class MrfNodeAddSubstract : MrfNodeBlendAddSubtractBase
    {
        // rage__mvNodeAddSubtract (6)

    }

    [TC(typeof(EXP))] public class MrfNodeFilter : MrfNodeFilterUnkBase
    {
        // rage__mvNodeFilter (7)

    }

    [TC(typeof(EXP))] public class MrfNodeUnk8 : MrfNodeFilterUnkBase
    {
        // rage__mvNode* (8)

    }

    [TC(typeof(EXP))] public class MrfNodeFrame : MrfNode
    {
        // rage__mvNodeFrame (9)

        public uint Unk1 { get; set; }
        public uint Flags { get; set; }
        public uint Unk2 { get; set; }
        public uint Unk3 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadUInt32();
            Flags = r.ReadUInt32();

            if ((Flags & 3) != 0)
                Unk2 = r.ReadUInt32();

            if ((Flags & 0x30) != 0)
                Unk3 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);
            w.Write(Flags);

            if ((Flags & 3) != 0)
                w.Write(Unk2);

            if ((Flags & 0x30) != 0)
                w.Write(Unk3);
        }

    }

    [TC(typeof(EXP))] public class MrfNodeUnk10 : MrfNodeValueBase
    {
        // rage__mvNode* (10)

    }

    [TC(typeof(EXP))] public class MrfNodeBlendN : MrfNodeNegativeBase
    {
        // rage__mvNodeBlendN (13)

    }

    [TC(typeof(EXP))] public class MrfNodeClip : MrfNodeNameFlagsBase
    {
        // rage__mvNodeClip (15)

        //eg "Clip_XXX" (lowercase hash, XXX number eg 012)
        //?- delta supplement
        //?- delta
        //?- looped
        //?- phase
        //?- rate


        public MetaHash VariableName { get; set; }
        public uint Unk2 { get; set; }
        public MetaHash DictName { get; set; }
        public MetaHash ClipName { get; set; }
        public MetaHash Unk5 { get; set; }
        public MetaHash Unk6 { get; set; }
        public uint Unk7 { get; set; }
        public uint Unk8 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            switch (Flags & 3)
            {
                case 1:
                    {
                        Unk2 = r.ReadUInt32();
                        DictName = new MetaHash(r.ReadUInt32());

                        if (Unk2 != 3)
                            ClipName = new MetaHash(r.ReadUInt32());

                        break;
                    }
                case 2:
                    VariableName = new MetaHash(r.ReadUInt32());
                    break;
            }

            if (((Flags >> 2) & 3) != 0)
                Unk5 = new MetaHash(r.ReadUInt32());

            if (((Flags >> 4) & 3) != 0)
                Unk6 = new MetaHash(r.ReadUInt32());

            if (((Flags >> 6) & 3) != 0)
                Unk7 = r.ReadUInt32();

            if (((Flags >> 6) & 3) != 0)
                Unk8 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            switch (Flags & 3)
            {
                case 1:
                    {
                        w.Write(Unk2);
                        w.Write(DictName);

                        if (Unk2 != 3)
                            w.Write(ClipName);

                        break;
                    }
                case 2:
                    w.Write(VariableName);
                    break;
            }

            if (((Flags >> 2) & 3) != 0)
                w.Write(Unk5);

            if (((Flags >> 4) & 3) != 0)
                w.Write(Unk6);

            if (((Flags >> 6) & 3) != 0)
                w.Write(Unk7);

            if (((Flags >> 6) & 3) != 0)
                w.Write(Unk8);
        }

    }

    [TC(typeof(EXP))] public class MrfNodeUnk17 : MrfNodeNameFlagsBase
    {
        // rage__mvNode* (17)

        public uint Unk1_Count { get; set; }
        public byte[] Unk1_Bytes { get; set; }
        public uint Unk2 { get; set; }
        public uint Unk3 { get; set; }
        public uint Unk4 { get; set; }
        public uint Unk5 { get; set; }
        public uint[] Unk6 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            if ((Flags & 3) == 1)
            {
                Unk1_Count = r.ReadUInt32();
                Unk1_Bytes = r.ReadBytes((int)Unk1_Count);
            }
            else if ((Flags & 3) == 2)
                Unk2 = r.ReadUInt32();

            if (((Flags >> 2) & 3) != 0)
                Unk3 = r.ReadUInt32();

            if (((Flags >> 4) & 3) != 0)
                Unk4 = r.ReadUInt32();

            if ((Flags >> 6) != 0)
                Unk5 = r.ReadUInt32();

            var flags = Flags >> 19;
            var unk6Count = (Flags >> 10) & 0xF;

            Unk6 = new uint[unk6Count];

            for (int i = 0; i < unk6Count; i++)
            {
                if ((flags & 3) != 0)
                    Unk6[i] = r.ReadUInt32();

                flags >>= 2;
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            if ((Flags & 3) == 1)
            {
                w.Write(Unk1_Count);
                w.Write(Unk1_Bytes);
            }
            else if ((Flags & 3) == 2)
                w.Write(Unk2);

            if (((Flags >> 2) & 3) != 0)
                w.Write(Unk3);

            if (((Flags >> 4) & 3) != 0)
                w.Write(Unk4);

            if ((Flags >> 6) != 0)
                w.Write(Unk5);

            var unk6Count = (Flags >> 10) & 0xF;

            if (unk6Count > 0)
            {
                foreach (var value in Unk6)
                    w.Write(value);
            }
        }

    }

    [TC(typeof(EXP))] public class MrfNodeUnk18 : MrfNodeNameFlagsBase
    {
        // rage__mvNode* (18)

        public uint Unk1 { get; set; }
        public uint Unk2 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadUInt32();

            if ((Flags & 3) != 0)
                Unk2 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);

            if ((Flags & 3) != 0)
                w.Write(Unk2);
        }

    }

    [TC(typeof(EXP))] public class MrfNodeExpression : MrfNodeNameFlagsBase
    {
        // rage__mvNodeExpression (19)

        public uint Unk1 { get; set; }
        public uint Unk2 { get; set; }
        public MetaHash ExpressionDict { get; set; }
        public MetaHash ExpressionName { get; set; }
        public MetaHash VariableName { get; set; }
        public uint Unk6 { get; set; }
        public uint[][] Unk7 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadUInt32();

            switch (Flags & 3)
            {
                case 1:
                    ExpressionDict = new MetaHash(r.ReadUInt32());
                    ExpressionName = new MetaHash(r.ReadUInt32());
                    break;
                case 2:
                    VariableName = new MetaHash(r.ReadUInt32());
                    break;
            }

            if (((Flags >> 2) & 3) != 0)
                Unk6 = r.ReadUInt32();

            var unk7Count = (Flags >> 28);

            if (unk7Count == 0)
                return;

            var unkHeaderFlag = (Flags >> 4) & 0xFFFFFF;
            var offset = 0;

            Unk7 = new uint[unk7Count][];

            for (int i = 0; i < unk7Count; i++)
            {
                var unkTypeFlag = (unkHeaderFlag >> offset) & 3;

                var value1 = r.ReadUInt32();
                uint value2 = 0;

                if (unkTypeFlag == 2 || unkTypeFlag == 1)
                    value2 = r.ReadUInt32();

                Unk7[i] = new uint[2] { value1, value2 };

                offset += 2;
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);

            switch (Flags & 3)
            {
                case 1:
                    w.Write(ExpressionDict);
                    w.Write(ExpressionName);
                    break;
                case 2:
                    w.Write(VariableName);
                    break;
            }

            if (((Flags >> 2) & 3) != 0)
                w.Write(Unk6);

            var unk7Count = (Flags >> 28);

            if (unk7Count == 0)
                return;

            var unkHeaderFlag = (Flags >> 4) & 0xFFFFFF;
            var offset = 0;

            foreach (var item in Unk7)
            {
                var unkTypeFlag = (unkHeaderFlag >> offset) & 3;

                w.Write(item[0]);

                if (unkTypeFlag == 2 || unkTypeFlag == 1)
                    w.Write(item[1]);

                offset += 2;
            }
        }

    }

    [TC(typeof(EXP))] public class MrfNodeUnk20 : MrfNodeNameFlagsBase
    {
        // rage__mvNode* (20)

        public uint Unk1 { get; set; }
        public uint Unk2 { get; set; }
        public uint Unk3 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadUInt32();

            if ((Flags & 3) != 0)
                Unk2 = r.ReadUInt32();
   
            if ((Flags & 0x30) != 0)
                Unk3 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);

            if ((Flags & 3) != 0)
                w.Write(Unk2);

            if ((Flags & 0x30) != 0)
                w.Write(Unk3);
        }

    }

    [TC(typeof(EXP))] public class MrfNodeProxy : MrfNodeNameFlagsBase
    {
        // rage__mvNodeProxy (21)

        //?- node
    }

    [TC(typeof(EXP))] public class MrfNodeAddN : MrfNodeNegativeBase
    {
        // rage__mvNodeAddN (22)

    }

    [TC(typeof(EXP))] public class MrfNodeIdentity : MrfNodeValueBase
    {
        // rage__mvNodeIdentity (23)

    }

    [TC(typeof(EXP))] public class MrfNodeUnk24 : MrfNodeNameFlagsBase
    {
        // rage__mvNode* (24)

        public uint Unk1 { get; set; }
        public uint Unk2 { get; set; }
        public uint Unk3 { get; set; }
        public MetaHash Unk4 { get; set; }
        public MetaHash Unk5 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadUInt32();
            Unk2 = r.ReadUInt32();

            if ((Flags & 0x180000) == 0x80000)
                Unk3 = r.ReadUInt32();

            switch (Flags & 3)
            {
                case 1:
                    Unk4 = new MetaHash(r.ReadUInt32());
                    Unk5 = new MetaHash(r.ReadUInt32());
                    break;
                case 2:
                    Unk5 = new MetaHash(r.ReadUInt32());
                    break;
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);
            w.Write(Unk2);

            if ((Flags & 0x180000) == 0x80000)
                w.Write(Unk3);

            switch (Flags & 3)
            {
                case 1:
                    w.Write(Unk4);
                    w.Write(Unk5);
                    break;
                case 2:
                    w.Write(Unk5);
                    break;
            }
        }

    }

    [TC(typeof(EXP))] public class MrfNodeUnk25 : MrfNodeNameFlagsBase
    {
        // rage__mvNode* (25)

        public uint Unk1 { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            if ((Flags & 3) != 0)
                Unk1 = r.ReadUInt32();
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            if ((Flags & 3) != 0)
                w.Write(Unk1);
        }

    }

    [TC(typeof(EXP))] public class MrfNodeMergeN : MrfNodeNegativeBase
    {
        // rage__mvNodeMergeN (26)

        //?- filter
        //?- input filter
        //?- weight
    }

    [TC(typeof(EXP))] public class MrfNodeState : MrfNodeStateBase
    {
        // rage__mvNodeState (27)

        public uint Unk1 { get; set; }
        public uint VariablesCount { get; set; }
        public uint Unk3 { get; set; }
        public uint EventsCount { get; set; }
        public uint Unk5 { get; set; }
        public uint Unk6Count { get; set; }
        public uint Unk7 { get; set; }
        public uint SignalsCount { get; set; }

        public MrfStructStateMainSection[] Sections { get; set; }
        public MrfStructStateVariable[] Variables { get; set; }
        public MrfStructStateEvent[] Events { get; set; }
        public MrfStructStateUnk6[] Unk6Items { get; set; }
        public MrfStructStateSignal[] Signals { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadUInt32();
            VariablesCount = r.ReadUInt32();
            Unk3 = r.ReadUInt32();
            EventsCount = r.ReadUInt32();
            Unk5 = r.ReadUInt32();
            Unk6Count = r.ReadUInt32();
            Unk7 = r.ReadUInt32();
            SignalsCount = r.ReadUInt32();


            if (StateSectionCount > 0)
            {
                Sections = new MrfStructStateMainSection[StateSectionCount];
                for (int i = 0; i < StateSectionCount; i++)
                    Sections[i] = new MrfStructStateMainSection(r);
            }

            if (VariablesCount > 0)
            {
                Variables = new MrfStructStateVariable[VariablesCount];
                for (int i = 0; i < VariablesCount; i++)
                    Variables[i] = new MrfStructStateVariable(r);
            }

            if (EventsCount > 0)
            {
                Events = new MrfStructStateEvent[EventsCount];
                for (int i = 0; i < EventsCount; i++)
                    Events[i] = new MrfStructStateEvent(r);
            }

            if (Unk6Count > 0)
            {
                Unk6Items = new MrfStructStateUnk6[Unk6Count];
                for (int i = 0; i < Unk6Count; i++)
                    Unk6Items[i] = new MrfStructStateUnk6(r);
            }

            if (SignalsCount > 0)
            {
                Signals = new MrfStructStateSignal[SignalsCount];
                for (int i = 0; i < SignalsCount; i++)
                    Signals[i] = new MrfStructStateSignal(r);
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);
            w.Write(VariablesCount);
            w.Write(Unk3);
            w.Write(EventsCount);
            w.Write(Unk5);
            w.Write(Unk6Count);
            w.Write(Unk7);
            w.Write(SignalsCount);

            if (Sections != null)
                foreach (var section in Sections)
                    section.Write(w);

            if (Variables != null)
                foreach (var item in Variables)
                    item.Write(w);

            if (Events != null)
                foreach (var item in Events)
                    item.Write(w);

            if (Unk6Items != null)
                foreach (var item in Unk6Items)
                    item.Write(w);

            if (Signals != null)
                foreach (var item in Signals)
                    item.Write(w);
        }

        public override string ToString()
        {
            return base.ToString() + " --- " + Unk1.ToString() + " - " + Unk3.ToString() + " - " + Unk5.ToString() + " - " + Unk7.ToString();
        }
    }

    [TC(typeof(EXP))] public class MrfNodeInvalid : MrfNodeValueBase
    {
        // rage__mvNodeInvalid (28)

    }

    [TC(typeof(EXP))] public class MrfNodeUnk29 : MrfNodeFilterUnkBase
    {
        // rage__mvNode* (29)

    }

    [TC(typeof(EXP))] public class MrfNodeSubNetworkClass : MrfNodeNameFlagsBase
    {
        // rage__mvNodeSubNetworkClass (30)

    }

    [TC(typeof(EXP))] public class MrfNodeUnk31 : MrfNodeNameFlagsBase
    {
        // rage__mvNode* (31)

        public uint Unk1 { get; set; }
        public uint Unk2_Count { get; set; }
        public uint[][] Unk2_Items { get; set; }
        public uint Unk3_Count { get; set; }
        public uint[][] Unk3_Items { get; set; }
        public uint Unk4_Count { get; set; }
        public uint[][] Unk4_Items { get; set; }
        public uint Unk5_Count { get; set; }
        public uint[][] Unk5_Items { get; set; }

        public override void Read(DataReader r)
        {
            base.Read(r);

            Unk1 = r.ReadUInt32();
            Unk2_Count = r.ReadUInt32();
            Unk3_Count = r.ReadUInt32();
            Unk4_Count = r.ReadUInt32();
            Unk5_Count = r.ReadUInt32();

            if (Unk3_Count > 0)
            {
                Unk3_Items = new uint[Unk3_Count][];

                for (int i = 0; i < Unk3_Count; i++)
                {
                    var value1 = r.ReadUInt32();
                    var value2 = r.ReadUInt32();
                    Unk3_Items[i] = new uint[] { value1, value2 };
                }
            }

            if (Unk4_Count > 0)
            {
                Unk4_Items = new uint[Unk4_Count][];

                for (int i = 0; i < Unk4_Count; i++)
                {
                    var value1 = r.ReadUInt32();
                    var value2 = r.ReadUInt32();
                    Unk4_Items[i] = new uint[] { value1, value2 };
                }
            }

            if (Unk5_Count > 0)
            {
                Unk5_Items = new uint[Unk5_Count][];

                for (int i = 0; i < Unk5_Count; i++)
                {
                    var value1 = r.ReadUInt32();
                    var value2 = r.ReadUInt32();
                    Unk5_Items[i] = new uint[] { value1, value2 };
                }
            }

            if (Unk2_Count > 0)
            {
                Unk2_Items = new uint[Unk2_Count][];

                for (int i = 0; i < Unk2_Count; i++)
                {
                    var value1 = r.ReadUInt32();
                    var value2 = r.ReadUInt32();
                    var value3 = r.ReadUInt32();
                    Unk2_Items[i] = new uint[] { value1, value2, value3 };
                }
            }
        }

        public override void Write(DataWriter w)
        {
            base.Write(w);

            w.Write(Unk1);
            w.Write(Unk2_Count);
            w.Write(Unk3_Count);
            w.Write(Unk4_Count);
            w.Write(Unk5_Count);

            if (Unk3_Count > 0)
            {
                foreach (var entry in Unk3_Items)
                {
                    w.Write(entry[0]);
                    w.Write(entry[1]);
                }
            }

            if (Unk4_Count > 0)
            {
                foreach (var entry in Unk4_Items)
                {
                    w.Write(entry[0]);
                    w.Write(entry[1]);
                }
            }

            if (Unk5_Count > 0)
            {
                foreach (var entry in Unk5_Items)
                {
                    w.Write(entry[0]);
                    w.Write(entry[1]);
                }
            }

            if (Unk2_Count > 0)
            {
                foreach (var entry in Unk2_Items)
                {
                    w.Write(entry[0]);
                    w.Write(entry[1]);
                    w.Write(entry[2]);
                }
            }
        }

    }
    
    #endregion

    [TC(typeof(EXP))] public class MrfFile : GameFile, PackedFile
    {
        public byte[] RawFileData { get; set; }
        public uint Magic { get; set; } = 0x45566F4D; // 'MoVE'
        public uint Version { get; set; } = 2;
        public uint HeaderUnk1 { get; set; } = 0;
        public uint HeaderUnk2 { get; set; } = 0;
        public uint HeaderUnk3 { get; set; } = 0;
        public uint DataLength { get; set; }
        public uint FlagsCount { get; set; }
        public uint Unk1_Count { get; set; }
        public uint Unk2_Count { get; set; }
        public uint Unk3_Count { get; set; }

        public MrfStructHeaderUnk1[] Unk1_Items { get; set; }
        public MrfStructHeaderUnk2[] Unk2_Items { get; set; }
        public MrfStructHeaderUnk3[] Unk3_Items { get; set; }
        public byte[] FlagsItems { get; set; }

        public MrfNode[] AllNodes { get; set; }
        public MrfNode RootNode { get; set; }

        public MrfFile() : base(null, GameFileType.Mrf)
        {
        }

        public MrfFile(RpfFileEntry entry) : base(entry, GameFileType.Mrf)
        {
            RpfFileEntry = entry;
        }

        public void Load(byte[] data, RpfFileEntry entry)
        {
            RawFileData = data;
            if (entry != null)
            {
                RpfFileEntry = entry;
                Name = entry.Name;
            }

            using (MemoryStream ms = new MemoryStream(data))
            {
                DataReader r = new DataReader(ms, Endianess.LittleEndian);

                Read(r);
            };
        }

        public byte[] Save()
        {
            MemoryStream s = new MemoryStream();
            DataWriter w = new DataWriter(s);

            Write(w);

            var buf = new byte[s.Length];
            s.Position = 0;
            s.Read(buf, 0, buf.Length);
            return buf;
        }

        private void Write(DataWriter w)
        {
            if (Magic != 0x45566F4D || Version != 2 || HeaderUnk1 != 0 || HeaderUnk2 != 0)
                throw new Exception("Failed to write MRF, header is invalid!");

            w.Write(Magic);
            w.Write(Version);
            w.Write(HeaderUnk1);
            w.Write(HeaderUnk2);
            w.Write(HeaderUnk3);
            w.Write(DataLength);
            w.Write(FlagsCount);

            // Unused in final game
            w.Write(Unk1_Count);

            if (Unk1_Count > 0)
            {
                foreach (var entry in Unk1_Items)
                {
                    w.Write(entry.Size);
                    w.Write(entry.Bytes);
                }
            }

            w.Write(Unk2_Count);

            if (Unk2_Count > 0)
            {
                foreach (var entry in Unk2_Items)
                {
                    w.Write(entry.Unk1);
                    w.Write(entry.Unk2);
                }
            }

            w.Write(Unk3_Count);

            if (Unk3_Count > 0)
            {
                foreach (var entry in Unk3_Items)
                {
                    w.Write(entry.Unk1);
                    w.Write(entry.Unk2);
                }
            }

            if (AllNodes != null)
            {
                foreach (var node in AllNodes)
                    node.Write(w);
            }

            for (int i = 0; i < FlagsCount; i++)
                w.Write(FlagsItems[i]);
        }

        private void Read(DataReader r)
        {
            Magic = r.ReadUInt32(); // Should be 'MoVE'
            Version = r.ReadUInt32(); // GTA5 = 2, RDR3 = 11
            HeaderUnk1 = r.ReadUInt32(); // Should be 0
            HeaderUnk2 = r.ReadUInt32();
            HeaderUnk3 = r.ReadUInt32(); // Should be 0
            DataLength = r.ReadUInt32();
            FlagsCount = r.ReadUInt32();

            if (Magic != 0x45566F4D || Version != 2 || HeaderUnk1 != 0 || HeaderUnk2 != 0)
                throw new Exception("Failed to read MRF, header is invalid!");

            // Unused in final game
            Unk1_Count = r.ReadUInt32();
            if (Unk1_Count > 0)
            {
                Unk1_Items = new MrfStructHeaderUnk1[Unk1_Count];

                for (int i = 0; i < Unk1_Count; i++)
                    Unk1_Items[i] = new MrfStructHeaderUnk1(r);
            }

            Unk2_Count = r.ReadUInt32();
            if (Unk2_Count > 0)
            {
                Unk2_Items = new MrfStructHeaderUnk2[Unk2_Count];

                for (int i = 0; i < Unk2_Count; i++)
                    Unk2_Items[i] = new MrfStructHeaderUnk2(r);
            }

            Unk3_Count = r.ReadUInt32();
            if (Unk3_Count > 0)
            {
                Unk3_Items = new MrfStructHeaderUnk3[Unk3_Count];

                for (int i = 0; i < Unk3_Count; i++)
                    Unk3_Items[i] = new MrfStructHeaderUnk3(r);
            }

            var nodes = new List<MrfNode>();

            while (true)
            {
                var index = nodes.Count;
                var offset = (int)r.Position;

                var node = ReadNode(r);
                
                if (node == null) break;

                node.FileIndex = index;
                node.FileOffset = offset;
                node.FileDataSize = ((int)r.Position) - offset;

                nodes.Add(node);
            }

            AllNodes = nodes.ToArray();

            if (FlagsCount != 0)
            {
                FlagsItems = new byte[FlagsCount];

                for (int i = 0; i < FlagsCount; i++)
                    FlagsItems[i] = r.ReadByte();
            }


            BuildNodeHierarchy();


            if (r.Position != r.Length)
                throw new Exception($"Failed to read MRF ({r.Position} / {r.Length})");
        }

        private MrfNode ReadNode(DataReader r)
        {
            var startPos = r.Position;
            var nodeType = (MrfNodeType)r.ReadUInt16();
            r.Position = startPos;

            if (nodeType <= MrfNodeType.None || nodeType >= MrfNodeType.Max)
            {
                if (r.Position != r.Length)//should only be at EOF
                { }
                return null;
            }

            var node = CreateNode(nodeType);

            node.Read(r);

            return node;
        }

        private MrfNode CreateNode(MrfNodeType infoType)
        {
            switch (infoType)
            {
                case MrfNodeType.StateMachineClass:
                    return new MrfNodeStateMachineClass();
                case MrfNodeType.Tail:
                    return new MrfNodeTail();
                case MrfNodeType.InlinedStateMachine:
                    return new MrfNodeInlinedStateMachine();
                case MrfNodeType.Unk4:
                    return new MrfNodeUnk4();
                case MrfNodeType.Blend:
                    return new MrfNodeBlend();
                case MrfNodeType.AddSubtract:
                    return new MrfNodeAddSubstract();
                case MrfNodeType.Filter:
                    return new MrfNodeFilter();
                case MrfNodeType.Unk8:
                    return new MrfNodeUnk8();
                case MrfNodeType.Frame:
                    return new MrfNodeFrame();
                case MrfNodeType.Unk10:
                    return new MrfNodeUnk10();
                case MrfNodeType.BlendN:
                    return new MrfNodeBlendN();
                case MrfNodeType.Clip:
                    return new MrfNodeClip();
                case MrfNodeType.Unk17:
                    return new MrfNodeUnk17();
                case MrfNodeType.Unk18:
                    return new MrfNodeUnk18();
                case MrfNodeType.Expression:
                    return new MrfNodeExpression();
                case MrfNodeType.Unk20:
                    return new MrfNodeUnk20();
                case MrfNodeType.Proxy:
                    return new MrfNodeProxy();
                case MrfNodeType.AddN:
                    return new MrfNodeAddN();
                case MrfNodeType.Identity:
                    return new MrfNodeIdentity();
                case MrfNodeType.Unk24:
                    return new MrfNodeUnk24();
                case MrfNodeType.Unk25:
                    return new MrfNodeUnk25();
                case MrfNodeType.MergeN:
                    return new MrfNodeMergeN();
                case MrfNodeType.State:
                    return new MrfNodeState();
                case MrfNodeType.Invalid:
                    return new MrfNodeInvalid();
                case MrfNodeType.Unk29:
                    return new MrfNodeUnk29();
                case MrfNodeType.SubNetworkClass:
                    return new MrfNodeSubNetworkClass();
                case MrfNodeType.Unk31:
                    return new MrfNodeUnk31();
            }

            throw new Exception($"A handler for ({infoType}) mrf node type is not valid");
        }


        private int BuildNodeHierarchy(MrfNode node = null, int index = 0)
        {
            if (AllNodes == null) return 0;
            if (AllNodes.Length <= index) return 0;

            if (node == null)
            {
                var rlist = new List<MrfNode>();
                for (int i = 0; i < AllNodes.Length; i++)
                {
                    var rnode = AllNodes[i];
                    rlist.Add(rnode);
                    i += BuildNodeHierarchy(rnode, i);
                }

                var smlist = new List<MrfNodeStateMachineClass>();
                var imlist = new List<MrfNodeInlinedStateMachine>();
                var snlist = new List<MrfNodeState>();
                foreach (var n in AllNodes)
                {
                    if (n is MrfNodeStateMachineClass sm) 
                    { 
                        smlist.Add(sm);
                        if (sm.ChildNodes != null) for (int i = 0; i < sm.ChildNodes.Length; i++) if (sm.ChildNodes[i].NodeIndex != i)
                                { }//sanity check - don't get here
                    }
                    if (n is MrfNodeInlinedStateMachine im)
                    {
                        imlist.Add(im);
                        if (im.ChildNodes != null) for (int i = 0; i < im.ChildNodes.Length; i++) if (im.ChildNodes[i].NodeIndex != i)
                                { }//sanity check - don't get here
                    }
                    if (n is MrfNodeState sn)
                    {
                        snlist.Add(sn);
                        if (sn.ChildNodes != null) for (int i = 0; i < sn.ChildNodes.Length; i++) if (sn.ChildNodes[i].NodeIndex != i)
                                { }//sanity check - don't get here
                    }
                }

                if (rlist.Count > 0)
                {
                    RootNode = rlist[0];
                }
                if (rlist.Count != 1)
                { }//sanity check - don't get here
                if (AllNodes.Length > 1000)
                { }
                return 0;
            }

            if (node is MrfNodeStateBase snode)
            {
                var c = 0;
                var clist = new List<MrfNode>();
                int ccount = snode.StateChildCount;
                if (ccount == 0)
                {
                    return 0;
                }
                for (int i = 0; i <= ccount; i++)
                {
                    if ((i == ccount) && (node is MrfNodeState))
                    { break; }
                    var cind = index + c + i + 1;
                    if (cind >= AllNodes.Length)
                    {
                        if (i != ccount)
                        { }//don't get here (tried to continue past the end of the array!)
                        break; 
                    }
                    var cnode = AllNodes[cind];
                    if (cnode is MrfNodeTail)
                    {
                        i--;
                        c++;
                        if (clist.Count > 0)
                        {
                            var prevnode = clist[clist.Count - 1];
                            if (prevnode is MrfNodeStateBase sprevnode)
                            {
                                if (sprevnode.TailNode != null)
                                { }//don't get here (tail node was already assigned?!?)
                                sprevnode.TailNode = cnode;
                                if (clist.Count == ccount)
                                { break; }//list is full, don't continue
                            }
                            else
                            { }//don't get here (previous node isn't a state??)
                        }
                        else
                        { }//don't get here (can't have tail without a previous node!)
                        continue;
                    }
                    else if (clist.Count == ccount)
                    { break; }//this node isn't a tail, but the list is already full, so it must belong to another node
                    if (cnode.NodeIndex != i)
                    { break; }//don't get here (node index mismatch!)
                    clist.Add(cnode);
                    c += BuildNodeHierarchy(cnode, cind);//recurse...
                }
                if (clist.Count != ccount)
                { }//don't get here (sanity check)
                snode.ChildNodes = clist.ToArray();
                return c + clist.Count;
            }

            return 0;
        }
    
    }
}
