(* Js_of_ocaml library
 * http://www.ocsigen.org/js_of_ocaml/
 * Copyright (C) 2011 Pierre Chambart
 * Laboratoire PPS - CNRS Université Paris Diderot
 *
 * This program 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, with linking exception;
 * either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *)

open Js
open Dom
open! Import

class type blob =
  object
    method size : int readonly_prop

    method _type : js_string t readonly_prop

    method slice : int -> int -> blob t meth

    method slice_withContentType : int -> int -> js_string t -> blob t meth
  end

let blob_constr = Unsafe.global##._Blob

type 'a make_blob =
  ?contentType:string -> ?endings:[ `Transparent | `Native ] -> 'a -> blob t

let rec filter_map f = function
  | [] -> []
  | v :: q -> (
      match f v with
      | None -> filter_map f q
      | Some v' -> v' :: filter_map f q)

let make_blob_options contentType endings =
  let options =
    filter_map
      (fun (name, v) ->
        match v with
        | None -> None
        | Some v -> Some (name, Unsafe.inject (string v)))
      [ "type", contentType
      ; ( "endings"
        , match endings with
          | None -> None
          | Some `Transparent -> Some "transparent"
          | Some `Native -> Some "native" )
      ]
  in
  match options with
  | [] -> undefined
  | l -> Unsafe.obj (Array.of_list l)

let blob_raw ?contentType ?endings a =
  let options = make_blob_options contentType endings in
  new%js blob_constr (array a) options

let blob_from_string ?contentType ?endings s =
  blob_raw ?contentType ?endings [| string s |]

let blob_from_any ?contentType ?endings l =
  let l =
    List.map
      (function
        | `arrayBuffer a -> Unsafe.inject a
        | `arrayBufferView a -> Unsafe.inject a
        | `string s -> Unsafe.inject (string s)
        | `js_string s -> Unsafe.inject s
        | `blob b -> Unsafe.inject b)
      l
  in
  blob_raw ?contentType ?endings (Array.of_list l)

class type file =
  object
    inherit blob

    method name : js_string t readonly_prop

    method lastModifiedDate : js_string t readonly_prop
  end

(* in firefox 3.0-3.5 file.name is not available, we use the nonstandard fileName instead *)
class type file_name_only =
  object
    method name : js_string t optdef readonly_prop

    method fileName : js_string t optdef readonly_prop
  end

let filename file =
  let file : file_name_only t = Js.Unsafe.coerce file in
  match Optdef.to_option file##.name with
  | None -> (
      match Optdef.to_option file##.fileName with
      | None -> failwith "can't retrieve file name: not implemented"
      | Some name -> name)
  | Some name -> name

type file_any = < > t

let doc_constr = Unsafe.global##._Document

module CoerceTo = struct
  external json : file_any -> 'a Opt.t = "%identity"

  let document (e : file_any) =
    if instanceof e doc_constr
    then Js.some (Unsafe.coerce e : element document t)
    else Js.null

  let blob (e : file_any) =
    if instanceof e blob_constr then Js.some (Unsafe.coerce e : #blob t) else Js.null

  let string (e : file_any) =
    if typeof e == string "string"
    then Js.some (Unsafe.coerce e : js_string t)
    else Js.null

  let arrayBuffer (e : file_any) =
    if instanceof e Typed_array.arrayBuffer
    then Js.some (Unsafe.coerce e : Typed_array.arrayBuffer t)
    else Js.null
end

class type fileList =
  object
    inherit [file] Dom.nodeList
  end

class type fileError =
  object
    method code : int readonly_prop
  end

class type ['a] progressEvent =
  object
    inherit ['a] event

    method lengthComputable : bool t readonly_prop

    method loaded : int readonly_prop

    method total : int readonly_prop
  end

class type progressEventTarget =
  object ('self)
    method onloadstart : ('self t, 'self progressEvent t) event_listener writeonly_prop

    method onprogress : ('self t, 'self progressEvent t) event_listener writeonly_prop

    method onload : ('self t, 'self progressEvent t) event_listener writeonly_prop

    method onabort : ('self t, 'self progressEvent t) event_listener writeonly_prop

    method onerror : ('self t, 'self progressEvent t) event_listener writeonly_prop

    method onloadend : ('self t, 'self progressEvent t) event_listener writeonly_prop
  end

type readyState =
  | EMPTY
  | LOADING
  | DONE

class type fileReader =
  object ('self)
    method readAsArrayBuffer : #blob t -> unit meth

    method readAsBinaryString : #blob t -> unit meth

    method readAsText : #blob t -> unit meth

    method readAsText_withEncoding : #blob t -> js_string t -> unit meth

    method readAsDataURL : #blob t -> unit meth

    method abort : unit meth

    method readyState : readyState readonly_prop

    method result : file_any readonly_prop

    method error : fileError t readonly_prop

    inherit progressEventTarget
  end

module ReaderEvent = struct
  type typ = fileReader progressEvent t Dom.Event.typ

  let loadstart = Event.make "loadstart"

  let progress = Event.make "progress"

  let abort = Event.make "abort"

  let error = Event.make "error"

  let load = Event.make "load"

  let loadend = Event.make "loadend"
end

let fileReader : fileReader t constr = Js.Unsafe.global##._FileReader

let addEventListener = Dom.addEventListener
