Editor/RbxlReader/DataType/DataTypeHelper.cs
using System.Linq;
using RbxlReader.Chunks;
using RbxlReader.Instances;
using Sandbox;

namespace RbxlReader.DataTypes;

public static class DataTypeHelper {
    /// <summary>
    /// PropertyType enum entry to actual C# Type
    /// </summary>
    public static readonly IReadOnlyDictionary<PropertyType, Type> Types = new Dictionary<PropertyType, Type>() {
        // Basics
        {PropertyType.Int, typeof(int)},
        {PropertyType.Float, typeof(float)},
        {PropertyType.Int64, typeof(long)},
        {PropertyType.Double, typeof(double)},
        {PropertyType.String, typeof(string)},
        {PropertyType.Bool, typeof(bool)},
        {PropertyType.Enum, typeof(uint)},

        // Positional
        {PropertyType.Vector2, typeof(Vector2)},
        {PropertyType.Vector3, typeof(Vector3)},
        {PropertyType.Vector3int16, typeof(Vector3int16)},
        {PropertyType.Ray, typeof(Ray)},
        {PropertyType.Rect, typeof(Rect)},
        {PropertyType.UDim, typeof(UDim)},
        {PropertyType.UDim2, typeof(UDim2)},
        {PropertyType.CFrame, typeof(CFrame)},
        {PropertyType.Quaternion, typeof(Quaternion)},

        // Color
        {PropertyType.Color3, typeof(Color3)},
        {PropertyType.Color3uint8, typeof(Color3)},
        {PropertyType.BrickColor, typeof(BrickColor)},

        // Sequence
        {PropertyType.NumberSequence, typeof(NumberSequence)},
        {PropertyType.NumberRange, typeof(NumberRange)},

        //  random stuff lol
        {PropertyType.OptionalCFrame, typeof(Optional<CFrame>)},
        {PropertyType.SecurityCapabilities, typeof(ulong)},
        {PropertyType.ProtectedString, typeof(ProtectedString)},
        {PropertyType.SharedString, typeof(SharedString)},

        {PropertyType.Axes, typeof(Axes)},
        {PropertyType.Faces, typeof(Faces)},

        {(PropertyType)31, typeof(byte)},
        {PropertyType.Ref, typeof(int)}

    };

    /// <summary>
    /// Property Type whitelist that indicates what properties to look for.
    /// Maybe permanent or maybe temporary idk, dont wanna work on something i wont use.
    /// </summary>
    public static readonly IReadOnlyList<PropertyType> UsedTypes = new List<PropertyType>() {
        PropertyType.String,
        PropertyType.Bool,
        PropertyType.Int,
        PropertyType.Int64,
        PropertyType.Float,
        PropertyType.Double,
        (PropertyType)18, //enum
        PropertyType.Ref,

        PropertyType.Vector3,
        PropertyType.CFrame,
        
        PropertyType.Color3,
        PropertyType.BrickColor,
        (PropertyType)26 //color3uint8
    };

    /// <summary>
    /// Parse the data left in PROP chunk. Need to read PROP header first (Class, PropertyName, etc.) before calling this method.
    /// Writes into props array.
    /// </summary>
    public static void ParsePropertiesInChunk(RbxlBinaryReader reader, PropertyType type, int instCount, InstanceProperty[] props) {
        var typeClass = Types[type];

        if (typeClass == null)
            throw new ArgumentNullException($"Type {type} isn't implemented.");
        

        //  shorthand functions
        var readInts = new Func<int[]>(() => reader.ReadInterleaved(instCount, reader.RotateInt32));
        var readFloats = new Func<float[]>(() => reader.ReadInterleaved(instCount, reader.RotateFloat));

        switch(type) {

            case PropertyType.String:
                readProps(props, instCount, i => {

                    string value = reader.ReadString();
                    
                    byte[] buffer = reader.GetLastStringBuffer();
                    props[i].RawBuffer = buffer;

                    return value;

                });
                break;
            
            case PropertyType.Bool: {
                readProps(props, instCount, i => reader.ReadBoolean());
                break;
            }
            
            case PropertyType.Int: {
                int[] ints = readInts();
                readProps(props, instCount, i => ints[i]);
                break;
            }

            case PropertyType.Int64: {
                long[] ints = reader.ReadInterleaved(instCount, reader.RotateInt64);
                readProps(props, instCount, i => ints[i]);
                break;
            }

            case PropertyType.Enum: {
                uint[] ints = reader.ReadInterleaved(instCount, BitConverter.ToUInt32);
                readProps(props, instCount, i => ints[i]);
                break;
            }
            
            case PropertyType.Float: {
                float[] floats = readFloats();
                readProps(props, instCount, i => floats[i]);
                break;
            }
            
            case PropertyType.Double: {
                readProps(props, instCount, i => reader.ReadDouble());
                break;
            }


            case PropertyType.Vector3: {
                float[] Xtable = readFloats(),
                        Ytable = readFloats(),
                        Ztable = readFloats();
                
                readProps(props, instCount, i => {
                    float x = Xtable[i],
                        y = Ytable[i],
                        z = Ztable[i];
                    
                    return new Vector3(x, y, z);
                });

                break;
            }

            case PropertyType.CFrame: {
                float[][] matrices = new float[instCount][];

                for (int i = 0; i < instCount; i++) {
                    byte rawOrientId = reader.ReadByte();
                    

                    if (rawOrientId > 0) {
                        int orientId = (rawOrientId - 1) % 36;
                        var cf = CFrame.FromOrientId(orientId);
                        matrices[i] = cf.GetComponents();

                    } else {

                        float[] matrix = new float[9];

                        for (int v = 0; v < 9; v++) {
                            float value = reader.ReadFloat();
                            matrix[v] = value;
                        }

                        matrices[i] = matrix;
                    }
                }

                float[] cfX = readFloats(),
                        cfY = readFloats(),
                        cfZ = readFloats();
                    
                CFrame[] cframes = new CFrame[instCount];
                for (int i = 0; i < instCount; i++) {
                    float[] matrix = matrices[i];

                    float x = cfX[i],
                        y = cfY[i],
                        z = cfZ[i];

                    float[] components;

                    if (matrix.Length == 12) {
                        matrix[0] = x;
                        matrix[1] = y;
                        matrix[2] = z;

                        components = matrix; 
                    } else {
                        float[] pos = new float[] {x,y,z};
                        components = pos.Concat(matrix).ToArray();
                    }

                    cframes[i] = new(components);
                }

                readProps(props, instCount, i => cframes[i]);
                break;
            }

            case PropertyType.Color3: {
                float[] arrR = readFloats(),
                        arrG = readFloats(),
                        arrB = readFloats();

                readProps(props, instCount, i => {
                    float r = arrR[i],
                        g = arrG[i],
                        b = arrB[i];
                    
                    return new Color3(r, g, b);
                });
                
                break;
            }
            
            case PropertyType.BrickColor: {
                int[] colorIds = readInts();
                readProps(props, instCount, i => {
                    BrickColor color = BrickColor.FromNumber(i);
                    return color;
                });

                break;
            }

            case PropertyType.Color3uint8: {
                byte[] arrR = reader.ReadBytes(instCount),
                    arrG = reader.ReadBytes(instCount),
                    arrB = reader.ReadBytes(instCount);

                readProps(props, instCount, i => {
                    float r = arrR[i],
                        g = arrG[i],
                        b = arrB[i];
                    
                    return new Color3(r, g, b);
            });
    
            break;
            }

            case PropertyType.Ref: {
                var ints = reader.ReadInstanceIds(instCount);
                readProps(props, instCount, i => ints[i]);
                break;
            }

            default: {
                break;
            }

        }
    }

    private static void readProps(InstanceProperty[] props, int instCount, Func<int, object> read) {
        for (int i = 0; i < instCount; i++) {
            var prop = props[i];

            if (prop == null)
                continue;
            
            prop.Value = read(i);
        }
    }

    public static bool ContainsUsedTypes(byte num) {
        foreach (PropertyType type in UsedTypes) {
            if ((PropertyType)num == type) {
                return true;
            }
        }

        return false;
    }
}