/*******************************************************************************
* Copyright 2012-2020 Intel Corporation.
*
* This software and the related documents are Intel copyrighted  materials,  and
* your use of  them is  governed by the  express license  under which  they were
* provided to you (License).  Unless the License provides otherwise, you may not
* use, modify, copy, publish, distribute,  disclose or transmit this software or
* the related documents without Intel's prior written permission.
*
* This software and the related documents  are provided as  is,  with no express
* or implied  warranties,  other  than those  that are  expressly stated  in the
* License.
*******************************************************************************/

#include <stdarg.h>

#include "base.h"
#include "base_ipp.h"

/*
// Dynamic strings
*/
DString::DString()
{
    m_pData = NULL;
    m_size  = 0;
    m_len   = 0;
    Replace((const vm_char*)&(VM_STRING("")), 0); // initialize with zero-length string to avoid typecast memory problems
}

DString::DString(const vm_char *str, size_t len)
{
    m_pData = NULL;
    m_size  = 0;
    m_len   = 0;
    if(!str)
        Replace((const vm_char*)&(VM_STRING("")), 0);
    else
        Replace(str, len);
}

DString::DString(const DString &src)
{
    m_pData = NULL;
    m_size  = 0;
    m_len   = 0;
    Replace(src.c_str(), src.Size());
}

DString::~DString()
{
    if(m_pData)
    {
        delete[] m_pData;
        m_pData = NULL;
    }
}

void DString::Clear()
{
    if(m_pData)
    {
        delete[] m_pData;
        m_pData = NULL;
    }
    m_size = 0;
    m_len  = 0;
    Replace((const vm_char*)&(VM_STRING("")), 0);
}

size_t DString::Replace(const vm_char* str, size_t len)
{
    if(!str)
        return 0;
    if(!len)
        len = vm_string_strlen(str, 0xFFFF);

    if(m_pData == str) // update len
    {
        m_len = (len > m_size)?m_size:len;
        return 0;
    }

    if(m_size < len + 1)
    {
        if(m_pData)
            delete[] m_pData;
        m_size  = len + 1;
        m_pData = allocate(m_size);
    }
    m_len = len;
    vm_memcpy(m_pData, str, len*sizeof(vm_char));
    m_pData[m_len] = VM_STRING('\0');

    return len;
}

size_t DString::Append(const vm_char* str, size_t len)
{
    if(!str)
        return 0;

    if(!len)
        len = vm_string_strlen(str, 0xFFFF);

    if(m_size < len + m_len + 1)
    {
        vm_char *pDataOld = m_pData;

        m_size  = len + m_len + 1;
        m_pData = allocate(m_size);
        if(pDataOld)
        {
            vm_memcpy(m_pData, pDataOld, m_len*sizeof(vm_char));
            delete[] pDataOld;
        }
    }
    vm_memcpy(&m_pData[m_len], str, len);
    m_len += len;
    m_pData[m_len] = VM_STRING('\0');
    return len;
}

int DString::Compare(const vm_char *str, bool bCaseSensitive)
{
    if(!str || !m_pData)
        return -1;

    if(bCaseSensitive)
        return vm_string_strcmp(m_pData, str);
    else
        return vm_string_stricmp(m_pData, str);
}

long long DString::Find(const vm_char *str, bool forward, bool bCaseSensitive)
{
    size_t     iSrcSize;
    int        iChar1;
    int        iChar2;
    size_t     iCount;

    if(!str || !m_pData)
        return -1;

    iSrcSize = vm_string_strlen(str, 0xFFFF);
    if(!m_len || !iSrcSize || iSrcSize > m_len)
        return -1;

    if(forward)
    {
        for(long long i = 0; (i < (long long)m_len) && ((i + iSrcSize) <= m_len); i++)
        {
            iCount = 0;

            for(long long k = 0; k < (long long)iSrcSize; k++)
            {
                if(bCaseSensitive)
                {
                    iChar1 = m_pData[i + k];
                    iChar2 = str[k];
                }
                else
                {
                    iChar1 = vm_string_tolower(m_pData[i + k]);
                    iChar2 = vm_string_tolower(str[k]);
                }

                if(iChar1 == iChar2)
                    iCount++;
                else
                    break;
            }

            if(iCount == iSrcSize)
                return i;
        }
    }
    else
    {
        for(long long i = m_len-iSrcSize; i >= 0; i--)
        {
            iCount = 0;

            for(long long k = 0; k < (long long)iSrcSize; k++)
            {
                if(bCaseSensitive)
                {
                    iChar1 = m_pData[i + k];
                    iChar2 = str[k];
                }
                else
                {
                    iChar1 = vm_string_tolower(m_pData[i + k]);
                    iChar2 = vm_string_tolower(str[k]);
                }

                if(iChar1 == iChar2)
                    iCount++;
                else
                    break;
            }

            if(iCount == iSrcSize)
                return i;
        }
    }

    return -1;
}

void DString::Split(DString source, vm_char code, std::vector<DString> &substr, bool forward, size_t max)
{
    if(!source.m_pData)
        return;

    size_t count  = 0;
    size_t subLen = 0;
    substr.clear();

    if(forward)
    {
        vm_char *pStart = source.m_pData;

        for(long long i = 0; i <= (long long)source.m_len; i++)
        {
            if(source.m_pData[i] == code || source.m_pData[i] == 0)
            {
                DString sub(pStart, subLen);
                substr.push_back(sub);
                pStart = &source.m_pData[i + 1];
                subLen = 0;
                count++;

                if(max && source.m_pData[i] != 0)
                {
                    if(max == 1) // Cannot write anymore
                        return;
                    else if(count >= max-1) // One entry remains, write remaining string and quit
                    {
                        sub.Replace(pStart);
                        substr.push_back(sub);
                        return;
                    }
                }
            }
            else
                subLen++;
        }
    }
    else
    {
        for(long long i = source.m_len-1; i >= 0; i--)
        {
            if(source.m_pData[i] == code)
            {
                DString sub(&source.m_pData[i+1], subLen);
                substr.push_back(sub);
                subLen = 0;
                count++;

                if(max && i)
                {
                    if(max == 1) // Cannot write anymore
                        return;
                    else if(count >= max-1) // One entry remains, write remaining string and quit
                    {
                        sub.Replace(source.m_pData, (size_t)(i));
                        substr.push_back(sub);
                        return;
                    }
                }
            }
            else if(!i)
            {
                DString sub(&source.m_pData[i], subLen+1);
                substr.push_back(sub);
            }
            else
                subLen++;
        }
    }
}

size_t DString::Count(vm_char code)
{
    if(!m_pData)
        return 0;

    size_t count = 0;

    for(size_t i = 0; i < m_len; i++)
    {
        if(m_pData[i] == code)
            count++;
    }

    return count;
}

size_t DString::Trim()
{
    return TrimRight() + TrimLeft();
}

size_t DString::TrimLeft()
{
    size_t spaces = 0;
    size_t i;

    if(!m_len)
        return 0;

    for(i = 0; i < m_len; i++)
    {
        if(m_pData[i] != VM_STRING(' '))
            break;

        spaces++;
    }

    if(spaces)
    {
        for(i = spaces; i < m_len; i++)
            m_pData[i - spaces] = m_pData[i];

        m_len -= spaces;
        m_pData[m_len] = VM_STRING('\0');
    }

    return spaces;
}

size_t DString::TrimRight()
{
    size_t spaces = 0;

    if(!m_len)
        return 0;

    for(size_t i = m_len - 1; ; i--)
    {
        if(m_pData[i] != VM_STRING(' '))
            break;

        spaces++;
        if(!i)
            break;
    }
    m_len = m_len - spaces;
    m_pData[m_len] = VM_STRING('\0');

    return spaces;
}

size_t DString::RemoveDuplicates(int code)
{
    size_t shift = 0;

    if(!m_len)
        return 0;

    for(size_t i = 0; i < m_len - 1; i++)
    {
        if((!code || code == m_pData[i]) && m_pData[i] == m_pData[i + 1])
            shift++;
        else if(shift)
            m_pData[i - shift + 1] = m_pData[i + 1];
    }
    m_len = m_len - shift;
    m_pData[m_len] = VM_STRING('\0');

    return shift;
}

size_t DString::Resize(size_t size)
{
    if(m_size < size + 1)
    {
        vm_char *pDataOld = m_pData;

        m_size = size + 1;
        m_pData = allocate(m_size);
        if(pDataOld)
        {
            vm_memcpy(m_pData, pDataOld, m_len*sizeof(vm_char));
            delete[] pDataOld;
        }

        m_pData[m_len] = VM_STRING('\0');
    }

    return m_size;
}

size_t DString::AdjustLength()
{
    m_len = vm_string_strlen(m_pData, 0xFFFF);
    if(m_len >= m_size)
    {
        m_len = m_size - 1;
        m_pData[m_len] = VM_STRING('\0');
    }
    return m_len;
}

DString DString::Format(const vm_char *format, ...)
{
    DString sOut;
    size_t  size;

    va_list argptr;
    va_start(argptr, format);
    size = vm_string_vsnprintf(NULL, 0, format, argptr);
    va_end(argptr);

    sOut.Resize(size);

    va_start(argptr, format);
    vm_string_vsnprintf((vm_char*)sOut.c_str(), size + sizeof(vm_char), format, argptr);
    va_end(argptr);

    return sOut.c_str();
}

/*
// File operations
*/
File::File()
{
    m_pFile = NULL;
}
File::File(const char* pFileName, const char* mode)
{
    m_pFile = NULL;
    Open(pFileName, mode);
}
File::~File()
{
    Close();
}
Status File::Open(const char* pFileName, const char* mode)
{
    if(!pFileName || !mode)
        return STS_ERR_INVALID_PARAMS;

    Close();

    m_fileName = pFileName;
    m_pFile = vm_file_fopen(pFileName, mode);
    if(!m_pFile)
    {
        PRINT_MESSAGE(DString::Format("Cannot open file: %s", pFileName).c_str());
        return STS_ERR_FILE_OPEN;
    }

    return STS_OK;
}
Status File::Close()
{
    if(m_pFile)
        vm_file_fclose(m_pFile);

    m_pFile = NULL;

    return STS_OK;
}

size_t File::Read(void *pBuffer, size_t elementSize, size_t count)
{
    if(!m_pFile)
        return 0;

    return vm_file_fread(pBuffer, elementSize, count, m_pFile);
}

size_t File::Write(void *pBuffer, size_t size, size_t count)
{
    if(!m_pFile)
        return 0;

    return vm_file_fwrite(pBuffer, size, count, m_pFile);
}

int File::Print(const char *format, ...)
{
    int size;

    if(!m_pFile)
        return STS_ERR_NOT_INITIALIZED;

    va_list argptr;
    va_start(argptr, format);
    size = vm_string_vfprintf(m_pFile, format, argptr);
    va_end(argptr);

    return size;
}

Status File::Seek(size_t pos, Origin origin)
{
    if(!m_pFile)
        return STS_ERR_NOT_INITIALIZED;

    vm_file_fseek(m_pFile, pos, origin);

    return STS_OK;
}

unsigned long long File::GetLen()
{
    if(!m_pFile)
        return 0;

    unsigned long long pos = vm_file_ftell(m_pFile);
    vm_file_fseek(m_pFile, 0, File::ORIGIN_END);
    unsigned long long len = vm_file_ftell(m_pFile);
    vm_file_fseek(m_pFile, pos, File::ORIGIN_BEGIN);

    return len;
}

/*
// Class to safely share arbitrary buffer between several objects
*/
SharedObject::SharedObject()
{
    pInst = new ObjContainer();
    pInst->counter  = 1;
    pInst->pObj     = NULL;
}
SharedObject::~SharedObject()
{
    pInst->counter--;
    if(pInst->counter == 0)
        delete pInst;
    pInst = NULL;
}
SharedObject::SharedObject(const SharedObject& right)
{
    pInst = right.pInst;
    pInst->counter++;
}
SharedObject& SharedObject::operator = (const SharedObject& right)
{
    if(this == &right)
        return *this;

    right.pInst->counter++;
    pInst->counter--;
    if(pInst->counter == 0)
        delete pInst;
    pInst = right.pInst;
    return *this;
}

void SharedObject::attach(void* obj)
{
    if(pInst->counter > 1)
    {
        pInst->counter--;
        pInst = new ObjContainer();
        pInst->counter = 1;
        pInst->pObj    = NULL;
    }
    assert(pInst->pObj == NULL);
    pInst->pObj = obj;
}
void* SharedObject::deattach()
{
    if(pInst->counter > 1)
    {
        pInst->counter--;
        pInst = new ObjContainer();
        pInst->counter = 1;
        pInst->pObj    = NULL;
        return NULL;
    }
    else
    {
        void* tmp = pInst->pObj;
        pInst->pObj = NULL;
        return tmp;
    }
}

/*
// CMD parser
*/
namespace cmd
{
/*
 * Function to parse command line according to options specified in pOptions argument.
 * Returns number of successfully parsed options
 */
Status OptParse(int argc, char **argv, const OptDef pOptions[])
{
    OptDef       *pCurOption;
    unsigned int  parsed   = 0;
    unsigned int  shift    = 0;
    bool          dash     = false;
    bool          ddash    = false;
    bool          match    = false;
    bool          keyMatch = false;
    unsigned int  i, k;
    int           j;

    std::vector<OptDef> options;
    if(argc == 1)
        return STS_OK;

    /* Determine number of options in opt table */
    for(i = 0; ; i++)
    {
        if(pOptions[i].keyType == KT_UNKNOWN)
            break;

        if(pOptions[i].keyType == KT_BOOL)
        {
            if(pOptions[i].flags&KF_OPTIONAL) // booleans cannot be optional keys
                return STS_ERR_INVALID_PARAMS;
        }
        else if(!pOptions[i].arraySize)
            return STS_ERR_INVALID_PARAMS;

        options.push_back(pOptions[i]);
    }

    for(j = 1; j < argc; j++)
    {
        dash     = false;
        ddash    = false;
        match    = false;
        keyMatch = false;
        shift    = 0;

        if(argv[j][0] && argv[j][1] && argv[j][2] && argv[j][0] == '-' && argv[j][1] == '-')
        {
            ddash = true;
            shift = 2;
        }
        else if(argv[j][0] && argv[j][1] && argv[j][0] == '-')
        {
            dash  = true;
            shift = 1;

            int escape = false;
            while(shift && argv[j][shift]) // sequence
            {
                match = false;

                for(k = 0; k < options.size(); k++)
                {
                    pCurOption = &options[k];

                    if(argv[j][shift] == options[k].keyShort)
                    {
                        if(pCurOption->keyType == KT_BOOL)
                        {
                            if(pCurOption->flags & KF_WAS_PARSED)
                                printf("Repeated option: -%c\n", argv[j][shift]);

                            *((bool*)pCurOption->pValue) = true;
                            pCurOption->flags |= KF_WAS_PARSED;

                            match = true;
                        }
                        else
                            escape = true;

                        break;
                    }
                }
                if(escape)
                    break;

                if(!match)
                    printf("Unknown option: -%c\n", argv[j][shift]);

                shift++;
            }
        }

        for(i = 0; i < options.size(); i++)
        {
            pCurOption  = &options[i];

            if(dash && pCurOption->keyShort && argv[j][shift] == pCurOption->keyShort)
            {
                if(pCurOption->flags & KF_WAS_PARSED)
                    printf("Repeated option: -%c. Previous value is replaced.\n", argv[j][shift]);

                if(argv[j][shift+1] != 0) // no space between key and value
                    shift++;
                else
                    shift = 0;

                keyMatch = true;
            }
            else if(ddash && pCurOption->keyLong)
            {
                size_t keyLen = vm_string_strlen(pCurOption->keyLong, 255);
                if(!keyLen)
                    continue;

                keyMatch = true;
                shift    = 2;
                while(argv[j][shift] && argv[j][shift] != '=')
                {
                    if(pCurOption->keyLong[shift-2] != argv[j][shift] || keyLen <= shift-2)
                    {
                        keyMatch = false;
                        break;
                    }
                    shift++;
                }
                if(!keyMatch)
                    continue;

                if(pCurOption->flags & KF_WAS_PARSED)
                    printf("Repeated option: %s. Previous value is replaced.\n", argv[j]);

                if(argv[j][shift] == '=')
                    shift++;
                else
                    shift = 0;
            }
            else if(dash || ddash)
                continue;

            if(((dash || ddash) && keyMatch) || (!(dash || ddash) && (pCurOption->flags&KF_OPTIONAL)))
            {
                if(pCurOption->keyType == KT_BOOL)
                {
                    if(pCurOption->flags&KF_OPTIONAL)
                        return STS_ERR_INVALID_PARAMS;

                    *((bool*)pCurOption->pValue) = true;
                    pCurOption->flags |= KF_WAS_PARSED;
                }
                else
                {
                    if(!shift && (j + 1 >= argc))
                        return STS_ERR_INVALID_PARAMS;

                    for(k = 0; k < pCurOption->arraySize; k++)
                    {
                        if(!shift && (!((pCurOption->flags&KF_OPTIONAL) && !k) || keyMatch))
                        {
                            if(argv[j+1][0] == '-' && !vm_string_isdigit(argv[j+1][1])) // next argument, check if it is minus
                                break;
                            j++;
                        }

                        switch(pCurOption->keyType)
                        {
                        case KT_INTEGER:
                            ((int*)pCurOption->pValue)[k] = vm_string_atoi(&argv[j][shift]);
                            break;
                        case KT_POSITIVE:
                            if( ( ( (int*)pCurOption->pValue)[k] = vm_string_atoi(&argv[j][shift]) ) <= 0)
                                return STS_ERR_INVALID_PARAMS;
                            break;
                        case KT_DOUBLE:
                            ((double*)pCurOption->pValue)[k] = vm_string_atof(&argv[j][shift]);
                            break;
                        case KT_STRING:
                            ((char**)pCurOption->pValue)[k] = &argv[j][shift];
                            break;
                        case KT_DSTRING:
                            ((DString*)pCurOption->pValue)[k] = &argv[j][shift];
                            break;
                        default:
                            break;
                        }
                        parsed++;
                        shift = 0;

                        if(j + 1 >= argc)
                            break;
                    }
                }
                pCurOption->flags |= KF_WAS_PARSED;
                match = true;
                break;
            }
        }
        if(!match && ddash) // invalid long key
            printf("Unknown option: %s\n", argv[j]);
        else if(!match && dash) // invalid short key
            printf("Unknown option: -%c\n", argv[j][shift]);
    }

    return STS_OK;
}

/*
 * Function to print option table as usage message
 */
void OptUsage(const OptDef pOptions[])
{
    char          sKeyMsg[64];
    char          sArgMsg[64];
    char          sSpaces[64];
    const OptDef *pCurOption;
    unsigned int  iKeysSize   = 0;
    unsigned int  iArgsSize   = 0;
    unsigned int  iMaxKeys    = 0;
    unsigned int  iMaxArgs    = 0;
    unsigned int  iPass;
    unsigned int  i, k;

    DString  desc;
    char    *pPtr;

    for(iPass = 0; iPass < 2; iPass++)
    {
        for(i = 0; ; i++)
        {
            pCurOption = &pOptions[i];
            if(KT_UNKNOWN == pCurOption->keyType)
                break;

            if(pCurOption->keyShort && pCurOption->keyLong && pCurOption->keyLong[0])
                iKeysSize = vm_string_snprintf(sKeyMsg, 64, "  -%c --%s", pCurOption->keyShort, pCurOption->keyLong);
            else if(pCurOption->keyLong && pCurOption->keyLong[0])
                iKeysSize = vm_string_snprintf(sKeyMsg, 64, "     --%s", pCurOption->keyLong);
            else if(pCurOption->keyShort)
                iKeysSize = vm_string_snprintf(sKeyMsg, 64, "  -%c", pCurOption->keyShort);
            else
                return;

            if(pCurOption->keyType == KT_BOOL)
                iArgsSize = vm_string_snprintf(sArgMsg, 64, "");
            else
            {
                if(pCurOption->arraySize == 1)
                    iArgsSize = vm_string_snprintf(sArgMsg, 64, "<1 arg>");
                else
                    iArgsSize = vm_string_snprintf(sArgMsg, 64, "<%d args>", pCurOption->arraySize);
            }

            if(iPass == 0)
            {
                if(iKeysSize > iMaxKeys)
                    iMaxKeys = iKeysSize;
                if(iArgsSize > iMaxArgs)
                    iMaxArgs = iArgsSize;
            }
            else
            {
                if(iKeysSize < iMaxKeys)
                {
                    for(k = 0; k < iMaxKeys - iKeysSize; k++)
                        sKeyMsg[iKeysSize + k] = ' ';
                    sKeyMsg[iMaxKeys] = 0;
                }
                printf("%s ", sKeyMsg);

                if(iArgsSize < iMaxArgs)
                {
                    for(k = 0; k < iMaxArgs - iArgsSize; k++)
                        sArgMsg[iArgsSize + k] = ' ';
                    sArgMsg[iMaxArgs] = 0;
                }
                printf("%s ", sArgMsg);

                pPtr = (char*)vm_string_strchr(pCurOption->pDescription, '\n');
                if(pPtr)
                {
                    unsigned int iPos = 0;

                    for(k = 0; k < iMaxKeys+iMaxArgs; k++)
                        sSpaces[k] = ' ';
                    sSpaces[ iMaxKeys+iMaxArgs] = 0;

                    desc = pCurOption->pDescription;

                    do
                    {
                        pPtr = (char*)vm_string_strchr(desc.c_str() + iPos, '\n');
                        if(pPtr)
                        {
                            pPtr[0] = 0;

                            if(iPos == 0)
                                printf("%s\n", desc.c_str());
                            else
                                printf(" %s%s\n", sSpaces, desc.c_str() + iPos);

                            iPos = (unsigned int)(pPtr - desc.c_str()) + 1;
                        }
                        else
                            printf(" %s%s\n\n", sSpaces, desc.c_str() + iPos);
                    } while(pPtr);
                }
                else
                    printf("%s\n", pCurOption->pDescription);
            }
        }
    }
}
}

/*
// Default strings utils
*/
const char* GetBaseStatusString(Status status)
{
    int iTableSize = sizeof(StringOfBaseStatus)/sizeof(StringOfBaseStatus[0]);

    for (int i = 0; i < iTableSize; i++)
    {
        if (StringOfBaseStatus[i].iCode == status)
            return StringOfBaseStatus[i].pString;
    }
    return sStatusEmpty;
}

static const char *sTestDirs[] = {
    "./",                       // Local path

    // Win default debug
    "../../common/data/",               // Common path
    "../../../../common/data/",         // Interfaces path

    // Manual run
    "../../../../common/data/",         // Common path
    "../../../../../common/data/",      // Interfaces path
    0};

DString CheckTestDirs(DString sTestFile)
{
    DString sDir;
    DString sOut;
    int i = 0;

    while(sTestDirs[i])
    {
        sDir = sTestDirs[i];
        sOut = sDir + sTestFile;
        if(vm_file_access(sOut, 0) != -1)
            return sOut;
        i++;
    }

    return sTestFile;
}

char* GetProgName(char* argv[])
{
    char *pName = argv[0];
    for(size_t i = vm_string_strlen(argv[0], 0xFFFF) - 1; i > 0; i--)
    {
        if(argv[0][i] == '\\' || argv[0][i] == '/')
        {
            pName = &argv[0][i+1];
            break;
        }
    }
    return pName;
}
