(**************************************************************************)
(*                                                                        *)
(*                                 OCaml                                  *)
(*                                                                        *)
(*             Xavier Leroy, projet Cristal, INRIA Rocquencourt           *)
(*                                                                        *)
(*   Copyright 2021 Institut National de Recherche en Informatique et     *)
(*     en Automatique.                                                    *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

type t = in_channel

type open_flag = Stdlib.open_flag =
  | Open_rdonly
  | Open_wronly
  | Open_append
  | Open_creat
  | Open_trunc
  | Open_excl
  | Open_binary
  | Open_text
  | Open_nonblock

let stdin = Stdlib.stdin
let open_bin = Stdlib.open_in_bin
let open_text = Stdlib.open_in
let open_gen = Stdlib.open_in_gen

let with_open openfun s f =
  let ic = openfun s in
  Fun.protect ~finally:(fun () -> Stdlib.close_in_noerr ic)
    (fun () -> f ic)

let with_open_bin s f =
  with_open Stdlib.open_in_bin s f

let with_open_text s f =
  with_open Stdlib.open_in s f

let with_open_gen flags perm s f =
  with_open (Stdlib.open_in_gen flags perm) s f

let seek = Stdlib.LargeFile.seek_in
let pos = Stdlib.LargeFile.pos_in
let length = Stdlib.LargeFile.in_channel_length
let close = Stdlib.close_in
let close_noerr = Stdlib.close_in_noerr

let input_char ic =
  match Stdlib.input_char ic with
  | c -> Some c
  | exception End_of_file -> None

let input_byte ic =
  match Stdlib.input_byte ic with
  | n -> Some n
  | exception End_of_file -> None

let input_line ic =
  match Stdlib.input_line ic with
  | s -> Some s
  | exception End_of_file -> None

let input = Stdlib.input

external unsafe_input_bigarray :
  t -> _ Bigarray.Array1.t -> int -> int -> int
  = "caml_ml_input_bigarray"

let input_bigarray ic buf ofs len =
  if ofs < 0 || len < 0 || ofs > Bigarray.Array1.dim buf - len
  then invalid_arg "input_bigarray"
  else unsafe_input_bigarray ic buf ofs len

let really_input ic buf pos len =
  match Stdlib.really_input ic buf pos len with
  | () -> Some ()
  | exception End_of_file -> None

let rec unsafe_really_input_bigarray ic buf ofs len =
  if len <= 0 then Some () else begin
    let r = unsafe_input_bigarray ic buf ofs len in
    if r = 0
    then None
    else unsafe_really_input_bigarray ic buf (ofs + r) (len - r)
  end

let really_input_bigarray ic buf ofs len =
  if ofs < 0 || len < 0 || ofs > Bigarray.Array1.dim buf - len
  then invalid_arg "really_input_bigarray"
  else unsafe_really_input_bigarray ic buf ofs len

let really_input_string ic len =
  match Stdlib.really_input_string ic len with
  | s -> Some s
  | exception End_of_file -> None

(* Read up to [len] bytes into [buf], starting at [ofs]. Return total bytes
   read. *)
let read_upto ic buf ofs len =
  let rec loop ofs len =
    if len = 0 then ofs
    else begin
      let r = Stdlib.input ic buf ofs len in
      if r = 0 then
        ofs
      else
        loop (ofs + r) (len - r)
    end
  in
  loop ofs len - ofs

(* Best effort attempt to return a buffer with >= (ofs + n) bytes of storage,
   and such that it coincides with [buf] at indices < [ofs].

   The returned buffer is equal to [buf] itself if it already has sufficient
   free space.

   The returned buffer may have *fewer* than [ofs + n] bytes of storage if this
   number is > [Sys.max_string_length]. However the returned buffer will
   *always* have > [ofs] bytes of storage. In the limiting case when [ofs = len
   = Sys.max_string_length] (so that it is not possible to resize the buffer at
   all), an exception is raised. *)

let ensure buf ofs n =
  let len = Bytes.length buf in
  if len >= ofs + n then buf
  else begin
    let new_len = ref len in
    while !new_len < ofs + n do
      new_len := 2 * !new_len + 1
    done;
    let new_len = !new_len in
    let new_len =
      if new_len <= Sys.max_string_length then
        new_len
      else if ofs < Sys.max_string_length then
        Sys.max_string_length
      else
        failwith "In_channel.input_all: channel content \
                  is larger than maximum string length"
    in
    let new_buf = Bytes.create new_len in
    Bytes.blit buf 0 new_buf 0 ofs;
    new_buf
  end

let input_all ic =
  let chunk_size = Sys.io_buffer_size in
  let initial_size =
    try
      Stdlib.in_channel_length ic - Stdlib.pos_in ic
    with Sys_error _ ->
      -1
  in
  let initial_size = if initial_size < 0 then chunk_size else initial_size in
  let initial_size =
    if initial_size <= Sys.max_string_length then
      initial_size
    else
      Sys.max_string_length
  in
  let buf = Bytes.create initial_size in
  let nread = read_upto ic buf 0 initial_size in
  if nread < initial_size then (* EOF reached, buffer partially filled *)
    Bytes.sub_string buf 0 nread
  else begin (* nread = initial_size, maybe EOF reached *)
    match Stdlib.input_char ic with
    | exception End_of_file ->
        (* EOF reached, buffer is completely filled *)
        Bytes.unsafe_to_string buf
    | c ->
        (* EOF not reached *)
        let rec loop buf ofs =
          let buf = ensure buf ofs chunk_size in
          let rem = Bytes.length buf - ofs in
          (* [rem] can be < [chunk_size] if buffer size close to
             [Sys.max_string_length] *)
          let r = read_upto ic buf ofs rem in
          if r < rem then (* EOF reached *)
            Bytes.sub_string buf 0 (ofs + r)
          else (* r = rem *)
            loop buf (ofs + rem)
        in
        let buf = ensure buf nread (chunk_size + 1) in
        Bytes.set buf nread c;
        loop buf (nread + 1)
  end

let [@tail_mod_cons] rec input_lines ic =
  match Stdlib.input_line ic with
  | line -> line :: input_lines ic
  | exception End_of_file -> []

let rec fold_lines f accu ic =
  match Stdlib.input_line ic with
  | line -> fold_lines f (f accu line) ic
  | exception End_of_file -> accu

let set_binary_mode = Stdlib.set_binary_mode_in

external is_binary_mode : in_channel -> bool = "caml_ml_is_binary_mode"

external isatty : t -> bool = "caml_sys_isatty"
