%
% This file is part of AtomVM.
%
% Copyright 2018-2023 Fred Dushin <fred@dushin.net>
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
%    http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

%%-----------------------------------------------------------------------------
%% @doc AtomVM-specific APIs
%%
%% This module contains functions that are specific to the AtomVM platform.
%% @end
%%-----------------------------------------------------------------------------
-module(atomvm).

-export([
    platform/0,
    random/0,
    rand_bytes/1,
    read_priv/2,
    add_avm_pack_binary/2,
    add_avm_pack_file/2,
    close_avm_pack/2,
    get_start_beam/1,
    posix_open/2,
    posix_open/3,
    posix_close/1,
    posix_read/2,
    posix_write/2,
    posix_seek/3,
    posix_pread/3,
    posix_pwrite/3,
    posix_fsync/1,
    posix_ftruncate/2,
    posix_mkfifo/2,
    posix_mkdir/2,
    posix_rmdir/1,
    posix_rename/2,
    posix_stat/1,
    posix_fstat/1,
    posix_clock_settime/2,
    posix_opendir/1,
    posix_closedir/1,
    posix_readdir/1,
    get_creation/0,
    subprocess/4
]).

-export_type([
    posix_fd/0,
    posix_open_flag/0,
    posix_whence/0,
    posix_stat_info/0,
    posix_dir/0
]).

-deprecated([
    {random, 0, next_version}
]).

-type platform_name() ::
    generic_unix
    | emscripten
    | esp32
    | pico
    | stm32.

-type avm_path() :: iodata().

-opaque posix_fd() :: binary().
-type posix_open_flag() ::
    o_exec
    | o_rdonly
    | o_rdwr
    | o_search
    | o_wronly
    | o_append
    | o_cloexec
    | o_creat
    | o_directory
    | o_dsync
    | o_excl
    | o_noctty
    | o_nofollow
    | o_rsync
    | o_sync
    | o_trunc
    | o_tty_atom.
-type posix_error() ::
    atom()
    | integer().
-type posix_whence() ::
    seek_set
    | seek_cur
    | seek_end.
-type posix_stat_info() :: #{
    st_dev := integer(),
    st_ino := integer(),
    st_mode := integer(),
    st_nlink := integer(),
    st_uid := integer(),
    st_gid := integer(),
    st_size := integer(),
    st_atime_s := integer(),
    st_mtime_s := integer(),
    st_ctime_s := integer()
}.

-opaque posix_dir() :: binary().

%%-----------------------------------------------------------------------------
%% @returns The platform name.
%% @doc     Return the platform moniker.
%%          You may use this function to uniquely identify the platform
%%          type on which your application is running.
%% @end
%%-----------------------------------------------------------------------------
-spec platform() -> platform_name().
platform() ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @returns random 32-bit integer.
%% @doc     Returns a random 32-bit integer value.
%%          This function will use a cryptographically strong RNG if available.
%%          Otherwise, the random value is generated using a PRNG.
%% @end
%%-----------------------------------------------------------------------------
-spec random() -> integer().
random() ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Len non-negative integer
%% @returns Binary containing random sequence of bytes of length Len.
%% @doc     Returns a binary containing random sequence of bytes of length Len.
%%          Supplying a negative value will result in a badarg error.
%%          This function will use a cryptographically strong RNG if available.
%%          Otherwise, the random value is generated using a PRNG.
%% @deprecated Use crypto:strong_rand_bytes/1 instead.
%% @end
%%-----------------------------------------------------------------------------
-spec rand_bytes(Len :: non_neg_integer()) -> binary().
rand_bytes(_Len) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   App application name.
%% @param   Path path to the resource.
%% @returns Binary containing the resource content.
%% @doc     This function allows to fetch priv/ resources content.
%% @end
%%-----------------------------------------------------------------------------
-spec read_priv(App :: atom(), Path :: list()) -> binary().
read_priv(_App, _Path) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   AVMData AVM data.
%% @param   Options Options, as a property list.
%% @returns `ok'
%% @doc     Add code from an AVM binary to your application.
%%
%%          This function will add the data in the `AVMData' parameter to
%%          your application.  The data is assumed to be valid AVM data (e.g, as
%%          generated by packbeam tooling).
%%
%%          Failure to properly load AVM data is result in a runtime `error'
%% @end
%%-----------------------------------------------------------------------------
-spec add_avm_pack_binary(AVMData :: binary(), Options :: [{name, Name :: atom()}]) ->
    ok | {error, any()}.
add_avm_pack_binary(_AVMData, _Options) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   AVMPath Path to AVM data.
%% @param   Options Options, as a property list.
%% @returns `ok'
%% @doc     Add code from an AVM binary to your application.
%%
%%          This function will add the data located in the `AVMPath' parameter to
%%          your application.  The data is assumed to be valid AVM data (e.g, as
%%          generated by packbeam tooling).
%%
%%          On `generic_unix' platforms, the `AVMPath' may be a valid file system
%%          path to an AVM file.
%%
%%          On `esp32' platforms, the `AVMPath' should be the name of an ESP32
%%          flash partition, prefixed with the string
%%          `/dev/partition/by-name/'.  Thus, for example, if you specify
%%          `/dev/partition/by-name/main2.app' as the `AVMPath', the ESP32
%%          flash should contain a data partition with the
%%          name `main2.app'
%%
%%          Failure to properly load AVM path is result in a runtime `error'
%% @end
%%-----------------------------------------------------------------------------
-spec add_avm_pack_file(AVMPath :: avm_path(), Options :: [{name, Name :: atom()}]) ->
    ok | {error, any()}.
add_avm_pack_file(_AVMPath, _Options) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Name the AVM name.
%% @param   Options Options, as a property list.
%% @returns `ok | error'
%% @doc     Close previously opened AVM binary from your application.
%%
%%          This function will close the data referenced by the `Name' parameter from
%%          your application.  The `Name' parameter must reference previously
%%          opened AVM data.
%%
%%          Failure to close AVM data is result in a runtime `error'
%% @end
%%-----------------------------------------------------------------------------
-spec close_avm_pack(Name :: atom(), Options :: []) -> ok | error.
close_avm_pack(_Name, _Options) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   AVM     Name of avm (atom)
%% @returns the name of the start module (with suffix)
%% @doc     Get the start beam for a given avm
%% @end
%%-----------------------------------------------------------------------------
-spec get_start_beam(AVM :: atom()) -> {ok, binary()} | {error, not_found}.
get_start_beam(_AVM) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Path    Path to the file to open
%% @param   Flags   List of flags passed to `open(3)'.
%% @returns A tuple with a file descriptor or an error tuple.
%% @doc     Open a file (on platforms that have `open(3)').
%% The file is automatically closed when the file descriptor is garbage
%% collected.
%%
%% Files are automatically opened with `O_NONBLOCK'. Other flags can be passed.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_open(Path :: iodata(), Flags :: [posix_open_flag()]) ->
    {ok, posix_fd()} | {error, posix_error()}.
posix_open(_Path, _Flags) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Path    Path to the file to open
%% @param   Flags   List of flags passed to `open(3)'.
%% @param   Mode    Mode passed to `open(3)' for created file.
%% @returns A tuple with a file descriptor or an error tuple.
%% @doc     Open a file (on platforms that have `open(3)').
%% This variant can be used to specify the mode for new file.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_open(Path :: iodata(), Flags :: [posix_open_flag()], Mode :: non_neg_integer()) ->
    {ok, posix_fd()} | {error, posix_error()}.
posix_open(_Path, _Flags, _Mode) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to a file to close
%% @returns `ok' or an error tuple
%% @doc     Close a file that was opened with `posix_open/2,3'
%% @end
%%-----------------------------------------------------------------------------
-spec posix_close(File :: posix_fd()) -> ok | {error, posix_error()}.
posix_close(_File) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to an open file
%% @param   Count   Maximum number of bytes to read
%% @returns a tuple with read bytes, `eof' or an error tuple
%% @doc     Read at most `Count' bytes from a file.
%% Files are open non-blocking. ˋatomvm:posix_select_read/3' can be used to
%% determine if the file can be read.
%% `eof' is returned if no more data can be read because the file cursor
%% reached the end.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_read(File :: posix_fd(), Count :: non_neg_integer()) ->
    {ok, binary()} | eof | {error, posix_error()}.
posix_read(_File, _Count) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to an open file
%% @param   Data    Data to write
%% @returns a tuple with the number of written bytes or an error tuple
%% @doc     Write data to a file.
%% Files are open non-blocking. ˋatomvm:posix_select_write/3' can be used to
%% determine if the file can be written.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_write(File :: posix_fd(), Data :: binary()) ->
    {ok, non_neg_integer()} | {error, posix_error()}.
posix_write(_File, _Data) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to an open file
%% @param   Offset  Offset in bytes
%% @param   Whence  Reference point for the offset
%% @returns a tuple with the resulting absolute offset or an error tuple
%% @doc     Reposition the file cursor using `lseek(2)'.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_seek(File :: posix_fd(), Offset :: integer(), Whence :: posix_whence()) ->
    {ok, integer()} | {error, posix_error()}.
posix_seek(_File, _Offset, _Whence) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to an open file
%% @param   Count   Maximum number of bytes to read
%% @param   Offset  Offset in bytes from the start of the file
%% @returns a tuple with read bytes, `eof' or an error tuple
%% @doc     Read at most `Count' bytes from a file at a given offset using
%% `pread(2)'. The file cursor is not modified.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_pread(File :: posix_fd(), Count :: non_neg_integer(), Offset :: non_neg_integer()) ->
    {ok, binary()} | eof | {error, posix_error()}.
posix_pread(_File, _Count, _Offset) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to an open file
%% @param   Data    Data to write
%% @param   Offset  Offset in bytes from the start of the file
%% @returns a tuple with the number of written bytes or an error tuple
%% @doc     Write data to a file at a given offset using `pwrite(2)'.
%% The file cursor is not modified.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_pwrite(File :: posix_fd(), Data :: binary(), Offset :: non_neg_integer()) ->
    {ok, non_neg_integer()} | {error, posix_error()}.
posix_pwrite(_File, _Data, _Offset) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to an open file
%% @returns `ok' or an error tuple
%% @doc     Flush file data to storage using `fsync(2)'.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_fsync(File :: posix_fd()) -> ok | {error, posix_error()}.
posix_fsync(_File) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to an open file
%% @param   Length  Desired file length in bytes
%% @returns `ok' or an error tuple
%% @doc     Truncate a file to a specified length using `ftruncate(2)'.
%% If the file is larger than `Length', the extra data is lost. If the file
%% is shorter, it is extended with null bytes.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_ftruncate(File :: posix_fd(), Length :: non_neg_integer()) ->
    ok | {error, posix_error()}.
posix_ftruncate(_File, _Length) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Path    Path to the new FIFO special file
%% @param   Mode    Permission bits for the new FIFO
%% @returns `ok' or an error tuple
%% @doc     Create a FIFO special file (named pipe) using `mkfifo(2)'.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_mkfifo(Path :: iodata(), Mode :: non_neg_integer()) ->
    ok | {error, posix_error()}.
posix_mkfifo(_Path, _Mode) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Path    Path to the directory to create
%% @param   Mode    Permission bits for the new directory
%% @returns `ok' or an error tuple
%% @doc     Create a directory using `mkdir(2)'.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_mkdir(Path :: iodata(), Mode :: non_neg_integer()) ->
    ok | {error, posix_error()}.
posix_mkdir(_Path, _Mode) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Path    Path to the directory to remove
%% @returns ok or an error tuple
%% @doc     Remove an empty directory using `rmdir(2)'.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_rmdir(Path :: iodata()) ->
    ok | {error, posix_error()}.
posix_rmdir(_Path) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
-spec posix_rename(OldPath :: iodata(), NewPath :: iodata()) ->
    ok | {error, posix_error()}.
posix_rename(_OldPath, _NewPath) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Path    Path to the file
%% @returns a tuple with a map of file information or an error tuple
%% @doc     Get file status using `stat(2)'.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_stat(Path :: iodata()) ->
    {ok, posix_stat_info()} | {error, posix_error()}.
posix_stat(_Path) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   File    Descriptor to an open file
%% @returns a tuple with a map of file information or an error tuple
%% @doc     Get file status from a file descriptor using `fstat(2)'.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_fstat(File :: posix_fd()) ->
    {ok, posix_stat_info()} | {error, posix_error()}.
posix_fstat(_File) ->
    erlang:nif_error(undefined).

%%
%% @param   ClockId The clock id
%% @param   ValueSinceUnixEpoch The value, in specified seconds and nanoseconds,
%% since the UNIX epoch (Jan 1, 1970)
%% @return  `ok' or an error tuple
%% @doc Set the system time.
%%
%% This function sets the system time to the specified value, expressed as a
%% tuple containing seconds and nanoseconds since the UNIX epoch (Jan 1, 1970).
%% Coordinates are all in UTC.
%%
%% Note.  Some systems may require special permissions to call this function.
%% @end
%%
-spec posix_clock_settime(
    ClockId :: realtime,
    ValueSinceUnixEpoch :: {Seconds :: integer(), Nanoseconds :: integer()}
) ->
    ok | {error, Reason :: posix_error()}.
posix_clock_settime(_ClockId, _Time) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Path    Path to the directory to open
%% @returns A tuple with a directory descriptor or an error tuple.
%% @doc     Open a file (on platforms that have `opendir(3)').
%% @end
%%-----------------------------------------------------------------------------
-spec posix_opendir(Path :: iodata()) ->
    {ok, posix_dir()} | {error, posix_error()}.
posix_opendir(_Path) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Dir    Descriptor to a directory to close
%% @returns `ok' or an error tuple
%% @doc     Close a directory that was opened with `posix_opendir/1'
%% @end
%%-----------------------------------------------------------------------------
-spec posix_closedir(Dir :: posix_dir()) -> ok | {error, posix_error()}.
posix_closedir(_Dir) ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Dir    Descriptor to an open directory
%% @returns a `{dirent, InodeNo, Name}' tuple, `eof' or an error tuple
%% @doc     Read a directory entry
%% `eof' is returned if no more data can be read because the directory cursor
%% reached the end.
%% @end
%%-----------------------------------------------------------------------------
-spec posix_readdir(Dir :: posix_dir()) ->
    {ok, {dirent, Inode :: integer(), Name :: binary()}} | eof | {error, posix_error()}.
posix_readdir(_Dir) ->
    erlang:nif_error(undefined).

%% @hidden
-spec get_creation() -> non_neg_integer().
get_creation() ->
    erlang:nif_error(undefined).

%%-----------------------------------------------------------------------------
%% @param   Path    path to the command to execute
%% @param   Args    arguments to pass to the command. First item is the name
%%                  of the command
%% @param   Envp    environment variables to pass to the command or `undefined'
%%                  to use environ (VM environment variables)
%% @param   Options options to run execve. Should be `[stdout]'
%% @returns a tuple with the process id and a fd to the stdout of the process.
%% @doc     Fork and execute a program using fork(2) and execve(2). Pipe stdout
%%          so output of the program can be read with `atomvm:posix_read/2'.
%% @end
%%-----------------------------------------------------------------------------
-spec subprocess(
    Path :: iodata(), Args :: [iodata()], Env :: [iodata()] | undefined, Options :: [stdout]
) ->
    {ok, non_neg_integer(), posix_fd()} | {error, posix_error()}.
subprocess(_Path, _Args, _Env, _Options) ->
    erlang:nif_error(undefined).
