/* Copyright (c) 2018-2020, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 */

#include "context.h"
#include "utils.h"
#include "nvmedia_isc.h"

#define DEFAULT_SENSOR_NUM 1

#define MASK_BITS(bits) ((1 << bits) - 1)

NvMediaStatus
InitializeExtImgDevice(
    NvEEPROMContext* context)
{
    ExtImgDevParam configParam;
    CaptureConfigParams *captureParams;
    uint32_t i;

    if (!context->allArgs->config.isUsed) {
        LOG_ERR("A configuration must be selected with -c\n");
        return NVMEDIA_STATUS_ERROR;
    }

    memset(&configParam, 0, sizeof(configParam));
    if (!context->allArgs->numSensors.isUsed){
        configParam.sensorsNum = DEFAULT_SENSOR_NUM;
    } else {
        configParam.sensorsNum = context->allArgs->numSensors.uIntValue;
    }
    context->numSensors = configParam.sensorsNum;

    captureParams = &context->allArgs->captureConfigCollection[context->allArgs->config.uIntValue];
    configParam.desAddr = captureParams->desAddr;
    configParam.brdcstSerAddr = captureParams->brdcstSerAddr;
    configParam.brdcstSensorAddr = captureParams->brdcstSensorAddr;
    configParam.i2cDevice = captureParams->i2cDevice;
    configParam.moduleName = captureParams->inputDevice;
    configParam.board = captureParams->board;
    configParam.resolution = captureParams->resolution;
    configParam.inputFormat = captureParams->inputFormat;
    configParam.interface = captureParams->interface;
    configParam.enableEmbLines =
        (captureParams->embeddedDataLinesTop || captureParams->embeddedDataLinesBottom) ?
            NVMEDIA_TRUE : NVMEDIA_FALSE;
    configParam.initialized = NVMEDIA_FALSE;
    configParam.enableSimulator = NVMEDIA_FALSE;
    configParam.slave = NVMEDIA_FALSE;
    configParam.enableVirtualChannels = NVMEDIA_FALSE;
    configParam.enableExtSync = NVMEDIA_FALSE;
    configParam.dutyRatio = 0.25;

    /* If the eeprom address argument is provided, override the address in the config file */
    if (context->allArgs->eepromAddress.isUsed) {
        configParam.e2pPhysicalAddr = context->allArgs->eepromAddress.uIntValue;
    } else {
        configParam.e2pPhysicalAddr = captureParams->e2pPhysicalAddr;
    }

    for (i=0; i < configParam.sensorsNum; i++) {
        configParam.serAddr[i] = captureParams->serAddr[i];
        configParam.sensorAddr[i] = captureParams->sensorAddr[i];
        configParam.e2pAddr[i] = captureParams->e2pAddr[i];
        configParam.sensorIdentifier[i] = captureParams->sensorIdentifier[i];
    }

    context->extImgDev = ExtImgDevInit(&configParam);
    if (context->extImgDev == NULL) {
        LOG_ERR("Unable to initialize ExtImgDevice\n");
        return NVMEDIA_STATUS_ERROR;
    }
    return NVMEDIA_STATUS_OK;
}

void
DeinitializeExtImgDevice(
    NvEEPROMContext* context)
{
    ExtImgDevDeinit(context->extImgDev);
}

NvMediaStatus
ReadImageFromFile(
    const char* filename,
    uint8_t *buffer,
    uint32_t *size)
{
    FILE* fp = fopen(filename, "r");
    if (fp == NULL) {
        LOG_ERR("Could not open file \"%s\"",filename);
        return NVMEDIA_STATUS_ERROR;
    }
    *size = fread(buffer, 1, *size, fp);
    if (!feof(fp)) {
        LOG_ERR("File provided is too long, maximum file size is %d bytes\n", *size);
        return NVMEDIA_STATUS_ERROR;
    }
    return NVMEDIA_STATUS_OK;

}

/* Numbers are stored in EEPROM in big endian format */
/* This converts from the host endianness to big endian */
void
HostToEEPROMU16(
    const uint16_t *src,
    uint8_t *dst,
    uint32_t count)
{
    uint32_t i;
    for (i = 0; i < count; i++) {
        dst[sizeof(uint16_t) * i + 0] = (src[i] >> 8) & 0xFF;
        dst[sizeof(uint16_t) * i + 1] = (src[i] >> 0) & 0xFF;
    }
}

void
HostToEEPROMU32(
    const uint32_t *src,
    uint8_t *dst,
    uint32_t count)
{
    uint32_t i;
    for (i = 0; i < count; i++) {
        dst[sizeof(uint32_t) * i + 0] =  (src[i] >> 24) & 0xFF;
        dst[sizeof(uint32_t) * i + 1] =  (src[i] >> 16) & 0xFF;
        dst[sizeof(uint32_t) * i + 2] =  (src[i] >>  8) & 0xFF;
        dst[sizeof(uint32_t) * i + 3] =  (src[i] >>  0) & 0xFF;
    }
}

/* Numbers are stored in EEPROM in big endian format */
/* This converts from the host endianness to big endian */
void
HostToEEPROMU64(
    const uint64_t *src,
    uint8_t *dst,
    uint32_t count)
{
    uint32_t i;
    for (i = 0; i < count; i++) {
        dst[sizeof(uint64_t) * i + 0] =  (src[i] >> 56) & 0xFF;
        dst[sizeof(uint64_t) * i + 1] =  (src[i] >> 48) & 0xFF;
        dst[sizeof(uint64_t) * i + 2] =  (src[i] >> 40) & 0xFF;
        dst[sizeof(uint64_t) * i + 3] =  (src[i] >> 32) & 0xFF;
        dst[sizeof(uint64_t) * i + 4] =  (src[i] >> 24) & 0xFF;
        dst[sizeof(uint64_t) * i + 5] =  (src[i] >> 16) & 0xFF;
        dst[sizeof(uint64_t) * i + 6] =  (src[i] >>  8) & 0xFF;
        dst[sizeof(uint64_t) * i + 7] =  (src[i] >>  0) & 0xFF;
    }
}

/* The EEPROM stores floats as big endian IEEE754.  ARM and x86 both
   store floats as IEEE754, so this only needs to convert endianess */
void
HostToEEPROMFloat(
    const float *src,
    uint8_t *dst,
    uint32_t count)
{
    HostToEEPROMU32((uint32_t*)src,
                    dst,
                    count);
}

/* Convert from big endian format to the host endianess */
void
EEPROMToHostU16(
    const uint8_t *src,
    uint16_t  *dst,
    uint32_t count)
{
    uint32_t i;
    for (i = 0; i < count; i++) {
        dst[i] = (src[sizeof(uint16_t)*i + 0] << 8) |
                 (src[sizeof(uint16_t)*i + 1] << 0) ;
    }
}

/* Convert from big endian format to the host endianess */
void
EEPROMToHostU32(
    const uint8_t *src,
    uint32_t *dst,
    uint32_t count)
{
    uint32_t i;
    for (i = 0; i < count; i++) {
        dst[i] = (src[sizeof(uint32_t)*i + 0] << 24) |
                 (src[sizeof(uint32_t)*i + 1] << 16) |
                 (src[sizeof(uint32_t)*i + 2] <<  8) |
                 (src[sizeof(uint32_t)*i + 3] <<  0) ;
    }
}

/* Convert from big endian format to the host endianess */
void
EEPROMToHostU64(
    const uint8_t *src,
    uint64_t *dst,
    uint32_t count)
{
    uint32_t i;
    for (i = 0; i < count; i++) {
        dst[i] = ((uint64_t)src[sizeof(uint64_t)*i + 0] << 56) |
                 ((uint64_t)src[sizeof(uint64_t)*i + 1] << 48) |
                 ((uint64_t)src[sizeof(uint64_t)*i + 2] << 40) |
                 ((uint64_t)src[sizeof(uint64_t)*i + 3] << 32) |
                 ((uint64_t)src[sizeof(uint64_t)*i + 4] << 24) |
                 ((uint64_t)src[sizeof(uint64_t)*i + 5] << 16) |
                 ((uint64_t)src[sizeof(uint64_t)*i + 6] <<  8) |
                 ((uint64_t)src[sizeof(uint64_t)*i + 7] <<  0) ;
    }
}

/* The EEPROM stores floats as big endian IEEE754.  ARM and x86 both
   store floats as IEEE754, so this only needs to convert endianess */
void
EEPROMToHostFloat(
    const uint8_t *src,
    float *dst,
    uint32_t count)
{
    EEPROMToHostU32(src,
                    (uint32_t*)dst,
                    count);
}

/* Convert from fixed point lsc data stored on EEPROM to floating point.
   Format is the two byte format string that describes how the fixed point
   data is formatted. */
NvMediaStatus
EEPROMToHostFixed(
    const uint8_t *buffer,
    float *dst,
    uint32_t count,
    uint8_t *format)
{
    uint32_t i;
    uint8_t signBits =      1;
    uint8_t integerBits =   (format[0] & 0x7F);
    uint8_t fractionBits =  (format[1]);

    uint16_t value;

    if (signBits + integerBits + fractionBits != 16) {
        LOG_ERR("LSC format specified is not 16 bits per value. "
                "Sign bits: %d, Integer bits: %d, Fraction bits: %d\n",
                signBits, integerBits, fractionBits);
        return NVMEDIA_STATUS_ERROR;
    }

    for (i = 0; i < count; i++) {
        EEPROMToHostU16(&buffer[i * sizeof(uint16_t)], &value, 1);
        /* Compute the integer part */
        dst[i] = (value >> fractionBits) & MASK_BITS(integerBits);
        /* Add the fractional part */
        dst[i] += (1.0 / (1 << fractionBits)) * (value & MASK_BITS(fractionBits));
        /* Incorporate the sign */
        if (((value >> (integerBits + fractionBits)) & MASK_BITS(signBits)) != 0) {
            dst[i] *= -1;
        }
    }
    return NVMEDIA_STATUS_OK;
}

