/***************************************************************************
                          avidemutils.cpp  -  description
                             -------------------
    begin                : Sun Nov 11 2001
    copyright            : (C) 2001 by mean
    email                : fixounet@free.fr
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#include "ADM_cpp.h"
#include "ADM_default.h"
#include <math.h>

#ifdef _WIN32
#include <windows.h>
#endif

#include "avifmt.h"
#include "avifmt2.h"
#include "fourcc.h"

#include "ADM_bitmap.h"
#include "ADM_coreUtils.h"
#include "prefs.h"

uint8_t mk_hex(uint8_t a, uint8_t b);
void memcpyswap(uint8_t *dest, uint8_t *src, uint32_t size);

// misc dump functions
extern "C"
{
    void mixDump_c(uint8_t *ptr, uint32_t len)
    {
        mixDump(ptr, len);
    }
}
void mixDump(uint8_t *ptr, uint32_t len)
{
    char string[200];
    char string2[200];
    char tiny[10];

    char *str, *str2;
    uint32_t i;

    str = (char *)string;
    str2 = (char *)string2;
    *str = *str2 = 0;

    for (i = 0; i < len; i++)
    {
        if (*ptr < 32 || *ptr > 126)
        {
            strcat(str, ".");
        }
        else
        {
            sprintf(tiny, "%c", *ptr);
            strcat(str, tiny);
        }

        sprintf(tiny, " %02x", *ptr);
        strcat(str2, tiny);
        ptr++;

        if ((i % 16) == 15)
        {
            printf("\n %04" PRIx32 " : %s  %s", (i >> 4) << 4, str, str2);
            *str = 0;
            *str2 = 0;
        }
    }
    // left over
    uint32_t leftover = len % 16;
    if (leftover)
    {
        for (i = 0; i < 16 - leftover; i++)
        {
            strcat(str, " ");
        }
        printf("\n %04" PRIx32 " : %s  %s", (len >> 4) << 4, str, str2);
    }
    printf("\n");
}
/*
    A bunch of Endianness swapper to ease handling
    avi on BE processor (sparc/Mac)

*/
void Endian_AviStreamHeader(AVIStreamHeader *s)
{

#ifdef ADM_BIG_ENDIAN
#define SWAP32(x) s->x = R32(s->x);
    SWAP32(fccType);
    SWAP32(fccHandler);
    SWAP32(dwFlags);
    SWAP32(dwInitialFrames);
    SWAP32(dwScale);
    SWAP32(dwRate);
    SWAP32(dwStart);
    SWAP32(dwLength);
    SWAP32(dwSuggestedBufferSize);
    SWAP32(dwQuality);
    SWAP32(dwSampleSize);
#undef SWAP32

#endif
}
void Endian_AviMainHeader(MainAVIHeader *m)
{
#ifdef ADM_BIG_ENDIAN
#define SWAP32(x) m->x = R32(m->x)

    SWAP32(dwMicroSecPerFrame);
    SWAP32(dwMaxBytesPerSec);
    SWAP32(dwPaddingGranularity);

    SWAP32(dwFlags);
    SWAP32(dwTotalFrames);
    SWAP32(dwInitialFrames);
    SWAP32(dwStreams);
    SWAP32(dwSuggestedBufferSize);

    SWAP32(dwWidth);
    SWAP32(dwHeight);

#undef SWAP32

#endif
}

void Endian_BitMapInfo(ADM_BITMAPINFOHEADER *b)
{
#ifdef ADM_BIG_ENDIAN
#define SWAP32(x) b->x = R32(b->x)
#define SWAP16(x) b->x = R16(b->x)

    SWAP32(biSize);
    SWAP32(biWidth);
    SWAP32(biHeight);
    SWAP16(biPlanes);
    SWAP16(biBitCount);
    SWAP32(biCompression);
    SWAP32(biSizeImage);
    SWAP32(biXPelsPerMeter);
    SWAP32(biYPelsPerMeter);
    SWAP32(biClrUsed);
#undef SWAP32(x)
#undef SWAP16
#endif
}

void Endian_WavHeader(WAVHeader *w)
{
#ifdef ADM_BIG_ENDIAN
#define SWAP32(x) w->x = R32(w->x)
#define SWAP16(x) w->x = R16(w->x)
    SWAP16(encoding);
    SWAP16(channels);
    SWAP32(frequency);
    SWAP32(byterate);
    SWAP16(blockalign);
    SWAP16(bitspersample);

#undef SWAP32
#undef SWAP16

#endif
}
// Here we copy in reverse order
// Useful to do LE/BE conv on nomber
//
void memcpyswap(uint8_t *dest, uint8_t *src, uint32_t size)
{
    dest += size - 1;
    while (size--)
    {
        *dest = *src;
        dest--;
        src++;
    }
}
/**
    \fn ADM_findMpegStartCode
    \brief Compatibility wrapper
*/
bool ADM_findMpegStartCode(uint8_t *start, uint8_t *end, uint8_t *outstartcode, uint32_t *offset)
{
    bool zero = false;
    return ADM_findAnnexBStartCode(start, end, outstartcode, offset, &zero);
}
/**
    \fn     ADM_findAnnexBStartCode
    \brief  Find Annex B video stream startcode 00 00 01 xx yy
            and check whether it is preceded by zero byte,
            return xx + offset to payload (yy).
*/
bool ADM_findAnnexBStartCode(uint8_t *start, uint8_t *end, uint8_t *outstartcode, uint32_t *offset, bool *zero)
{
    uint32_t startcode = 0xffffffff;
    uint8_t *ptr = start;
    *zero = false;
    end--;

    while (ptr < end)
    {
        startcode = (startcode << 8) + *ptr;
        if (1 == (startcode & 0xffffff))
        {
            if (!(startcode >> 24))
                *zero = true;
            *outstartcode = ptr[1];
            *offset = ptr - start + 2;
            return true;
        }
        ptr++;
    }
    return false; // startcode not found
}

//**********************************************************
// Convert \ to \\
// Needed for win32 which uses \ to store filename+path
//**********************************************************
char *ADM_escape(const ADM_filename *incoming)
{
    char *out, *cur;
    size_t to_escape = 0;
    size_t l = 0;

    if (incoming)
        l = strlen((char *)incoming);
    if (!l)
    {
        printf("[ADM_escape] Null string ?\n");
        out = new char[1];
        out[0] = 0;
        return out;
    }

    for (int i = 0; i < l; i++)
        if (incoming[i] == '\\')
            to_escape++;
    out = new char[l + to_escape + 1];
    cur = out;
    for (int i = 0; i < l; i++)
    {
        *cur++ = incoming[i];
        if (incoming[i] == '\\')
            *cur++ = incoming[i];
    }
    *cur++ = 0;
    return out;
}
/**
    \fn ADM_computeBitrate
    \brief         Return average bitrate in bit/s
*/
uint32_t ADM_computeBitrate(uint32_t fps1000, uint32_t nbFrame, uint32_t sizeInMB)
{
    double db, ti;
    uint32_t vbr = 0;

    db = sizeInMB;
    db = db * 1024. * 1024. * 8.;
    // now deb is in Bits

    // compute duration
    ti = nbFrame;
    ti *= 1000;
    ti /= fps1000; // nb sec
    db = db / ti;

    vbr = (uint32_t)floor(db);
    return vbr;
}
/**
    \fn ADM_computeBitrate
    \brief         Return average bitrate in bit/s
*/
bool ADM_computeAverageBitrateFromDuration(uint64_t duration, uint32_t sizeInMB, uint32_t *avgInKbits)
{
    float f;
    if (duration == ADM_NO_PTS)
    {
        ADM_error("[ADM_computeBitrateFromDuration] No source duration!\n");
        return false;
    }
    if (!duration)
    {
        ADM_error("[ADM_computeBitrateFromDuration] No source duration!\n");
        return false;
    }
    f = sizeInMB;
    f = f * 1024 * 1024 * 8; // in bits
    f *= 1000 * 1000;
    f /= duration; // bit/s
    f /= 1000;     // in kbps
    *avgInKbits = (uint32_t)f;
    return true;
}
/**
        \fn ADM_UsecFromFps1000
*/
uint32_t ADM_UsecFromFps1000(uint32_t fps1000)
{
    double f;
    if (fps1000 > 1000000)
        fps1000 = 1000000; // safe default;
    if (!fps1000)
        fps1000 = 25000; // safe default;

    f = fps1000;
    f = 1 / f;
    f = f * 1000;    // In seconds
    f = f * 1000000; // In us;
    return (uint32_t)floor(f);
}
/**
    \fn ADM_Fps1000FromUs
    \brief time increment to fps1000
*/
uint32_t ADM_Fps1000FromUs(uint64_t us)
{
    if (us < 1000)
        return 1000;
    // Avoid accumulation of rounding errors for std fps
#define KNOW(x, y, z)                                                                                                  \
    if (us >= x && us <= y)                                                                                            \
        return z;
    KNOW(16666, 16667, 60000)
    KNOW(16683, 16684, 59940)
    KNOW(33333, 33334, 30000)
    KNOW(33366, 33367, 29970)
#undef KNOW
    double f;
    f = us;
    f = 1000000. / f;
    f *= 1000;
    return (uint32_t)(f + 0.5);
}
void printBih(ADM_BITMAPINFOHEADER *bi)
{
#undef X_DUMP
#define X_DUMP(x) printf(#x ":\t\t:%ld\n", (long int)bi->x);
    X_DUMP(biSize);
    X_DUMP(biWidth);
    X_DUMP(biHeight);
    X_DUMP(biBitCount);
    X_DUMP(biCompression);
    fourCC::print(bi->biCompression);
    printf("\n");
    X_DUMP(biSizeImage);
    X_DUMP(biXPelsPerMeter);
    X_DUMP(biYPelsPerMeter);
    X_DUMP(biClrUsed);
}

/* Compute aspect ration from common ARWidth / AR Height value */
typedef struct
{
    int width;
    int height;
    ADM_ASPECT ar;
    const char *string;
} ARDescriptor;

ARDescriptor allArs[] = {
    {8, 9, ADM_ASPECT_4_3, QT_TRANSLATE_NOOP("adm", "NTSC 4:3")},
    {32, 27, ADM_ASPECT_16_9, QT_TRANSLATE_NOOP("adm", "NTSC 16:9")},
    {128, 81, ADM_ASPECT_16_9, QT_TRANSLATE_NOOP("adm", "NTSC 16:9")},
    {16, 15, ADM_ASPECT_4_3, QT_TRANSLATE_NOOP("adm", "PAL 4:3")},
    {64, 45, ADM_ASPECT_16_9, QT_TRANSLATE_NOOP("adm", "PAL 16:9")},
    {1, 1, ADM_ASPECT_1_1, QT_TRANSLATE_NOOP("adm", "1:1")},
};
const char *unknown = QT_TRANSLATE_NOOP("adm", "Unknown");

ADM_ASPECT getAspectRatioFromAR(uint32_t width, uint32_t height, const char **string)
{
    int mx = sizeof(allArs) / sizeof(ARDescriptor);
    ARDescriptor *desc = allArs;
    for (int i = 0; i < mx; i++)
    {

        if (width == desc->width && height == desc->height)
        {
            *string = desc->string;
            return desc->ar;
        }
        desc++;
    }
    *string = unknown;
    return ADM_ASPECT_1_1;
}

// Get nice value for a priority level (0 - 4).
int32_t ADM_getNiceValue(uint32_t priorityLevel)
{
    switch (priorityLevel)
    {
    case 0: // High
        return -18;
        break;
    case 1: // Above Normal
        return -10;
        break;
    case 2: // Normal
        return 0;
        break;
    case 3: // Below Normal
        return 10;
        break;
    case 4: // Low
        return 18;
        break;
    }
    return 0;
}
uint8_t mk_hex(uint8_t a, uint8_t b)
{
    int a1 = a, b1 = b;

    if (a >= 'a')
    {
        a1 = a1 + 10;
        a1 = a1 - 'a';
    }
    else
        a1 = a1 - '0';

    if (b >= 'a')
    {
        b1 = b1 + 10;
        b1 = b1 - 'a';
    }
    else
        b1 = b1 - '0';

    return (a1 << 4) + b1;
}
/**
    \fn ADM_splitSequencedFile
    \brief split file foobar001.foo into foobar, 001 and 3
*/
bool ADM_splitSequencedFile(const char *filename, char **left, char **right, uint32_t *nbDigit, uint32_t *base)
{
    const char *dot = strrchr(filename, '.');
    *left = NULL;
    *right = NULL;

    // count the decimals before the dot
    int decimals = 1;
    while ((dot != NULL) && ((dot - decimals) != filename) && (dot[0 - decimals] >= '0') && (dot[0 - decimals] <= '9'))
    {
        decimals++;
    }
    decimals--;

    // Nuv files can have 20 decimals
    // Keep it down to 10000
    if (decimals > 4)
        decimals = 4;
    // split the filename in <left>, <number> and <right>
    // -----
    if (!decimals)
        return false;
    // <left> part

    *left = new char[(dot - filename - decimals) + 1];
    char *aleft = *left;
    strncpy(aleft, filename, (dot - filename - decimals));
    aleft[(dot - filename - decimals)] = '\0';

    // <number> part
    char *number = new char[decimals + 1];
    strncpy(number, (dot - decimals), decimals);
    number[decimals] = '\0';
    *base = atoi(number);
    *nbDigit = decimals;

    delete[] number;

    // <right> part
    *right = new char[strlen(dot) + 1];
    strcpy(*right, dot);
    return true;
}
/**
 *  \fn ADM_probeSequencedFile
 *  \brief Check whether there are several sequentially named files with sizes matching the pattern.
 *  \param fileName Path to the first file.
 *  \param[in] fragmentSize Control file size pattern to match
 *      0: disable probing alltogether
 *      1: use defaults, either 256*2^n MiB up to 4 GiB or a custom value from preferences
 *      negative values: skip size check
 *      else fragment size in MiB to match
 *  \param[out] fragmentSize Size of the first file in MiB if several files are found, 0 if only one
 *      do not use when size check was skipped
 *  \return Number of files or a negative value on error.
 */
int ADM_probeSequencedFile(const char *fileName, int *fragmentSize)
{
    if (!fragmentSize || !*fragmentSize)
        return 0;

    char *left = NULL;
    char *right = NULL;
    uint32_t nbDigit, base;
    if (false == ADM_splitSequencedFile(fileName, &left, &right, &nbDigit, &base))
    {
        *fragmentSize = 0;
        return 0;
    }
    // can we open the file?
    int64_t sz = ADM_fileSize(fileName);
    if (sz < 0) // nope
    {
        delete[] left;
        delete[] right;
        return sz;
    }

    bool skipSizeCheck = *fragmentSize < 0;
    // check whether the filesize approx. matches 2^n GiB, the usual
    // threshold for automatically split streams
    uint32_t count = 0;
    int rounds = 5;
    uint64_t fileSize, threshold, tolerance;

    if (!skipSizeCheck)
    {
        bool useCustomSize;
        if (false == prefs->get(DEFAULT_MULTILOAD_USE_CUSTOM_SIZE, &useCustomSize))
            useCustomSize = false;
        uint32_t customSize;
        if (false == prefs->get(DEFAULT_MULTILOAD_CUSTOM_SIZE_M, &customSize))
            customSize = 0;

        tolerance = 1 << 20;    // 1 MiB
        if (*fragmentSize == 1) // equivalent to FP_PROBE
        {
            if (useCustomSize && customSize)
                *fragmentSize = customSize;
        }
        if (*fragmentSize == 1) // try out default values
        {
            threshold = 1 << 28; // we start at 256 MiB, this value is hardcoded in some devices
        }
        else
        {
            threshold = ((uint64_t)*fragmentSize) << 20;
            if (*fragmentSize > 999)
                tolerance <<= 3; // 8 MiB from ~0.98 GiB on
            rounds = 1;
        }
        *fragmentSize = 0;

        fileSize = sz;
        for (int i = 0; i < rounds; i++)
        {
            if (!i && fileSize < threshold - tolerance)
                break;
            if (fileSize >= threshold - tolerance && fileSize <= threshold + tolerance)
            {
                count = 1;
                *fragmentSize = threshold >> 20;
                break;
            }
            threshold <<= 1;
            if (i == 1)
                tolerance <<= 3; // 8 MiB starting with 1 GiB fragment size
        }
        if (!count)
        {
            delete[] left;
            delete[] right;
            return 0;
        }
    }
    else
    {
        count = 1; // the first sequence to check
    }

    // check if at least one sequence exists...
    std::string aLeft(left);
    std::string aRight(right);
    delete[] left;
    delete[] right;

    char match[16];
    match[0] = '%';
    match[1] = '0';
    sprintf(match + 2, "%d", nbDigit); // snprintf instead ...
    strcat(match, "d");
    match[15] = 0;

    while (true)
    {
        char names[16];
        sprintf(names, match, base + count);
        std::string middle(names);
        std::string target = aLeft + middle + aRight;
        sz = ADM_fileSize(target.c_str());
        if (sz < 0) // no such file
        {
            count--;
            break;
        }
        if (skipSizeCheck)
        {
            count++;
            continue;
        }
        if (sz > threshold + tolerance) // the next set of files, reject it
        {
            count--;
            break;
        }
        if (sz < threshold - tolerance) // the last one
            break;
        count++;
    }
    return count;
}
// EOF
