(*
  Copyright (C) 2003  Bardur Arantsson
  Copyright (C) 2004-2010 The Savonet Team

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

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*)

type device

type t = 
  { device         : device ;
    mutable closed : bool } 

type driver_kind_t = [`LIVE | `FILE | `UNKNOWN]

type byte_format_t = [`LITTLE_ENDIAN | `BIG_ENDIAN | `NATIVE | `UNKNOWN]

type driver_t = 
  { id         : int ;
    kind       : driver_kind_t ;
    short_name : string ;
    name       : string ;
    comment    : string ;
    author     : string ;
    priority   : int ;
    preferred_byte_format : byte_format_t ;
    options    : string list
  }

type internal_driver_t = int

exception Closed
exception Invalid_value

let () = 
  Callback.register_exception "ocaml_ao_exn_invalid_value" Invalid_value

external _initialize : unit -> unit = "ocaml_ao_stubs_initialize"

external _shutdown : unit -> unit = "ocaml_ao_stubs_shutdown"

let () =
  _initialize ();
  at_exit _shutdown

external driver_kind :  internal_driver_t -> driver_kind_t = "ocaml_ao_stubs_driver_kind"

external driver_name :  internal_driver_t -> string = "ocaml_ao_stubs_driver_name"

external driver_short_name :  internal_driver_t -> string = "ocaml_ao_stubs_driver_short_name"

external driver_comment :  internal_driver_t -> string = "ocaml_ao_stubs_driver_comment"

external driver_author :  internal_driver_t -> string = "ocaml_ao_stubs_driver_author"

external driver_priority :  internal_driver_t -> int = "ocaml_ao_stubs_driver_priority"

external driver_preferred_byte_format :  internal_driver_t -> byte_format_t = "ocaml_ao_stubs_driver_preferred_byte_format"

external driver_options :  internal_driver_t -> string list = "ocaml_ao_stubs_driver_options"

let driver_of_internal_driver x = 
  { id = x;
    kind = driver_kind x;
    short_name = driver_short_name x;
    name = driver_name x;
    comment = driver_comment x;
    author = driver_author x;
    priority = driver_priority x;
    preferred_byte_format = driver_preferred_byte_format x;
    options = driver_options x
  } 

external get_default_driver : unit ->  internal_driver_t = "ocaml_ao_stubs_get_default_driver"

let get_default_driver () = 
  driver_of_internal_driver (get_default_driver ())

external get_drivers : unit ->  internal_driver_t list = "ocaml_ao_stubs_get_drivers"

let get_drivers () = 
  List.map driver_of_internal_driver (get_drivers ())

external find_driver : string ->  internal_driver_t = "ocaml_ao_stubs_find_driver"

let find_driver x =  
  driver_of_internal_driver (find_driver x)

let drivers = get_drivers ()

external close : device -> unit =
  "ocaml_ao_stubs_close"

let gc_close x = 
  if not x.closed then
    close x.device 

let close x =
  if x.closed then
    raise Closed ;
  close x.device ;
  x.closed <- true

external open_live_aux : int -> int -> int -> string -> byte_format_t -> (string*string) list -> internal_driver_t -> device =
  "ocaml_ao_stubs_open_live_aux_bytecode" "ocaml_ao_stubs_open_live_aux_native"

let open_live
      ?bits:(bits=16)
      ?rate:(rate=44100)
      ?channels:(channels=2)
      ?channels_matrix
      ?byte_format:(byte_format=`LITTLE_ENDIAN)
      ?options:(options=[])
      ?driver:(driver=get_default_driver ()) () =
  let channels_matrix = 
    match channels_matrix with
      | None -> ""
      | Some x -> x
  in
  let dev = open_live_aux bits rate channels 
                          channels_matrix byte_format 
                          options driver.id
  in
  let ret = { device = dev ; closed = false } in
  Gc.finalise gc_close ret ;
  ret

external open_file_aux : int -> int -> int -> string -> byte_format_t -> (string*string) list -> internal_driver_t -> bool -> string -> device =
  "ocaml_ao_stubs_open_file_aux_bytecode" "ocaml_ao_stubs_open_file_aux_native"

let open_file
  ?bits:(bits=16)
  ?rate:(rate=44100)
  ?channels:(channels=2)
  ?channels_matrix
  ?byte_format:(byte_format=`LITTLE_ENDIAN)
  ?options:(options=[])
  ?driver:(driver=get_default_driver ())
  ?overwrite:(overwrite=false)
  (filename:string) =
   let channels_matrix =
     match channels_matrix with
       | None -> ""
       | Some x -> x
   in
   let dev = open_file_aux bits rate channels channels_matrix 
                                byte_format options driver.id 
                                overwrite filename 
   in
   let ret = { device = dev; closed = false } in
   Gc.finalise gc_close ret ;
   ret

external play : device -> string -> unit =
  "ocaml_ao_stubs_play"

let play x s = 
  if x.closed then
    raise Closed ;
  play x.device s

let driver_kind d = d.kind
let driver_name d = d.name
let driver_short_name d = d.short_name
let driver_comment d = d.comment
let driver_author d = d.author
let driver_priority d = d.priority
let driver_preferred_byte_format d = d.preferred_byte_format
let driver_options d = d.options
