/*
 * Copyright 2021 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import Foundation

/// ``FlatBufferBuilder`` builds a `FlatBuffer` through manipulating its internal state.
///
/// This is done by creating a ``ByteBuffer`` that hosts the incoming data and
/// has a hardcoded growth limit of `2GiB` which is set by the Flatbuffers standards.
///
/// ```swift
/// var builder = FlatBufferBuilder()
/// ```
/// The builder should be always created as a variable, since it would be passed into the writers
///
@frozen
public struct FlatBufferBuilder {

  /// Storage for the Vtables used in the buffer are stored in here, so they would be written later in EndTable
  @usableFromInline internal var _vtableStorage = VTableStorage()
  /// Flatbuffer data will be written into
  @usableFromInline internal var _bb: ByteBuffer

  /// Reference Vtables that were already written to the buffer
  private var _vtables: [UOffset] = []
  /// A check if the buffer is being written into by a different table
  private var isNested = false
  /// Dictonary that stores a map of all the strings that were written to the buffer
  private var stringOffsetMap: [String: Offset] = [:]
  /// A check to see if finish(::) was ever called to retreive data object
  private var finished = false
  /// A check to see if the buffer should serialize Default values
  private var serializeDefaults: Bool

  /// Current alignment for the buffer
  var _minAlignment: Int = 0 {
    didSet {
      _bb.alignment = _minAlignment
    }
  }

  /// Gives a read access to the buffer's size
  public var size: UOffset { _bb.size }

  /// Data representation of the buffer
  ///
  /// Should only be used after ``finish(offset:addPrefix:)`` is called
  public var data: Data {
    assert(finished, "Data shouldn't be called before finish()")
    return Data(
      bytes: _bb.memory.advanced(by: _bb.writerIndex),
      count: _bb.capacity &- _bb.writerIndex)
  }

  /// Returns the underlying bytes in the ``ByteBuffer``
  ///
  /// Note: This should be used with caution.
  public var fullSizedByteArray: [UInt8] {
    let ptr = UnsafeBufferPointer(
      start: _bb.memory.assumingMemoryBound(to: UInt8.self),
      count: _bb.capacity)
    return Array(ptr)
  }

  /// Returns the written bytes into the ``ByteBuffer``
  ///
  /// Should only be used after ``finish(offset:addPrefix:)`` is called
  public var sizedByteArray: [UInt8] {
    assert(finished, "Data shouldn't be called before finish()")
    let cp = _bb.capacity &- _bb.writerIndex
    let start = _bb.memory.advanced(by: _bb.writerIndex)
      .bindMemory(to: UInt8.self, capacity: cp)

    let ptr = UnsafeBufferPointer(start: start, count: cp)
    return Array(ptr)
  }

  /// Returns the original ``ByteBuffer``
  ///
  /// Returns the current buffer that was just created
  /// with the offsets, and data written to it.
  public var buffer: ByteBuffer { _bb }

  /// Returns a newly created sized ``ByteBuffer``
  ///
  /// returns a new buffer that is sized to the data written
  /// to the main buffer
  public var sizedBuffer: ByteBuffer {
    assert(finished, "Data shouldn't be called before finish()")
    return ByteBuffer(
      memory: _bb.memory.advanced(by: _bb.reader),
      count: Int(_bb.size))
  }

  // MARK: - Init

  /// Initialize the buffer with a size
  /// - Parameters:
  ///   - initialSize: Initial size for the buffer
  ///   - force: Allows default to be serialized into the buffer
  ///
  /// This initializes a new builder with an initialSize that would initialize
  /// a new ``ByteBuffer``. ``FlatBufferBuilder`` by default doesnt serialize defaults
  /// however the builder can be force by passing true for `serializeDefaults`
  public init(
    initialSize: Int32 = 1024,
    serializeDefaults force: Bool = false)
  {
    assert(initialSize > 0, "Size should be greater than zero!")
    guard isLitteEndian else {
      fatalError(
        "Reading/Writing a buffer in big endian machine is not supported on swift")
    }
    serializeDefaults = force
    _bb = ByteBuffer(initialSize: Int(initialSize))
  }

  /// Clears the builder and the buffer from the written data.
  mutating public func clear() {
    _minAlignment = 0
    isNested = false
    stringOffsetMap = [:]
    _vtables = []
    _vtableStorage.clear()
    _bb.clear()
  }

  // MARK: - Create Tables

  /// Checks if the required fields were serialized into the buffer
  /// - Parameters:
  ///   - table: offset for the table
  ///   - fields: Array of all the important fields to be serialized
  ///
  /// *NOTE: Never call this function, this is only supposed to be called
  /// by the generated code*
  mutating public func require(table: Offset, fields: [Int32]) {
    for field in fields {
      let start = _bb.capacity &- Int(table.o)
      let startTable = start &- Int(_bb.read(def: Int32.self, position: start))
      let isOkay = _bb.read(
        def: VOffset.self,
        position: startTable &+ Int(field)) != 0
      assert(isOkay, "Flatbuffers requires the following field")
    }
  }

  /// Finished the buffer by adding the file id and then calling finish
  /// - Parameters:
  ///   - offset: Offset of the table
  ///   - fileId: Takes the fileId
  ///   - prefix: if false it wont add the size of the buffer
  ///
  /// ``finish(offset:fileId:addPrefix:)`` should be called at the end of creating
  /// a table
  /// ```swift
  /// var root = SomeObject
  ///   .createObject(&builder,
  ///   name: nameOffset)
  /// builder.finish(
  ///   offset: root,
  ///   fileId: "ax1a",
  ///   addPrefix: true)
  /// ```
  /// File id would append a file id name at the end of the written bytes before,
  /// finishing the buffer.
  ///
  /// Whereas, if `addPrefix` is true, the written bytes would
  /// include the size of the current buffer.
  mutating public func finish(
    offset: Offset,
    fileId: String,
    addPrefix prefix: Bool = false)
  {
    let size = MemoryLayout<UOffset>.size
    preAlign(
      len: size &+ (prefix ? size : 0) &+ FileIdLength,
      alignment: _minAlignment)
    assert(fileId.count == FileIdLength, "Flatbuffers requires file id to be 4")
    _bb.push(string: fileId, len: 4)
    finish(offset: offset, addPrefix: prefix)
  }

  /// Finished the buffer by adding the file id, offset, and prefix to it.
  /// - Parameters:
  ///   - offset: Offset of the table
  ///   - prefix: if false it wont add the size of the buffer
  ///
  /// ``finish(offset:addPrefix:)`` should be called at the end of creating
  /// a table
  /// ```swift
  /// var root = SomeObject
  ///   .createObject(&builder,
  ///   name: nameOffset)
  /// builder.finish(
  ///   offset: root,
  ///   addPrefix: true)
  /// ```
  /// If `addPrefix` is true, the written bytes would
  /// include the size of the current buffer.
  mutating public func finish(
    offset: Offset,
    addPrefix prefix: Bool = false)
  {
    notNested()
    let size = MemoryLayout<UOffset>.size
    preAlign(len: size &+ (prefix ? size : 0), alignment: _minAlignment)
    push(element: refer(to: offset.o))
    if prefix { push(element: _bb.size) }
    _vtableStorage.clear()
    finished = true
  }

  /// ``startTable(with:)`` will let the builder know, that a new object is being serialized.
  ///
  /// The function will fatalerror if called while there is another object being serialized.
  /// ```swift
  /// let start = Monster
  ///   .startMonster(&fbb)
  /// ```
  /// - Parameter numOfFields: Number of elements to be written to the buffer
  /// - Returns: Offset of the newly started table
  mutating public func startTable(with numOfFields: Int) -> UOffset {
    notNested()
    isNested = true
    _vtableStorage.start(count: numOfFields)
    return _bb.size
  }

  /// ``endTable(at:)`` will let the ``FlatBufferBuilder`` know that the
  /// object that's written to it is completed
  ///
  /// This would be called after all the elements are serialized,
  /// it will add the current vtable into the ``ByteBuffer``.
  /// The functions will `fatalError` in case the object is called
  /// without ``startTable(with:)``, or the object has exceeded  the limit of 2GB.
  ///
  /// - Parameter startOffset:Start point of the object written
  /// - returns: The root of the table
  mutating public func endTable(at startOffset: UOffset)  -> UOffset {
    assert(isNested, "Calling endtable without calling starttable")
    let sizeofVoffset = MemoryLayout<VOffset>.size
    let vTableOffset = push(element: SOffset(0))

    let tableObjectSize = vTableOffset &- startOffset
    assert(tableObjectSize < 0x10000, "Buffer can't grow beyond 2 Gigabytes")
    let _max = Int(_vtableStorage.maxOffset) &+ sizeofVoffset

    _bb.fill(padding: _max)
    _bb.write(
      value: VOffset(tableObjectSize),
      index: _bb.writerIndex &+ sizeofVoffset,
      direct: true)
    _bb.write(value: VOffset(_max), index: _bb.writerIndex, direct: true)

    var itr = 0
    while itr < _vtableStorage.writtenIndex {
      let loaded = _vtableStorage.load(at: itr)
      itr = itr &+ _vtableStorage.size
      guard loaded.offset != 0 else { continue }
      let _index = (_bb.writerIndex &+ Int(loaded.position))
      _bb.write(
        value: VOffset(vTableOffset &- loaded.offset),
        index: _index,
        direct: true)
    }

    _vtableStorage.clear()
    let vt_use = _bb.size

    var isAlreadyAdded: Int?

    let vt2 = _bb.memory.advanced(by: _bb.writerIndex)
    let len2 = vt2.load(fromByteOffset: 0, as: Int16.self)

    for table in _vtables {
      let position = _bb.capacity &- Int(table)
      let vt1 = _bb.memory.advanced(by: position)
      let len1 = _bb.read(def: Int16.self, position: position)
      if len2 != len1 || 0 != memcmp(vt1, vt2, Int(len2)) { continue }

      isAlreadyAdded = Int(table)
      break
    }

    if let offset = isAlreadyAdded {
      let vTableOff = Int(vTableOffset)
      let space = _bb.capacity &- vTableOff
      _bb.write(value: Int32(offset &- vTableOff), index: space, direct: true)
      _bb.pop(_bb.capacity &- space)
    } else {
      _bb.write(value: Int32(vt_use &- vTableOffset), index: Int(vTableOffset))
      _vtables.append(_bb.size)
    }
    isNested = false
    return vTableOffset
  }

  // MARK: - Builds Buffer

  /// Asserts to see if the object is not nested
  @usableFromInline
  mutating internal func notNested()  {
    assert(!isNested, "Object serialization must not be nested")
  }

  /// Changes the minimuim alignment of the buffer
  /// - Parameter size: size of the current alignment
  @inline(__always)
  mutating internal func minAlignment(size: Int) {
    if size > _minAlignment {
      _minAlignment = size
    }
  }

  /// Gets the padding for the current element
  /// - Parameters:
  ///   - bufSize: Current size of the buffer + the offset of the object to be written
  ///   - elementSize: Element size
  @inline(__always)
  mutating internal func padding(
    bufSize: UInt32,
    elementSize: UInt32) -> UInt32
  {
    ((~bufSize) &+ 1) & (elementSize - 1)
  }

  /// Prealigns the buffer before writting a new object into the buffer
  /// - Parameters:
  ///   - len:Length of the object
  ///   - alignment: Alignment type
  @usableFromInline
  mutating internal func preAlign(len: Int, alignment: Int) {
    minAlignment(size: alignment)
    _bb.fill(padding: Int(padding(
      bufSize: _bb.size &+ UOffset(len),
      elementSize: UOffset(alignment))))
  }

  /// Prealigns the buffer before writting a new object into the buffer
  /// - Parameters:
  ///   - len: Length of the object
  ///   - type: Type of the object to be written
  @usableFromInline
  mutating internal func preAlign<T: Scalar>(len: Int, type: T.Type) {
    preAlign(len: len, alignment: MemoryLayout<T>.size)
  }

  /// Refers to an object that's written in the buffer
  /// - Parameter off: the objects index value
  @usableFromInline
  mutating internal func refer(to off: UOffset) -> UOffset {
    let size = MemoryLayout<UOffset>.size
    preAlign(len: size, alignment: size)
    return _bb.size &- off &+ UInt32(size)
  }

  /// Tracks the elements written into the buffer
  /// - Parameters:
  ///   - offset: The offset of the element witten
  ///   - position: The position of the element
  @usableFromInline
  mutating internal func track(offset: UOffset, at position: VOffset) {
    _vtableStorage.add(loc: FieldLoc(offset: offset, position: position))
  }

  // MARK: - Inserting Vectors

  /// ``startVector(_:elementSize:)`` creates a new vector within buffer
  ///
  /// The function checks if there is a current object being written, if
  /// the check passes it creates a buffer alignment of `length * elementSize`
  /// ```swift
  /// builder.startVector(
  ///   int32Values.count, elementSize: 4)
  /// ```
  ///
  /// - Parameters:
  ///   - len: Length of vector to be created
  ///   - elementSize: Size of object type to be written
  mutating public func startVector(_ len: Int, elementSize: Int) {
    notNested()
    isNested = true
    preAlign(len: len &* elementSize, type: UOffset.self)
    preAlign(len: len &* elementSize, alignment: elementSize)
  }

  /// ``endVector(len:)`` ends the currently created vector
  ///
  /// Calling ``endVector(len:)`` requires the length, of the current
  /// vector. The length would be pushed to indicate the count of numbers
  /// within the vector. If ``endVector(len:)`` is called without
  /// ``startVector(_:elementSize:)`` it asserts.
  ///
  /// ```swift
  /// let vectorOffset = builder.
  ///   endVector(len: int32Values.count)
  /// ```
  ///
  /// - Parameter len: Length of the buffer
  /// - Returns: Returns the current ``Offset`` in the ``ByteBuffer``
  mutating public func endVector(len: Int) -> Offset {
    assert(isNested, "Calling endVector without calling startVector")
    isNested = false
    return Offset(offset: push(element: Int32(len)))
  }

  /// Creates a vector of type ``Scalar`` into the ``ByteBuffer``
  ///
  /// ``createVector(_:)-4swl0`` writes a vector of type Scalars into
  /// ``ByteBuffer``. This is a convenient method instead of calling,
  /// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
  /// ```swift
  /// let vectorOffset = builder.
  ///   createVector([1, 2, 3, 4])
  /// ```
  ///
  /// The underlying implementation simply calls ``createVector(_:size:)-4lhrv``
  ///
  /// - Parameter elements: elements to be written into the buffer
  /// - returns: ``Offset`` of the vector
  mutating public func createVector<T: Scalar>(_ elements: [T]) -> Offset {
    createVector(elements, size: elements.count)
  }

  ///  Creates a vector of type Scalar in the buffer
  ///
  /// ``createVector(_:)-4swl0`` writes a vector of type Scalars into
  /// ``ByteBuffer``. This is a convenient method instead of calling,
  /// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
  /// ```swift
  /// let vectorOffset = builder.
  ///   createVector([1, 2, 3, 4], size: 4)
  /// ```
  ///
  /// - Parameter elements: Elements to be written into the buffer
  /// - Parameter size: Count of elements
  /// - returns: ``Offset`` of the vector
  mutating public func createVector<T: Scalar>(
    _ elements: [T],
    size: Int) -> Offset
  {
    let size = size
    startVector(size, elementSize: MemoryLayout<T>.size)
    _bb.push(elements: elements)
    return endVector(len: size)
  }

  /// Creates a vector of type ``Enum`` into the ``ByteBuffer``
  ///
  /// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into
  /// ``ByteBuffer``. This is a convenient method instead of calling,
  /// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
  /// ```swift
  /// let vectorOffset = builder.
  ///   createVector([.swift, .cpp])
  /// ```
  ///
  /// The underlying implementation simply calls ``createVector(_:size:)-7cx6z``
  ///
  /// - Parameter elements: elements to be written into the buffer
  /// - returns: ``Offset`` of the vector
  mutating public func createVector<T: Enum>(_ elements: [T]) -> Offset {
    createVector(elements, size: elements.count)
  }

  /// Creates a vector of type ``Enum`` into the ``ByteBuffer``
  ///
  /// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into
  /// ``ByteBuffer``. This is a convenient method instead of calling,
  /// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
  /// ```swift
  /// let vectorOffset = builder.
  ///   createVector([.swift, .cpp])
  /// ```
  ///
  /// - Parameter elements: Elements to be written into the buffer
  /// - Parameter size: Count of elements
  /// - returns: ``Offset`` of the vector
  mutating public func createVector<T: Enum>(
    _ elements: [T],
    size: Int) -> Offset
  {
    let size = size
    startVector(size, elementSize: T.byteSize)
    for e in elements.reversed() {
      _bb.push(value: e.value, len: T.byteSize)
    }
    return endVector(len: size)
  }

  /// Creates a vector of already written offsets
  ///
  /// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into
  /// ``ByteBuffer``. This is a convenient method instead of calling,
  /// ``startVector(_:elementSize:)`` and then ``endVector(len:)``.
  ///
  /// The underlying implementation simply calls ``createVector(ofOffsets:len:)``
  ///
  /// ```swift
  /// let namesOffsets = builder.
  ///   createVector(ofOffsets: [name1, name2])
  /// ```
  /// - Parameter offsets: Array of offsets of type ``Offset``
  /// - returns: ``Offset`` of the vector
  mutating public func createVector(ofOffsets offsets: [Offset]) -> Offset {
    createVector(ofOffsets: offsets, len: offsets.count)
  }

  /// Creates a vector of already written offsets
  ///
  /// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into
  /// ``ByteBuffer``. This is a convenient method instead of calling,
  /// ``startVector(_:elementSize:)`` and then ``endVector(len:)``
  ///
  /// ```swift
  /// let namesOffsets = builder.
  ///   createVector(ofOffsets: [name1, name2])
  /// ```
  ///
  /// - Parameter offsets: Array of offsets of type ``Offset``
  /// - Parameter size: Count of elements
  /// - returns: ``Offset`` of the vector
  mutating public func createVector(
    ofOffsets offsets: [Offset],
    len: Int) -> Offset
  {
    startVector(len, elementSize: MemoryLayout<Offset>.size)
    for o in offsets.reversed() {
      push(element: o)
    }
    return endVector(len: len)
  }

  /// Creates a vector of strings
  ///
  /// ``createVector(ofStrings:)`` creates a vector of `String` into
  /// ``ByteBuffer``. This is a convenient method instead of manually
  /// creating the string offsets, you simply pass it to this function
  /// and it would write the strings into the ``ByteBuffer``.
  /// After that it calls ``createVector(ofOffsets:)``
  ///
  /// ```swift
  /// let namesOffsets = builder.
  ///   createVector(ofStrings: ["Name", "surname"])
  /// ```
  ///
  /// - Parameter str: Array of string
  /// - returns: ``Offset`` of the vector
  mutating public func createVector(ofStrings str: [String]) -> Offset {
    var offsets: [Offset] = []
    for s in str {
      offsets.append(create(string: s))
    }
    return createVector(ofOffsets: offsets)
  }

  /// Creates a vector of type ``NativeStruct``.
  ///
  /// Any swift struct in the generated code, should confirm to
  /// ``NativeStruct``. Since the generated swift structs are padded
  /// to the `FlatBuffers` standards.
  ///
  /// ```swift
  /// let offsets = builder.
  ///   createVector(ofStructs: [NativeStr(num: 1), NativeStr(num: 2)])
  /// ```
  ///
  /// - Parameter structs: A vector of ``NativeStruct``
  /// - Returns: ``Offset`` of the vector
  mutating public func createVector<T: NativeStruct>(ofStructs structs: [T])
    -> Offset
  {
    startVector(
      structs.count * MemoryLayout<T>.size,
      elementSize: MemoryLayout<T>.alignment)
    for i in structs.reversed() {
      _ = create(struct: i)
    }
    return endVector(len: structs.count)
  }

  // MARK: - Inserting Structs

  /// Writes a ``NativeStruct`` into the ``ByteBuffer``
  ///
  /// Adds a native struct that's build and padded according
  /// to `FlatBuffers` standards. with a predefined position.
  ///
  /// ```swift
  /// let offset = builder.create(
  ///   struct: NativeStr(num: 1),
  ///   position: 10)
  /// ```
  ///
  /// - Parameters:
  ///   - s: ``NativeStruct`` to be inserted into the ``ByteBuffer``
  ///   - position: The  predefined position of the object
  /// - Returns: ``Offset`` of written struct
  @discardableResult
  mutating public func create<T: NativeStruct>(
    struct s: T, position: VOffset) -> Offset
  {
    let offset = create(struct: s)
    _vtableStorage.add(loc: FieldLoc(
      offset: _bb.size,
      position: VOffset(position)))
    return offset
  }

  /// Writes a ``NativeStruct`` into the ``ByteBuffer``
  ///
  /// Adds a native struct that's build and padded according
  /// to `FlatBuffers` standards, directly into the buffer without
  /// a predefined position.
  ///
  /// ```swift
  /// let offset = builder.create(
  ///   struct: NativeStr(num: 1))
  /// ```
  ///
  /// - Parameters:
  ///   - s: ``NativeStruct`` to be inserted into the ``ByteBuffer``
  /// - Returns: ``Offset`` of written struct
  @discardableResult
  mutating public func create<T: NativeStruct>(
    struct s: T) -> Offset
  {
    let size = MemoryLayout<T>.size
    preAlign(len: size, alignment: MemoryLayout<T>.alignment)
    _bb.push(struct: s, size: size)
    return Offset(offset: _bb.size)
  }

  // MARK: - Inserting Strings

  /// Insets a string into the buffer of type `UTF8`
  ///
  /// Adds a swift string into ``ByteBuffer`` by encoding it
  /// using `UTF8`
  ///
  /// ```swift
  /// let nameOffset = builder
  ///   .create(string: "welcome")
  /// ```
  ///
  /// - Parameter str: String to be serialized
  /// - returns: ``Offset`` of inserted string
  mutating public func create(string str: String?) -> Offset {
    guard let str = str else { return Offset() }
    let len = str.utf8.count
    notNested()
    preAlign(len: len &+ 1, type: UOffset.self)
    _bb.fill(padding: 1)
    _bb.push(string: str, len: len)
    push(element: UOffset(len))
    return Offset(offset: _bb.size)
  }

  /// Insets a shared string into the buffer of type `UTF8`
  ///
  /// Adds a swift string into ``ByteBuffer`` by encoding it
  /// using `UTF8`. The function will check if the string,
  /// is already written to the ``ByteBuffer``
  ///
  /// ```swift
  /// let nameOffset = builder
  ///   .createShared(string: "welcome")
  ///
  ///
  /// let secondOffset = builder
  ///   .createShared(string: "welcome")
  ///
  /// assert(nameOffset.o == secondOffset.o)
  /// ```
  ///
  /// - Parameter str: String to be serialized
  /// - returns: ``Offset`` of inserted string
  mutating public func createShared(string str: String?) -> Offset {
    guard let str = str else { return Offset() }
    if let offset = stringOffsetMap[str] {
      return offset
    }
    let offset = create(string: str)
    stringOffsetMap[str] = offset
    return offset
  }

  // MARK: - Inseting offsets

  /// Writes the ``Offset`` of an already written table
  ///
  /// Writes the ``Offset`` of a table if not empty into the
  /// ``ByteBuffer``
  ///
  /// - Parameters:
  ///   - offset: ``Offset`` of another object to be written
  ///   - position: The predefined position of the object
  mutating public func add(offset: Offset, at position: VOffset) {
    if offset.isEmpty { return }
    add(element: refer(to: offset.o), def: 0, at: position)
  }

  /// Pushes a value of type ``Offset`` into the ``ByteBuffer``
  /// - Parameter o: ``Offset``
  /// - returns: Current position of the ``Offset``
  @discardableResult
  mutating public func push(element o: Offset) -> UOffset {
    push(element: refer(to: o.o))
  }

  // MARK: - Inserting Scalars to Buffer

  /// Writes a ``Scalar`` value into ``ByteBuffer``
  ///
  /// ``add(element:def:at:)`` takes in a default value, and current value
  /// and the position within the `VTable`. The default value would not
  /// be serialized if the value is the same as the current value or
  /// `serializeDefaults` is equal to false.
  ///
  /// If serializing defaults is important ``init(initialSize:serializeDefaults:)``,
  /// passing true for `serializeDefaults` would do the job.
  ///
  /// ```swift
  /// // Adds 10 to the buffer
  /// builder.add(element: Int(10), def: 1, position 12)
  /// ```
  ///
  /// *NOTE: Never call this manually*
  ///
  /// - Parameters:
  ///   - element: Element to insert
  ///   - def: Default value for that element
  ///   - position: The predefined position of the element
  mutating public func add<T: Scalar>(
    element: T,
    def: T,
    at position: VOffset)
  {
    if element == def && !serializeDefaults { return }
    track(offset: push(element: element), at: position)
  }

  /// Writes a optional ``Scalar`` value into ``ByteBuffer``
  ///
  /// Takes an optional value to be written into the ``ByteBuffer``
  ///
  /// *NOTE: Never call this manually*
  ///
  /// - Parameters:
  ///   - element: Optional element of type scalar
  ///   - position: The predefined position of the element
  mutating public func add<T: Scalar>(element: T?, at position: VOffset) {
    guard let element = element else { return }
    track(offset: push(element: element), at: position)
  }

  /// Pushes a values of type ``Scalar`` into the ``ByteBuffer``
  ///
  /// *NOTE: Never call this manually*
  ///
  /// - Parameter element: Element to insert
  /// - returns: Postion of the Element
  @discardableResult
  mutating public func push<T: Scalar>(element: T) -> UOffset {
    let size = MemoryLayout<T>.size
    preAlign(
      len: size,
      alignment: size)
    _bb.push(value: element, len: size)
    return _bb.size
  }

}

extension FlatBufferBuilder: CustomDebugStringConvertible {

  public var debugDescription: String {
    """
    buffer debug:
    \(_bb)
    builder debug:
    { finished: \(finished), serializeDefaults: \(serializeDefaults), isNested: \(isNested) }
    """
  }

  /// VTableStorage is a class to contain the VTable buffer that would be serialized into buffer
  @usableFromInline
  internal class VTableStorage {
    /// Memory check since deallocating each time we want to clear would be expensive
    /// and memory leaks would happen if we dont deallocate the first allocated memory.
    /// memory is promised to be available before adding `FieldLoc`
    private var memoryInUse = false
    /// Size of FieldLoc in memory
    let size = MemoryLayout<FieldLoc>.stride
    /// Memeory buffer
    var memory: UnsafeMutableRawBufferPointer!
    /// Capacity of the current buffer
    var capacity: Int = 0
    /// Maximuim offset written to the class
    var maxOffset: VOffset = 0
    /// number of fields written into the buffer
    var numOfFields: Int = 0
    /// Last written Index
    var writtenIndex: Int = 0

    /// Creates the memory to store the buffer in
    @usableFromInline
    init() {
      memory = UnsafeMutableRawBufferPointer.allocate(
        byteCount: 0,
        alignment: 0)
    }

    deinit {
      memory.deallocate()
    }

    /// Builds a buffer with byte count of fieldloc.size * count of field numbers
    /// - Parameter count: number of fields to be written
    @inline(__always)
    func start(count: Int) {
      assert(count >= 0, "number of fields should NOT be negative")
      let capacity = count &* size
      ensure(space: capacity)
    }

    /// Adds a FieldLoc into the buffer, which would track how many have been written,
    /// and max offset
    /// - Parameter loc: Location of encoded element
    func add(loc: FieldLoc) {
      memory.baseAddress?.advanced(by: writtenIndex).storeBytes(
        of: loc,
        as: FieldLoc.self)
      writtenIndex = writtenIndex &+ size
      numOfFields = numOfFields &+ 1
      maxOffset = max(loc.position, maxOffset)
    }

    /// Clears the data stored related to the encoded buffer
    func clear() {
      maxOffset = 0
      numOfFields = 0
      writtenIndex = 0
    }

    /// Ensure that the buffer has enough space instead of recreating the buffer each time.
    /// - Parameter space: space required for the new vtable
    @inline(__always)
    func ensure(space: Int) {
      guard space &+ writtenIndex > capacity else { return }
      memory.deallocate()
      memory = UnsafeMutableRawBufferPointer.allocate(
        byteCount: space,
        alignment: size)
      capacity = space
    }

    /// Loads an object of type `FieldLoc` from buffer memory
    /// - Parameter index: index of element
    /// - Returns: a FieldLoc at index
    @inline(__always)
    func load(at index: Int) -> FieldLoc {
      memory.load(fromByteOffset: index, as: FieldLoc.self)
    }

  }

  internal struct FieldLoc {
    var offset: UOffset
    var position: VOffset
  }

}
