
open Unix

let src = ref ""
let dst = ref ""

let buflen = ref 1024

let input_string chan len =
  let ans = String.create len in
    (* TODO: check length *)
    ignore (input chan ans 0 len) ;
    ans

let input_int chan =
  let buf = input_string chan 4 in
    (int_of_char buf.[0])
    + (int_of_char buf.[1]) lsl 8
    + (int_of_char buf.[2]) lsl 16
    + (int_of_char buf.[3]) lsl 24

let input_short chan =
  let buf = input_string chan 2 in
    (int_of_char buf.[0]) + (int_of_char buf.[1]) lsl 8

let compression = ref 5
let ogg = ref false
let usage = "usage: encode [options] source destination"

let _ =
  Arg.parse
    [
      "--compression", Arg.Int (fun b -> compression := b),
      "Compression level." ;
      "--ogg", Arg.Bool (fun b -> ogg := b),
      "Encoder in ogg format." ;
    ]
    (
      let pnum = ref (-1) in
        (fun s -> incr pnum; match !pnum with
           | 0 -> src := s
           | 1 -> dst := s
           | _ -> Printf.eprintf "Error: too many arguments\n"; exit 1
        )
    ) usage;
  if !src = "" || !dst = "" then
    (
      Printf.printf "%s\n" usage;
      exit 1
    );
  let ic = open_in_bin !src in
    (* TODO: improve! *)
    if input_string ic 4 <> "RIFF" then invalid_arg "No RIFF tag";
    ignore (input_string ic 4);
    if input_string ic 4 <> "WAVE" then invalid_arg "No WAVE tag";
    if input_string ic 4 <> "fmt " then invalid_arg "No fmt tag";
    let _ = input_int ic in
    let _ = input_short ic in (* TODO: should be 1 *)
    let channels = input_short ic in
    let infreq = input_int ic in
    let _ = input_int ic in (* bytes / s *)
    let _ = input_short ic in (* block align *)
    let bits = input_short ic in
    if (bits <> 16) then
      failwith "only s16le is supported for now.." ;
    let params = 
      { Flac.Encoder.
          channels = channels ;
          sample_rate = infreq ;
          bits_per_sample = bits ;
          compression_level = Some !compression;
          total_samples = None
      }
    in
    let comments = [("TITLE","Encoding example")] in
    let encode,finish = 
      if not !ogg then
        let enc = Flac.Encoder.File.create ~comments params !dst in
        let encode buf =
            Flac.Encoder.process enc.Flac.Encoder.File.enc
                                 enc.Flac.Encoder.File.callbacks 
                                 buf
        in
        let finish () = 
          Flac.Encoder.finish enc.Flac.Encoder.File.enc
                              enc.Flac.Encoder.File.callbacks ;
          Unix.close enc.Flac.Encoder.File.fd;
        in
       encode,finish
      else
        let os = Ogg.Stream.create () in
        let oc = open_out !dst in
        let enc,p,l = Ogg_flac.Encoder.create ~comments params os in
        Ogg.Stream.put_packet os p;
        output_string oc (Ogg.Stream.flush os) ;
        List.iter (Ogg.Stream.put_packet os) l;
        output_string oc (Ogg.Stream.flush os) ;
        flush oc ;
        let encode buf =
            Flac.Encoder.process enc Ogg_flac.Encoder.callbacks
                                     buf;
            output_string oc (Ogg.Stream.pagesout os);
        in
        let finish () = 
          Flac.Encoder.finish enc Ogg_flac.Encoder.callbacks;
          output_string oc (Ogg.Stream.flush os) ;
          close_out oc
        in
        encode,finish
    in
    let start = Unix.time () in
      Printf.printf
        "Input detected: PCM WAVE %d channels, %d Hz, %d bits\n%!"
        channels infreq bits;
      Printf.printf
        "Encoding to: %s %d channels, %d Hz, compression level: %d\nPlease wait...\n%!"
        (if !ogg then "OGG/FLAC" else "FLAC") channels infreq !compression;
      if input_string ic 4 <> "data" then invalid_arg "No data tag";
      (* This ensures the actual audio data will start on a new page, as per
       * spec. *)
      let buflen = channels*bits/8*(!buflen) in
      let buf = String.create buflen in
        begin try while true do
          really_input ic buf 0 (String.length buf);
          encode (Flac.Encoder.from_s16le buf channels)
        done;
        with End_of_file -> () end;
        finish ();
        close_in ic;
        Printf.printf "Finished in %.0f seconds.\n" ((Unix.time ())-.start);
        Gc.full_major () ;
        Gc.full_major ()
