(* Because other the syntax s.[x] causes trouble *)
module String = Dune_caml.String

include struct
  [@@@warning "-32-3"]
  let capitalize_ascii   = String.capitalize
  let uncapitalize_ascii = String.uncapitalize
  let uppercase_ascii    = String.uppercase
  let lowercase_ascii    = String.lowercase
  let equal (a:string) b = Pervasives.(=) a b
  let index_opt s ch =
    match String.index s ch with
    | i -> Some i
    | exception Not_found -> None
  let index_from_opt s i ch =
    match String.index_from s i ch with
    | i -> Some i
    | exception Not_found -> None
  let rindex_opt s ch =
    match String.rindex s ch with
    | i -> Some i
    | exception Not_found -> None
  let rindex_from_opt s i ch =
    match String.rindex_from s i ch with
    | i -> Some i
    | exception Not_found -> None
end

include StringLabels

let compare a b = Ordering.of_int (String.compare a b)

module T = struct
  type t = StringLabels.t
  let compare = compare
  let equal (x : t) (y : t) = x = y
  let hash (s : t) = Hashtbl.hash s
  let to_dyn s = Dyn.String s
end

let to_dyn = T.to_dyn

let equal : string -> string -> bool = (=)
let hash = Hashtbl.hash

let capitalize   = capitalize_ascii
let uncapitalize = uncapitalize_ascii
let uppercase    = uppercase_ascii
let lowercase    = lowercase_ascii

let index = index_opt
let index_from = index_from_opt
let rindex = rindex_opt
let rindex_from = rindex_from_opt

let break s ~pos =
  (sub s ~pos:0 ~len:pos,
   sub s ~pos ~len:(length s - pos))

let is_empty s = length s = 0

let rec check_prefix s ~prefix len i =
  i = len || s.[i] = prefix.[i] && check_prefix s ~prefix len (i + 1)

let rec check_suffix s ~suffix suffix_len offset i =
  i = suffix_len ||
  s.[offset + i] = suffix.[i] &&
  check_suffix s ~suffix suffix_len offset (i + 1)

let is_prefix s ~prefix =
  let len = length s in
  let prefix_len = length prefix in
  len >= prefix_len && (check_prefix s ~prefix prefix_len 0)

let is_suffix s ~suffix =
  let len = length s in
  let suffix_len = length suffix in
  len >= suffix_len &&
  (check_suffix s ~suffix suffix_len (len - suffix_len) 0)

let drop_prefix s ~prefix =
  if is_prefix s ~prefix then
    if length s = length prefix then
      Some ""
    else
      Some (sub s ~pos:(length prefix) ~len:(length s - length prefix))
  else
    None

let drop_suffix s ~suffix =
  if is_suffix s ~suffix then
    if length s = length suffix then
      Some s
    else
      Some (sub s ~pos:0 ~len:(length s - length suffix))
  else
    None

let extract_words s ~is_word_char =
  let rec skip_blanks i =
    if i = length s then
      []
    else if is_word_char s.[i] then
      parse_word i (i + 1)
    else
      skip_blanks (i + 1)
  and parse_word i j =
    if j = length s then
      [sub s ~pos:i ~len:(j - i)]
    else if is_word_char s.[j] then
      parse_word i (j + 1)
    else
      sub s ~pos:i ~len:(j - i) :: skip_blanks (j + 1)
  in
  skip_blanks 0

let extract_comma_space_separated_words s =
  extract_words s ~is_word_char:(function
    | ',' | ' ' | '\t' | '\n' -> false
    | _ -> true)

let extract_blank_separated_words s =
  extract_words s ~is_word_char:(function
    | ' ' | '\t' -> false
    | _ -> true)

let lsplit2 s ~on =
  match index s on with
  | None -> None
  | Some i ->
    Some
      (sub s ~pos:0 ~len:i,
       sub s ~pos:(i + 1) ~len:(length s - i - 1))

let lsplit2_exn s ~on =
  match lsplit2 s ~on with
  | Some s -> s
  | None ->
    Code_error.raise "lsplit2_exn"
      [ "s", String s
      ; "on", Char on
      ]

let rsplit2 s ~on =
  match rindex s on with
  | None -> None
  | Some i ->
    Some
      (sub s ~pos:0 ~len:i,
       sub s ~pos:(i + 1) ~len:(length s - i - 1))

include String_split

let escape_only c s =
  let n = ref 0 in
  let len = length s in
  for i = 0 to len - 1 do
    if unsafe_get s i = c then incr n;
  done;
  if !n = 0 then s
  else (
    let b = Bytes.create (len + !n) in
    n := 0;
    for i = 0 to len - 1 do
      if unsafe_get s i = c then (
        Bytes.unsafe_set b !n '\\';
        incr n;
      );
      Bytes.unsafe_set b !n (unsafe_get s i);
      incr n
    done;
    Bytes.unsafe_to_string b
  )

let longest_map l ~f =
  List.fold_left l ~init:0 ~f:(fun acc x ->
    max acc (length (f x)))

let longest l = longest_map l ~f:Fn.id

let longest_prefix = function
  | [] -> ""
  | [x] -> x
  | x :: xs ->
    let rec loop len i =
      if i < len && List.for_all xs ~f:(fun s -> s.[i] = x.[i]) then
        loop len (i + 1)
      else
        i
    in
    let len =
      List.fold_left ~init:(length x) ~f:(fun acc x -> min acc (length x)) xs in
    sub ~pos:0 x ~len:(loop len 0)

let exists =
  let rec loop s i len f =
    if i = len then
      false
    else
      f (unsafe_get s i) || loop s (i + 1) len f
  in
  fun s ~f ->
    loop s 0 (length s) f

let for_all =
  let rec loop s i len f =
    i = len ||
    (f (unsafe_get s i) && loop s (i + 1) len f)
  in
  fun s ~f ->
    loop s 0 (length s) f

let maybe_quoted s =
  let escaped = escaped s in
  if s == escaped || s = escaped then
    s
  else
    Printf.sprintf {|"%s"|} escaped

module O = Comparable.Make(T)
module Set = struct
  include O.Set

  let pp fmt t =
    Format.fprintf fmt "Set (@[%a@])"
      (Format.pp_print_list Format.pp_print_string
         ~pp_sep:(fun fmt () -> Format.fprintf fmt "@ "))
      (to_list t)
end

module Map = struct
  include O.Map
  let pp f fmt t =
    Format.pp_print_list (fun fmt (k, v) ->
      Format.fprintf fmt "@[<hov 2>(%s@ =@ %a)@]" k f v
    ) fmt (to_list t)
end
module Table = Hashtbl.Make(T)

let enumerate_gen s =
  let s = " " ^ s ^ " " in
  let rec loop = function
    | [] -> []
    | [x] -> [x]
    | [x; y] -> [x; s; y]
    | x :: l -> x :: ", " :: loop l
  in
  fun l -> concat (loop l) ~sep:""

let enumerate_and = enumerate_gen "and"
let enumerate_or  = enumerate_gen "or"
let enumerate_one_of = function
  | [x] -> x
  | s -> "One of " ^ enumerate_or s

let concat ~sep = function
  | [] -> ""
  | [x] -> x
  | xs -> concat ~sep xs

let take s len =
  sub s ~pos:0 ~len:(min (length s) len)

let drop s n =
  let len = length s in
  sub s ~pos:(min n len) ~len:(max (len - n) 0)

let split_n s n =
  let len = length s in
  let n = min n len in
  ( sub s ~pos:0 ~len:n
  , sub s ~pos:n ~len:(len - n)
  )

let findi =
  let rec loop s len ~f i =
    if i >= len then
      None
    else if f (String.unsafe_get s i) then
      Some i
    else
      loop s len ~f (i + 1)
  in
  fun s ~f -> loop s (String.length s) ~f 0

let need_quoting s =
  let len = String.length s in
  len = 0
  ||
  let rec loop i =
    if i = len then false
    else
      match s.[i] with
      | ' ' | '\"' | '(' | ')' | '{' | '}' | ';' | '#' ->
          true
      | _ ->
          loop (i + 1)
  in
  loop 0

let quote_for_shell s = if need_quoting s then Dune_caml.Filename.quote s else s
