Code/Wasm/GlobalSection.cs
using System.Collections.Generic;
using System.IO;
using WasmBox.Wasm.Binary;

namespace WasmBox.Wasm {
    /// <summary>
    /// Represents a global section.
    /// </summary>
    public sealed class GlobalSection : Section {
        /// <summary>
        /// Creates an empty global section.
        /// </summary>
        public GlobalSection() {
            this.GlobalVariables = new List<GlobalVariable>();
        }

        /// <summary>
        /// Creates a global from the given list of global variables.
        /// </summary>
        /// <param name="globalVariables">The global section's list of global variables.</param>
        public GlobalSection(IEnumerable<GlobalVariable> globalVariables)
            : this(globalVariables, new byte[0]) {
        }

        /// <summary>
        /// Creates a global section from the given list of global variables and additional payload.
        /// </summary>
        /// <param name="globalVariables">The global section's list of global variables.</param>
        /// <param name="extraPayload">The global section's additional payload.</param>
        public GlobalSection(IEnumerable<GlobalVariable> globalVariables, byte[] extraPayload) {
            this.GlobalVariables = new List<GlobalVariable>(globalVariables);
            this.ExtraPayload = extraPayload;
        }

        /// <inheritdoc/>
        public override SectionName Name => new SectionName(SectionCode.Global);

        /// <summary>
        /// Gets this global section's list of global variables.
        /// </summary>
        /// <returns>A list of global variable definitions.</returns>
        public List<GlobalVariable> GlobalVariables { get; private set; }

        /// <summary>
        /// This global section's additional payload.
        /// </summary>
        /// <returns>The additional payload, as an array of bytes.</returns>
        public byte[] ExtraPayload { get; set; }

        /// <summary>
        /// Writes this WebAssembly section's payload to the given binary WebAssembly writer.
        /// </summary>
        /// <param name="writer">The writer to which the payload is written.</param>
        public override void WritePayloadTo(BinaryWasmWriter writer) {
            writer.WriteVarUInt32((uint)GlobalVariables.Count);
            foreach (var index in GlobalVariables) {
                index.WriteTo(writer);
            }
            writer.Writer.Write(ExtraPayload);
        }

        /// <summary>
        /// Reads the global section with the given header.
        /// </summary>
        /// <param name="header">The section header.</param>
        /// <param name="reader">The WebAssembly file reader.</param>
        /// <returns>The parsed section.</returns>
        public static GlobalSection ReadSectionPayload(SectionHeader header, BinaryWasmReader reader) {
            long startPos = reader.Position;
            // Read the global variable definitions.
            uint count = reader.ReadVarUInt32();
            var globalVars = new List<GlobalVariable>();
            for (uint i = 0; i < count; i++) {
                globalVars.Add(GlobalVariable.ReadFrom(reader));
            }

            // Skip any remaining bytes.
            var extraPayload = reader.ReadRemainingPayload(startPos, header);
            return new GlobalSection(globalVars, extraPayload);
        }

        /// <inheritdoc/>
        public override void Dump(TextWriter writer) {
            writer.Write(Name.ToString());
            writer.Write("; number of entries: ");
            writer.Write(GlobalVariables.Count);
            writer.WriteLine();
            var indentedWriter = DumpHelpers.CreateIndentedTextWriter(writer);
            for (int i = 0; i < GlobalVariables.Count; i++) {
                writer.Write("#{0}:", i);
                indentedWriter.WriteLine();
                GlobalVariables[i].Dump(indentedWriter);
                writer.WriteLine();
            }
            if (ExtraPayload.Length > 0) {
                writer.Write("Extra payload size: ");
                writer.Write(ExtraPayload.Length);
                writer.WriteLine();
                DumpHelpers.DumpBytes(ExtraPayload, writer);
                writer.WriteLine();
            }
        }
    }

    /// <summary>
    /// Describes a global variable's type and mutability.
    /// </summary>
    public sealed class GlobalType {
        /// <summary>
        /// Creates a global type from the given content type and mutability.
        /// </summary>
        /// <param name="contentType">The type of content in the global type.</param>
        /// <param name="isMutable">The global type's mutability.</param>
        public GlobalType(WasmValueType contentType, bool isMutable) {
            this.ContentType = contentType;
            this.IsMutable = isMutable;
        }

        /// <summary>
        /// Gets or sets the type of content stored in globals of this type.
        /// </summary>
        /// <returns>The type of content stored in globals of this type.</returns>
        public WasmValueType ContentType { get; set; }

        /// <summary>
        /// Gets or sets the mutability of globals of this type.
        /// </summary>
        /// <returns>The mutability of globals of this type.</returns>
        public bool IsMutable { get; set; }

        /// <summary>
        /// Reads a global variable type from the given WebAssembly reader.
        /// </summary>
        /// <param name="reader">The WebAssembly reader to use.</param>
        /// <returns>The global variable type that was read.</returns>
        public static GlobalType ReadFrom(BinaryWasmReader reader) {
            return new GlobalType(reader.ReadWasmValueType(), reader.ReadVarUInt1());
        }

        /// <summary>
        /// Writes this global variable type to the given WebAssembly writer.
        /// </summary>
        /// <param name="writer">The WebAssembly writer to use.</param>
        public void WriteTo(BinaryWasmWriter writer) {
            writer.WriteWasmValueType(ContentType);
            writer.WriteVarUInt1(IsMutable);
        }

        /// <summary>
        /// Writes a textual representation of this global variable type to the given writer.
        /// </summary>
        /// <param name="writer">The writer to which text is written.</param>
        public void Dump(TextWriter writer) {
            writer.Write("{type: ");
            DumpHelpers.DumpWasmType(ContentType, writer);
            writer.Write(", is_mutable: ");
            writer.Write(IsMutable);
            writer.Write("}");
        }
    }

    /// <summary>
    /// Describes a global variable's type, mutability and initial value.
    /// </summary>
    public sealed class GlobalVariable {
        /// <summary>
        /// Creates a global variable definition from the given type and initial value.
        /// </summary>
        /// <param name="type">The global variable definition's type.</param>
        /// <param name="initialValue">The global variable definition's initial value.</param>
        public GlobalVariable(GlobalType type, InitializerExpression initialValue) {
            this.Type = type;
            this.InitialValue = initialValue;
        }

        /// <summary>
        /// Gets or sets a description of this global variable.
        /// </summary>
        /// <returns>The global variable's description.</returns>
        public GlobalType Type { get; set; }

        /// <summary>
        /// Gets or sets this global variable's initial value.
        /// </summary>
        /// <returns>The initial value.</returns>
        public InitializerExpression InitialValue { get; set; }

        /// <summary>
        /// Reads a global variable definition from the given WebAssembly reader.
        /// </summary>
        /// <param name="reader">The WebAssembly reader to use.</param>
        /// <returns>The global variable definition that was read.</returns>
        public static GlobalVariable ReadFrom(BinaryWasmReader reader) {
            return new GlobalVariable(
                GlobalType.ReadFrom(reader),
                InitializerExpression.ReadFrom(reader));
        }

        /// <summary>
        /// Writes this global variable definition to the given WebAssembly writer.
        /// </summary>
        /// <param name="writer">The WebAssembly writer to use.</param>
        public void WriteTo(BinaryWasmWriter writer) {
            Type.WriteTo(writer);
            InitialValue.WriteTo(writer);
        }

        /// <summary>
        /// Writes a textual representation of this global variable definition to the given writer.
        /// </summary>
        /// <param name="writer">The writer to which text is written.</param>
        public void Dump(TextWriter writer) {
            writer.Write("- Type: ");
            Type.Dump(writer);
            writer.WriteLine();
            writer.Write("- Initial value:");
            var indentedWriter = DumpHelpers.CreateIndentedTextWriter(writer);
            foreach (var instruction in InitialValue.BodyInstructions) {
                indentedWriter.WriteLine();
                instruction.Dump(indentedWriter);
            }
        }
    }
}