open CCSexp
module T = (val Containers_testlib.make ~__FILE__ ())
include T;;

t @@ fun () ->
CCResult.to_opt (parse_string "(abc d/e/f \"hello \\\" () world\" )") <> None
;;

t @@ fun () ->
CCResult.to_opt (parse_string "(abc ( d e ffff   ) \"hello/world\")") <> None
;;

t @@ fun () -> CCResult.to_opt (parse_string "\"\123\bcoucou\"") <> None

let eq' =
  eq ~printer:(function
    | Ok x -> to_string x
    | Error e -> "error " ^ e)
;;

eq' (parse_string "(a b)") (Ok (`List [ `Atom "a"; `Atom "b" ]));;
eq' (parse_string "(a\n ;coucou\n b)") (Ok (`List [ `Atom "a"; `Atom "b" ]));;

eq'
  (parse_string "(a #; (foo bar\n (1 2 3)) b)")
  (Ok (`List [ `Atom "a"; `Atom "b" ]))
;;

eq' (parse_string "#; (a b) (c d)") (Ok (`List [ `Atom "c"; `Atom "d" ]));;
eq' (parse_string "#; (a b) 1") (Ok (`Atom "1"))

let eq' =
  eq ~printer:(function
    | Ok x -> String.concat ";" @@ List.map to_string x
    | Error e -> "error " ^ e)
;;

eq'
  (parse_string_list "(a b)(c)")
  (Ok [ `List [ `Atom "a"; `Atom "b" ]; `List [ `Atom "c" ] ])
;;

eq' (parse_string_list "  ") (Ok []);;

eq'
  (parse_string_list "(a\n ;coucou\n b)")
  (Ok [ `List [ `Atom "a"; `Atom "b" ] ])
;;

eq'
  (parse_string_list "#; (a b) (c d) e ")
  (Ok [ `List [ `Atom "c"; `Atom "d" ]; `Atom "e" ])
;;

eq' (parse_string_list "#; (a b) 1") (Ok [ `Atom "1" ])

let sexp_bijective s = to_string s |> parse_string = Ok s;;

eq
  ~printer:CCFormat.(to_string (Dump.result pp))
  (Ok (`List [ `Atom "" ]))
  (parse_string "(\"\")")
;;

t @@ fun () -> sexp_bijective (`List [ `Atom "" ])

let sexp_gen =
  let mkatom a = `Atom a and mklist l = `List l in
  let atom = Q.Gen.(map mkatom (string_size ~gen:printable (1 -- 30))) in
  let gen =
    Q.Gen.(
      sized
        (fix (fun self n st ->
             match n with
             | 0 -> atom st
             | _ ->
               oneof_weighted
                 [
                   1, atom; 2, map mklist (list_size (0 -- 10) (self (n / 10)));
                 ]
                 st)))
  in
  let rec small = function
    | `Atom s -> String.length s
    | `List l -> List.fold_left (fun n x -> n + small x) 0 l
  and print = function
    | `Atom s -> Printf.sprintf "`Atom \"%s\"" s
    | `List l -> "`List " ^ Q.Print.list print l
  and shrink = function
    | `Atom s -> Q.Iter.map mkatom (Q.Shrink.string s)
    | `List l -> Q.Iter.map mklist (Q.Shrink.list ~shrink l)
  in
  Q.make ~print ~small ~shrink gen
;;

q ~count:100 sexp_gen sexp_bijective;;

(* regression for #338 *)
t @@ fun () ->
Printexc.record_backtrace true;
let cases =
  [
    "\"\\256\"";
    "\"\\722\02622222\\\\\n\r<\\\\\\\\\"\\222222222\\\\\"\"\2032!2222\\\\\"\"";
    "\"\n\r<\\t\023\n\203\\622222222\\\\\"\"\2032!2222\\\\\"\"";
    "\"\n\
     \r<@t\023\n\
     \203\\2222D2\n\
     \r22222\01622222222222222222222222\203\\292242\222 2\\\\\">K2";
    "\"\n\r<\\t\023\n\203\\272222222\\\\\"\"\2032\0042222\\\\\"\"";
    "\"\023\n\
     \203\\5222\n\
     \r<\\t\023\n\
     \203\\52222222\\\\\"2\\\216\216\216\216\216\\\\\"\216\216\216\216\216\216\216\216\216222222222222222\147";
    "\"\\722\02622222\\\\\n\r<\\\\\\\\\"\\222222222\\\\\"\"\2032!2222\\\\\"\"";
  ]
in
cases
|> List.iter (fun s ->
       try ignore (parse_string s)
       with e ->
         let st = Printexc.get_backtrace () in
         print_endline @@ Printexc.to_string e ^ "\n" ^ st;
         assert false);
true

module CS = CCCanonical_sexp

module Csexp_arg = struct
  open Csexp

  type t = Csexp.t

  let atom s = Atom s
  let list l = List l

  let match_ s ~atom ~list =
    match s with
    | Atom s -> atom s
    | List l -> list l

  type loc = unit

  let make_loc = None
  let atom_with_loc ~loc:() = atom
  let list_with_loc ~loc:() = list
end

module CS0 = CS.Make (Csexp_arg)
module Sexp0 = CCSexp.Make (Csexp_arg)

let gen_csexp (str : string Q.Gen.t) : CS0.t Q.Gen.t =
  let open Q.Gen in
  let open Csexp in
  ( fix @@ fun self depth ->
    let mklist n = list_size (0 -- n) (self (depth + 1)) >|= fun l -> List l in
    oneof_weighted
    @@ List.flatten
         [
           [ (3, str >|= fun s -> Atom s) ];
           (match depth with
           | 0 -> [ 4, mklist 25 ]
           | 1 -> [ 2, mklist 7 ]
           | 2 -> [ 1, mklist 2 ]
           | _ -> []);
         ] )
    0

let rec shrink_csexp (s : Csexp.t) : Csexp.t Q.Iter.t =
  let open Csexp in
  let open Q.Iter in
  match s with
  | Atom s -> Q.Shrink.string s >|= fun s -> Atom s
  | List l -> Q.Shrink.list ~shrink:shrink_csexp l >|= fun l -> List l

let arb_csexp_pp =
  let genstr = Q.Gen.(string_size ~gen:Q.Gen.printable (0 -- 15)) in
  Q.make ~print:Sexp0.to_string ~shrink:shrink_csexp (gen_csexp genstr)

let arb_csexp_arb =
  (* binary-ready *)
  let genchar = Q.Gen.(0 -- 255 >|= Char.chr) in
  let genstr = Q.Gen.(string_size ~gen:genchar (0 -- 15)) in
  Q.make ~print:Sexp0.to_string ~shrink:shrink_csexp (gen_csexp genstr)

module Make
    (X : sig
      val arb : Csexp.t Q.arbitrary
    end)
    () =
struct
  open X;;

  q ~count:2_000 arb @@ fun sexp ->
  let s = CS0.to_string sexp in
  match Csexp.parse_string s with
  | Ok sexp' -> sexp = sexp'
  | Error (_, msg) -> Q.Test.fail_report msg
  ;;

  q ~count:2_000 arb @@ fun sexp ->
  let s = Csexp.to_string sexp in
  match CS0.parse_string s with
  | Ok sexp' -> sexp = sexp'
  | Error msg -> Q.Test.fail_report msg

  let init () = ()
end

let () =
  let module M1 =
    Make
      (struct
        let arb = arb_csexp_pp
      end)
      ()
  in
  let module M2 =
    Make
      (struct
        let arb = arb_csexp_arb
      end)
      ()
  in
  M1.init ();
  M2.init ();
  ()
