/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson 2017-2018. All Rights Reserved.
 *
 * 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.
 *
 * %CopyrightEnd%
 */

typedef int posix_errno_t;

enum efile_modes_t {
    EFILE_MODE_READ = (1 << 0),
    EFILE_MODE_WRITE = (1 << 1), /* Implies truncating file when used alone. */
    EFILE_MODE_APPEND = (1 << 2),
    EFILE_MODE_EXCLUSIVE = (1 << 3),
    EFILE_MODE_SYNC = (1 << 4),

    EFILE_MODE_SKIP_TYPE_CHECK = (1 << 5), /* Special for device files on Unix. */
    EFILE_MODE_NO_TRUNCATE = (1 << 6), /* Special for reopening on VxWorks. */

    EFILE_MODE_READ_WRITE = EFILE_MODE_READ | EFILE_MODE_WRITE
};

enum efile_access_t {
    EFILE_ACCESS_NONE = 0,
    EFILE_ACCESS_READ = 1,
    EFILE_ACCESS_WRITE = 2,
    EFILE_ACCESS_READ_WRITE = EFILE_ACCESS_READ | EFILE_ACCESS_WRITE
};

enum efile_seek_t {
    EFILE_SEEK_BOF,
    EFILE_SEEK_CUR,
    EFILE_SEEK_EOF
};

enum efile_filetype_t {
    EFILE_FILETYPE_DEVICE,
    EFILE_FILETYPE_DIRECTORY,
    EFILE_FILETYPE_REGULAR,
    EFILE_FILETYPE_SYMLINK,
    EFILE_FILETYPE_OTHER
};

enum efile_advise_t {
    EFILE_ADVISE_NORMAL,
    EFILE_ADVISE_RANDOM,
    EFILE_ADVISE_SEQUENTIAL,
    EFILE_ADVISE_WILL_NEED,
    EFILE_ADVISE_DONT_NEED,
    EFILE_ADVISE_NO_REUSE
};

enum efile_state_t {
    EFILE_STATE_IDLE = 0,
    EFILE_STATE_BUSY = 1,
    EFILE_STATE_CLOSE_PENDING = 2,
    EFILE_STATE_CLOSED = 3
};

typedef struct {
    Sint64 size;            /* Size of file */
    Uint32 type;            /* Type of file -- one of EFILE_FILETYPE_*. */
    Uint32 access;          /* Access to file -- one of EFILE_ACCESS_*. */
    Uint32 mode;            /* Access permissions -- bit field. */
    Uint32 links;           /* Number of links to file. */
    Uint32 major_device;    /* Major device or file system. */
    Uint32 minor_device;    /* Minor device (for devices). */
    Uint32 inode;           /* Inode number. */
    Uint32 uid;             /* User id of owner. */
    Uint32 gid;             /* Group id of owner. */
    Sint64 a_time;          /* Last time the file was accessed. */
    Sint64 m_time;          /* Last time the file was modified. */
    Sint64 c_time;          /* Windows: creation time, Unix: last inode
                             * change. */
} efile_fileinfo_t;

/* The smallest value that can be converted freely between universal, local,
 * and POSIX time, as required by read_file_info/2. Corresponds to
 * {{1902,1,1},{0,0,0}} */
#define EFILE_MIN_FILETIME -2145916800

/* Initializes an efile_data_t; must be used in efile_open on success. */
#define EFILE_INIT_RESOURCE(__d, __modes) do { \
        erts_atomic32_init_acqb(&(__d)->state, EFILE_STATE_IDLE); \
        (__d)->posix_errno = 0; \
        (__d)->modes = __modes; \
    } while(0)

typedef struct {
    erts_atomic32_t state;

    posix_errno_t posix_errno;
    enum efile_modes_t modes;

    ErlNifMonitor monitor;
} efile_data_t;

typedef ErlNifBinary efile_path_t;

/* @brief Translates the given "raw name" into the format expected by the APIs
 * used by the underlying implementation. The result is transient and does not
 * need to be released.
 *
 * This may change the structure of the path and its results should never be
 * passed on to the user. Refer to the OS-specific implementation for details.
 *
 * @param path The term to translate; it must have been encoded with
 * prim_file:internal_native2name for compatibility reasons. */
posix_errno_t efile_marshal_path(ErlNifEnv *env, ERL_NIF_TERM path, efile_path_t *result);

/* @brief Returns the underlying handle as an implementation-defined term.
 *
 * This is an internal function intended to support tests and tricky
 * operations like sendfile(2). */
ERL_NIF_TERM efile_get_handle(ErlNifEnv *env, efile_data_t *d);

/* @brief Read until EOF or the given iovec has been filled.
 *
 * @return -1 on failure, or the number of bytes read on success. The return
 * value will be 0 if no bytes could be read before EOF or the end of the
 * iovec. */
Sint64 efile_readv(efile_data_t *d, SysIOVec *iov, int iovlen);

/* @brief Write the entirety of the given iovec.
 *
 * @return -1 on failure, or the number of bytes written on success. "Partial"
 * failures will be reported with -1 and not the number of bytes we managed to
 * write to disk before the failure. */
Sint64 efile_writev(efile_data_t *d, SysIOVec *iov, int iovlen);

/* @brief As \c efile_readv, but starting from a file offset. */
Sint64 efile_preadv(efile_data_t *d, Sint64 offset, SysIOVec *iov, int iovlen);

/* @brief As \c efile_writev, but starting from a file offset. */
Sint64 efile_pwritev(efile_data_t *d, Sint64 offset, SysIOVec *iov, int iovlen);

int efile_seek(efile_data_t *d, enum efile_seek_t seek, Sint64 offset, Sint64 *new_position);

int efile_sync(efile_data_t *d, int data_only);

int efile_advise(efile_data_t *d, Sint64 offset, Sint64 length, enum efile_advise_t advise);
int efile_allocate(efile_data_t *d, Sint64 offset, Sint64 length);
int efile_truncate(efile_data_t *d);

posix_errno_t efile_open(const efile_path_t *path, enum efile_modes_t modes,
        ErlNifResourceType *nif_type, efile_data_t **d);

/** @brief Closes a file. The file must have entered the CLOSED state prior to
 * calling this to prevent double close.
 *
 * Note that the file is completely invalid after this point, so the error code
 * is provided in \c error rather than d->posix_errno. */
int efile_close(efile_data_t *d, posix_errno_t *error);

/* **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** */

posix_errno_t efile_read_info(const efile_path_t *path, int follow_link, efile_fileinfo_t *result);

/** @brief Sets the file times to the given values. Refer to efile_fileinfo_t
 * for a description of each. */
posix_errno_t efile_set_time(const efile_path_t *path, Sint64 a_time, Sint64 m_time, Sint64 c_time);

/** @brief On Unix, this sets the file permissions according to the docs for
 * file:write_file_info/2. On Windows it uses the "owner write permission" flag
 * to toggle whether the file is read-only or not. */
posix_errno_t efile_set_permissions(const efile_path_t *path, Uint32 permissions);

/** @brief On Unix, this will set the owner/group to the given values. It will
 * do nothing on other platforms. */
posix_errno_t efile_set_owner(const efile_path_t *path, Sint32 owner, Sint32 group);

/** @brief Resolves the final path of the given link. */
posix_errno_t efile_read_link(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result);

/** @brief Lists the contents of the given directory.
 * @param result [out] A list of all the directory/file names contained in the
 * given directory. */
posix_errno_t efile_list_dir(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result);

/** @brief Changes the name of an existing file or directory, from old_path
 * to new_path.
 *
 * If old_path and new_path refer to the same file or directory, it does
 * nothing and returns success. Otherwise if new_path already exists, it will
 * be deleted and replaced by src subject to the following conditions:
 *
 *     If old_path is a directory, new_path may be an empty directory.
 *     If old_path is a file, new_path may be a file.
 *
 * Neither of these are guaranteed to be atomic. In any other situation where
 * new_path already exists, the rename will fail.
 *
 * Some possible error codes:
 *
 * - EACCES:     Either paths or one of their parent directories can't be read
 *               and/or written.
 * - EEXIST:     new_path is a non-empty directory.
 * - EINVAL:     old_path is a root directory or new_path is a subdirectory
 *               of new_path.
 * - EISDIR:     new_path is a directory, but old_path is not.
 * - ENOTDIR:    old_path is a directory, but new_path is not.
 * - ENOENT:     old_path doesn't exist, or either path is "".
 * - EXDEV:      The paths are on different filesystems.
 *
 * The implementation of rename may allow cross-filesystem renames,
 * but the caller should be prepared to emulate it with copy and
 * delete if errno is EXDEV. */
posix_errno_t efile_rename(const efile_path_t *old_path, const efile_path_t *new_path);

posix_errno_t efile_make_hard_link(const efile_path_t *existing_path, const efile_path_t *new_path);
posix_errno_t efile_make_soft_link(const efile_path_t *existing_path, const efile_path_t *new_path);
posix_errno_t efile_make_dir(const efile_path_t *path);

posix_errno_t efile_del_file(const efile_path_t *path);
posix_errno_t efile_del_dir(const efile_path_t *path);

posix_errno_t efile_get_cwd(ErlNifEnv *env, ERL_NIF_TERM *result);
posix_errno_t efile_set_cwd(const efile_path_t *path);

/** @brief A Windows-specific function for returning the working directory of a
 * given device.
 *
 * @param device_index The drive index; 1 for A, 2 for B, etc.
 * @param result [out] The working directory of the given device
 */
posix_errno_t efile_get_device_cwd(ErlNifEnv *env, int device_index, ERL_NIF_TERM *result);

/** @brief A Windows-specific function for returning the 8.3-name of a given
 * file or directory. */
posix_errno_t efile_altname(ErlNifEnv *env, const efile_path_t *path, ERL_NIF_TERM *result);
