/-
Copyright (c) 2018 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import data.buffer system.random

inductive io.error
| other     : string → io.error
| sys       : nat → io.error

inductive io.mode
| read | write | read_write | append

inductive io.process.stdio
| piped
| inherit
| null

structure io.process.spawn_args :=
/- Command name. -/
(cmd : string)
/- Arguments for the process -/
(args : list string := [])
/- Configuration for the process' stdin handle. -/
(stdin := stdio.inherit)
/- Configuration for the process' stdout handle. -/
(stdout := stdio.inherit)
/- Configuration for the process' stderr handle. -/
(stderr := stdio.inherit)
/- Working directory for the process. -/
(cwd : option string := none)
/- Environment variables for the process. -/
(env : list (string × option string) := [])

class monad_io (m : Type → Type → Type) :=
[monad    : Π e, monad (m e)]
-- TODO(Leo): use monad_except after it is merged
(catch    : Π e₁ e₂ α, m e₁ α → (e₁ → m e₂ α) → m e₂ α)
(fail     : Π e α, e → m e α)
(iterate  : Π e α, α → (α → m e (option α)) → m e α)
-- Primitive Types
(handle   : Type)

class monad_io_terminal (m : Type → Type → Type) :=
(put_str      : string → m io.error unit)
(get_line     : m io.error string)
(cmdline_args : list string)

open monad_io (handle)

class monad_io_net_system (m : Type → Type → Type) [monad_io m] :=
(socket  : Type)
(listen  : string → nat → m io.error socket)
(accept  : socket → m io.error socket)
(connect : string → m io.error socket)
(recv    : socket → nat → m io.error char_buffer)
(send    : socket → char_buffer → m io.error unit)
(close   : socket → m io.error unit)

class monad_io_file_system (m : Type → Type → Type) [monad_io m] :=
/- Remark: in Haskell, they also provide  (Maybe TextEncoding) and  NewlineMode -/
(mk_file_handle : string → io.mode → bool → m io.error (handle m))
(is_eof         : (handle m) → m io.error bool)
(flush          : (handle m) → m io.error unit)
(close          : (handle m) → m io.error unit)
(read           : (handle m) → nat → m io.error char_buffer)
(write          : (handle m) → char_buffer → m io.error unit)
(get_line       : (handle m) → m io.error char_buffer)
(stdin          : m io.error (handle m))
(stdout         : m io.error (handle m))
(stderr         : m io.error (handle m))
(file_exists    : string → m io.error bool)
(dir_exists     : string → m io.error bool)
(remove         : string → m io.error unit)
(rename         : string → string → m io.error unit)
(mkdir          : string → bool → m io.error bool)
(rmdir          : string → m io.error bool)

meta class monad_io_serial (m : Type → Type → Type) [monad_io m] :=
(serialize      : (handle m) → expr → m io.error unit)
(deserialize    : (handle m) → m io.error expr)

class monad_io_environment (m : Type → Type → Type) :=
(get_env : string → m io.error (option string))
-- we don't provide set_env as it is (thread-)unsafe (at least with glibc)
(get_cwd : m io.error string)
(set_cwd : string → m io.error unit)

class monad_io_process (m : Type → Type → Type) [monad_io m] :=
(child  : Type)
(stdin  : child → (handle m))
(stdout : child → (handle m))
(stderr : child → (handle m))
(spawn  : io.process.spawn_args → m io.error child)
(wait   : child → m io.error nat)
(sleep  : nat → m io.error unit)

class monad_io_random (m : Type → Type → Type) :=
(set_rand_gen : std_gen → m io.error unit)
(rand         : nat → nat → m io.error nat)

instance monad_io_is_monad (m : Type → Type → Type) (e : Type) [monad_io m] : monad (m e) :=
monad_io.monad e

instance monad_io_is_monad_fail (m : Type → Type → Type) [monad_io m] : monad_fail (m io.error) :=
{ fail := λ α s, monad_io.fail _ _ (io.error.other s) }

instance monad_io_is_alternative (m : Type → Type → Type) [monad_io m] : alternative (m io.error) :=
{ orelse  := λ α a b, monad_io.catch _ _ _ a (λ _, b),
  failure := λ α, monad_io.fail _ _ (io.error.other "failure") }
