// Copyright 2025 International Digital Economy Academy
//
// 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.

///|
suberror MappingError {
  InvalidCode(Code)
  InvalidName
  NotImplemented
  NotConnected
  DuplicatedEntry
  UnknownElement
  NotSdl2Compatible
}

///|
let _mapping_error_keepalive : Array[MappingError] = [
  NotImplemented,
  NotConnected,
  DuplicatedEntry,
  NotSdl2Compatible,
]

///|
pub fn MappingError::invalid_code(self : MappingError) -> Code? {
  match self {
    InvalidCode(code) => Some(code)
    _ => None
  }
}

///|
suberror ParseSdlMappingError {
  UnknownHatDirection
  ParseError(ParserError)
}

///|
pub struct Mapping {
  mappings : Array[(Code, AxisOrBtn)]
  mut name : String
  default : Bool
  mut hats_mapped : Int
}

///|
pub fn Mapping::new() -> Mapping {
  { mappings: [], name: "", default: false, hats_mapped: 0 }
}

///|
pub fn Mapping::new_default() -> Mapping {
  { mappings: [], name: "", default: true, hats_mapped: 0 }
}

///|
fn axis_same(a : Axis, b : Axis) -> Bool {
  a.to_index() == b.to_index()
}

///|
fn button_same(a : Button, b : Button) -> Bool {
  a.to_index() == b.to_index()
}

///|
fn axis_or_btn_same(a : AxisOrBtn, b : AxisOrBtn) -> Bool {
  match a {
    Axis(ax) =>
      match b {
        Axis(bx) => axis_same(ax, bx)
        Btn(_) => false
      }
    Btn(btn) =>
      match b {
        Axis(_) => false
        Btn(bn) => button_same(btn, bn)
      }
  }
}

///|
fn insert_mapping(
  mappings : Array[(Code, AxisOrBtn)],
  code : Code,
  el : AxisOrBtn,
) -> Unit {
  for i in 0.. Unit {
  insert_mapping(self.mappings, code, el)
}

///|
pub fn Mapping::set_hats_mapped(self : Mapping, hats : Int) -> Unit {
  self.hats_mapped = hats
}

///|
pub fn Mapping::map(self : Mapping, code : Code) -> AxisOrBtn? {
  for pair in self.mappings {
    let (c, v) = pair
    if c == code {
      return Some(v)
    }
  }
  None
}

///|
pub fn Mapping::map_rev(self : Mapping, el : AxisOrBtn) -> Code? {
  for pair in self.mappings {
    let (c, v) = pair
    if axis_or_btn_same(v, el) {
      return Some(c)
    }
  }
  None
}

///|
pub fn Mapping::name(self : Mapping) -> String {
  self.name
}

///|
pub fn Mapping::is_default(self : Mapping) -> Bool {
  self.default
}

///|
pub fn Mapping::hats_mapped(self : Mapping) -> Int {
  self.hats_mapped
}

///|
pub fn Mapping::entries(self : Mapping) -> Array[(Code, AxisOrBtn)] {
  self.mappings.copy()
}

///|
pub fn Mapping::parse_sdl_mapping(
  line : String,
  buttons : Array[Code],
  axes : Array[Code],
) -> Mapping raise ParseSdlMappingError {
  let mapping = Mapping::new()
  let parser = Parser::new(line)
  while true {
    let token_opt = parser.next_token() catch {
      Error(EmptyValue, _) => continue
      e => raise ParseError(e)
    }
    match token_opt {
      None => break
      Some(token) =>
        match token {
          Uuid(_) => ()
          Platform(_) => ()
          Name(n) => mapping.name = n
          AxisMapping(idx, to, _, _, _) =>
            if idx >= 0 && idx < axes.length() {
              insert_mapping(mapping.mappings, axes[idx], to)
            }
          ButtonMapping(idx, to, _) =>
            if idx >= 0 && idx < buttons.length() {
              insert_mapping(mapping.mappings, buttons[idx], to)
            }
          HatMapping(hat, direction, to, _) => {
            if hat != 0 {
              continue
            }
            let (from_axis, from_btn) = match direction {
              1 => (AXIS_DPADY, BTN_DPAD_UP)
              4 => (AXIS_DPADY, BTN_DPAD_DOWN)
              2 => (AXIS_DPADX, BTN_DPAD_RIGHT)
              8 => (AXIS_DPADX, BTN_DPAD_LEFT)
              0 => continue
              _ => raise UnknownHatDirection
            }
            if to.is_button() {
              match to {
                Btn(DPadLeft | DPadRight) =>
                  insert_mapping(mapping.mappings, from_axis, Axis(DPadX))
                Btn(DPadUp | DPadDown) =>
                  insert_mapping(mapping.mappings, from_axis, Axis(DPadY))
                _ => ()
              }
              insert_mapping(mapping.mappings, from_btn, to)
            } else {
              insert_mapping(mapping.mappings, from_axis, to)
            }
            mapping.hats_mapped = mapping.hats_mapped | direction
          }
        }
    }
  }
  mapping
}

///|
pub struct MappingData {
  buttons : Array[Code?]
  axes : Array[Code?]
}

///|
pub fn MappingData::new() -> MappingData {
  {
    buttons: Array::make(BUTTON_COUNT, None),
    axes: Array::make(AXIS_COUNT, None),
  }
}

///|
pub fn MappingData::button(self : MappingData, idx : Button) -> Code? {
  self.buttons[idx.to_index()]
}

///|
pub fn MappingData::axis(self : MappingData, idx : Axis) -> Code? {
  self.axes[idx.to_index()]
}

///|
pub fn MappingData::insert_btn(
  self : MappingData,
  from : Code,
  to : Button,
) -> Code? {
  let i = to.to_index()
  let prev = self.buttons[i]
  self.buttons[i] = Some(from)
  prev
}

///|
pub fn MappingData::insert_axis(
  self : MappingData,
  from : Code,
  to : Axis,
) -> Code? {
  let i = to.to_index()
  let prev = self.axes[i]
  self.axes[i] = Some(from)
  prev
}

///|
pub fn MappingData::remove_button(self : MappingData, idx : Button) -> Code? {
  let i = idx.to_index()
  let prev = self.buttons[i]
  self.buttons[i] = None
  prev
}

///|
pub fn MappingData::remove_axis(self : MappingData, idx : Axis) -> Code? {
  let i = idx.to_index()
  let prev = self.axes[i]
  self.axes[i] = None
  prev
}

///|
fn is_name_valid(name : String) -> Bool {
  for c in name {
    if c == ',' {
      return false
    }
  }
  true
}

///|
fn index_of_code(list : Array[Code], code : Code) -> Int? {
  for i in 0.. String raise MappingError {
  let idx = match index_of_code(buttons, ev_code) {
    Some(i) => i
    None => raise InvalidCode(ev_code)
  }
  insert_mapping(mappings, ev_code, Btn(mapped_btn))
  sdl_mappings + "\{ident}:b\{idx},"
}

///|
fn add_axis(
  ident : String,
  ev_code : Code,
  mapped_axis : Axis,
  axes : Array[Code],
  sdl_mappings : String,
  mappings : Array[(Code, AxisOrBtn)],
) -> String raise MappingError {
  let idx = match index_of_code(axes, ev_code) {
    Some(i) => i
    None => raise InvalidCode(ev_code)
  }
  insert_mapping(mappings, ev_code, Axis(mapped_axis))
  sdl_mappings + "\{ident}:a\{idx},"
}

///|
pub fn Mapping::from_data(
  data : MappingData,
  buttons : Array[Code],
  axes : Array[Code],
  name : String,
  uuid : Uuid,
) -> (Mapping, String) raise MappingError {
  if !is_name_valid(name) {
    raise InvalidName
  }
  if data.button(Unknown) is Some(_) || data.axis(Unknown) is Some(_) {
    raise UnknownElement
  }
  let mappings : Array[(Code, AxisOrBtn)] = []
  let mut sdl_mappings = "\{uuid.simple()},\{name},"
  if data.axis(LeftStickX) is Some(code) {
    sdl_mappings = add_axis(
      "leftx",
      code,
      LeftStickX,
      axes,
      sdl_mappings,
      mappings,
    )
  }
  if data.axis(LeftStickY) is Some(code) {
    sdl_mappings = add_axis(
      "lefty",
      code,
      LeftStickY,
      axes,
      sdl_mappings,
      mappings,
    )
  }
  if data.axis(LeftZ) is Some(code) {
    sdl_mappings = add_axis("leftz", code, LeftZ, axes, sdl_mappings, mappings)
  }
  if data.axis(RightStickX) is Some(code) {
    sdl_mappings = add_axis(
      "rightx",
      code,
      RightStickX,
      axes,
      sdl_mappings,
      mappings,
    )
  }
  if data.axis(RightStickY) is Some(code) {
    sdl_mappings = add_axis(
      "righty",
      code,
      RightStickY,
      axes,
      sdl_mappings,
      mappings,
    )
  }
  if data.axis(RightZ) is Some(code) {
    sdl_mappings = add_axis(
      "rightz",
      code,
      RightZ,
      axes,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(South) is Some(code) {
    sdl_mappings = add_button("a", code, South, buttons, sdl_mappings, mappings)
  }
  if data.button(East) is Some(code) {
    sdl_mappings = add_button("b", code, East, buttons, sdl_mappings, mappings)
  }
  if data.button(C) is Some(code) {
    sdl_mappings = add_button("c", code, C, buttons, sdl_mappings, mappings)
  }
  if data.button(West) is Some(code) {
    sdl_mappings = add_button("x", code, West, buttons, sdl_mappings, mappings)
  }
  if data.button(North) is Some(code) {
    sdl_mappings = add_button("y", code, North, buttons, sdl_mappings, mappings)
  }
  if data.button(Z) is Some(code) {
    sdl_mappings = add_button("z", code, Z, buttons, sdl_mappings, mappings)
  }
  if data.button(LeftTrigger) is Some(code) {
    sdl_mappings = add_button(
      "leftshoulder",
      code,
      LeftTrigger,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(RightTrigger) is Some(code) {
    sdl_mappings = add_button(
      "rightshoulder",
      code,
      RightTrigger,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(LeftTrigger2) is Some(code) {
    sdl_mappings = add_button(
      "lefttrigger",
      code,
      LeftTrigger2,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(RightTrigger2) is Some(code) {
    sdl_mappings = add_button(
      "righttrigger",
      code,
      RightTrigger2,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(Select) is Some(code) {
    sdl_mappings = add_button(
      "back",
      code,
      Select,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(Start) is Some(code) {
    sdl_mappings = add_button(
      "start",
      code,
      Start,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(Mode) is Some(code) {
    sdl_mappings = add_button(
      "guide",
      code,
      Mode,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(LeftThumb) is Some(code) {
    sdl_mappings = add_button(
      "leftstick",
      code,
      LeftThumb,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(RightThumb) is Some(code) {
    sdl_mappings = add_button(
      "rightstick",
      code,
      RightThumb,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(DPadUp) is Some(code) {
    sdl_mappings = add_button(
      "dpup",
      code,
      DPadUp,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(DPadDown) is Some(code) {
    sdl_mappings = add_button(
      "dpdown",
      code,
      DPadDown,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(DPadLeft) is Some(code) {
    sdl_mappings = add_button(
      "dpleft",
      code,
      DPadLeft,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  if data.button(DPadRight) is Some(code) {
    sdl_mappings = add_button(
      "dpright",
      code,
      DPadRight,
      buttons,
      sdl_mappings,
      mappings,
    )
  }
  ({ mappings, name, default: false, hats_mapped: 0 }, sdl_mappings)
}

///|
pub struct MappingDb {
  mappings : Array[(Uuid, String)]
}

///|
const INCLUDED_MAPPINGS_FALLBACK : String = "030000005e0400008e02000002010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,"

///|
pub fn MappingDb::new() -> MappingDb {
  { mappings: [] }
}

///|
fn index_of_substring(s : String, pat : String) -> Int? {
  let n = s.length()
  let m = pat.length()
  if m == 0 {
    return Some(0)
  }
  if n < m {
    return None
  }
  for i in 0..<(n - m + 1) {
    let mut ok = true
    for j in 0.. Int? {
  for i in 0.. String? {
  match index_of_substring(line, "platform:") {
    None => None
    Some(idx) => {
      let start = idx + "platform:".length()
      if start >= line.length() {
        return Some("")
      }
      let tail = line[start:line.length()].to_owned()
      let end = match find_first_comma(tail) {
        None => tail.length()
        Some(i) => i
      }
      Some(tail[0:end].to_owned())
    }
  }
}

///|
fn mapping_line_matches_platform(line : String) -> Bool {
  let current = runtime_sdl_platform_name()
  match mapping_line_platform(line) {
    None => true
    Some(p) => p == current
  }
}

///|
fn upsert_mapping(db : MappingDb, uuid : Uuid, line : String) -> Unit {
  for i in 0.. String {
  if line.length() == 0 {
    return line
  }
  let last = line.code_unit_at(line.length() - 1).to_int()
  if last == '\r'.to_int() {
    line[0:line.length() - 1].to_owned()
  } else {
    line
  }
}

///|
pub fn MappingDb::insert(self : MappingDb, s : String) -> Unit {
  for line_view in s.split("\n") {
    let line = trim_trailing_cr(line_view.to_owned())
    if line.length() == 0 {
      continue
    }
    if !mapping_line_matches_platform(line) {
      continue
    }
    let guid_end = match find_first_comma(line) {
      None => line.length()
      Some(i) => i
    }
    let guid = line[0:guid_end].to_owned()
    try {
      let uuid = Uuid::parse(guid)
      upsert_mapping(self, uuid, line)
    } catch {
      _ => ()
    }
  }
}

///|
pub fn MappingDb::add_included_mappings(self : MappingDb) -> Unit {
  let platform = runtime_sdl_platform_name()
  if platform == "Mac OS X" {
    self.insert(included_mappings_macos_blob())
  } else if platform == "Linux" {
    self.insert(included_mappings_linux_blob())
  } else if platform == "Windows" {
    self.insert(included_mappings_windows_blob())
  } else {
    self.insert(INCLUDED_MAPPINGS_FALLBACK)
  }
}

///|
pub fn MappingDb::add_env_mappings(self : MappingDb) -> Unit {
  let env = runtime_env_sdl_gamecontrollerconfig()
  if env.length() != 0 {
    self.insert(env)
  }
}

///|
pub fn MappingDb::get(self : MappingDb, uuid : Uuid) -> String? {
  for pair in self.mappings {
    let (u, v) = pair
    if u.simple() == uuid.simple() {
      return Some(v)
    }
  }
  None
}

///|
pub fn MappingDb::len(self : MappingDb) -> Int {
  self.mappings.length()
}