Wasm/WasmFile.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using WasmBox.Wasm.Binary;

namespace WasmBox.Wasm {
    /// <summary>
    /// Represents a WebAssembly file.
    /// </summary>
    public sealed class WasmFile {
        /// <summary>
        /// Creates an empty WebAssembly file.
        /// </summary>
        public WasmFile()
            : this(VersionHeader.MvpHeader) { }

        /// <summary>
        /// Creates an empty WebAssembly file with the given header.
        /// </summary>
        /// <param name="header">The WebAssembly version header.</param>
        public WasmFile(VersionHeader header)
            : this(header, Enumerable.Empty<Section>()) { }

        /// <summary>
        /// Creates a WebAssembly file from the given list of sections.
        /// </summary>
        /// <param name="header">The WebAssembly version header.</param>
        /// <param name="sections">The list of all sections in the WebAssembly file.</param>
        public WasmFile( VersionHeader header, IEnumerable<Section> sections ) {
            this.Header = header;
            this.Sections = new List<Section>( sections );
        }

        /// <summary>
        /// Gets the WebAssembly version header for this file.
        /// </summary>
        /// <returns>The WebAssembly version header.</returns>
        public VersionHeader Header { get; set; }

        /// <summary>
        /// Gets a list of all sections in this file.
        /// </summary>
        /// <returns>All sections in this file.</returns>
        public List<Section> Sections { get; private set; }

        /// <summary>
        /// Gets or sets this module's name as defined in the names section.
        /// </summary>
        /// <value>
        /// The module's name if the names section defines a module name entry;
        /// otherwise, <c>null</c>.
        /// </value>
        public string ModuleName {
            get {
                return ModuleNameEntryOrNull?.ModuleName;
            }
            set {
                var entry = ModuleNameEntryOrNull;
                if (entry == null) {
                    AddNameEntry(new ModuleNameEntry(value));
                }
                else {
                    entry.ModuleName = value;
                }
            }
        }

        private ModuleNameEntry ModuleNameEntryOrNull {
            get {
                var nameSection = GetFirstSectionOrNull<NameSection>();
                if (nameSection == null) {
                    return null;
                }
                else {
                    var firstModuleNameEntry = nameSection.Names.OfType<ModuleNameEntry>().FirstOrDefault();
                    if (firstModuleNameEntry == null) {
                        return null;
                    }
                    else {
                        return firstModuleNameEntry;
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the index of this module's entry point function, if any.
        /// </summary>
        /// <value>An entry point index.</value>
        public uint? StartFunctionIndex {
            get {
                var startSection = GetFirstSectionOrNull<StartSection>();
                return startSection?.StartFunctionIndex;
            }
            set {
                if (value.HasValue) {
                    var startSection = GetFirstSectionOrNull<StartSection>();
                    if (startSection == null) {
                        InsertSection(new StartSection(value.Value));
                    }
                    else {
                        startSection.StartFunctionIndex = value.Value;
                    }
                }
                else {
                    Sections.RemoveAll(s => s is StartSection);
                }
            }
        }

        /// <summary>
        /// Gets a list of all sections of the given type.
        /// </summary>
        /// <returns>A list of sections with the given type.</returns>
        public IReadOnlyList<T> GetSections<T>()
            where T : Section {
            var results = new List<T>();
            for (int i = 0; i < Sections.Count; i++) {
                var sec = Sections[i];
                if (sec is T) {
                    results.Add((T)sec);
                }
            }
            return results;
        }

        /// <summary>
        /// Gets a list of all sections with the given section name.
        /// </summary>
        /// <param name="name">The section name to look for.</param>
        /// <returns>A list of sections with the given section name.</returns>
        public IReadOnlyList<Section> GetSections(SectionName name) {
            var results = new List<Section>();
            for (int i = 0; i < Sections.Count; i++) {
                var sec = Sections[i];
                if (sec.Name == name) {
                    results.Add(sec);
                }
            }
            return results;
        }

        /// <summary>
        /// Gets the first section with the given name. If no such section exists,
        /// <c>null</c> is returned.
        /// </summary>
        /// <param name="name">The section name to look for.</param>
        /// <returns>The first section with the given name, if it exists; otherwise, <c>null</c>.</returns>
        public Section GetFirstSectionOrNull(SectionName name) {
            for (int i = 0; i < Sections.Count; i++) {
                var sec = Sections[i];
                if (sec.Name == name) {
                    return sec;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets the first section of the given type. If no such section exists,
        /// <c>null</c> is returned.
        /// </summary>
        /// <returns>The first section of the given type, if it exists; otherwise, <c>null</c>.</returns>
        public T GetFirstSectionOrNull<T>()
            where T : Section {
            for (int i = 0; i < Sections.Count; i++) {
                var sec = Sections[i];
                if (sec is T) {
                    return (T)sec;
                }
            }
            return default(T);
        }

        /// <summary>
        /// Writes this WebAssembly file to the given stream using the binary WebAssembly file encoding.
        /// </summary>
        /// <param name="target">The stream to write to.</param>
        public void WriteBinaryTo(Stream target) {
            var writer = new BinaryWriter(target);
            var wasmWriter = new BinaryWasmWriter(writer);
            wasmWriter.WriteFile(this);
        }

		/// <summary>
		/// Writes a textual representation of this WebAssembly file to the given text writer.
		/// Note that this representation is intended as a human-readable debugging format that may
		/// change at any time, not as a first-class textual WebAssembly module encoding.
		/// </summary>
		/// <param name="writer">The text writer use.</param>
		public void Dump(TextWriter writer) {
            writer.Write(
                "WebAssembly module; magic number: {0}, version number: {1}",
                DumpHelpers.FormatHex(Header.Magic),
                Header.Version);

            foreach (var section in Sections) {
                writer.WriteLine();
                section.Dump(writer);
            }
        }
        
        public static WasmFile ReadBinary( Span<byte> source ) {
            using var stream = new MemoryStream(source.ToArray(), writable: false);
            return ReadBinary( stream );
        }

        /// <summary>
        /// Reads a binary WebAssembly from the given stream.
        /// </summary>
        /// <param name="source">The stream from which a WebAssembly file is to be read.</param>
        /// <returns>The WebAssembly file.</returns>
        public static WasmFile ReadBinary(Stream source) {
            // Create a WebAssembly reader and read the file.
            var reader = new BinaryReader(source);
            var wasmReader = new BinaryWasmReader(reader);
            return wasmReader.ReadFile();
        }

        /// <summary>
        /// Reads a binary WebAssembly from the given stream.
        /// </summary>
        /// <param name="source">The stream from which a WebAssembly file is to be read.</param>
        /// <param name="streamIsEmpty">Tests if the input stream is empty.</param>
        /// <returns>The WebAssembly file.</returns>
        public static WasmFile ReadBinary(Stream source, Func<bool> streamIsEmpty) {
            // Create a WebAssembly reader and read the file.
            var reader = new BinaryReader(source);
            var wasmReader = new BinaryWasmReader(reader, streamIsEmpty);
            return wasmReader.ReadFile();
        }

        /// <summary>
        /// Inserts a new section into the WebAssembly file.
        /// The section is inserted in a way that preserves the ordering
        /// of sections as specified by the WebAssembly binary format.
        /// </summary>
        /// <param name="section">The section to insert.</param>
        /// <returns>The index in the section list at which <paramref name="section"/> is inserted.</returns>
        public int InsertSection(Section section) {
            if (!section.Name.IsCustom) {
                // The WebAssembly binary format requires that non-custom sections
                // are ordered by their codes.
                for (int i = 0; i < Sections.Count; i++) {
                    if (!Sections[i].Name.IsCustom && section.Name.Code < Sections[i].Name.Code) {
                        Sections.Insert(i, section);
                        return i;
                    }
                }
            }
            Sections.Add(section);
            return Sections.Count - 1;
        }

        /// <summary>
        /// Adds a name entry to the names section, defining a new names section
        /// if one doesn't exist already.
        /// </summary>
        /// <param name="entry">A name entry to add.</param>
        /// <returns>The index in the name section of the newly added name entry.</returns>
        public uint AddNameEntry(NameEntry entry) {
            var names = GetFirstSectionOrNull<NameSection>();
            if (names == null) {
                InsertSection(names = new NameSection());
            }
            names.Names.Add(entry);
            return (uint)names.Names.Count - 1;
        }

        /// <summary>
        /// Adds a user-defined memory to this module's memory section, defining
        /// a new memory section if one doesn't exist already.
        /// </summary>
        /// <param name="memory">The memory to add.</param>
        /// <returns>The index in the memory section of the newly added memory.</returns>
        public uint AddMemory(MemoryType memory) {
            var memories = GetFirstSectionOrNull<MemorySection>();
            if (memories == null) {
                InsertSection(memories = new MemorySection());
            }
            memories.Memories.Add(memory);
            return (uint)memories.Memories.Count - 1;
        }

        /// <summary>
        /// Adds a data segment to this module's data section, defining
        /// a new data section if one doesn't exist already.
        /// </summary>
        /// <param name="segment">The data segment to add.</param>
        /// <returns>The index in the data section of the newly added data segment.</returns>
        public uint AddDataSegment(DataSegment segment) {
            var data = GetFirstSectionOrNull<DataSection>();
            if (data == null) {
                InsertSection(data = new DataSection());
            }
            data.Segments.Add(segment);
            return (uint)data.Segments.Count - 1;
        }

        /// <summary>
        /// Adds an import to this module's import section, defining
        /// a new import section if one doesn't exist already.
        /// </summary>
        /// <param name="import">The import to add.</param>
        /// <returns>The index in the import section of the newly added import.</returns>
        public uint AddImport(ImportedValue import) {
            var imports = GetFirstSectionOrNull<ImportSection>();
            if (imports == null) {
                InsertSection(imports = new ImportSection());
            }
            imports.Imports.Add(import);
            return (uint)imports.Imports.Count - 1;
        }

        /// <summary>
        /// Adds an export to this module's export section, defining
        /// a new export section if one doesn't exist already.
        /// </summary>
        /// <param name="export">The export to add.</param>
        /// <returns>The index in the export section of the newly added export.</returns>
        public uint AddExport(ExportedValue export) {
            var exports = GetFirstSectionOrNull<ExportSection>();
            if (exports == null) {
                InsertSection(exports = new ExportSection());
            }
            exports.Exports.Add(export);
            return (uint)exports.Exports.Count - 1;
        }

        /// <summary>
        /// Adds a function type to this module's type section, defining
        /// a new type section if one doesn't exist already.
        /// </summary>
        /// <param name="type">The type to add.</param>
        /// <returns>The index in the type section of the newly added function type.</returns>
        public uint AddFunctionType(FunctionType type) {
            var types = GetFirstSectionOrNull<TypeSection>();
            if (types == null) {
                InsertSection(types = new TypeSection());
            }
            types.FunctionTypes.Add(type);
            return (uint)types.FunctionTypes.Count - 1;
        }

        /// <summary>
        /// Adds a table to this module's type section, defining
        /// a new table section if one doesn't exist already.
        /// </summary>
        /// <param name="table">The table to add.</param>
        /// <returns>The index in the table section of the newly added table.</returns>
        public uint AddTable(TableType table) {
            var tables = GetFirstSectionOrNull<TableSection>();
            if (tables == null) {
                InsertSection(tables = new TableSection());
            }
            tables.Tables.Add(table);
            return (uint)tables.Tables.Count - 1;
        }

        /// <summary>
        /// Adds a element segment to this module's element section, defining
        /// a new element section if one doesn't exist already.
        /// </summary>
        /// <param name="segment">The element segment to add.</param>
        /// <returns>The index in the element section of the newly added element segment.</returns>
        public uint AddElementSegment(ElementSegment segment) {
            var elements = GetFirstSectionOrNull<ElementSection>();
            if (elements == null) {
                InsertSection(elements = new ElementSection());
            }
            elements.Segments.Add(segment);
            return (uint)elements.Segments.Count - 1;
        }

        /// <summary>
        /// Adds a function definition to this module.
        /// </summary>
        /// <param name="functionTypeIndex">The index in the type section of the function's type.</param>
        /// <param name="functionBody">The body of the function to define.</param>
        /// <returns>The index in the function section of the newly added function definition.</returns>
        public uint AddFunction(uint functionTypeIndex, FunctionBody functionBody) {
            var funs = GetFirstSectionOrNull<FunctionSection>();
            if (funs == null) {
                InsertSection(funs = new FunctionSection());
            }
            var code = GetFirstSectionOrNull<CodeSection>();
            if (code == null) {
                InsertSection(code = new CodeSection());
            }
            funs.FunctionTypes.Add(functionTypeIndex);
            code.Bodies.Add(functionBody);
            return (uint)funs.FunctionTypes.Count - 1;
        }

        /// <summary>
        /// Adds a global variable definition to this module.
        /// </summary>
        /// <param name="globalVariable">A global variable definition to introduce.</param>
        /// <returns>The index in the global section of the newly added global variable definition.</returns>
        public uint AddGlobal(GlobalVariable globalVariable) {
            var globals = GetFirstSectionOrNull<GlobalSection>();
            if (globals == null) {
                InsertSection(globals = new GlobalSection());
            }
            globals.GlobalVariables.Add(globalVariable);
            return (uint)globals.GlobalVariables.Count - 1;
        }
    }
}