/**
 * DRIF - Developers RAW image format
 *
 * MIT License
 * Copyright (c) 2018 Ovidiu Ionescu
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stdint.h>
#include <stdio.h>

#if defined(Q_OS_MAC) || defined(Q_OS_OPENBSD) || defined(Q_OS_FREEBSD)
#include <stdlib.h>
#else
#include <malloc.h>
#endif

// DRIF Public API

#ifndef DRIF_API_H_INC
#define DRIF_API_H_INC

// DRIF version
#define DRIF_VER (0x01 | (0x00 << 8) | (0x00 << 16) | ('S' << 24))

// footer size reserved at end of the file
#define DRIF_FOOTER_SZ 512

// DRIF magic
#define DRIF_MAGIC ('D' | ('R' << 8) | ('I' << 16) | ('F' << 24))

#ifndef DRIFAPI
#ifdef __cplusplus
#define DRIFAPI extern "C"
#else
#define DRIFAPI extern
#endif
#endif

// DRIF error codes
enum {
    DRIF_OK = 0,
    DRIF_ERR_NULLPTR,
    DRIF_ERR_FMT,
    DRIF_ERR_MAX_SIZE,
    DRIF_ERR_MIN_SIZE,
    DRIF_ERR_READ_FILE,
    DRIF_ERR_WRITE_FILE,
    DRIF_ERR_NO_MEMORY,
};

// DRIF formats
enum {
    DRIF_FMT_YUV = (1 << 0),
    DRIF_FMT_RGB = (1 << 1),
};

#define DRIF_MKFMT(fmt_type, fmt_id) (((fmt_type) << 16) | (fmt_id))

enum {
    DRIF_FMT_RGB888 = DRIF_MKFMT(DRIF_FMT_RGB, 0),
    DRIF_FMT_BGR888 = DRIF_MKFMT(DRIF_FMT_RGB, 1),
    DRIF_FMT_RGB888P = DRIF_MKFMT(DRIF_FMT_RGB, 2),
    DRIF_FMT_BGR888P = DRIF_MKFMT(DRIF_FMT_RGB, 3),

    DRIF_FMT_RGBA8888 = DRIF_MKFMT(DRIF_FMT_RGB, 4),
    DRIF_FMT_BGRA8888 = DRIF_MKFMT(DRIF_FMT_RGB, 5),
    DRIF_FMT_RGBA8888P = DRIF_MKFMT(DRIF_FMT_RGB, 6),
    DRIF_FMT_BGRA8888P = DRIF_MKFMT(DRIF_FMT_RGB, 7),

    DRIF_FMT_GRAY = DRIF_MKFMT(DRIF_FMT_YUV, 0),

    DRIF_FMT_YUV420P = DRIF_MKFMT(DRIF_FMT_YUV, 1),
    DRIF_FMT_YVU420P = DRIF_MKFMT(DRIF_FMT_YUV, 2),
    DRIF_FMT_NV12 = DRIF_MKFMT(DRIF_FMT_YUV, 3),
    DRIF_FMT_NV21 = DRIF_MKFMT(DRIF_FMT_YUV, 4),
};

// DRIF max / min dimensions
#define DRIF_MAX_W (1 << 15)
#define DRIF_MAX_H (1 << 15)

#define DRIF_MIN_W (1)
#define DRIF_MIN_H (1)

// DRIF Api functions
DRIFAPI uint8_t *drifLoadImg(const char *szFileName, uint32_t *w, uint32_t *h, uint32_t *f);
DRIFAPI int drifSaveImg(const char *szFileName, uint32_t w, uint32_t h, uint32_t f, const void *data);
DRIFAPI void drifFreeImg(void *ptr_from_load);
DRIFAPI uint32_t drifGetSize(const uint32_t w, const uint32_t h, const uint32_t f);

#endif // DRIF_API_H_INC

// DRIF Private
// NOLINTBEGIN(misc-definitions-in-headers)
#ifdef DRIF_IMAGE_IMPL

// DRIF footer structure
typedef struct {
    uint32_t magic;
    uint32_t version;
    uint32_t w;
    uint32_t h;
    uint32_t f;

} drif_footer_t;

static int isDrifFmtValid(uint32_t f)
{
    switch (f) {
    case DRIF_FMT_RGB888:
    case DRIF_FMT_BGR888:
    case DRIF_FMT_RGB888P:
    case DRIF_FMT_BGR888P:
    case DRIF_FMT_RGBA8888:
    case DRIF_FMT_BGRA8888:
    case DRIF_FMT_RGBA8888P:
    case DRIF_FMT_BGRA8888P:
    case DRIF_FMT_GRAY:
    case DRIF_FMT_YUV420P:
    case DRIF_FMT_YVU420P:
    case DRIF_FMT_NV12:
    case DRIF_FMT_NV21:
        return 1;
    }

    return 0;
}

uint8_t *drifLoadImg(const char *szFileName, uint32_t *w, uint32_t *h, uint32_t *f)
{
    // check input params
    if (nullptr == szFileName || nullptr == w || nullptr == h || nullptr == f)
        return nullptr;

    // open file for reading
    FILE *fp = fopen(szFileName, "rb");

    // check if file opened ok
    if (nullptr == fp)
        return nullptr;

    // move to footer offeset
    fseek(fp, 0L, SEEK_END);
    long sz = ftell(fp);
    fseek(fp, sz - DRIF_FOOTER_SZ, SEEK_SET);

    // read footer
    char buffer[DRIF_FOOTER_SZ] = {0};
    if (1 != fread((void *)buffer, DRIF_FOOTER_SZ, 1, fp)) {
        fclose(fp);
        return nullptr;
    }

    const auto *footer = (drif_footer_t *)buffer;

    // check magic & format
    if (DRIF_MAGIC != footer->magic || !isDrifFmtValid(footer->f)) {
        fclose(fp);
        return nullptr;
    }

    // read image
    uint32_t dataSize = drifGetSize(footer->w, footer->h, footer->f);
    void *img = malloc(dataSize);

    if (nullptr == img) {
        fclose(fp);
        return nullptr;
    }

    // move at file start
    fseek(fp, 0L, SEEK_SET);
    size_t nElem = fread(img, dataSize, 1, fp);
    fclose(fp);

    if (1 != nElem) {
        free(img);
        return nullptr;
    }

    // export image info
    *w = footer->w;
    *h = footer->h;
    *f = footer->f;

    return (uint8_t *)img;
}

int drifSaveImg(const char *szFileName, const uint32_t w, const uint32_t h, const uint32_t f, const void *data)
{
    // check input params
    if (nullptr == szFileName || nullptr == data)
        return DRIF_ERR_NULLPTR;

    // check params
    if (!isDrifFmtValid(f))
        return DRIF_ERR_FMT;

    // check for max dimensions
    if (w > DRIF_MAX_W || h > DRIF_MAX_H)
        return DRIF_ERR_MAX_SIZE;

    // check for min dimensions
    if (w < DRIF_MIN_W || h < DRIF_MIN_H)
        return DRIF_ERR_MIN_SIZE;

    // open file for writing
    FILE *fp = fopen(szFileName, "wb");

    // check if file opened ok
    if (nullptr == fp)
        return DRIF_ERR_WRITE_FILE;

    // prepare footer
    char buffer[DRIF_FOOTER_SZ] = {0};
    auto *footer = (drif_footer_t *)buffer;
    footer->magic = DRIF_MAGIC;
    footer->version = DRIF_VER;
    footer->w = w;
    footer->h = h;
    footer->f = f;

    // compute image size
    uint32_t dataSize = drifGetSize(w, h, f);
    // write image
    if (1 != fwrite(data, dataSize, 1, fp))
        return DRIF_ERR_WRITE_FILE;

    // write footer
    if (1 != fwrite((void *)buffer, DRIF_FOOTER_SZ, 1, fp))
        return DRIF_ERR_WRITE_FILE;

    // close file and flush contents
    fclose(fp);

    return DRIF_OK;
}

void drifFreeImg(void *ptr_from_load)
{
    free(ptr_from_load);
}

uint32_t drifGetSize(const uint32_t w, const uint32_t h, const uint32_t f)
{
    switch (f) {
    case DRIF_FMT_RGB888:
    case DRIF_FMT_BGR888:
    case DRIF_FMT_RGB888P:
    case DRIF_FMT_BGR888P:
        return w * h * 3;

    case DRIF_FMT_RGBA8888:
    case DRIF_FMT_BGRA8888:
    case DRIF_FMT_RGBA8888P:
    case DRIF_FMT_BGRA8888P:
        return w * h * 4;

    case DRIF_FMT_YUV420P:
    case DRIF_FMT_YVU420P:
    case DRIF_FMT_NV12:
    case DRIF_FMT_NV21:
        return w * h * 3 / 2;

    case DRIF_FMT_GRAY:
        return w * h;

    default:
        break;
    }

    return 0;
}

#endif
// NOLINTEND(misc-definitions-in-headers)
