////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// THIS IS AUTO-GENERATED CODE.  PLEASE DO NOT EDIT (File bug reports against tools).
///
/// Auto-generated by: Parameter Parser V5.5.1 (2411131018)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file  parameterutils.cpp
/// @brief Auto-generated Chromatix parameter file
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "parameterutils.h"
#include <cmath>

#if defined(CAMX)
#include <camxdebugprint.h>
#elif defined(__ANDROID__)
#include <android/log.h>
#endif

void ParameterLogging::Write(
    IParameterLoggingAPI* const logAPI,
    ParameterLogLevel level,
    const CHAR* const file,
    INT32 const       line,
    const CHAR* const str, ...)
{
    if (logAPI == nullptr || logAPI->LoggingEnabled(level))
    {
        CHAR message[MESSAGE_LEN];
        va_list args;
        va_start(args, str);
        PARAMETER_VSPRINTF(message, MESSAGE_LEN, str, args);
        va_end(args);

        const CHAR* fileName = GetFileName(file);

#if defined(CAMX)
        const CHAR* levelChar = "D";
        CamxLog     logLevel  = CamxLogDebug;
        if (level == ParameterLogLevel::Error)
        {
            levelChar = "E";
            logLevel  = CamxLogError;
        }
        else if (level == ParameterLogLevel::Warning)
        {
            levelChar = "W";
            logLevel  = CamxLogWarning;
        }
#endif

#if defined(CAMX) || !defined(__ANDROID__)
        const CHAR* levelStr = "[DEBUG]";
        if (level == ParameterLogLevel::Error)
        {
            levelStr = "[ERROR]";
        }
        else if (level == ParameterLogLevel::Warning)
        {
            levelStr = "[WARNING]";
        }
#elif defined(__ANDROID__)
        android_LogPriority levelEnum = ANDROID_LOG_DEBUG;
        if (level == ParameterLogLevel::Error)
        {
            levelEnum = ANDROID_LOG_ERROR;
        }
        else if (level == ParameterLogLevel::Warning)
        {
            levelEnum = ANDROID_LOG_WARN;
        }
#endif

        if (logAPI == nullptr)
        {
#if defined(CAMX)
            CamX::Log::LogSystem(CamxLogGroupUtils, levelChar, logLevel, "%s ParameterParser: %s:%d: %s", levelStr, fileName, line, message);
#elif defined(__ANDROID__)
            __android_log_print(levelEnum, "ParameterParser", "%s:%d: %s", fileName, line, message);
#else
            (void)printf("%s ParameterParser: %s:%d: %s", levelStr, fileName, line, message);
#endif
        }
        else if (logAPI->GetLoggingIdentifier() != nullptr)
        {
#if defined(CAMX)
            CamX::Log::LogSystem(CamxLogGroupUtils, levelChar, logLevel, "%s ParameterParser: %p (%s): %s:%d: %s", levelStr, logAPI, 
                logAPI->GetLoggingIdentifier(), fileName, line, message);
#elif defined(__ANDROID__)
            __android_log_print(levelEnum, "ParameterParser", "%p (%s): %s:%d: %s",
                logAPI, logAPI->GetLoggingIdentifier(), fileName, line, message);
#else
            (void)printf("%s ParameterParser: %p (%s): %s:%d: %s", levelStr, logAPI, logAPI->GetLoggingIdentifier(),
                fileName, line, message);
#endif
        }
        else
        {
#if defined(CAMX)
            CamX::Log::LogSystem(CamxLogGroupUtils, levelChar, logLevel, "%s ParameterParser: %p %s:%d: %s", levelStr, logAPI, fileName, line, message);
#elif defined(__ANDROID__)
            __android_log_print(levelEnum, "ParameterParser", "%p %s:%d: %s",
                logAPI, fileName, line, message);
#else
            (void)printf("%s ParameterParser: %p: %s:%d: %s", levelStr, logAPI, fileName, line, message);
#endif
        }
    }
}

const CHAR* ParameterLogging::GetFileName(
    const CHAR* const file)
{
    const CHAR* result = "FileNameIsNULL";
    if (file != nullptr)
    {
        SIZE_T pos = PARAMETER_STRLEN(file) - 1;
        while (pos > 0 && file[pos] != '/' && file[pos] != '\\')
        {
            pos--;
        }
        if (file[pos] == '/' || file[pos] == '\\')
        {
            pos++;
        }

        result = &file[pos];
    }

    return result;
}

bool ParameterUtils::ReadString(
    CHAR* const   value,
    BYTE* const   buffer,
    UINT32 const  length,
    UINT32* const pos,
    UINT32 const  destStrSize,
    UINT32 const  maxChars,
    BYTE const    alignment,
    IParameterLoggingAPI* logAPI)
{
    UINT32 chars  = PARAMETER_MIN(maxChars, length - (*pos));
    bool   result = false;

    if (value != nullptr)
    {
        PARAMETER_MEMCPY(value, destStrSize, (CHAR*)&buffer[*pos], chars);
        value[destStrSize - 1] = '\0';
        result = true;
    }
    else
    {
        PARAMETER_LOG_ERROR(logAPI, "ReadBuffer failed to allocate space for a string of %d length\n", chars);
    }

    *pos += maxChars + REMAINDER(maxChars, alignment);

    return result;
}

CHAR* ParameterUtils::ReadString(
    BYTE* const   buffer,
    UINT32 const  length,
    UINT32* const pos,
    UINT32 const  maxChars,
    BYTE const    alignment)
{
    CHAR* result = nullptr;
    if (buffer != nullptr)
    {
        UINT32 chars = 0;
        while (chars < maxChars && (*pos) + chars < length && buffer[(*pos) + chars] != 0)
        {
            chars++;
        }

        result = PARAMETER_NEW CHAR[(UINT32)(chars + 1)];
        if (result != nullptr)
        {
            PARAMETER_STRCPY(result, chars + 1, (CHAR*)&buffer[*pos]);
            result[chars] = '\0';

            *pos += (maxChars + REMAINDER(maxChars, alignment));
        }
    }

    return result;
}

bool ParameterUtils::ReadBuffer(
    BYTE* const   value,
    BYTE* const   buffer,
    UINT32 const  length,
    UINT32* const pos,
    UINT32        count,
    BYTE const    alignment,
    IParameterLoggingAPI* logAPI)
{
    UINT32 bytesCopied = 0U;
    if (buffer == nullptr)
    {
        PARAMETER_LOG_ERROR(logAPI, "ReadBuffer called with nullptr buffer\n");
    }
    else if (value == nullptr)
    {
        PARAMETER_LOG_ERROR(logAPI, "ReadBuffer called with nullptr value\n");
    }
    else
    {
        bytesCopied = (count <= length - (*pos)) ? count : length - (*pos);
        PARAMETER_MEMCPY(value, bytesCopied, (CHAR*)&buffer[*pos], bytesCopied);

        *pos += count + REMAINDER(count, alignment);
        if (bytesCopied != count)
        {
            PARAMETER_LOG_ERROR(logAPI, "ReadBuffer failed, read %d bytes, expected %d bytes\n", bytesCopied, count);
        }
    }

    return bytesCopied == count;
}

bool ParameterUtils::WriteString(
    const CHAR*   value,
    BYTE*         buffer,
    UINT32 const  length,
    UINT32* const pos,
    bool const    includeNul,
    IParameterLoggingAPI* logAPI)
{
    bool   result = false;
    SIZE_T const strLen = value == nullptr ? 0 : PARAMETER_STRLEN(value);
    if (*pos + strLen + 1 < length)
    {
        if (value == nullptr)
        {
            value = "";
        }

        PARAMETER_STRCPY((CHAR*)&buffer[*pos], length - *pos, value);
        result = true;

        *pos += (UINT32)strLen;
        if (includeNul)
        {
            (*pos)++;
        }
    }
    else
    {
        if (value != nullptr)
        {
            PARAMETER_LOG_ERROR(logAPI, "Insufficient space in buffer to write string: %s\n", value);
        }
        else
        {
            PARAMETER_LOG_ERROR(logAPI, "Insufficient space in buffer to write string\n");
        }
    }

    return result;
}

INT32 ParameterUtils::GetStringPos(
    const CHAR* const input,
    CHAR const  ch)
{
    INT32 result = -1;
    if (input != nullptr)
    {
        const SIZE_T length = PARAMETER_STRLEN(input);

        for (UINT32 pos = 0; result < 0 && pos < length; pos++)
        {
            if (input[pos] == ch)
            {
                result = pos;
            }
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// GetSubString - Get substring from input (returns up to the separator position)
///
/// Return value - New input string position (separator string terminator position)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
UINT32 ParameterUtils::GetSubString(
    const CHAR* const input,
    CHAR const        separator,
    CHAR* const       subString,
    UINT32 const      size)
{
    UINT32 result = 0;
    if (input != nullptr && subString != nullptr && size > 0)
    {
        const SIZE_T inputSize = PARAMETER_STRLEN(input);

        while (result < inputSize && result + 1 < size && input[result] != separator)
        {
            subString[result] = input[result];
            result++;
        }
        subString[result] = '\0';
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// GetVariable - Get variable name and array index  (if exists) from input string
///
/// Return value - New input string position
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
UINT32 ParameterUtils::GetVariable(
    const CHAR* const  input,
    CHAR* const  name,
    UINT32&      index,
    SIZE_T const size)
{
    constexpr SIZE_T VAR_SIZE = 129;
    constexpr SIZE_T IDX_SIZE = 10;
    CHAR varName[VAR_SIZE]    = "";

    UINT32 result        = ParameterUtils::GetSubString(input, '.', varName, VAR_SIZE);
    const INT32 idxStart = ParameterUtils::GetStringPos(varName, '[');
    if (idxStart >= 0)
    {
        if ((UINT32)idxStart + 1 < result)
        {
            CHAR   idx[VAR_SIZE];
            const UINT32 idxEnd = ParameterUtils::GetSubString(varName + idxStart + 1, ']', idx, IDX_SIZE);
            if (idxEnd > 0)
            {
                index  = (UINT32)StringToDouble(idx);
            }
        }
        varName[idxStart] = '\0';
    }

    PARAMETER_STRCPY(name, size, varName);

    if (result > 0 && input[result] == '.')
    {
        result++;
    }

    return result;
}

double ParameterUtils::StringToDouble(
    const CHAR* const input,
    bool&             valid)
{
    double result = 0;
    valid = false;

    if (input != nullptr)
    {
        INT32 const length = (UINT32)PARAMETER_STRLEN(input);

        if (length > 0)
        {
            INT32 const minusPos = GetStringPos(input, '-');
            INT32 dotPos   = GetStringPos(input, '.');

            INT32 start = 0;
            if (minusPos >= 0)
            {
                start = minusPos + 1;
            }

            if (dotPos < 0)
            {
                dotPos = length;
            }

            double multiplier = pow(10.0, dotPos - 1 - start);
            UINT32 dotCount = 0;

            valid = true;
            for (INT32 pos = start; valid && pos < length; pos++)
            {
                if (input[pos] == '.')
                {
                    dotCount++;
                    valid = dotCount == 1;
                }
                else if (input[pos] >= '0' && input[pos] <= '9')
                {
                    result     += (input[pos] - '0') * multiplier; // Add in this position's value
                    multiplier /= 10; // Adjust multiplier for the next position
                }
                else
                {
                    valid = false;
                }
            }

            if (minusPos >= 0)
            {
                result *= -1; // Make the value negative
            }
        }
    }

    if (!valid)
    {
        result = 0;
    }

    return result;
}

void ParameterUtils::DoubleToString(
    double const input,
    CHAR* const  string,
    UINT32 const size,
    UINT32 const decimals,
    bool&  valid)
{
    valid = false;
    if (string != nullptr && size > 0)
    {
        UINT32 pos = 0;
        double value = input;
        if (value < 0)
        {
            value *= -1; // Make the value negative
            string[0] = '-';
            pos = 1;
        }
        if (size > 1)
        {
            // Using log10 to calculate digits and pow to calculate divisor is not 100% accurate, doing it manaully

            double divisor = 1.0;           // Initialize the divisor
            while (value / divisor >= 10.0) // Check if we are done
            {
                divisor *= 10.0;            // Adjust the divisor
            }

            INT32  dotPos   = -1;
            UINT32 decCount = 0;
            while (pos + 1 < size && (value < 0 || value > 0 || divisor >= 1) && (decCount == 0 || decCount < decimals))
            {
                const UINT32 digit = (UINT32)((UINT64)(value / divisor)); // Calculate the digit value
                string[pos++] = (CHAR)('0' + digit);
                value -= digit * divisor; // Calculate the digits location value and Subtract
                divisor /= 10; // Update the divisor for the next position

                if (value < 1.0 && divisor < 1.0 && dotPos < 0 && pos + 1 < size && decimals > 0)
                {
                    dotPos = pos++;
                    string[dotPos] = '.';
                }
                else if (dotPos >= 0)
                {
                    decCount++;
                }
            }

            if ((value < 0 || value > 0) && dotPos >= 0)
            {
                // Perform rounding
                const UINT32 nextDigit = (UINT32)((UINT64)(value / divisor));
                if (nextDigit > 4)
                {
                    INT32 roundPos = pos - 1;
                    bool  done = false;
                    while (roundPos >= 0 && !done)
                    {
                        if (string[roundPos] == '.' || string[roundPos] == '9')
                        {
                            roundPos--;
                            if (roundPos > dotPos)
                            {
                                pos--;
                            }
                            else if (roundPos == dotPos || roundPos + 1 < dotPos)
                            {
                                string[roundPos + 1] = '0';
                            }
                        }
                        else
                        {
                            string[roundPos]++;
                            done = true;
                        }
                    }

                    if (!done && pos > 1 && pos + 1 < size)
                    {
                        pos++;
                        dotPos++;
                        // Did not finish rounding, shift all values down and add a 1 at the beginning
                        for (INT32 i = pos - 2; i >= 0; i--)
                        {
                            string[i + 1] = string[i];
                        }
                        string[0] = '1';
                    }
                }
            }

            // Clear all trailing zeros, except one, after the decimal point
            while (dotPos >= 0 && pos - 1 > (UINT32)dotPos + 1 && string[pos - 1] == '0')
            {
                pos--;
            }

            if (pos == 0 || (pos + 1 < size && pos - 1 == (UINT32)dotPos))
            {
                // Make sure there is at least one character (a zero) in the string, and at least one after the decimal place
                string[pos++] = '0';
            }

            valid = value <= 1.0;
        }
        string[pos] = '\0';
    }
}


ParameterParserInfo::ParameterParserInfo()
{
    Name       = nullptr;
    Type       = nullptr;
    Value[0]   = '\0';
    IsArray    = false;
    IsOptional = false;
    Elements   = 0;
}

ParameterParserInfo::ParameterParserInfo(const CHAR* const name, const CHAR* const type)
{
    Name       = name;
    Type       = type;
    Value[0]   = '\0';
    IsArray    = false;
    IsOptional = false;
    Elements   = 0;
}

ParameterParserInfo::ParameterParserInfo(const CHAR* const name, const CHAR* const type, double const value, UINT32 const decimals)
{
    Name       = name;
    Type       = type;
    Value[0]   = '\0';
    IsArray    = false;
    IsOptional = false;
    Elements   = 0;

    ParameterUtils::DoubleToString(value, Value, MAX_VALUE, decimals);
}

ParameterParserInfo::ParameterParserInfo(const CHAR* const name, const CHAR* const type, const CHAR* const value)
{
    Name       = name;
    Type       = type;
    Value[0]   = '\0';
    IsArray    = false;
    IsOptional = false;
    Elements   = 0;

    PARAMETER_STRCPY(Value, MAX_VALUE, value);
}

ParameterParserInfo::ParameterParserInfo(const CHAR* const name, const CHAR* const type, bool const isArray, bool const isOptional, UINT32 const elements)
{
    Name       = name;
    Type       = type;
    Value[0]   = '\0';
    IsArray    = isArray;
    IsOptional = isOptional;
    Elements   = elements;
}

bool DupObjInfo::Release(DupObjInfo* const obj)
{
    bool result = true;
    if (obj != nullptr)
    {
        obj->RefCount--;
        if (obj->RefCount > 0)
        {
            result = false;
        }
        else
        {
            obj->Object = nullptr;
        }
    }

    return result;
}

DupObjInfo::DupObjInfo(UINT32 const count, const CHAR* const file, UINT32 const line)
{
    Object   = nullptr;
    Count    = count;
    File     = file;
    Line     = line;
    RefCount = 0;
}

DupObjInfo::~DupObjInfo()
{
    Object   = nullptr;
    Count    = 0;
    File     = nullptr;
    Line     = 0;
    RefCount = 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief ParameterMutex: Provides shared and recursive functionality
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Lock function
/// @param shared - True if locking as shared
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterMutex::Lock(bool shared)
{
    std::unique_lock<std::mutex> sync(m_mutex);
    if (shared)
    {
        m_condition.wait(sync, [this] {return CanLockShared(); });
        IncrementShared();
    }
    else
    {
        m_condition.wait(sync, [this] {return CanLock(); });

        if (IsOwner())
        {
            m_count++;
        }
        else
        {
            m_owner = std::this_thread::get_id();
            m_count = 1;
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Lock function
/// @param shared - True if locking as shared
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterMutex::Unlock(bool shared)
{
    std::unique_lock<std::mutex> sync(m_mutex);
    if (shared)
    {
        DecrementShared();
    }
    else
    {
        if (m_count == 0)
        {
            PARAMETER_LOG_ERROR(nullptr, "ParameterMutex::Unlock called when lock count is zero\n");
        }
        else
        {
            m_count -= 1;
        }

        if (m_count == 0)
        {
            m_owner = std::thread::id {};
        }
    }
    m_condition.notify_all();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ParameterBinaryFile Constructor
///
/// Constructing the ParameterBinaryFile object will open the file and add a reference if the file is valid
/// After constructing, check the return value of IsValid() to verify if the file was opened
///
/// @param fileName  Name of the file to open
/// @param logAPI    Logging API used for logging error messages
/// @param CRCCheck  Check CRC if true
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ParameterBinaryFile::ParameterBinaryFile(
    const CHAR*                 fileName,
    IParameterLoggingAPI* const logAPI,
    bool                        CRCCheck)
{
    m_file       = nullptr;
    m_length     = 0;
    m_refCnt     = 0;
    m_isValid    = false;
    m_isValidCRC = false;
    m_isModified = false;
    m_logAPI     = logAPI;
    m_modTime    = 0;

    PARAMETER_INITIALIZE(m_fileName);

    if (fileName != nullptr)
    {
        SIZE_T nameLength = PARAMETER_STRLEN(fileName);

        if (nameLength < MAX_FILE_LENGTH - 1)
        {
            PARAMETER_STRCPY(m_fileName, MAX_FILE_LENGTH, fileName);
            Reference(1);
            m_isValid = m_file != nullptr;
            if (m_isValid && CRCCheck)
            {
                ValidateCRC();
            }
        }
        else
        {
            PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile constructor file name length (%lld) exceeds max length: %d\n", nameLength, MAX_FILE_LENGTH - 1);
        }
    }
    else
    {
        PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile constructor called with null file name\n");
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ParameterBinaryFile Descructor
///
/// Deconstruct the ParameterBinaryFile object, will close the file if opened
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ParameterBinaryFile::~ParameterBinaryFile()
{
    const ParameterMutexLock flock{ m_mutex, false, true }; // Unique LOCK

    if (m_refCnt > 0)
    {
        if (m_refCnt > 1)
        {
            // One refernce is acceptable so this class can be contructed/destructed without using reference counting
            // More than one reference indicates improper reference counting use
            PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile deleted with outstanding references: %d\n", m_refCnt);
        }
        Reference(-m_refCnt);
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Read
///
/// Reads the specified number of bytes from the file at the specified offset + pos
///
/// @param value     Buffer to read into
/// @param offset    Location in the file to start from
/// @param pos       offset + position in the file to read from, position is updated after read
/// @param count     Number of bytes to read
/// @param alignment Number of bytes to align the returned pos value by
///
/// @return Buffer Pointer or null if read failed
///                Caller is responsible for freeing the returned buffer using PARAMETER_DELETE[]
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ParameterBinaryFile::Read(
    BYTE* const   value,
    UINT32 const  offset,
    UINT32* const pos,
    SIZE_T const  count,
    BYTE const    alignment)
{
    SIZE_T readSize = 0U;
    const ParameterMutexLock flock{ m_mutex, false, true }; // Unique LOCK

    if (m_file == nullptr)
    {
        PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read called with null file\n");
    }
    else if (value == nullptr)
    {
        PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read called with null value\n");
    }
    else if (pos == nullptr)
    {
        PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read called with null pos\n");
    }
    else if (m_length == 0)
    {
        PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read called with a zero length file\n");
    }
    else if (offset + *pos + count > m_length)
    {
        PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read attempted to read past the end of the file, offset: %u, pos: %u, count: %u, length: %u\n",
            offset, *pos, count, m_length);
    }
    else if (m_isValid && !m_isModified)
    {
        constexpr UINT32 MAX_RETRIES = 3;
        UINT32 retries = 0;

        do
        {
            INT32 fseekReturn = PARAMETER_FSEEK(m_file, offset + *pos, SEEK_SET);
            if (fseekReturn == 0)
            {
                readSize = PARAMETER_FREAD(value, count, 1, count, m_file);
                if (readSize != count)
                {
                    PARAMETER_LOG_ERROR(m_logAPI, "Read failed to read from file, ferror: %d, offset: %u, pos: %u, requested: %lld, actual: %lld\n",
                        ferror(m_file), offset, *pos, count, readSize);
                }
                else
                {
                    *pos += (UINT32)(count + REMAINDER(count, alignment));
                }
            }
            else
            {
                PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read fseek failed, ferror: %d, return value %d, offset: %d\n", ferror(m_file), fseekReturn, offset);
            }

            retries++;
            if (count != readSize && retries <= MAX_RETRIES)
            {
                PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read failed, retrying\n");
                Close();
                Open(true);
            }
        } while (count != readSize && retries <= MAX_RETRIES && !m_isModified);
    }

    return readSize == count && !m_isModified;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Read
///
/// Reads the specified number of bytes from the file at the specified offset
///
/// @param offset  Location in the file to start reading
/// @param count   Number of bytes to read
///
/// @return Buffer Pointer or null if read failed
///                Caller is responsible for freeing the returned buffer using PARAMETER_DELETE[]
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BYTE* ParameterBinaryFile::Read(
    UINT32 const offset,
    SIZE_T const count)
{
    UINT8* result = nullptr;

    if (count == 0)
    {
        PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read called with 0 count\n");
    }
    else if (m_isValid && !m_isModified)
    {
        result = PARAMETER_NEW UINT8[count];
        if (result != nullptr)
        {
            UINT32 pos = 0;
            if (!Read(result, offset, &pos, count, 1))
            {
                PARAMETER_DELETE[] result;
                result = nullptr;
            }
        }
        else
        {
            PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Read failed, out of memory, errno: %d\n", errno);
        }
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// FileReference
///
/// @brief Adjusts the reference count, and opens or closes the file as needed
/// @param adjust - how much to adjust the reference count
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
INT32 ParameterBinaryFile::Reference(
    INT32 adjust)
{
    const ParameterMutexLock flock{ m_mutex, false, true }; // Unique LOCK

    if (adjust > 0)
    {
        if (m_file == nullptr)
        {
            if (m_refCnt != 0)
            {
                PARAMETER_LOG_ERROR(m_logAPI, "FileReference Adjust: %d, RefCount: %d, Binary File was null when file reference was %u\n", adjust, m_refCnt);
            }
            PARAMETER_LOG_DEBUG(m_logAPI, "FileReference Adjust: %d, RefCount: %u, opening the file\n", adjust, m_refCnt);
            Open(false);

            if (m_file == nullptr)
            {
                PARAMETER_LOG_ERROR(m_logAPI, "FileReference Adjust: %d, RefCount: %d, Failed to open binary file '%s' errno: %d\n", adjust, m_refCnt, m_fileName, errno);
            }
        }
        m_refCnt += adjust;
    }
    else if (adjust < 0)
    {
        if (m_file != nullptr)
        {
            if (m_refCnt + adjust < 0)
            {
                PARAMETER_LOG_ERROR(m_logAPI, "FileReference Adjust: %d, RefCount: %d, File Reference is less than adjust amount\n", adjust, m_refCnt);
            }
            m_refCnt += adjust;
            if (m_refCnt <= 0)
            {
                PARAMETER_LOG_DEBUG(m_logAPI, "FileReference Adjust: %d, RefCount: %u, closing the file\n", adjust, m_refCnt);
                Close();
                m_refCnt = 0;
            }
        }
        else
        {
            PARAMETER_LOG_ERROR(m_logAPI, "FileReference Remove: %d, Ref Cnt: %d, Called when file was already closed\n", adjust, m_refCnt);
        }
    }

    return m_refCnt;
}

void ParameterBinaryFile::Close()
{
    // This is a private function called while the mutex is locked, no need to lock it here
    if (m_file != nullptr)
    {
        PARAMETER_FCLOSE(m_file);
        m_file = nullptr;
    }
}

void ParameterBinaryFile::Open(bool logFailure)
{
    // This is a private function called while the mutex is locked, no need to lock it here
    if (m_file == nullptr)
    {
        PARAMETER_FOPEN(m_file, m_fileName);
        if (m_file != nullptr)
        {
            struct stat stats;
            if (PARAMETER_FSTAT(m_file, &stats) == 0)
            {
                if (m_modTime == 0)
                {
                    m_length  = stats.st_size;
                    m_modTime = stats.st_mtime;
                }
                else if (m_modTime != stats.st_mtime)
                {
                    m_isModified = true;
                    m_length     = stats.st_size;
                    m_modTime    = stats.st_mtime;
                    PARAMETER_LOG_WARNING(m_logAPI, "ParameterBinaryFile::Open, file modified, must reload\n", errno, m_fileName);
                }
            }
        }
        else if (logFailure)
        {
            PARAMETER_LOG_ERROR(m_logAPI, "ParameterBinaryFile::Open failed to open file, errno: %d, file name: %s\n", errno, m_fileName);
        }
    }
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ValidateCRC
///
/// @brief Reads and validates CRC value in the binary buffer
///
/// @param buffer   Binary parameter buffer
/// @param length   Length of the binary parameter buffer
/// @param logAPI   Log API for error reporting
///
/// @return True if CRC is valid
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ParameterBinaryFile::ValidateCRC(
    BYTE* const  buffer,
    const UINT32 length,
    IParameterLoggingAPI* logAPI)
{
    bool result = false;

    if (length > 4)
    {
        UINT32 actualCRC   = 0;
        UINT32 expectedCRC = 0xFFFFFFFF;
        for (UINT32 i = 0; i < length - 4; i++)
        {
            UINT32 const index = (expectedCRC ^ buffer[i]) & 0xffU;
            expectedCRC = (expectedCRC >> 8U) ^ m_crcLookupTable[index];
        }
        expectedCRC = ~expectedCRC;

        UINT32 pos = length - 4;
        result = ParameterUtils::Read(&actualCRC, buffer, length, &pos, 1, logAPI);
        if (result)
        {
            if (actualCRC != expectedCRC)
            {
                PARAMETER_LOG_ERROR(logAPI, "Binary file CRC validation failed. Expected: %8x, Actual: %8x\n", expectedCRC, actualCRC);
                result = false;
            }
        }
        else
        {
            PARAMETER_LOG_ERROR(logAPI, "Binary file CRC validation failed. Failed to read CRC at end of file\n");
        }
    }
    else
    {
        PARAMETER_LOG_ERROR(logAPI, "Binary file CRC validation failed. Buffer too small\n");
    }

    return result;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Reload
///
/// Reloads and re-validates the file
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool ParameterBinaryFile::Reload()
{
    m_file       = nullptr;
    m_length     = 0;
    m_refCnt     = 0;
    m_isValid    = false;
    m_isValidCRC = false;
    m_isModified = false;
    m_modTime    = 0;

    Close();
    Open(true);

    m_isValid = m_file != nullptr;
    if (m_isValid)
    {
        ValidateCRC();
        PARAMETER_FSEEK(m_file, 0, SEEK_END);
        PARAMETER_FTELL(m_file, m_length);
        PARAMETER_FSEEK(m_file, 0, SEEK_SET);

        if (m_refCnt <= 0)
        {
            Close();
        }
    }

    return m_isValidCRC;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// ValidateCRC
///
/// @brief Reads and validates CRC value in the binary buffer
///
/// @param buffer   Binary parameter buffer
/// @param length   Length of the binary parameter buffer
///
/// @return True if CRC is valid
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ParameterBinaryFile::ValidateCRC()
{
    if (m_isValid)
    {
        BYTE* const buffer = PARAMETER_NEW BYTE[m_length];
        UINT32      pos    = 0;

        (void)Read(buffer, 0, &pos, m_length, 1);
        m_isValidCRC = ValidateCRC(buffer, m_length, m_logAPI);
        PARAMETER_DELETE[] buffer;
    }
}

// Standard reference CRC32 lookup table
// From Microsoft Systems Journal, March 1995, pp. 107-108
UINT32 ParameterBinaryFile::m_crcLookupTable[256] =
{
     0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
     0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
     0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
     0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
     0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
     0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
     0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
     0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
     0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
     0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
     0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
     0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
     0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
     0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
     0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
     0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
     0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
     0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
     0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
     0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
     0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
     0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
     0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
     0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
     0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
     0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
     0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
     0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
     0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
     0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
     0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
     0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
     0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
     0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
     0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
     0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
     0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
     0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
     0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
     0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
     0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
     0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
     0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
     0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
     0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
     0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
     0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
     0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
     0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
     0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
     0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
     0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
     0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
     0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
     0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
     0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
     0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
     0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
     0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
     0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
     0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
     0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
     0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
     0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};

