#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include "Log.h"
#include "File.h"


namespace vendor {
namespace samsung {
namespace hardware {
namespace security {
namespace drk {

#ifndef UINT_MAX
#define UINT_MAX 0xffffffffU
#endif

#define CHECK_INTEGER_OVERFLOW(a, b)                            \
    if ((uint32_t)a > (UINT_MAX - (uint32_t)b)) {               \
        LOGME("The integer overflow occurs");                   \
        ret = ERR_COMMON_INVALID_ARGUMENT;                      \
        goto end;                                               \
    }


File::File(): fd(-1), fdmode(0), filesize(0), index(0)
{
    memset(path, 0x0, sizeof(path));
}

File::File(char *fpath): fd(-1), fdmode(0), filesize(0), index(0)
{
    memset(path, 0x0, sizeof(path));
    if (openFile(fpath, READ) != NOT_ERROR ) {
        fd = -1;
    }
}

File::File(char *fpath, int mode): fd(-1), fdmode(0), filesize(0), index(0)
{
    memset(path, 0x0, sizeof(path));
    if (openFile(fpath, mode) != NOT_ERROR ) {
        fd = -1;
    }
}

File::~File()
{
    closeFile();
}

int32_t File::openFile(char *fpath, int mode)
{
    int32_t ret = NOT_ERROR;
    int     lcoflags = O_WRONLY | O_CREAT | O_SYNC;
    struct stat fileInfo;

    if (fpath == NULL || strlen(fpath) > MAX_FILE_PATH_LEN) {
        ret = ERR_INVALID_ARGUMENT;
        goto end;
    }

    fdmode = mode;
    memcpy(path, fpath, strlen(fpath));

    if (mode == READ) {
        if (stat(path, &fileInfo) != 0) {
            LOGME("Failed to stat (%s)", strerror(errno));
            return ERR_FILE_STAT_FAILED;
        }

        if (fileInfo.st_size == 0) {
            LOGME("file size has wrong. (%s)\n", strerror(errno));
            return ERR_FILE_SIZE_ZERO;
        }

        filesize = (uint32_t)fileInfo.st_size;

        if ((fd = open(path, O_RDONLY)) < 0) {
            LOGME("Failed to open. (%s)\n", strerror(errno));
            ret = ERR_FILE_NOT_OPEN;
            goto end;
        }
    } else {
        ret = makeDir(path, (int32_t)strlen(path));
        if (ret != NOT_ERROR) {
            LOGME("Failed to dir. (%s)\n", strerror(errno));
            goto end;
        }

        if (mode == WRITE) {
            lcoflags |= O_TRUNC;
        } else {
            if (stat(path, &fileInfo) == 0 || fileInfo.st_size > 0) {
                filesize = (uint32_t)fileInfo.st_size;
            }
        }

        if ((fd = open(path, lcoflags, 0640)) < 0) {
            LOGME("Failed to open. (%s)\n", strerror(errno));
            ret = ERR_FILE_NOT_OPEN;
            goto end;
        }
        if (fchmod(fd, 0640) == -1) {
            LOGME("Failed to change mode. (%s)\n", strerror(errno));
            ret = ERR_FILE_NOT_CHMOD;
            goto end;
        }

        if (mode == APPEND) {
            lseek(fd, (size_t)0, SEEK_END);
        }
    }
    ret = NOT_ERROR;
end:
    return ret;
}

int32_t File::readFile(uint8_t *data, uint32_t *data_len)
{
    int32_t ret = NOT_ERROR;
    Bytes out;

    if (data == NULL || data_len == NULL) {
        ret = ERR_INVALID_ARGUMENT;
        goto end;
    }

    ret = readFile(out);
    if (ret != NOT_ERROR) {
        *data_len = 0;
        goto end;
    }
    if (*data_len < out.length()) {
        ret = ERR_BUFFER_OVERFLOW;
        *data_len = 0;
        goto end;
    }

    memcpy(data, (char *)out, out.length());
    *data_len = out.length();

end:
    return ret;
}

int32_t File::readFile(uint8_t *data, uint32_t data_len)
{
    int32_t   ret = NOT_ERROR;
    int32_t   read_bytes = 0,
              left_bytes = 0,
              read_pos   = 0;

    if (fd < 0 || data == NULL || data_len == 0 || data_len > filesize) {
        ret = ERR_INVALID_ARGUMENT;
        goto end;
    }
    left_bytes = data_len;
    while ((left_bytes > 0) &&
           ((read_bytes = read(fd, data + read_pos, left_bytes)) > 0)) {
        read_pos    += read_bytes;
        left_bytes  -= read_bytes;
    }

    if (left_bytes != 0) {
        memset(data, 0, data_len);
        ret = ERR_FILE_READ;
    } else {
        ret = NOT_ERROR;
    }

end:
    return ret;
}

int32_t File::readFile(Bytes& out)
{
    int32_t  ret = NOT_ERROR;
    int32_t  read_bytes = 0,
             read_pos   = 0;
    uint8_t *read_buf = NULL;

    if (fd < 0) {
        ret = ERR_FILE_NOT_OPEN;
        goto end;
    }

    read_buf   = (uint8_t *)calloc(filesize + 1, sizeof(uint8_t));
    if (read_buf == NULL) {
        LOGME("Failed to malloc %s \n", strerror(errno));
        ret = ERR_COMMON_MALLOC_FAILED;
        goto end;
    }

    while ((filesize > (uint32_t)read_pos) &&
           ((read_bytes = read(fd, read_buf + read_pos, (filesize - read_pos))) > 0)) {
        read_pos  += read_bytes;
    }
    out.set(read_buf, read_pos);
    ret = NOT_ERROR;
end:
    if (read_buf) {
        memset(read_buf, 0x0, filesize);
        free(read_buf);
        read_buf = NULL;
    }
    return ret;
}

#define FILE_LC_BUFFER_SIZE    512
int32_t File::readLine(uint8_t *out, uint32_t *out_len) {
    int32_t   read_bytes = 0,
              left_bytes = FILE_LC_BUFFER_SIZE,
              read_pos   = 0;
    char      *end, *start;
    char      buffer[FILE_LC_BUFFER_SIZE + 1] = {0};

    if (fd < 0 || out == NULL || out_len == NULL || *out_len == 0) {
        return ERR_INVALID_ARGUMENT;
    }

    if (index >= filesize) {
        LOGMI("EOF");
        return ERR_FILE_READ_FAILED;
    }

    if (lseek(fd, index, SEEK_SET) < 0) {
        LOGMI("it can't jump to offset in file");
        return ERR_FILE_READ_FAILED;
    }
    while ((left_bytes > 0) &&
           ((read_bytes = read(fd, buffer + read_pos, left_bytes)) > 0)) {
        read_pos    += read_bytes;
        left_bytes  -= read_bytes;
    }

    start = buffer;
    end   = buffer + read_pos;
    while (start < end) {
        if(*start == '\n') {
            break;
        }
        start++;
    }
    *start = 0;    //start == '\n' or &end

    read_pos = start - buffer + 1;
    if (read_pos > *out_len) {
        return ERR_BUFFER_OVERFLOW;
    }

    memcpy(out, buffer, read_pos);
    index = index + read_pos - ((start == end) ? 1 : 0);
    *out_len = read_pos - 1;
    return NOT_ERROR;
}

int32_t File::writeFile(Bytes& in)
{
    return writeFile((uint8_t *)in, in.length());
}

int32_t File::writeFile(uint8_t *data, uint32_t data_len)
{
    int32_t     ret     = NOT_ERROR;
    uint32_t    written = 0;

    if (fd < 0) {
        ret = ERR_FILE_NOT_OPEN;
        goto end;
    }

    CHECK_INTEGER_OVERFLOW(filesize, data_len);
    filesize += data_len;

    if (filesize > max_filesize) {
        LOGME("Cannot append to file. (length = %d)", filesize);
        ret = ERR_FILE_WRITE;
        goto end;
    }

    if ((written = (uint32_t)write(fd, data, data_len)) != data_len) {
        LOGME("Cannot write file (length = %d, written = %d)", data_len, written);
        ret = ERR_FILE_WRITE;
        goto end;
    }

    if (fsync(fd) != 0) {
        LOGME("Failed to fsync : %d; %s\n", fd, strerror(errno));
        ret = ERR_FILE_SYNC_FAILED;
        goto end;

    }
    ret = NOT_ERROR;
end:
    return ret;
}

int32_t File::makeDir(char *fpath, int32_t fpath_len)
{
    int32_t ret = NOT_ERROR;
    char    buf[MAX_FILE_PATH_LEN + 1] = {0};
    char   *pBuf = buf;

    if (fpath == NULL || fpath_len <= 0 || fpath_len > MAX_FILE_PATH_LEN) {
        ret = ERR_COMMON_INVALID_ARGUMENT;
        goto end;
    }

    memcpy(buf, fpath, fpath_len);

    while ((pBuf = strchr(pBuf, '/'))) {
        if (pBuf != buf) {
            *pBuf = '\0';
            if (access(buf, F_OK) != 0) {
                if (mkdir(buf, S_IRWXU | S_IRWXG) == 0) {
                    // To fix mkdir() bug.
                    if (chmod(buf, S_IRWXU | S_IRWXG) != NOT_ERROR) {
                        LOGME("chmod path : %s failed.", buf);
                    }
                } else {
                    LOGMI("Failed to dir. (%s) %s\n", buf, strerror(errno));
                }
            }
            *pBuf = '/';
        }
        pBuf++;
    }
end:
    memset(buf, 0, sizeof(buf));
    return ret;
}

uint32_t File::getFileSize()
{
    if (fd < 0) {
        return 0;
    }
    return filesize;
}

int32_t File::resetFileIndex()
{
    if ( fd >= 0 ) {
        index = 0;
        lseek(fd, 0, SEEK_SET);
        return NOT_ERROR;
    }
    return ERR_FILE_NOT_OPEN;
}

void File::closeFile()
{
    if (fd >= 0) {
        memset(path, 0x0, sizeof(path));
        close(fd);
        fd = -1;
    }
}

}  // namespace drk
}  // namespace security
}  // namespace hardware
}  // namespace samsung
}  // namespace vendor
