///|
pub struct ElfInfo {
  abi : String
  dependencies : Array[String]
  imports : Array[String]
  exports : Array[String]
  soname : String?
  segments : Array[ElfSegment]
}

///|
pub struct ElfSegment {
  offset : Int
  file_size : Int
  memory_size : UInt64
  address : UInt64
  flags : Int
  alignment : UInt64
}

///|
pub fn ElfSegment::to_json(self : ElfSegment) -> Json {
  {
    "offset": self.offset,
    "fileSize": self.file_size,
    "memorySize": self.memory_size.to_string(),
    "address": self.address.to_string(),
    "flags": self.flags,
    "alignment": self.alignment.to_string(),
  }
}

///|
priv struct ElfSection {
  kind : Int
  offset : Int
  size : Int
  link : Int
  entry_size : Int
  address : UInt64
  flags : UInt64
}

///|
fn binary_range(
  bytes : Bytes,
  offset : Int,
  size : Int,
) -> Unit raise ConfigError {
  if offset < 0 ||
    size < 0 ||
    offset > bytes.length() ||
    size > bytes.length() - offset {
    raise Invalid("elf: truncated or overflowing binary range")
  }
}

///|
fn elf_word(
  bytes : Bytes,
  offset : Int,
  size : Int,
) -> UInt64 raise ConfigError {
  binary_range(bytes, offset, size)
  let mut result = 0UL
  for index in 0.. Int raise ConfigError {
  let result = elf_word(bytes, offset, size)
  if result > 2147483647UL {
    raise Invalid("elf: offsets larger than INT32_MAX are unsupported")
  }
  result.to_int()
}

///|
fn elf_string(
  bytes : Bytes,
  table : ElfSection,
  offset : Int,
) -> String raise ConfigError {
  if offset < 0 || offset >= table.size {
    raise Invalid("elf: string table index out of bounds")
  }
  let result = StringBuilder()
  let mut index = offset
  while index < table.size {
    let byte = bytes[table.offset + index]
    if byte == 0 {
      return result.to_string()
    }
    if byte.to_int() < 32 || byte.to_int() > 126 {
      raise Invalid("elf: non-ASCII dynamic string")
    }
    result.write_char(byte.to_char())
    index += 1
  }
  raise Invalid("elf: unterminated dynamic string")
}

///|
fn elf_sections(bytes : Bytes) -> Array[ElfSection] raise ConfigError {
  let offset = elf_offset(bytes, 40, 8)
  let entry_size = elf_offset(bytes, 58, 2)
  let count = elf_offset(bytes, 60, 2)
  if entry_size != 64 || count == 0 {
    raise Invalid("elf: unsupported section table")
  }
  binary_range(bytes, offset, count * entry_size)
  let sections : Array[ElfSection] = []
  for index in 0.. Unit raise ConfigError {
  for section in sections {
    if section.size == 0 || (section.flags & 2UL) == 0UL {
      continue
    }
    let size = section.size.to_uint64()
    if section.address > 0xffffffffffffffffUL - size {
      raise Invalid("elf: section virtual address overflow")
    }
    let mut mapped = false
    for segment in segments {
      if section.address >= segment.address &&
        section.address - segment.address <= segment.memory_size &&
        size <= segment.memory_size - (section.address - segment.address) {
        if section.kind != 8 {
          if section.offset < segment.offset ||
            section.offset - segment.offset > segment.file_size ||
            section.size > segment.file_size - (section.offset - segment.offset) {
            continue
          }
        }
        if (section.flags & 4UL) != 0UL && (segment.flags & 1) == 0 {
          raise Invalid("elf: executable section in a non-executable segment")
        }
        if (section.flags & 1UL) != 0UL && (segment.flags & 2) == 0 {
          raise Invalid("elf: writable section in a read-only segment")
        }
        mapped = true
        break
      }
    }
    if !mapped {
      raise Invalid("elf: allocated section is outside mapped segments")
    }
  }
}

///|
fn linked_strings(
  sections : Array[ElfSection],
  section : ElfSection,
) -> ElfSection raise ConfigError {
  if section.link < 0 || section.link >= sections.length() {
    raise Invalid("elf: invalid linked section")
  }
  let table = sections[section.link]
  if table.kind != 3 {
    raise Invalid("elf: linked section is not a string table")
  }
  table
}

///|
fn inspect_programs(
  bytes : Bytes,
  sections : Array[ElfSection],
) -> Array[ElfSegment] raise ConfigError {
  let offset = elf_offset(bytes, 32, 8)
  let entry_size = elf_offset(bytes, 54, 2)
  let count = elf_offset(bytes, 56, 2)
  if count == 0 || entry_size != 56 {
    raise Invalid("elf: missing or unsupported program headers")
  }
  binary_range(bytes, offset, count * entry_size)
  let mut dynamic = false
  let segments : Array[ElfSegment] = []
  let mut executable = false
  let mut writable = false
  for index in 0.. {
          section.kind == 6 &&
          section.offset == file_offset &&
          section.size == file_size &&
          section.address == address
        }) {
        raise Invalid(
          "elf: dynamic program segment disagrees with section table",
        )
      }
      dynamic = true
    }
    if kind == 1 {
      let segment : ElfSegment = {
        offset: file_offset,
        file_size,
        memory_size: elf_word(bytes, start + 40, 8),
        address: elf_word(bytes, start + 16, 8),
        flags: elf_offset(bytes, start + 4, 4),
        alignment: elf_word(bytes, start + 48, 8),
      }
      if segment.memory_size < file_size.to_uint64() {
        raise Invalid("elf: file image exceeds mapped segment")
      }
      if segment.flags > 7 || (segment.flags & 3) == 3 {
        raise Invalid("elf: invalid or writable executable segment")
      }
      let alignment = segment.alignment
      if alignment > 1UL {
        if (alignment & (alignment - 1UL)) != 0UL ||
          segment.address % alignment != file_offset.to_uint64() % alignment {
          raise Invalid("elf: invalid load segment alignment")
        }
      }
      if segment.address > 0xffffffffffffffffUL - segment.memory_size {
        raise Invalid("elf: virtual address overflow")
      }
      if (segment.flags & 1) != 0 {
        executable = true
      }
      if (segment.flags & 2) != 0 {
        writable = true
      }
      segments.push(segment)
    }
  }
  if !dynamic {
    raise Invalid("elf: shared library has no dynamic program segment")
  }
  if !executable || !writable {
    raise Invalid("elf: missing executable or writable load segment")
  }
  segments
}

///|
pub fn inspect_elf(bytes : Bytes) -> ElfInfo raise ConfigError {
  binary_range(bytes, 0, 64)
  if bytes[0] != 127 || bytes[1] != 69 || bytes[2] != 76 || bytes[3] != 70 {
    raise Invalid("elf: invalid magic; host objects are not accepted")
  }
  if bytes[4] != 2 || bytes[5] != 1 || bytes[6] != 1 {
    raise Invalid("elf: requires little-endian ELF64 version 1")
  }
  if elf_word(bytes, 16, 2) != 3UL ||
    elf_word(bytes, 20, 4) != 1UL ||
    elf_word(bytes, 52, 2) != 64UL {
    raise Invalid("elf: requires an ELF64 shared object")
  }
  let abi = match elf_offset(bytes, 18, 2) {
    183 => "arm64-v8a"
    62 => "x86_64"
    _ => raise Invalid("elf: unsupported machine architecture")
  }
  let sections = elf_sections(bytes)
  let segments = inspect_programs(bytes, sections)
  verify_section_mapping(sections, segments)
  let dependencies : Array[String] = []
  let imports : Array[String] = []
  let exports : Array[String] = []
  let mut soname : String? = None
  let mut dynamic = false
  let mut symbols = false
  for section in sections {
    if section.kind == 6 {
      dynamic = true
      if section.entry_size != 16 || section.size % 16 != 0 {
        raise Invalid("elf: malformed dynamic table")
      }
      let strings = linked_strings(sections, section)
      let mut terminated = false
      for index in 0..<(section.size / 16) {
        let offset = section.offset + index * 16
        let tag = elf_word(bytes, offset, 8)
        if tag == 0UL {
          terminated = true
          break
        }
        if tag == 1UL || tag == 14UL || tag == 15UL || tag == 29UL {
          let name = elf_string(
            bytes,
            strings,
            elf_offset(bytes, offset + 8, 8),
          )
          if tag == 1UL {
            dependencies.push(name)
          } else if tag == 14UL {
            soname = Some(name)
          } else if name.contains(":") || name.has_prefix("/") {
            raise Invalid("elf: absolute runtime search path")
          }
        }
      }
      if !terminated {
        raise Invalid("elf: dynamic table lacks terminator")
      }
    } else if section.kind == 11 {
      symbols = true
      if section.entry_size != 24 || section.size % 24 != 0 {
        raise Invalid("elf: malformed dynamic symbol table")
      }
      let strings = linked_strings(sections, section)
      for index in 0..<(section.size / 24) {
        let offset = section.offset + index * 24
        let name = elf_string(bytes, strings, elf_offset(bytes, offset, 4))
        if name == "" {
          continue
        }
        if elf_word(bytes, offset + 6, 2) == 0UL {
          imports.push(name)
        } else {
          exports.push(name)
        }
      }
    }
  }
  if !dynamic || !symbols {
    raise Invalid("elf: missing dynamic metadata")
  }
  if !dependencies.contains("libace_napi.z.so") {
    raise Invalid("elf: missing HarmonyOS Node-API dependency")
  }
  if !imports.contains("napi_module_register") {
    raise Invalid("elf: missing Node-API registration symbol")
  }
  for dependency in dependencies {
    if dependency.contains("/") ||
      dependency.contains("\\") ||
      dependency.contains(":") ||
      dependency.contains(".dll") ||
      dependency == "libc++_shared.so" {
      raise Invalid(
        "elf: unexpected host or shared runtime dependency " + dependency,
      )
    }
  }
  { abi, dependencies, imports, exports, soname, segments, }
}

///|
pub fn ElfInfo::to_json(self : ElfInfo) -> Json {
  {
    "abi": self.abi,
    "dependencies": self.dependencies.to_json(),
    "imports": self.imports.to_json(),
    "exports": self.exports.to_json(),
    "soname": self.soname.to_json(),
    "segments": self.segments.map(s => s.to_json()).to_json(),
  }
}