%
% 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_clock_settime/2,
    posix_opendir/1,
    posix_closedir/1,
    posix_readdir/1
]).

-export_type([
    posix_fd/0,
    posix_open_flag/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().

-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   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).
