/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsVersionComparator.h"

#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#if defined(_WIN32) && !defined(UPDATER_NO_STRING_GLUE_STL)
#include <wchar.h>
#include "Char16.h"
#endif

#ifdef _WIN32
// from Mozilla's nsAlgorithm.h
template <class T>
inline const T&
XPCOM_MIN(const T& aA, const T& aB)
{
    return aB < aA ? aB : aA;
}
#endif

struct VersionPart
{
    int32_t     numA;

    const char* strB;    // NOT null-terminated, can be a null pointer
    uint32_t    strBlen;

    int32_t     numC;

    char*       extraD;  // null-terminated
};

#ifdef _WIN32
struct VersionPartW
{
    int32_t     numA;

    wchar_t*    strB;    // NOT null-terminated, can be a null pointer
    uint32_t    strBlen;

    int32_t     numC;

    wchar_t*    extraD;  // null-terminated

};
#endif

/**
 * Parse a version part into a number and "extra text".
 *
 * @returns A pointer to the next versionpart, or null if none.
 */
static char*
ParseVP(char* aPart, VersionPart& aResult)
{
    char* dot;

    aResult.numA = 0;
    aResult.strB = nullptr;
    aResult.strBlen = 0;
    aResult.numC = 0;
    aResult.extraD = nullptr;

    if (!aPart)
    {
        return aPart;
    }

    dot = strchr(aPart, '.');
    if (dot)
    {
        *dot = '\0';
    }

    if (aPart[0] == '*' && aPart[1] == '\0')
    {
        aResult.numA = INT32_MAX;
        aResult.strB = "";
    }
    else
    {
        aResult.numA = strtol(aPart, const_cast<char**>(&aResult.strB), 10);
    }

    if (!*aResult.strB)
    {
        aResult.strB = nullptr;
        aResult.strBlen = 0;
    }
    else
    {
        if (aResult.strB[0] == '+')
        {
            static const char kPre[] = "pre";

            ++aResult.numA;
            aResult.strB = kPre;
            aResult.strBlen = sizeof(kPre) - 1;
        }
        else
        {
            const char* numstart = strpbrk(aResult.strB, "0123456789+-");
            if (!numstart)
            {
                aResult.strBlen = strlen(aResult.strB);
            }
            else
            {
                aResult.strBlen = numstart - aResult.strB;

                aResult.numC = strtol(numstart, &aResult.extraD, 10);
                if (!*aResult.extraD)
                {
                    aResult.extraD = nullptr;
                }
            }
        }
    }

    if (dot)
    {
        ++dot;

        if (!*dot)
        {
            dot = nullptr;
        }
    }

    return dot;
}


/**
 * Parse a version part into a number and "extra text".
 *
 * @returns A pointer to the next versionpart, or null if none.
 */
#ifdef _WIN32
static wchar_t*
ParseVP(wchar_t* aPart, VersionPartW& aResult)
{

    wchar_t* dot;

    aResult.numA = 0;
    aResult.strB = nullptr;
    aResult.strBlen = 0;
    aResult.numC = 0;
    aResult.extraD = nullptr;

    if (!aPart)
    {
        return aPart;
    }

    dot = wcschr(aPart, '.');
    if (dot)
    {
        *dot = '\0';
    }

    if (aPart[0] == '*' && aPart[1] == '\0')
    {
        aResult.numA = INT32_MAX;
        aResult.strB = L"";
    }
    else
    {
        aResult.numA = wcstol(aPart, const_cast<wchar_t**>(&aResult.strB), 10);
    }

    if (!*aResult.strB)
    {
        aResult.strB = nullptr;
        aResult.strBlen = 0;
    }
    else
    {
        if (aResult.strB[0] == '+')
        {
            static wchar_t kPre[] = L"pre";

            ++aResult.numA;
            aResult.strB = kPre;
            aResult.strBlen = sizeof(kPre) - 1;
        }
        else
        {
            const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-");
            if (!numstart)
            {
                aResult.strBlen = wcslen(aResult.strB);
            }
            else
            {
                aResult.strBlen = numstart - aResult.strB;

                aResult.numC = wcstol(numstart, &aResult.extraD, 10);
                if (!*aResult.extraD)
                {
                    aResult.extraD = nullptr;
                }
            }
        }
    }

    if (dot)
    {
        ++dot;

        if (!*dot)
        {
            dot = nullptr;
        }
    }

    return dot;
}
#endif

// compare two null-terminated strings, which may be null pointers
static int32_t
ns_strcmp(const char* aStr1, const char* aStr2)
{
    // any string is *before* no string
    if (!aStr1)
    {
        return aStr2 != 0;
    }

    if (!aStr2)
    {
        return -1;
    }

    return strcmp(aStr1, aStr2);
}

// compare two length-specified string, which may be null pointers
static int32_t
ns_strnncmp(const char* aStr1, uint32_t aLen1,
            const char* aStr2, uint32_t aLen2)
{
    // any string is *before* no string
    if (!aStr1)
    {
        return aStr2 != 0;
    }

    if (!aStr2)
    {
        return -1;
    }

    for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2)
    {
        if (*aStr1 < *aStr2)
        {
            return -1;
        }

        if (*aStr1 > *aStr2)
        {
            return 1;
        }
    }

    if (aLen1 == 0)
    {
        return aLen2 == 0 ? 0 : -1;
    }

    return 1;
}

// compare two int32_t
static int32_t
ns_cmp(int32_t aNum1, int32_t aNum2)
{
    if (aNum1 < aNum2)
    {
        return -1;
    }

    return aNum1 != aNum2;
}

/**
 * Compares two VersionParts
 */
static int32_t
CompareVP(VersionPart& aVer1, VersionPart& aVer2)
{
    int32_t r = ns_cmp(aVer1.numA, aVer2.numA);
    if (r)
    {
        return r;
    }

    r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen);
    if (r)
    {
        return r;
    }

    r = ns_cmp(aVer1.numC, aVer2.numC);
    if (r)
    {
        return r;
    }

    return ns_strcmp(aVer1.extraD, aVer2.extraD);
}

/**
 * Compares two VersionParts
 */
#ifdef _WIN32
static int32_t
CompareVP(VersionPartW& aVer1, VersionPartW& aVer2)
{
    int32_t r = ns_cmp(aVer1.numA, aVer2.numA);
    if (r)
    {
        return r;
    }

    r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen));
    if (r)
    {
        return r;
    }

    r = ns_cmp(aVer1.numC, aVer2.numC);
    if (r)
    {
        return r;
    }

    if (!aVer1.extraD)
    {
        return aVer2.extraD != 0;
    }

    if (!aVer2.extraD)
    {
        return -1;
    }

    return wcscmp(aVer1.extraD, aVer2.extraD);
}
#endif

namespace mozilla {

#ifdef _WIN32
int32_t
CompareVersions(const wchar_t* aStrA, const wchar_t* aStrB)
{
    wchar_t* A2 = wcsdup(aStrA);
    if (!A2)
    {
        return 1;
    }

    wchar_t* B2 = wcsdup(aStrB);
    if (!B2)
    {
        free(A2);
        return 1;
    }

    int32_t result;
    wchar_t* a = A2;
    wchar_t* b = B2;

    do
    {
        VersionPartW va, vb;

        a = ParseVP(a, va);
        b = ParseVP(b, vb);

        result = CompareVP(va, vb);
        if (result)
        {
            break;
        }

    }
    while (a || b);

    free(A2);
    free(B2);

    return result;
}
#endif

int32_t
CompareVersions(const char* aStrA, const char* aStrB)
{
    char* A2 = strdup(aStrA);
    if (!A2)
    {
        return 1;
    }

    char* B2 = strdup(aStrB);
    if (!B2)
    {
        free(A2);
        return 1;
    }

    int32_t result;
    char* a = A2;
    char* b = B2;

    do
    {
        VersionPart va, vb;

        a = ParseVP(a, va);
        b = ParseVP(b, vb);

        result = CompareVP(va, vb);
        if (result)
        {
            break;
        }

    }
    while (a || b);

    free(A2);
    free(B2);

    return result;
}

} // namespace mozilla

