///|
pub(all) struct KeyPair {
  public_key : String
  secret_key : String
}

///|
pub(all) struct Signature {
  r : String
  s : String
}

///|
pub fn hex_to_bytes(hex : String) -> Array[Byte] {
  let len = hex.length() / 2
  let bytes : Array[Byte] = Array::make(len, b'\x00')
  for i = 0; i < len; i = i + 1 {
    let hi = hex_char_val(hex[i * 2].to_int())
    let lo = hex_char_val(hex[i * 2 + 1].to_int())
    bytes[i] = ((hi << 4) | lo).to_byte()
  }
  bytes
}

///|
fn hex_char_val(c : Int) -> Int {
  if c >= 48 && c <= 57 {
    c - 48
  } else if c >= 97 && c <= 102 {
    c - 87
  } else if c >= 65 && c <= 70 {
    c - 55
  } else {
    0
  }
}

///|
fn bytes_to_hex_str(bytes : Array[Byte]) -> String {
  let mut result = ""
  for i = 0; i < bytes.length(); i = i + 1 {
    let b = bytes[i].to_int()
    let hi = (b >> 4) & 0x0F
    let lo = b & 0x0F
    let ch = if hi < 10 {
      (hi + 48).unsafe_to_char()
    } else {
      (hi - 10 + 97).unsafe_to_char()
    }
    let cl = if lo < 10 {
      (lo + 48).unsafe_to_char()
    } else {
      (lo - 10 + 97).unsafe_to_char()
    }
    result = result + ch.to_string() + cl.to_string()
  }
  result
}

///|
fn bytes_concat(a : Array[Byte], b : Array[Byte]) -> Array[Byte] {
  let result : Array[Byte] = Array::new(capacity=a.length() + b.length())
  for i = 0; i < a.length(); i = i + 1 {
    result.push(a[i])
  }
  for i = 0; i < b.length(); i = i + 1 {
    result.push(b[i])
  }
  result
}

///|
fn clamp_scalar(h : Array[Byte]) -> Array[Byte] {
  let clamped : Array[Byte] = Array::make(32, b'\x00')
  for i = 0; i < 32; i = i + 1 {
    clamped[i] = h[i]
  }
  clamped[0] = (clamped[0].to_int() & 248).to_byte()
  clamped[31] = (clamped[31].to_int() & 127).to_byte()
  clamped[31] = (clamped[31].to_int() | 64).to_byte()
  clamped
}

///|
pub fn generate_keypair(seed : String) -> KeyPair {
  let seed_bytes = hex_to_bytes(seed)
  let h = sha512(seed_bytes)
  let scalar_bytes = clamp_scalar(h)
  let scalar = le_bytes_to_bigint(scalar_bytes)
  let pub_point = scalar_mult(scalar, base_point())
  let pub_bytes = point_encode(pub_point)
  KeyPair::{ public_key: bytes_to_hex_str(pub_bytes), secret_key: seed }
}

///|
pub fn sign(message : String, keypair : KeyPair) -> Signature {
  let seed_bytes = hex_to_bytes(keypair.secret_key)
  let msg_bytes = string_to_utf8(message)
  let h = sha512(seed_bytes)
  let scalar_bytes = clamp_scalar(h)
  let scalar = le_bytes_to_bigint(scalar_bytes)
  // prefix = h[32..64]
  let prefix : Array[Byte] = Array::make(32, b'\x00')
  for i = 0; i < 32; i = i + 1 {
    prefix[i] = h[i + 32]
  }
  // r = SHA-512(prefix || message) mod L
  let r_hash = sha512(bytes_concat(prefix, msg_bytes))
  let r = le_bytes_to_bigint(r_hash) % ed_L
  let r_point = scalar_mult(r, base_point())
  let r_bytes = point_encode(r_point)
  let a_bytes = hex_to_bytes(keypair.public_key)
  // k = SHA-512(R || A || message) mod L
  let k_input = bytes_concat(bytes_concat(r_bytes, a_bytes), msg_bytes)
  let k_hash = sha512(k_input)
  let k = le_bytes_to_bigint(k_hash) % ed_L
  // S = (r + k * scalar) mod L
  let s_val = (r + k * scalar) % ed_L
  let s_bytes = bigint_to_le_bytes(s_val, 32)
  Signature::{ r: bytes_to_hex_str(r_bytes), s: bytes_to_hex_str(s_bytes) }
}

///|
pub fn verify(
  message : String,
  signature : Signature,
  public_key : String,
) -> Bool {
  let msg_bytes = string_to_utf8(message)
  let r_bytes = hex_to_bytes(signature.r)
  let a_bytes = hex_to_bytes(public_key)
  let s_bytes = hex_to_bytes(signature.s)
  let s_val = le_bytes_to_bigint(s_bytes)
  if s_val >= ed_L {
    return false
  }
  let r_point = match point_decode(r_bytes) {
    None => return false
    Some(p) => p
  }
  let a_point = match point_decode(a_bytes) {
    None => return false
    Some(p) => p
  }
  // k = SHA-512(R || A || message) mod L
  let k_input = bytes_concat(bytes_concat(r_bytes, a_bytes), msg_bytes)
  let k_hash = sha512(k_input)
  let k = le_bytes_to_bigint(k_hash) % ed_L
  // Check: [S]B == R + [k]A
  let lhs = scalar_mult(s_val, base_point())
  let rhs = point_add(r_point, scalar_mult(k, a_point))
  let lhs_enc = point_encode(lhs)
  let rhs_enc = point_encode(rhs)
  bytes_equal(lhs_enc, rhs_enc)
}

///|
fn bytes_equal(a : Array[Byte], b : Array[Byte]) -> Bool {
  if a.length() != b.length() {
    return false
  }
  for i = 0; i < a.length(); i = i + 1 {
    if a[i] != b[i] {
      return false
    }
  }
  true
}

///|
pub fn signature_to_hex(sig : Signature) -> String {
  sig.r + sig.s
}