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

///|
/// Trampolines used to bridge native JACK worker threads (C) to MoonBit closures.

///|
fn sample_format_from_tag(tag : UInt) -> @core.SampleFormat {
  match tag {
    1 => F32
    _ => F32
  }
}

///|
fn call_output_data_callback(
  data_callback : (@core.Data, @core.OutputCallbackInfo) -> Unit,
  sample_format_tag : UInt,
  bytes : FixedArray[Byte],
  callback_secs : Int64,
  callback_nanos : Int,
  playback_secs : Int64,
  playback_nanos : Int,
) -> Unit {
  let fmt = sample_format_from_tag(sample_format_tag)
  let data = @core.Data(fmt, bytes)
  let callback = @core.StreamInstant(callback_secs, callback_nanos)
  let playback = @core.StreamInstant(playback_secs, playback_nanos)
  let ts = @core.OutputStreamTimestamp(callback, playback)
  let info = @core.OutputCallbackInfo(ts)
  data_callback(data, info)
}

///|
let call_output_data_callback_fr : FuncRef[
  (
    (@core.Data, @core.OutputCallbackInfo) -> Unit,
    UInt,
    FixedArray[Byte],
    Int64,
    Int,
    Int64,
    Int,
  ) -> Unit,
] = fn(
  data_callback,
  sample_format_tag,
  bytes,
  callback_secs,
  callback_nanos,
  playback_secs,
  playback_nanos,
) {
  call_output_data_callback(
    data_callback, sample_format_tag, bytes, callback_secs, callback_nanos, playback_secs,
    playback_nanos,
  )
}

///|
fn call_input_data_callback(
  data_callback : (@core.Data, @core.InputCallbackInfo) -> Unit,
  sample_format_tag : UInt,
  bytes : FixedArray[Byte],
  callback_secs : Int64,
  callback_nanos : Int,
  capture_secs : Int64,
  capture_nanos : Int,
) -> Unit {
  let fmt = sample_format_from_tag(sample_format_tag)
  let data = @core.Data(fmt, bytes)
  let callback = @core.StreamInstant(callback_secs, callback_nanos)
  let capture = @core.StreamInstant(capture_secs, capture_nanos)
  let ts = @core.InputStreamTimestamp(callback, capture)
  let info = @core.InputCallbackInfo(ts)
  data_callback(data, info)
}

///|
let call_input_data_callback_fr : FuncRef[
  (
    (@core.Data, @core.InputCallbackInfo) -> Unit,
    UInt,
    FixedArray[Byte],
    Int64,
    Int,
    Int64,
    Int,
  ) -> Unit,
] = fn(
  data_callback,
  sample_format_tag,
  bytes,
  callback_secs,
  callback_nanos,
  capture_secs,
  capture_nanos,
) {
  call_input_data_callback(
    data_callback, sample_format_tag, bytes, callback_secs, callback_nanos, capture_secs,
    capture_nanos,
  )
}

///|
fn call_stream_error_callback(
  error_callback : (@core.StreamError) -> Unit,
  op_tag : Int,
  status : Int,
) -> Unit {
  let op = match op_tag {
    1 => "jack_client_open"
    2 => "jack_activate"
    3 => "jack_port_register"
    4 => "jack_set_process_callback"
    5 => "jack_xrun"
    6 => "jack_sample_rate"
    7 => "jack_shutdown"
    _ => "JackStream"
  }
  let err = if op_tag == 5 {
    @core.stream_error_buffer_underrun()
  } else if op_tag == 6 {
    @core.stream_error_stream_invalidated()
  } else if op_tag == 7 {
    @core.stream_error_backend_specific("JACK was shut down")
  } else if op_tag == 1 && status != 0 {
    // Opening a JACK client failed; most commonly the server is not running or unreachable.
    @core.stream_error_device_not_available()
  } else if op_tag == 0 {
    let code = if status < 0 { 0 - status } else { status }
    if code == 2 || code == 19 || code == 111 {
      // ENOENT / ENODEV / ECONNREFUSED
      @core.stream_error_device_not_available()
    } else if code == 32 {
      // EPIPE (broken pipe): treat as invalidated.
      @core.stream_error_stream_invalidated()
    } else {
      @core.stream_error_backend_specific("\{op} (status \{status})")
    }
  } else if op_tag == 2 || op_tag == 3 || op_tag == 4 {
    // Activation/registration failures invalidate the stream graph.
    @core.stream_error_stream_invalidated()
  } else {
    @core.stream_error_backend_specific("\{op} (status \{status})")
  }
  error_callback(err)
}

///|
let call_stream_error_callback_fr : FuncRef[
  ((@core.StreamError) -> Unit, Int, Int) -> Unit,
] = fn(error_callback, op_tag, status) {
  call_stream_error_callback(error_callback, op_tag, status)
}