///|
/// A required unsigned scalar at its schema-defined position in a header.
pub(all) struct FrameMember {
  name : String
  offset : Int32
  primitive : Primitive
  min_value : Int32
  max_value : Int32
} derive(Debug, Eq)

///|
/// The compiled wire layout of a message header or a group dimension.
pub(all) struct FrameLayout {
  size : Int32
  members : Array[FrameMember]
} derive(Debug, Eq)

///|
fn FrameLayout::entry(
  self : FrameLayout,
  name : String,
) -> FrameMember raise SbeError {
  for entry in self.members {
    if entry.name == name {
      return entry
    }
  }
  raise InvalidSchema("missing framing member: \{name}", 0)
}

///|
fn framing_bound(
  value : String?,
  primitive : Primitive,
  default~ : Int32,
) -> Int32 raise SbeError {
  match value {
    None => default
    Some(text) => {
      let number = scalar_from_literal(primitive, text).as_unsigned()
      if number > 2147483647UL {
        2147483647
      } else {
        number.to_int()
      }
    }
  }
}

///|
fn SchemaCompiler::frame_layout(
  self : SchemaCompiler,
  name : String,
  required : Array[String],
) -> FrameLayout raise SbeError {
  let (entries, size) = match self.resolve(name, 0) {
    Composite(entries, size) => (entries, size)
    _ => raise InvalidSchema("framing type must be a composite: \{name}", 0)
  }
  let members : Array[FrameMember] = []
  for entry in entries {
    if !required.contains(entry.name) &&
      entry.name != "numGroups" &&
      entry.name != "numVarDataFields" {
      raise Unsupported("unknown framing member: \{name}.\{entry.name}")
    }
    let encoding = match self.resolve(entry.type_name, 0) {
      Encoded(encoding) if encoding.length == 1 &&
        encoding.presence == Required &&
        entry.since_version == 0 &&
        !encoding.primitive.is_signed() &&
        !encoding.primitive.is_float() &&
        encoding.primitive != Char => encoding
      _ =>
        raise InvalidSchema(
          "framing member must be an unversioned unsigned scalar: \{entry.name}",
          0,
        )
    }
    let primitive = encoding.primitive
    let physical_max = match primitive {
      UInt8 => 255
      UInt16 => 65535
      _ => 2147483647
    }
    let minimum = framing_bound(encoding.min_value, primitive, default=0)
    let maximum = framing_bound(
      encoding.max_value,
      primitive,
      default=physical_max,
    )
    if minimum > maximum || maximum > physical_max {
      raise InvalidSchema("invalid framing bounds: \{entry.name}", 0)
    }
    members.push({
      name: entry.name,
      offset: entry.offset,
      primitive,
      min_value: minimum,
      max_value: maximum,
    })
  }
  let layout = FrameLayout::{ size, members, }
  for field in required {
    ignore(layout.entry(field))
  }
  layout
}

///|
fn count_active(block : BlockDef, version : Int32) -> (Int32, Int32) {
  let mut groups = 0
  let mut data = 0
  for group in block.groups {
    if group.since_version <= version {
      groups += 1
    }
  }
  for field in block.data {
    if field.since_version <= version {
      data += 1
    }
  }
  (groups, data)
}

///|
fn FrameLayout::write(
  self : FrameLayout,
  writer : Writer,
  values : Map[String, Int32],
  block : BlockDef,
  version : Int32,
) -> Unit raise SbeError {
  let start = writer.position()
  let (groups, data) = count_active(block, version)
  for entry in self.members {
    let value = match entry.name {
      "numGroups" => groups
      "numVarDataFields" => data
      name => values.get(name).unwrap_or(-1)
    }
    if value < entry.min_value || value > entry.max_value {
      raise InvalidValue(entry.name, "value exceeds framing range")
    }
    writer.pad_to(start + entry.offset)
    writer.write_uint(value.to_uint64(), entry.primitive.width())
  }
  writer.pad_to(start + self.size)
}

///|
fn FrameLayout::read(
  self : FrameLayout,
  reader : Reader,
) -> Map[String, Int32] raise SbeError {
  let start = reader.position()
  reader.require(start, self.size)
  let fields : Map[String, Int32] = Map([])
  for entry in self.members {
    reader.seek(start + entry.offset)
    let value = reader.read_uint(entry.primitive.width())
    if value < entry.min_value.to_uint64() ||
      value > entry.max_value.to_uint64() {
      raise InvalidMessage("out-of-range framing member: \{entry.name}")
    }
    fields[entry.name] = value.to_int()
  }
  reader.seek(start + self.size)
  fields
}

///|
fn check_frame_counts(
  fields : Map[String, Int32],
  block : BlockDef,
  version : Int32,
) -> Unit raise SbeError {
  let (groups, data) = count_active(block, version)
  if fields.get("numGroups").unwrap_or(groups) != groups ||
    fields.get("numVarDataFields").unwrap_or(data) != data {
    raise InvalidMessage("framing counts do not match the acting schema")
  }
}