//! Write system information

use std;
use fmod::{self, ChannelControl};

static INDENT : std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new (0);

//
//  fn write_info_system
//
#[expect(clippy::missing_panics_doc)]
pub fn write_info_system <W : std::io::Write>
  (writer : &mut W, system : &fmod::System) -> Result <(), std::io::Error>
{
  let indent = indent_string();
  writeln!(writer, "{indent}# fmod system info #")?;
  writeln!(writer, "{}{:<36} {}",   indent, "fmod system: version:",
    system.get_version_string().unwrap())?;
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: output:",
    system.get_output().unwrap())?;
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: num drivers:",
    system.get_num_drivers().unwrap())?;
  for i in 0..system.get_num_drivers().unwrap() {
    let driver_info = system.get_driver_info (i).unwrap();
    writeln!(writer, "{indent}fmod system: driver info[{i}]:")?;
    writeln!(writer, "{indent}    {driver_info:?}")?;
  }
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: record num drivers:",
    system.get_record_num_drivers().unwrap())?;
  for i in 0..system.get_record_num_drivers().unwrap().0 {
    let (info, state) = system.get_record_driver_info (i).unwrap();
    writeln!(writer, "{indent}fmod system: record driver info[{i}]:")?;
    writeln!(writer, "{indent}    {info:?}")?;
    writeln!(writer, "{indent}    {state:?}")?;
  }
  writeln!(writer, "{}{:<36} {}", indent, "fmod system: driver:",
    system.get_driver().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod system: software channels:",
    system.get_software_channels().unwrap())?;
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: channels playing:",
    system.get_channels_playing().unwrap())?;
  let (samplerate, speakermode, numrawspeakers) =
    system.get_software_format().unwrap();
  writeln!(writer, "{indent}fmod system: software format:")?;
  writeln!(writer, "{indent}    sample rate:      {samplerate}")?;
  writeln!(writer, "{indent}    speaker mode:     {speakermode:?}")?;
  writeln!(writer, "{indent}    num raw speakers: {numrawspeakers}")?;
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: dsp buffer size:",
    system.get_dsp_buffer_size().unwrap())?;
  // TODO: get_advanced_settings
  writeln!(writer, "{indent}fmod system: speaker mode channels:")?;
  for i in 0..fmod::Speakermode::MAX as u32 {
    use fmod::FromPrimitive;
    let mode = fmod::Speakermode::from_u32 (i).unwrap();
    writeln!(writer, "{}    {:?}: {}",
      indent, mode, system.get_speaker_mode_channels (mode).unwrap())?;
  }
  writeln!(writer, "{indent}fmod system: num plugins:")?;
  writeln!(writer, "{}    Plugintype::Output: {:?}", indent,
    system.get_num_plugins (fmod::Plugintype::Output).unwrap())?;
  writeln!(writer, "{}    Plugintype::Codec:  {:?}", indent,
    system.get_num_plugins (fmod::Plugintype::Codec).unwrap())?;
  writeln!(writer, "{}    Plugintype::DSP:    {:?}", indent,
    system.get_num_plugins (fmod::Plugintype::Dsp).unwrap())?;
  // TODO: get_plugin_handle, get_plugin_info
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: output by plugin:",
    system.get_output_by_plugin().unwrap())?;
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: 3d num listeners:",
    system.get_3d_num_listeners().unwrap())?;
  for i in 0..system.get_3d_num_listeners().unwrap() {
    let attributes = system.get_3d_listener_attributes (i).unwrap();
    writeln!(writer, "{indent}fmod system: 3d listener attributes[{i}]:")?;
    writeln!(writer, "{}    pos:      {:?}", indent, attributes.pos)?;
    writeln!(writer, "{}    vel:      {:?}", indent, attributes.vel)?;
    writeln!(writer, "{}    forward:  {:?}", indent, attributes.forward)?;
    writeln!(writer, "{}    up:       {:?}", indent, attributes.up)?;
  }
  writeln!(writer, "{indent}fmod system: 3d speaker position:")?;
  let driver_id     = system.get_driver().unwrap();
  let driver_info   = system.get_driver_info (driver_id).unwrap();
  #[expect(clippy::cast_sign_loss)]
  for i in 0..driver_info.speakermodechannels as u32 {
    use fmod::FromPrimitive;
    let speaker = fmod::Speaker::from_u32 (i).unwrap();
    writeln!(writer, "{}    {:?}:          {:?}",
      indent, speaker, system.get_speaker_position (speaker).unwrap())?;
  }
  let (doppler_scale, distance_factor, roll_off_scale) =
    system.get_3d_settings().unwrap();
  writeln!(writer, "{indent}fmod system: 3d settings:")?;
  writeln!(writer, "{indent}    doppler scale:   {doppler_scale}")?;
  writeln!(writer, "{indent}    distance factor: {distance_factor}")?;
  writeln!(writer, "{indent}    roll off scale:  {roll_off_scale}")?;
  let (buffer_size, time_unit) = system.get_stream_buffer_size().unwrap();
  writeln!(writer, "{}{:<36} ({}, {:?})", indent,
    "fmod system: stream buffer size:", buffer_size, time_unit)?;
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: cpu usage:",
    system.get_cpu_usage().unwrap())?;
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod system: sound ram:",
    system.get_sound_ram().unwrap())?;
  // TODO: get_master_channel_group
  // TODO: get_master_sound_group
  writeln!(writer, "{indent}fmod system: reverb properties:")?;
  #[expect(clippy::cast_possible_wrap)]
  for i in 0..fmod::dsp::REVERB_MAXINSTANCES as i32 {
    writeln!(writer, "{indent}    instance[{i}]:")?;
    indent_bump (4);
    let properties = show_reverb_properties (&system.get_reverb_properties (i)
      .unwrap());
    indent_unbump (4);
    write!(writer, "{properties}")?;
  }
  // TODO: get_reverb_ambient_properties
  writeln!(writer, "{}{:<36} {}", indent, "fmod system: geometry settings:",
    system.get_geometry_settings().unwrap())?;
  Ok(())
} // end fn write_info_system

//
//  fn write_info_reverb3d
//
#[expect(clippy::missing_panics_doc)]
pub fn write_info_reverb3d <W : std::io::Write>
  (writer : &mut W, reverb : &fmod::Reverb3d) -> Result <(), std::io::Error>
{
  let indent = indent_string();
  writeln!(writer, "{indent}# fmod reverb info #")?;
  writeln!(writer, "{}fmod reverb: active:   {}", indent,
    reverb.get_active().unwrap())?;
  let attributes = reverb.get_3d_attributes().unwrap();
  writeln!(writer, "{indent}fmod reverb: 3d attributes:")?;
  writeln!(writer, "{}    position:     {:?}", indent, attributes.position)?;
  writeln!(writer, "{}    min distance: {:?}", indent, attributes.mindistance)?;
  writeln!(writer, "{}    max distance: {:?}", indent, attributes.maxdistance)?;
  write!(writer, "{}fmod reverb: properties: {}", indent,
    show_reverb_properties (&reverb.get_properties().unwrap()))?;
  Ok(())
} // end fn write_info_reverb

//
//  fn write_info_dsp
//
#[expect(clippy::missing_panics_doc)]
pub fn write_info_dsp <W : std::io::Write>
  (writer : &mut W, dsp : &fmod::Dsp) -> Result <(), std::io::Error>
{
  let indent = indent_string();
  writeln!(writer, "{indent}# fmod dsp info #")?;
  writeln!(writer, "{}{:<24} {:?}", indent, "fmod dsp: type:",
    dsp.get_type().unwrap())?;
  writeln!(writer, "{}{:<24} {}",   indent, "fmod dsp: active:",
    dsp.get_active().unwrap())?;
  // TODO: active speakers ?
  writeln!(writer, "{}{:<24} {}",   indent, "fmod dsp: bypass:",
    dsp.get_bypass().unwrap())?;
  writeln!(writer, "{}{:<24} {}",   indent, "fmod dsp: num inputs:",
    dsp.get_num_inputs().unwrap())?;
  writeln!(writer, "{}{:<24} {}",   indent, "fmod dsp: num outputs:",
    dsp.get_num_outputs().unwrap())?;
  // TODO: show inputs/outputs ?
  // TODO: parameters ?
  // TODO: memory info ?
  Ok(())
} // end fn write_info_dsp

//
//  fn write_info_channel
//
#[expect(clippy::missing_panics_doc)]
pub fn write_info_channel <W : std::io::Write> (
  writer  : &mut W,
  channel : &fmod::Channel
) -> Result <(), std::io::Error> {
  let indent = indent_string();
  writeln!(writer, "{indent}# fmod channel info #")?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel: index:",
    channel.get_index().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel: channel group:",
    channel.get_channel_group().unwrap().get_name().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel: priority:",
    channel.get_priority().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel: is virtual:",
    channel.is_virtual().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel: position (ms):",
    channel.get_position (fmod::Timeunit::MS).unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel: loop count:",
    channel.get_loop_count().unwrap())?;
  let (loop_start, loop_end) =
    channel.get_loop_points (fmod::Timeunit::MS, fmod::Timeunit::MS).unwrap();
  writeln!(writer, "{}{:<36} (start: {}, end: {})", indent,
    "fmod channel: loop points (ms):", loop_start, loop_end)?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel: frequency:",
    channel.get_frequency().unwrap())?;
  // TODO: speaker levels ?
  // TODO: input channel mixes ?
  // TODO: dsp head ?
  // TODO: memory info ?
  writeln!(writer, "{indent}fmod channel: channel control:")?;
  write_info_channel_control (writer, channel)?;
  Ok(())
} // end fn write_info_channel

//
//  fn write_info_channel_control
//
#[expect(clippy::missing_panics_doc)]
pub fn write_info_channel_control <W : std::io::Write, C : ChannelControl> (
  writer  : &mut W,
  control : &C
) -> Result <(), std::io::Error> {
  let indent = indent_string();
  writeln!(writer, "{indent}# fmod channel control info #")?;
  // TODO: the following fails for a newly created channel, how to check that
  // these will not fail?
  writeln!(writer, "{}{:<36} Mode({:b})", indent, "fmod channel control: mode:",
    control.get_mode().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: is playing:",
    control.is_playing().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: paused:",
    control.get_paused().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: volume:",
    control.get_volume().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: mute:",
    control.get_mute().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: audibility:",
    control.get_audibility().unwrap())?;
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: low pass gain:",
    control.get_low_pass_gain().unwrap())?;
  let delay = control.get_delay().unwrap();
  writeln!(writer, "{}{:<36} {:?}", indent, "fmod channel control: delay:",
    delay)?;
  writeln!(writer, "{indent}fmod channel control: reverb properties (wet level):")?;
  #[expect(clippy::cast_possible_wrap)]
  for i in 0..fmod::dsp::REVERB_MAXINSTANCES as i32 {
    writeln!(writer, "{}    instance[{}]: {}",
      indent, i, control.get_reverb_properties (i).unwrap())?;
  }
  let num_dsps = control.get_num_dsps().unwrap();
  writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: num dsps:",
    num_dsps)?;
  for i in 0..num_dsps {
    writeln!(writer, "{indent}fmod channel control: dsp[{i}]:")?;
    #[expect(clippy::cast_possible_wrap)]
    let dsp = control.get_dsp (i as i32).unwrap();
    indent_bump (4);
    write_info_dsp (writer, &dsp)?;
    indent_unbump (4);
  }
  match control.get_3d_attributes() {
    Ok ((position, velocity)) => {
      writeln!(writer, "{indent}fmod channel control: 3d attributes:")?;
      writeln!(writer, "{indent}    position: {position:?}")?;
      writeln!(writer, "{indent}    velocity: {velocity:?}")?;
      writeln!(writer, "{}{:<36} {:?}", indent,
        "fmod channel control: 3d min max distance:",
        control.get_3d_min_max_distance().unwrap())?;
      writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: 3d spread:",
        control.get_3d_spread().unwrap())?;
      writeln!(writer, "{}{:<36} {}", indent, "fmod channel control: 3d doppler level:",
        control.get_3d_doppler_level().unwrap())?;
      writeln!(writer, "{}{:<36} {:?}", indent,
        "fmod channel control: 3d custom rolloff:",
        control.get_3d_custom_rolloff().unwrap())?;
      writeln!(writer, "{}{:<36} {:?}", indent,
        "fmod channel control: 3d distance filter:",
        control.get_3d_distance_filter().unwrap())?;
      writeln!(writer, "{}{:<36} {:?}", indent,
        "fmod channel control: 3d occlusion:",
        control.get_3d_occlusion().unwrap())?;
      writeln!(writer, "{}{:<36} {:?}", indent,
        "fmod channel control: 3d cone orientation:",
        control.get_3d_cone_orientation().unwrap())?;
      let (inside_cone_angle, outside_cone_angle, outside_volume) =
        control.get_3d_cone_settings().unwrap();
      writeln!(writer, "{indent}fmod channel control: 3d cone settings:")?;
      writeln!(writer, "{indent}    inside cone angle:  {inside_cone_angle}")?;
      writeln!(writer, "{indent}    outside cone angle: {outside_cone_angle}")?;
      writeln!(writer, "{indent}    outside volume:     {outside_volume}")?;
    }
    Err (fmod::Error::Needs3d) => {}
    err => {
      writeln!(writer, "channel control 3d attributes error: {err:?}")?;
      unreachable!()
    }
  }
  Ok (())
}

//
//  fn write_info_channel_group
//
#[expect(clippy::missing_panics_doc)]
pub fn write_info_channel_group <W : std::io::Write> (
  writer : &mut W,
  group  : &fmod::ChannelGroup
) -> Result <(), std::io::Error> {
  let indent = indent_string();
  writeln!(writer, "{indent}# fmod channel group info #")?;
  writeln!(writer, "{}{:<40} {}", indent, "fmod channel group: name:",
    group.get_name().unwrap())?;
  writeln!(writer, "{}{:<40} {}", indent, "fmod channel group: is master:",
    group.is_master().unwrap())?;
  if let Ok (Some (parent_group)) = group.get_parent_group() {
    writeln!(writer, "{}{:<40} {}", indent, "fmod channel group: parent group:",
      parent_group.get_name().unwrap())?;
  }
  let num_groups = group.get_num_groups().unwrap();
  writeln!(writer, "{}{:<40} {}", indent, "fmod channel group: num groups:",
    num_groups)?;
  /*
  for i in 0..num_groups {
    writeln!(writer, "{}fmod channel group: subgroup[{}]:", indent, i)?;
    indent_bump (4);
    write_info_channel_group (writer, &group.get_group (i).unwrap())?;
    indent_unbump (4);
  }
  */
  let num_channels = group.get_num_channels().unwrap();
  writeln!(writer, "{}{:<40} {}", indent, "fmod channel group: num channels:",
    num_channels)?;
  for i in 0..num_channels {
    writeln!(writer, "{indent}fmod channel group: channel[{i}]:")?;
    indent_bump (4);
    write_info_channel (writer, &group.get_channel (i).unwrap())?;
    indent_unbump (4);
  }
  writeln!(writer, "{indent}fmod channelgroup: channel control:")?;
  write_info_channel_control (writer, group)?;
  Ok(())
} // end fn write_info_channel_group

//
//  fn write_info_sound
//
#[expect(clippy::missing_panics_doc)]
pub fn write_info_sound <W : std::io::Write> (
  writer  : &mut W,
  sound   : &fmod::Sound
) -> Result <(), std::io::Error> {
  let indent = indent_string();
  writeln!(writer, "{indent}# fmod sound info #")?;
  writeln!(writer, "{}{:<32} {}",   indent, "fmod sound: name:",
    sound.get_name().unwrap())?;
  writeln!(writer, "{}{:<32} {}",   indent, "fmod sound: length (ms):",
    sound.get_length (fmod::Timeunit::MS).unwrap())?;
  writeln!(writer, "{}{:<32} {:?}", indent, "fmod sound: format:",
    sound.get_format().unwrap())?;
  writeln!(writer, "{}{:<32} {}",   indent, "fmod sound: num sub sounds:",
    sound.get_num_sub_sounds().unwrap())?;
  // TODO: sub sounds
  writeln!(writer, "{}{:<32} {:?}", indent, "fmod sound: num tags:",
    sound.get_num_tags().unwrap())?;
  // TODO: tags
  writeln!(writer, "{}{:<32} {:?}", indent, "fmod sound: open state:",
    sound.get_open_state().unwrap())?;
  writeln!(writer, "{}{:<32} Mode({:b})", indent, "fmod sound: mode:",
    sound.get_mode().unwrap())?;
  writeln!(writer, "{}{:<32} {}",   indent, "fmod sound: loop count:",
    sound.get_loop_count().unwrap())?;
  let (loop_start, loop_end) =
    sound.get_loop_points (fmod::Timeunit::MS, fmod::Timeunit::MS).unwrap();
  writeln!(writer, "{}{:<32} (start: {}, end: {})", indent,
    "fmod sound: loop points (ms):",
    loop_start, loop_end)?;
  // TODO: the following returns Err (Format):
  /*
  writeln!(writer,
    "fmod sound: music speed:      {}", sound.get_music_speed().unwrap()
  )?;
  */
  let (frequency, priority) = sound.get_defaults().unwrap();
  writeln!(writer, "{indent}fmod sound: defaults:")?;
  writeln!(writer, "{indent}    frequency:  {frequency}")?;
  writeln!(writer, "{indent}    priority:   {priority}")?;
  writeln!(writer, "{}{:<32} {}", indent, "fmod sound: num sync points:",
    sound.get_num_sync_points().unwrap())?;
  // TODO: sync point info
  writeln!(writer, "{}{:<32} {:?}", indent, "fmod sound: 3d min max distance:",
    sound.get_3d_min_max_distance().unwrap())?;
  let (inside_cone_angle, outside_cone_angle, outside_volume)
    = sound.get_3d_cone_settings().unwrap();
  writeln!(writer, "{indent}fmod sound: 3d cone settings:")?;
  writeln!(writer, "{indent}    inside cone angle:   {inside_cone_angle}")?;
  writeln!(writer, "{indent}    outside cone angle:  {outside_cone_angle}")?;
  writeln!(writer, "{indent}    outside volume:      {outside_volume}")?;
  // TODO: 3d custom rolloffs ?
  // TODO: memory info
  Ok(())
} // end fn write_info_sound

//
//  fn show_reverb_properties
//
pub fn show_reverb_properties (reverb_properties : &fmod::reverb3d::Properties)
  -> String
{
  use std::fmt::Write;
  let indent = indent_string();
  let mut s = String::new();
  writeln!(s, "{indent}ReverbProperties {{").unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "decay time:",
    reverb_properties.decay_time).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "early delay:",
    reverb_properties.early_delay).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "late delay:",
    reverb_properties.late_delay).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "hf reference:",
    reverb_properties.hf_reference).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "hf decay ratio:",
    reverb_properties.hf_decay_ratio).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "diffusion:",
    reverb_properties.diffusion).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "density:",
    reverb_properties.density).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "low shelf frequency:",
    reverb_properties.low_shelf_frequency).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "low shelf gain:",
    reverb_properties.low_shelf_gain).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "high cut:",
    reverb_properties.high_cut).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "early late mix:",
    reverb_properties.early_late_mix).unwrap();
  writeln!(s, "{}    {:<22} {}", indent, "wet level:",
    reverb_properties.wet_level).unwrap();
  writeln!(s, "{indent}}}").unwrap();
  s
} // end fn show_reverb_properties

//
//  private
//
fn indent_bump (spaces : u32) -> String {
  INDENT.fetch_add (spaces, std::sync::atomic::Ordering::SeqCst);
  indent_string()
}

fn indent_unbump (spaces : u32) -> String {
  INDENT.fetch_sub (spaces, std::sync::atomic::Ordering::SeqCst);
  indent_string()
}

fn indent_string () -> String {
  use std::iter::FromIterator;
  let indent = INDENT.load (std::sync::atomic::Ordering::SeqCst) as usize;
  String::from_iter (std::iter::repeat_n (' ', indent))
}

fn _pretty_indent (d : &dyn std::fmt::Debug) -> String {
  let indent = indent_string();
  let s = format!("{d:#?}");
  let mut lines = s.lines();
  let first = lines.next().unwrap();
  #[expect(clippy::format_collect)]
  let mut rest : String = lines.map (|line| format!("{indent}{line}\n")).collect();
  rest.pop().unwrap();
  [first.to_string(), "\n".to_string(), rest].concat()
}
