(*
 * Copyright (c) 2015 Thomas Gazagnaire <thomas@gazagnaire.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *)

open Lwt.Infix
open Mirage_flow_combinators

let pp_buf ppf buf = Fmt.string ppf (Cstruct.to_string buf)
let eq_buf b1 b2 = Cstruct.to_string b1 = Cstruct.to_string b2

let cstruct = Alcotest.testable pp_buf eq_buf
let fail fmt = Fmt.kstr (fun s -> Alcotest.fail s) fmt

let check_buffer = Alcotest.(check cstruct)
let check_buffers = Alcotest.(check @@ list cstruct)

let check_ok_buffer msg buf = function
  | Ok (`Data b) -> check_buffer msg buf b
  | Ok `Eof      -> fail "%s: eof" msg
  | Error e      -> fail "%s: error=%a" msg F.pp_error e

let check_ok_unit msg = function
  | Ok ()   -> ()
  | Error e -> fail "%s: error=%a" msg F.pp_error e

let check_ok_write msg = function
  | Ok ()   -> ()
  | Error e -> fail "%s: error=%a" msg F.pp_write_error e

let check_closed msg = function
  | Ok ()         -> fail "%s: not closed" msg
  | Error `Closed -> ()
  | Error e       -> fail "%s: error=%a" msg F.pp_write_error e

let check_eof msg = function
  | Ok `Eof -> ()
  | Ok _    -> fail "%s: ok" msg
  | Error e -> fail "%s: error=%a" msg F.pp_error e

let cs str = Cstruct.of_string str
let cb str = Cstruct.of_bytes str

let css = List.map cs
let cbs = List.map cb

let filter x =
  let zero = Cstruct.of_string "" in
  List.filter ((<>) zero) x

let input_string () =
  let input = "xxxxxxxxxx" in
  let ic = F.string ~input () in
  F.read ic >>= fun x1 ->
  F.read ic >>= fun x2 ->
  F.write ic (cs "hihi") >>= fun r ->
  check_ok_buffer "read 1" (cs input) x1;
  check_eof "read 2" x2;
  check_closed "write"  r;
  Lwt.return_unit

let output_string () =
  let output = Bytes.of_string "xxxxxxxxxx" in
  let oc = F.string ~output () in
  F.write oc (cs  "hell") >>= fun x1 ->
  F.write oc (cs   "o! ") >>= fun x2 ->
  F.write oc (cs "world") >>= fun x3 ->
  F.read oc >>= fun r ->
  check_buffer "result" (cb output) (cs "hello! wor");
  check_ok_write "write 1" x1;
  check_ok_write "write 2" x2;
  check_closed   "write 3" x3;
  check_eof      "read"    r;
  Lwt.return_unit

let input_strings () =
  let input = [ ""; "123"; "45"; "6789"; "0" ] in
  let ic = F.strings ~input () in
  F.read ic >>= fun x1 ->
  F.read ic >>= fun x2 ->
  F.read ic >>= fun x3 ->
  F.read ic >>= fun x4 ->
  F.read ic >>= fun y ->
  F.read ic >>= fun z ->
  F.write ic (cs "hihi") >>= fun w ->
  check_ok_buffer "read 1" (cs  "123") x1;
  check_ok_buffer "read 2" (cs   "45") x2;
  check_ok_buffer "read 3" (cs "6789") x3;
  check_ok_buffer "read 4" (cs    "0") x4;
  check_eof       "read 5" y;
  check_eof       "read 6" z;
  check_closed    "write"  w;
  Lwt.return_unit

let output_strings () =
  let output = List.map Bytes.of_string ["xxx"; ""; "xx"; "xxx"; ] in
  let oc = F.strings ~output () in
  F.write oc (cs  "hell") >>= fun x1 ->
  F.write oc (cs   "o! ") >>= fun x2 ->
  F.write oc (cs "world") >>= fun x3 ->
  F.read oc >>= fun r ->
  check_buffers "result" (filter (cbs output)) (css ["hel"; "lo"; "! w"]);
  check_ok_write "write 1" x1;
  check_ok_write "write 2" x2;
  check_closed   "write 3" x3;
  check_eof     "read"    r;
  Lwt.return_unit

let input_cstruct () =
  let input = Cstruct.of_string "xxxxxxxxxx" in
  let ic = F.cstruct ~input () in
  F.read ic >>= fun x1 ->
  F.read ic >>= fun x2 ->
  F.write ic (cs "hihi") >>= fun r ->
  check_ok_buffer "read 1" input x1;
  check_eof       "read 2" x2;
  check_closed    "write"  r;
  Lwt.return_unit

let output_cstruct () =
  let output = Cstruct.of_string "xxxxxxxxxx" in
  let oc = F.cstruct ~output () in
  F.write oc (cs  "hell") >>= fun x1 ->
  F.write oc (cs   "o! ") >>= fun x2 ->
  F.write oc (cs "world") >>= fun x3 ->
  F.read oc >>= fun r ->
  check_buffer  "result" output (cs "hello! wor");
  check_ok_write "write 1" x1;
  check_ok_write "write 2" x2;
  check_closed   "write 3" x3;
  check_eof     "read"    r;
  Lwt.return_unit

let input_cstructs () =
  let inputs = List.map cs [ "123"; "45"; ""; "6789"; "0" ] in
  let ic = F.cstructs ~input:inputs () in
  F.read ic >>= fun x1 ->
  F.read ic >>= fun x2 ->
  F.read ic >>= fun x3 ->
  F.read ic >>= fun x4 ->
  F.read ic >>= fun y ->
  F.read ic >>= fun z ->
  F.write ic (cs "hihi") >>= fun w ->
  check_ok_buffer "read 1" (cs  "123") x1;
  check_ok_buffer "read 2" (cs   "45") x2;
  check_ok_buffer "read 3" (cs "6789") x3;
  check_ok_buffer "read 4" (cs    "0") x4;
  check_eof       "read 5 "y;
  check_eof       "read 6" z;
  check_closed    "read 7" w;
  Lwt.return_unit

let output_cstructs () =
  let output = List.map cs [ ""; "xxx"; "xx"; "xxx" ] in
  let oc = F.cstructs ~output () in
  F.write oc (cs  "hell") >>= fun x1 ->
  F.write oc (cs   "o! ") >>= fun x2 ->
  F.write oc (cs "world") >>= fun x3 ->
  F.read oc >>= fun r ->
  check_buffers "result" (filter output) (css ["hel"; "lo"; "! w"]);
  check_ok_write "write 1" x1;
  check_ok_write "write 2" x2;
  check_closed   "write 3" x3;
  check_eof     "read"    r;
  Lwt.return_unit

module Lwt_io_flow = Mirage_flow_unix.Make(F)

let input_lwt_io () =
  let ic = F.strings ~input:["1"; "234"; "56"; "78\n90"] () in
  let lic = Lwt_io_flow.ic ic in
  Lwt_io.read_line lic >>= fun l ->
  check_buffer "result" (cs "12345678") (cs l);
  Lwt.return_unit

let output_lwt_io () =
  let output = css ["xxxx";"xxxx"; "xxxxxx"] in
  let oc = F.cstructs ~output () in
  let loc = Lwt_io_flow.oc oc in
  Lwt_io.write_line loc "Hello world!" >>= fun () ->
  Lwt_io.flush loc >>= fun () ->
  check_buffers "result" (css ["Hell"; "o wo"; "rld!\nx"]) output;
  Lwt.return_unit

let run f () = Lwt_main.run (f ())

let string = [
  "input" , `Quick, run input_string;
  "output", `Quick, run output_string;
]

let strings = [
  "input" , `Quick, run input_strings;
  "output", `Quick, run output_strings;
]

let cstruct = [
  "input" , `Quick, run input_cstruct;
  "output", `Quick, run output_cstruct;
]

let cstructs = [
  "input" , `Quick, run input_cstructs;
  "output", `Quick, run output_cstructs;
]

let lwt_io = [
  "input" , `Quick, run input_lwt_io;
  "output", `Quick, run output_lwt_io;
]
let () =
  Alcotest.run "mirage-flow" [
    "string"  , string;
    "strings" , strings;
    "cstruct" , cstruct;
    "cstructs", cstructs;
    "lwt-io"  , lwt_io;
  ]
