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

// Base 64 encoding and decoding
// This implementation follows RFC 4648 for  encoding and decoding.

///|
/// index is between 0 and 63, inclusive.
fn index_to_char(index : Int) -> Char {
  match index {
    0..<26 => (index + 'A').unsafe_to_char()
    26..<52 => (index - 26 + 'a').unsafe_to_char()
    52..<62 => (index - 52 + '0').unsafe_to_char()
    62 => '+'
    63 => '/'
    _ => panic()
  }
}

///|
/// assuming the character is valid, returns an index between 0 and 63, inclusive.
fn char_to_index(char : Char) -> Int raise InvalidChar {
  match char {
    'A'..='Z' => char.to_int() - 'A'
    'a'..='z' => char.to_int() - 'a' + 26
    '0'..='9' => char.to_int() - '0' + 52
    '+' => 62
    '/' => 63
    _ => raise InvalidChar(char)
  }
}

///|
priv struct Encoder {
  mut i : Int
  mut buffer : Int
}

///|
fn Encoder::new() -> Encoder {
  { i: 0, buffer: 0 }
}

///|
#locals(cb)
fn Encoder::encode_to(
  self : Encoder,
  bytes : BytesView,
  cb : (Char) -> Unit,
  padding? : Bool = false,
) -> Unit {
  for byte in bytes {
    let byte = byte.to_int()
    match self.i {
      0 => {
        cb(index_to_char(byte >> 2))
        self.buffer = (byte & 0b11) << 4
        self.i = 1
      }
      1 => {
        cb(index_to_char(self.buffer | (byte >> 4)))
        self.buffer = (byte & 0b1111) << 2
        self.i = 2
      }
      2 => {
        cb(index_to_char(self.buffer | (byte >> 6)))
        cb(index_to_char(byte & 0b111111))
        self.buffer = 0
        self.i = 0
      }
      _ => panic()
    }
  }
  if padding {
    match self.i {
      0 => ()
      1 => {
        cb(index_to_char(self.buffer))
        cb('=')
        cb('=')
      }
      2 => {
        cb(index_to_char(self.buffer))
        cb('=')
      }
      _ => panic()
    }
    self.i = 0
  }
}

///|
/// Encode binary to ascii text following  defined in RFC 4648
pub fn encode(bytes : BytesView) -> String {
  let builder = StringBuilder::new()
  let encoder = Encoder::new()
  encoder.encode_to(bytes, fn(ch) { builder.write_char(ch) }, padding=true)
  builder.to_string()
}

///|
priv struct Decoder {
  mut i : Int
  mut buffer : Int
}

///|
fn Decoder::new() -> Decoder {
  { i: 0, buffer: 0 }
}

///|
#locals(cb)
fn Decoder::decode_to(
  self : Decoder,
  input : StringView,
  cb : (Byte) -> Unit,
) -> Unit raise InvalidChar {
  for ch in input {
    match self.i % 4 {
      0 => {
        self.buffer = char_to_index(ch) << 2
        self.i = 1
      }
      1 => {
        let idx = char_to_index(ch)
        cb((self.buffer | (idx >> 4)).to_byte())
        self.buffer = (idx & 0b1111) << 4
        self.i = 2
      }
      2 if ch != '=' => {
        let idx = char_to_index(ch)
        cb((self.buffer | (idx >> 2)).to_byte())
        self.buffer = (idx & 0b11) << 6
        self.i = 3
      }
      3 if ch != '=' => {
        let idx = char_to_index(ch)
        cb((self.buffer | idx).to_byte())
        self.buffer = 0
        self.i = 0
      }
      _ => break
    }
  }
}

///|
pub fn decode(input : StringView) -> Bytes raise InvalidChar {
  let decoder = Decoder::new()
  let output = FixedArray::make(input.length() * 3 / 4, b'\x00')
  let mut i = 0
  decoder.decode_to(input, fn(byte) {
    output[i] = byte
    i += 1
  })
  Bytes::from_array(output[:i])
}

///|
pub suberror InvalidChar Char derive(Show)