(*****************************************************************************

  Liquidsoap, a programmable audio stream generator.
  Copyright 2003-2023 Savonet team

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details, fully stated in the COPYING
  file at the root of the liquidsoap distribution.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA

 *****************************************************************************)

(** Decode WAV files. *)

let log = Log.make ["decoder"; "wav/aiff"]

(** {1 Generic decoder} *)

exception End_of_stream

let really_input input buf ofs len =
  let rec f pos =
    if pos < len then (
      let i = input buf (ofs + pos) (len - pos) in
      if i = 0 then raise End_of_stream else f (pos + i))
  in
  f ofs

let input fn = fn

let input_byte input =
  let buf = Bytes.create 1 in
  let i = input buf 0 1 in
  if i = 0 then raise End_of_stream;
  int_of_char (Bytes.get buf 0)

let seek input len =
  let s = Bytes.create len in
  ignore (really_input input s 0 len)

let input_ops =
  { Wav_aiff.really_input; input_byte; input; seek; close = (fun _ -> ()) }

(* It might be more efficient to write our code for an input
 * channel and use directly the one we have when decoding files
 * or external processes, if we could wrap the input function used
 * for decoding stream (in http and harbor) as an in_channel. *)
let create ?header input =
  let decoder = ref (fun ~buffer:_ -> assert false) in
  let header = ref header in
  let main_decoder ~samplerate ~buffer remaining =
    let remaining = ref remaining in
    let bytes_to_get = Utils.pagesize * 64 in
    let buf = Bytes.create bytes_to_get in
    fun converter ->
      let bytes_to_get =
        if !remaining = -1 then bytes_to_get else min !remaining bytes_to_get
      in
      let bytes = input.Decoder.read buf 0 bytes_to_get in
      if !remaining <> -1 then remaining := !remaining - bytes;
      if bytes = 0 then raise End_of_stream;
      let content = converter buf 0 bytes in
      buffer.Decoder.put_pcm ~samplerate content
  in
  let read_header () =
    let iff_header = Wav_aiff.read_header input_ops input.Decoder.read in
    let samplesize = Wav_aiff.sample_size iff_header in
    let channels = Wav_aiff.channels iff_header in
    let samplerate = Wav_aiff.sample_rate iff_header in
    let datalen = Wav_aiff.data_length iff_header in
    let datalen = if datalen <= 0 then -1 else datalen in
    let format = Wav_aiff.format_of_handler iff_header in
    let converter = Decoder_utils.from_iff ~samplesize ~channels ~format in
    let format_descr = match format with `Wav -> "WAV" | `Aiff -> "AIFF" in
    log#info "%s header read (%d Hz, %d bits, %d bytes), starting decoding..."
      format_descr samplerate samplesize datalen;
    header := Some (format, samplesize, channels, samplerate, datalen);
    decoder := main_decoder ~samplerate datalen converter
  in
  begin
    match !header with
      | None -> decoder := fun ~buffer:_ -> read_header ()
      | Some (format, samplesize, channels, samplerate, datalen) ->
          let converter =
            Decoder_utils.from_iff ~format ~samplesize ~channels
          in
          decoder := main_decoder ~samplerate datalen converter
  end;
  let seek ticks =
    match (input.Decoder.lseek, input.Decoder.tell, !header) with
      | Some seek, Some tell, Some (_, samplesize, channels, samplerate, _) -> (
          (* seek is in absolute position *)
          let duration = Frame.seconds_of_main ticks in
          let samples = int_of_float (duration *. float samplerate) in
          let bytes = samples * samplesize * channels / 8 in
          try
            let pos = tell () in
            let ret = seek (pos + bytes) in
            let samples = 8 * (ret - pos) / (samplesize * channels) in
            let duration = float samples /. float samplerate in
            Frame.main_of_seconds duration
          with _ -> 0)
      | _, _, _ -> 0
  in
  {
    Decoder.decode = (fun buffer -> !decoder ~buffer);
    seek;
    eof = (fun _ -> ());
  }

(* File decoding *)

let file_type ~metadata:_ ~ctype:_ filename =
  let header = Wav_aiff.fopen filename in
  Fun.protect
    ~finally:(fun () -> Wav_aiff.close header)
    (fun () ->
      let channels =
        let channels = Wav_aiff.channels header in
        let sample_rate = Wav_aiff.sample_rate header in
        let ok_message s =
          log#info "%s recognized as WAV file (%s,%dHz,%d channels)."
            (Lang_string.quote_string filename)
            s sample_rate channels
        in
        match Wav_aiff.sample_size header with
          | 8 ->
              ok_message "u8";
              channels
          | 16 ->
              ok_message "s16le";
              channels
          | 24 ->
              ok_message "s24le";
              channels
          | 32 ->
              ok_message "s32le";
              channels
          | _ ->
              log#info
                "Only 8, 16, 24 and 32 bit WAV files are supported at the \
                 moment..";
              0
      in
      Some
        (Frame.Fields.make
           ~audio:
             (Frame_base.format_of_channels ~pcm_kind:Content.Audio.kind
                channels)
           ()))

let wav_mime_types =
  Dtools.Conf.list
    ~p:(Decoder.conf_mime_types#plug "wav")
    "Mime-types used for guessing WAV format"
    ~d:["audio/vnd.wave"; "audio/wav"; "audio/wave"; "audio/x-wav"]

let wav_file_extensions =
  Dtools.Conf.list
    ~p:(Decoder.conf_file_extensions#plug "wav")
    "File extensions used for guessing WAV format" ~d:["wav"; "wave"]

let wav_priority =
  Dtools.Conf.int
    ~p:(Decoder.conf_priorities#plug "wav")
    "Priority for the WAV decoder" ~d:1

let create_file_decoder ~metadata:_ ~ctype filename =
  Decoder.opaque_file_decoder ~filename ~ctype (create ?header:None)

let () =
  Plug.register Decoder.decoders "wav" ~doc:"Decode file or streams as WAV."
    {
      Decoder.priority = (fun () -> wav_priority#get);
      file_extensions = (fun () -> Some wav_file_extensions#get);
      mime_types = (fun () -> Some wav_mime_types#get);
      file_type;
      file_decoder = Some create_file_decoder;
      stream_decoder = Some (fun ~ctype:_ _ -> create ?header:None);
    }

let aiff_mime_types =
  Dtools.Conf.list
    ~p:(Decoder.conf_mime_types#plug "aiff")
    "Mime-types used for guessing AIFF format"
    ~d:["audio/x-aiff"; "audio/aiff"]

let aiff_file_extensions =
  Dtools.Conf.list
    ~p:(Decoder.conf_file_extensions#plug "aiff")
    "File extensions used for guessing AIFF format" ~d:["aiff"; "aif"; "aifc"]

let aiff_priorities =
  Dtools.Conf.int
    ~p:(Decoder.conf_priorities#plug "aiff")
    "Priority for the AIFF decoder" ~d:1

let () =
  Plug.register Decoder.decoders "aiff"
    ~doc:"Decode as AIFF any file with a correct header."
    {
      Decoder.priority = (fun () -> aiff_priorities#get);
      file_extensions = (fun () -> Some aiff_file_extensions#get);
      mime_types = (fun () -> Some aiff_mime_types#get);
      file_type;
      file_decoder = Some create_file_decoder;
      stream_decoder = Some (fun ~ctype:_ _ -> create ?header:None);
    }

let () =
  let duration ~metadata:_ file =
    let w = Wav_aiff.fopen file in
    let ret = Wav_aiff.duration w in
    Wav_aiff.close w;
    ret
  in
  Plug.register Request.dresolvers "wav/aiff"
    ~doc:"Native computation of wav and aiff files duration." duration

let basic_mime_types =
  Dtools.Conf.list
    ~p:(Decoder.conf_mime_types#plug "basic")
    "Mime-types used for guessing PCM/BASIC format" ~d:["audio/basic"]

let basic_priorities =
  Dtools.Conf.int
    ~p:(Decoder.conf_priorities#plug "basic")
    "Priority for the PCM/BASIC decoder" ~d:1

let () =
  Plug.register Decoder.decoders "pcm/basic"
    ~doc:"Decode audio/basic as headerless stereo U8 PCM at 8kHz."
    {
      Decoder.priority = (fun () -> basic_priorities#get);
      file_extensions = (fun () -> None);
      mime_types = (fun () -> Some basic_mime_types#get);
      file_type = (fun ~metadata:_ ~ctype:_ _ -> None);
      file_decoder = None;
      stream_decoder =
        Some (fun ~ctype:_ _ -> create ~header:(`Wav, 8, 2, 8000, -1));
    }
