// 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.

///|
pub(all) enum AxisRange {
  LowerHalf
  UpperHalf
  Full
}

///|
pub(all) enum Token {
  Uuid(Uuid)
  Platform(String)
  Name(String)
  AxisMapping(Int, AxisOrBtn, AxisRange, AxisRange, Bool)
  ButtonMapping(Int, AxisOrBtn, AxisRange)
  HatMapping(Int, Int, AxisOrBtn, AxisRange)
}

///|
pub(all) enum ParserErrorKind {
  InvalidGuid
  InvalidKeyValPair
  InvalidValue
  EmptyValue
  UnknownAxis
  UnknownButton
  InvalidParserState
  UnexpectedEnd
}

///|
suberror ParserError {
  Error(ParserErrorKind, Int)
}

///|
pub fn ParserError::kind(self : ParserError) -> ParserErrorKind {
  match self {
    Error(kind, _) => kind
  }
}

///|
pub fn ParserError::position(self : ParserError) -> Int {
  match self {
    Error(_, pos) => pos
  }
}

///|
priv enum State {
  Uuid
  Name
  KeyVal
  Invalid
}

///|
priv struct Parser {
  data : String
  mut pos : Int
  mut state : State
}

///|
fn Parser::new(mapping : String) -> Parser {
  { data: mapping, pos: 0, state: State::Uuid }
}

///|
fn next_comma_or_end(data : String, pos : Int) -> Int {
  let n = data.length()
  for i in pos.. Int? {
  for i in pos.. String raise ParserError {
  data.sub(start~, end~).to_string() catch {
    _ => raise ParserError::Error(ParserErrorKind::InvalidParserState, start)
  }
}

///|
fn parse_u16_decimal(s : String, pos : Int) -> Int raise ParserError {
  if s.length() == 0 {
    raise ParserError::Error(ParserErrorKind::InvalidValue, pos)
  }
  let mut acc = 0
  for c in s {
    if c is ('0'..='9') {
      acc = acc * 10 + (c.to_int() - '0'.to_int())
    } else {
      raise ParserError::Error(ParserErrorKind::InvalidValue, pos)
    }
  }
  acc
}

///|
let axes_sdl : Array[String] = [
  "a", "b", "back", "c", "dpdown", "dpleft", "dpright", "dpup", "guide", "leftshoulder",
  "leftstick", "lefttrigger", "leftx", "lefty", "leftz", "misc1", "misc2", "paddle1",
  "paddle2", "paddle3", "paddle4", "rightshoulder", "rightstick", "righttrigger",
  "rightx", "righty", "rightz", "start", "touchpad", "x", "y", "z",
]

///|
let axes : Array[AxisOrBtn] = [
  AxisOrBtn::Btn(Button::South),
  AxisOrBtn::Btn(Button::East),
  AxisOrBtn::Btn(Button::Select),
  AxisOrBtn::Btn(Button::C),
  AxisOrBtn::Btn(Button::DPadDown),
  AxisOrBtn::Btn(Button::DPadLeft),
  AxisOrBtn::Btn(Button::DPadRight),
  AxisOrBtn::Btn(Button::DPadUp),
  AxisOrBtn::Btn(Button::Mode),
  AxisOrBtn::Btn(Button::LeftTrigger),
  AxisOrBtn::Btn(Button::LeftThumb),
  AxisOrBtn::Btn(Button::LeftTrigger2),
  AxisOrBtn::Axis(Axis::LeftStickX),
  AxisOrBtn::Axis(Axis::LeftStickY),
  AxisOrBtn::Axis(Axis::LeftZ),
  AxisOrBtn::Btn(Button::Unknown),
  AxisOrBtn::Btn(Button::Unknown),
  AxisOrBtn::Btn(Button::Unknown),
  AxisOrBtn::Btn(Button::Unknown),
  AxisOrBtn::Btn(Button::Unknown),
  AxisOrBtn::Btn(Button::Unknown),
  AxisOrBtn::Btn(Button::RightTrigger),
  AxisOrBtn::Btn(Button::RightThumb),
  AxisOrBtn::Btn(Button::RightTrigger2),
  AxisOrBtn::Axis(Axis::RightStickX),
  AxisOrBtn::Axis(Axis::RightStickY),
  AxisOrBtn::Axis(Axis::RightZ),
  AxisOrBtn::Btn(Button::Start),
  AxisOrBtn::Btn(Button::Unknown),
  AxisOrBtn::Btn(Button::West),
  AxisOrBtn::Btn(Button::North),
  AxisOrBtn::Btn(Button::Z),
]

///|
fn lookup_axis_or_btn(
  key : String,
  kind : ParserErrorKind,
  pos : Int,
) -> AxisOrBtn raise ParserError {
  for i in 0.. Token? raise ParserError {
  if self.pos >= self.data.length() {
    None
  } else {
    Some(
      match self.state {
        State::Uuid => self.parse_uuid()
        State::Name => self.parse_name()
        State::KeyVal => self.parse_key_val()
        State::Invalid =>
          raise ParserError::Error(
            ParserErrorKind::InvalidParserState,
            self.pos,
          )
      },
    )
  }
}

///|
fn Parser::parse_uuid(self : Parser) -> Token raise ParserError {
  let next_comma = next_comma_or_end(self.data, self.pos)
  let uuid_field = slice_to_string(self.data, self.pos, next_comma)
  let uuid = Token::Uuid(Uuid::parse(uuid_field)) catch {
    _ => {
      self.state = State::Invalid
      raise ParserError::Error(ParserErrorKind::InvalidGuid, self.pos)
    }
  }
  if next_comma == self.data.length() {
    self.state = State::Invalid
    raise ParserError::Error(ParserErrorKind::UnexpectedEnd, self.pos)
  }
  self.state = State::Name
  self.pos = next_comma + 1
  uuid
}

///|
fn Parser::parse_name(self : Parser) -> Token raise ParserError {
  let next_comma = next_comma_or_end(self.data, self.pos)
  let name = slice_to_string(self.data, self.pos, next_comma)
  self.state = State::KeyVal
  self.pos = next_comma + 1
  Token::Name(name)
}

///|
fn Parser::parse_key_val(self : Parser) -> Token raise ParserError {
  let next_comma = next_comma_or_end(self.data, self.pos)
  let pos = self.pos
  let _pair = slice_to_string(self.data, self.pos, next_comma)
  self.pos = next_comma + 1
  let colon = match find_char(self.data, pos, next_comma, ':') {
    Some(i) => i
    None => raise ParserError::Error(ParserErrorKind::InvalidKeyValPair, pos)
  }
  if find_char(self.data, colon + 1, next_comma, ':') is Some(_) {
    raise ParserError::Error(ParserErrorKind::InvalidKeyValPair, pos)
  }
  let key = slice_to_string(self.data, pos, colon)
  let value = slice_to_string(self.data, colon + 1, next_comma)
  if value.length() == 0 {
    raise ParserError::Error(ParserErrorKind::EmptyValue, pos)
  }
  if key == "platform" {
    return Token::Platform(value)
  }
  let (key, output) = match key {
    ['+', .. rest] => (rest.to_string(), AxisRange::UpperHalf)
    ['-', .. rest] => (rest.to_string(), AxisRange::LowerHalf)
    _ => (key, AxisRange::Full)
  }
  let (from_str, input, inverted, is_axis) = match value {
    ['+', 'a', .. rest] => {
      let mut body = rest.to_string()
      let mut inv = false
      if body.length() > 0 &&
        body.code_unit_at(body.length() - 1).to_int() == '~'.to_int() {
        inv = true
        body = (try! body[0:body.length() - 1]).to_string()
      }
      (body, AxisRange::UpperHalf, inv, true)
    }
    ['-', 'a', .. rest] => {
      let mut body = rest.to_string()
      let mut inv = false
      if body.length() > 0 &&
        body.code_unit_at(body.length() - 1).to_int() == '~'.to_int() {
        inv = true
        body = (try! body[0:body.length() - 1]).to_string()
      }
      (body, AxisRange::LowerHalf, inv, true)
    }
    ['a', .. rest] => {
      let mut body = rest.to_string()
      let mut inv = false
      if body.length() > 0 &&
        body.code_unit_at(body.length() - 1).to_int() == '~'.to_int() {
        inv = true
        body = (try! body[0:body.length() - 1]).to_string()
      }
      (body, AxisRange::Full, inv, true)
    }
    ['b', .. rest] => (rest.to_string(), AxisRange::Full, false, false)
    ['h', .. rest] => {
      let body = rest.to_string()
      let dot = match find_char(body, 0, body.length(), '.') {
        Some(i) => i
        None => raise ParserError::Error(ParserErrorKind::InvalidValue, pos)
      }
      let hat = parse_u16_decimal((try! body[0:dot]).to_string(), pos + 1)
      let direction = parse_u16_decimal(
        (try! body[dot + 1:body.length()]).to_string(),
        pos + dot + 1,
      )
      let to = lookup_axis_or_btn(key, ParserErrorKind::UnknownButton, pos)
      return Token::HatMapping(hat, direction, to, output)
    }
    _ => raise ParserError::Error(ParserErrorKind::InvalidValue, pos)
  }
  let from = parse_u16_decimal(from_str, pos)
  if is_axis {
    let to = lookup_axis_or_btn(key, ParserErrorKind::UnknownAxis, pos)
    Token::AxisMapping(from, to, input, output, inverted)
  } else {
    let to = lookup_axis_or_btn(key, ParserErrorKind::UnknownButton, pos)
    Token::ButtonMapping(from, to, output)
  }
}