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

///|
/// JACK backend (Linux-only for now).
///
/// This backend is primarily intended for CI validation of our callback-thread stream model
/// in a JACK environment (using the dummy driver).
///
/// Implementation notes:
/// - Mirrors upstream CPAL: expose one input device and one output device (JACK client ports).

///|
pub struct Host {
  _unit : Unit
} derive(Debug, Eq)

///|
pub struct Device {
  id : String
  direction : @core.DeviceDirection
} derive(Debug, Eq)

///|
pub fn default_host() -> Host {
  { _unit: () }
}

///|
fn default_output_device() -> Device {
  { id: "cpal_client_out", direction: @core.DeviceDirection::output() }
}

///|
fn default_input_device() -> Device {
  { id: "cpal_client_in", direction: @core.DeviceDirection::input() }
}

///|
pub fn Host::devices(_self : Host) -> Array[Device] {
  if @core.native_os() == Linux {
    match default_server_config() {
      None => []
      Some(_) => [default_input_device(), default_output_device()]
    }
  } else {
    []
  }
}

///|
pub fn Host::default_output_device(_self : Host) -> Device? {
  if @core.native_os() == Linux {
    match default_server_config() {
      None => None
      Some(_) => Some(default_output_device())
    }
  } else {
    None
  }
}

///|
pub fn Host::default_input_device(_self : Host) -> Device? {
  if @core.native_os() == Linux {
    match default_server_config() {
      None => None
      Some(_) => Some(default_input_device())
    }
  } else {
    None
  }
}

///|
pub fn Device::name(_self : Device) -> String {
  if @core.native_os() == Linux {
    _self.id
  } else {
    _self.id
  }
}

///|
pub fn Device::description(
  _self : Device,
) -> @core.DeviceDescription raise @core.DeviceNameError {
  let name = Device::name(_self)
  if name.length() == 0 {
    raise @core.device_name_error_backend_specific(
      "jack device description: empty name",
    )
  }
  // Mirrors upstream CPAL: only name + direction are filled for JACK.
  @core.DeviceDescriptionBuilder(name).direction(_self.direction).build()
}

///|
pub fn Device::id(_self : Device) -> @core.DeviceId {
  DeviceId(Jack, _self.id)
}

///|
pub fn Device::supports_input(_self : Device) -> Bool {
  if @core.native_os() != Linux {
    false
  } else {
    match default_server_config() {
      None => false
      Some(_) => true
    }
  }
}

///|
pub fn Device::supports_output(_self : Device) -> Bool {
  if @core.native_os() != Linux {
    false
  } else {
    match default_server_config() {
      None => false
      Some(_) => true
    }
  }
}

///|
pub fn Device::supported_input_configs(
  _self : Device,
) -> Array[@core.SupportedStreamConfigRange] {
  if @core.native_os() != Linux {
    return []
  }
  supported_configs(_self)
}

///|
pub fn Device::supported_output_configs(
  _self : Device,
) -> Array[@core.SupportedStreamConfigRange] {
  if @core.native_os() != Linux {
    return []
  }
  supported_configs(_self)
}

///|
pub fn Device::default_input_config(
  _self : Device,
) -> @core.SupportedStreamConfig raise @core.DefaultStreamConfigError {
  if @core.native_os() != Linux {
    raise @core.default_stream_config_error_backend_specific(
      "jack default_input_config: not available on this OS",
    )
  }
  default_config(_self)
}

///|
pub fn Device::default_output_config(
  _self : Device,
) -> @core.SupportedStreamConfig raise @core.DefaultStreamConfigError {
  if @core.native_os() != Linux {
    raise @core.default_stream_config_error_backend_specific(
      "jack default_output_config: not available on this OS",
    )
  }
  default_config(_self)
}

///|
fn default_config(
  _self : Device,
) -> @core.SupportedStreamConfig raise @core.DefaultStreamConfigError {
  match default_server_config() {
    None => raise @core.default_stream_config_error_device_not_available()
    Some((sr, buf)) =>
      SupportedStreamConfig(2, sr, Range(min=buf, max=buf), F32)
  }
}

///|
fn supported_configs(_self : Device) -> Array[@core.SupportedStreamConfigRange] {
  match default_server_config() {
    None => []
    Some((sr, buf)) => {
      // Mirrors upstream cpal's JACK backend: fixed sample rate/buffer size from server,
      // float32 sample format, and a small set of common channel counts.
      let chans = [1, 2, 4, 6, 8, 16, 24, 32, 48, 64]
      let buf_range = @core.SupportedBufferSize::Range(min=buf, max=buf)
      let out : Array[@core.SupportedStreamConfigRange] = []
      for ch in chans {
        out.push(SupportedStreamConfigRange(ch, sr, sr, buf_range, F32))
      }
      out
    }
  }
}