/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
   file Copyright.txt or https://cmake.org/licensing for details.  */
#include "cmTimestamp.h"

#include <cstring>
#include <sstream>
#include <stdlib.h>

#include "cmSystemTools.h"

std::string
cmTimestamp::CurrentTime(const std::string& formatString, bool utcFlag)
{
    time_t      currentTimeT = time(nullptr);
    std::string source_date_epoch;
    cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch);
    if(!source_date_epoch.empty())
    {
        std::istringstream iss(source_date_epoch);
        iss >> currentTimeT;
        if(iss.fail() || !iss.eof())
        {
            cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer");
            exit(27);
        }
    }
    if(currentTimeT == time_t(-1))
    {
        return std::string();
    }

    return CreateTimestampFromTimeT(currentTimeT, formatString, utcFlag);
}

std::string
cmTimestamp::FileModificationTime(const char*        path,
                                  const std::string& formatString, bool utcFlag)
{
    std::string real_path =
        cmSystemTools::GetRealPathResolvingWindowsSubst(path);

    if(!cmsys::SystemTools::FileExists(real_path))
    {
        return std::string();
    }

    time_t mtime = cmsys::SystemTools::ModifiedTime(real_path);
    return CreateTimestampFromTimeT(mtime, formatString, utcFlag);
}

std::string
cmTimestamp::CreateTimestampFromTimeT(time_t timeT, std::string formatString,
                                      bool utcFlag) const
{
    if(formatString.empty())
    {
        formatString = "%Y-%m-%dT%H:%M:%S";
        if(utcFlag)
        {
            formatString += "Z";
        }
    }

    struct tm timeStruct;
    memset(&timeStruct, 0, sizeof(timeStruct));

    struct tm* ptr = nullptr;
    if(utcFlag)
    {
        ptr = gmtime(&timeT);
    } else
    {
        ptr = localtime(&timeT);
    }

    if(ptr == nullptr)
    {
        return std::string();
    }

    timeStruct = *ptr;

    std::string result;
    for(std::string::size_type i = 0; i < formatString.size(); ++i)
    {
        char c1 = formatString[i];
        char c2 = (i + 1 < formatString.size()) ? formatString[i + 1]
                                                : static_cast<char>(0);

        if(c1 == '%' && c2 != 0)
        {
            result += AddTimestampComponent(c2, timeStruct, timeT);
            ++i;
        } else
        {
            result += c1;
        }
    }

    return result;
}

time_t
cmTimestamp::CreateUtcTimeTFromTm(struct tm& tm) const
{
#if defined(_MSC_VER) && _MSC_VER >= 1400
    return _mkgmtime(&tm);
#else
    // From Linux timegm() manpage.

    std::string tz_old;
    bool const  tz_was_set = cmSystemTools::GetEnv("TZ", tz_old);
    tz_old                 = "TZ=" + tz_old;

    // The standard says that "TZ=" or "TZ=[UNRECOGNIZED_TZ]" means UTC.
    // It seems that "TZ=" does NOT work, at least under Windows
    // with neither MSVC nor MinGW, so let's use explicit "TZ=UTC"

    cmSystemTools::PutEnv("TZ=UTC");

    tzset();

    time_t result = mktime(&tm);

#    ifdef CMAKE_BUILD_WITH_CMAKE
    if(tz_was_set)
    {
        cmSystemTools::PutEnv(tz_old);
    } else
    {
        cmSystemTools::UnsetEnv("TZ");
    }
#    else
    // No UnsetEnv during bootstrap.  This is good enough for CMake itself.
    cmSystemTools::PutEnv(tz_old);
    static_cast<void>(tz_was_set);
#    endif

    tzset();

    return result;
#endif
}

std::string
cmTimestamp::AddTimestampComponent(char flag, struct tm& timeStruct,
                                   const time_t timeT) const
{
    std::string formatString = "%";
    formatString += flag;

    switch(flag)
    {
        case 'a':
        case 'A':
        case 'b':
        case 'B':
        case 'd':
        case 'H':
        case 'I':
        case 'j':
        case 'm':
        case 'M':
        case 'S':
        case 'U':
        case 'w':
        case 'y':
        case 'Y':
        case '%':
            break;
        case 's':  // Seconds since UNIX epoch (midnight 1-jan-1970)
        {
            // Build a time_t for UNIX epoch and substract from the input
            // "timeT":
            struct tm tmUnixEpoch;
            memset(&tmUnixEpoch, 0, sizeof(tmUnixEpoch));
            tmUnixEpoch.tm_mday = 1;
            tmUnixEpoch.tm_year = 1970 - 1900;

            const time_t unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch);
            if(unixEpoch == -1)
            {
                cmSystemTools::Error("Error generating UNIX epoch in "
                                     "STRING(TIMESTAMP ...). Please, file a "
                                     "bug report against CMake");
                return std::string();
            }

            std::ostringstream ss;
            ss << static_cast<long int>(difftime(timeT, unixEpoch));
            return ss.str();
        }
        default:
        {
            return formatString;
        }
    }

    char buffer[16];

    size_t size =
        strftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct);

    return std::string(buffer, size);
}
