/******************************************************************************
 *
 * Purpose:  Implementation of MSGCommand class. Parse the src_dataset
 *           string that is meant for the MSG driver.
 * Author:   Bas Retsios, retsios@itc.nl
 *
 ******************************************************************************
 * Copyright (c) 2004, ITC
 *
 * SPDX-License-Identifier: MIT
 ******************************************************************************/

#include "cpl_port.h"  // Must be first.

#include "msgcommand.h"
#include <cstdlib>
#include <cstdio>

#include <algorithm>

using namespace std;

#ifdef _WIN32
#define PATH_SEP '\\'
#else
#define PATH_SEP '/'
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

MSGCommand::MSGCommand()
    : cDataConversion('N'), iNrCycles(1), sRootFolder(""), sTimeStamp(""),
      iStep(1), fUseTimestampFolder(true)
{
    for (int i = 0; i < 12; ++i)
        channel[i] = 0;
}

std::string MSGCommand::sTrimSpaces(std::string const &str)
{
    std::string::size_type iStart = 0;

    while ((iStart < str.length()) && (str[iStart] == ' '))
        ++iStart;

    std::string::size_type iLength = str.length() - iStart;

    while ((iLength > 0) && (str[iStart + iLength - 1] == ' '))
        --iLength;

    return str.substr(iStart, iLength);
}

std::string MSGCommand::sNextTerm(std::string const &str, int &iPos)
{
    std::string::size_type iOldPos = iPos;
    iPos = static_cast<int>(str.find(',', iOldPos));
    // FIXME: the int vs size_t is messy !
    iPos = static_cast<int>(
        std::min(static_cast<size_t>(iPos), str.find(')', iOldPos)));
    if (iPos >= 0 && static_cast<size_t>(iPos) > iOldPos)
    {
        std::string sRet = str.substr(iOldPos, iPos - iOldPos);
        if (str[iPos] != ')')
            ++iPos;
        return sTrimSpaces(sRet);
    }
    else
        return "";
}

static bool fTimeStampCorrect(std::string const &sTimeStamp)
{
    if (sTimeStamp.length() != 12)
        return false;

    for (int i = 0; i < 12; ++i)
    {
        if (sTimeStamp[i] < '0' || sTimeStamp[i] > '9')
            return false;
    }

    return true;
}

std::string MSGCommand::parse(std::string const &command_line)
{
    // expected:
    // MSG(folder,timestamp,channel,in_same_folder,data_conversion,nr_cycles,step)
    // or
    // MSG(folder,timestamp,(channel,channel,...,channel),in_same_folder,data_conversion,nr_cycles,step)
    // or
    // <path>\H-000-MSG1__-MSG1________.....

    std::string sErr("");

    std::string sID = command_line.substr(0, 4);
    if (sID.compare("MSG(") == 0)
    {
        int iPos = 4;  // after bracket open
        sRootFolder = sNextTerm(command_line, iPos);
        if (sRootFolder.length() > 0)
        {
            if (sRootFolder[sRootFolder.length() - 1] != PATH_SEP)
                sRootFolder += PATH_SEP;
            sTimeStamp = sNextTerm(command_line, iPos);
            if (fTimeStampCorrect(sTimeStamp))
            {
                try  // for eventual exceptions
                {
                    while ((iPos < static_cast<int>(command_line.length())) &&
                           (command_line[iPos] == ' '))
                        ++iPos;
                    if (iPos < static_cast<int>(command_line.length()) &&
                        command_line[iPos] == '(')
                    {
                        ++iPos;  // skip the ( bracket
                        int i = 1;
                        std::string l_sChannel = sNextTerm(command_line, iPos);
                        while (command_line[iPos] != ')')
                        {
                            int iChan = atoi(l_sChannel.c_str());
                            if (iChan >= 1 && iChan <= 12)
                                channel[iChan - 1] = i;
                            else
                                sErr =
                                    "Channel numbers must be between 1 and 12";
                            l_sChannel = sNextTerm(command_line, iPos);
                            ++i;
                        }
                        int iChan = atoi(l_sChannel.c_str());
                        if (iChan >= 1 && iChan <= 12)
                            channel[iChan - 1] = i;
                        else
                            sErr = "Channel numbers must be between 1 and 12";
                        ++iPos;  // skip the ) bracket
                        while (
                            (iPos < static_cast<int>(command_line.length())) &&
                            (command_line[iPos] == ' '))
                            ++iPos;
                        if (iPos < static_cast<int>(command_line.length()) &&
                            command_line[iPos] == ',')
                            ++iPos;
                    }
                    else
                    {
                        std::string l_sChannel = sNextTerm(command_line, iPos);
                        int iChan = atoi(l_sChannel.c_str());
                        if (iChan >= 1 && iChan <= 12)
                            channel[iChan - 1] = 1;
                        else
                            sErr = "Channel numbers must be between 1 and 12";
                    }
                    std::string sInRootFolder = sNextTerm(command_line, iPos);
                    if ((sInRootFolder.compare("N") != 0) &&
                        (sInRootFolder.compare("Y") != 0))
                        sErr = "Please specify N for data that is in a date "
                               "dependent folder or Y for data that is in "
                               "specified folder.";
                    else
                        fUseTimestampFolder = (sInRootFolder.compare("N") == 0);
                    std::string sDataConversion = sNextTerm(command_line, iPos);
                    cDataConversion = (sDataConversion.length() > 0)
                                          ? sDataConversion[0]
                                          : 'N';
                    std::string sNrCycles = sNextTerm(command_line, iPos);
                    iNrCycles = atoi(sNrCycles.c_str());
                    if (iNrCycles < 1)
                        iNrCycles = 1;
                    std::string sStep = sNextTerm(command_line, iPos);
                    iStep = atoi(sStep.c_str());
                    if (iStep < 1)
                        iStep = 1;
                    while ((iPos < static_cast<int>(command_line.length())) &&
                           (command_line[iPos] == ' '))
                        ++iPos;
                    // additional correctness checks
                    if (iPos < static_cast<int>(command_line.length()) &&
                        command_line[iPos] != ')')
                        sErr = "Invalid syntax. Please review the MSG(...) "
                               "statement.";
                    else if ((iNrChannels() > 1) && (channel[11] != 0))
                        sErr = "It is not possible to combine channel 12 (HRV) "
                               "with the other channels.";
                    else if (iNrChannels() == 0 && sErr.length() == 0)
                        sErr = "At least one channel should be specified.";
                    else if ((cDataConversion != 'N') &&
                             (cDataConversion != 'B') &&
                             (cDataConversion != 'R') &&
                             (cDataConversion != 'L') &&
                             (cDataConversion != 'T'))
                        sErr = "Please specify N(o change), B(yte conversion), "
                               "R(adiometric calibration), L(radiometric using "
                               "central wavelength) or T(reflectance or "
                               "temperature) for data conversion.";
                }
                catch (...)
                {
                    sErr =
                        "Invalid syntax. Please review the MSG(...) statement.";
                }
            }
            else
                sErr = "Timestamp should be exactly 12 digits.";
        }
        else
            sErr = "A folder must be filled in indicating the root of the "
                   "image data folders.";
    }
    else if (command_line.find("H-000-MSG") != std::string::npos)
    {
        const size_t iPos = command_line.find("H-000-MSG");
        if ((command_line.length() - iPos) == 61)
        {
            fUseTimestampFolder = false;
            sRootFolder = command_line.substr(0, iPos);
            sTimeStamp = command_line.substr(iPos + 46, 12);
            if (fTimeStampCorrect(sTimeStamp))
            {
                int iChan = iChannel(command_line.substr(iPos + 26, 9));
                if (iChan >= 1 && iChan <= 12)
                {
                    channel[iChan - 1] = 1;
                    cDataConversion = 'N';
                    iNrCycles = 1;
                    iStep = 1;
                }
                else
                    sErr = "Channel numbers must be between 1 and 12";
            }
            else
                sErr = "Timestamp should be exactly 12 digits.";
        }
        else
            sErr = "-";  // the source data set it is not for the MSG driver
    }
    else
        sErr = "-";  // the source data set it is not for the MSG driver
    return sErr;
}

int MSGCommand::iNrChannels() const
{
    int iRet = 0;
    for (int i = 0; i < 12; ++i)
        if (channel[i] != 0)
            ++iRet;

    return iRet;
}

int MSGCommand::iChannel(int iChannelNumber) const
{
    // return the iChannelNumber-th channel
    // iChannelNumber is a value between 1 and 12
    // note that channels are ordered. their order number is the value in the
    // array As we can't combine channel 12 with channels 1 to 11, it does not
    // make sense to inquire for iNr == 12
    int iRet = 0;
    if (iChannelNumber <= iNrChannels())
    {
        while ((iRet < 12) && (channel[iRet] != iChannelNumber))
            ++iRet;
    }

    // will return a number between 1 and 12
    return iRet + 1;
}

int MSGCommand::iNrStrips(int iChannel)
{
    if (iChannel == 12)
        return 24;
    else if (iChannel >= 1 && iChannel <= 11)
        return 8;
    else
        return 0;
}

int MSGCommand::iChannel(std::string const &sChannel)
{
    if (sChannel.compare("VIS006___") == 0)
        return 1;
    else if (sChannel.compare("VIS008___") == 0)
        return 2;
    else if (sChannel.compare("IR_016___") == 0)
        return 3;
    else if (sChannel.compare("IR_039___") == 0)
        return 4;
    else if (sChannel.compare("WV_062___") == 0)
        return 5;
    else if (sChannel.compare("WV_073___") == 0)
        return 6;
    else if (sChannel.compare("IR_087___") == 0)
        return 7;
    else if (sChannel.compare("IR_097___") == 0)
        return 8;
    else if (sChannel.compare("IR_108___") == 0)
        return 9;
    else if (sChannel.compare("IR_120___") == 0)
        return 10;
    else if (sChannel.compare("IR_134___") == 0)
        return 11;
    else if (sChannel.compare("HRV______") == 0)
        return 12;
    else
        return 0;
}

std::string MSGCommand::sChannel(int iChannel)
{
    switch (iChannel)
    {
        case 1:
            return "VIS006___";
            break;
        case 2:
            return "VIS008___";
            break;
        case 3:
            return "IR_016___";
            break;
        case 4:
            return "IR_039___";
            break;
        case 5:
            return "WV_062___";
            break;
        case 6:
            return "WV_073___";
            break;
        case 7:
            return "IR_087___";
            break;
        case 8:
            return "IR_097___";
            break;
        case 9:
            return "IR_108___";
            break;
        case 10:
            return "IR_120___";
            break;
        case 11:
            return "IR_134___";
            break;
        case 12:
            return "HRV______";
            break;
        default:
            return "_________";
            break;
    }
}

std::string MSGCommand::sTimeStampToFolder(const std::string &sTimeStamp)
{
    std::string sYear(sTimeStamp.substr(0, 4));
    std::string sMonth(sTimeStamp.substr(4, 2));
    std::string sDay(sTimeStamp.substr(6, 2));
    return sYear + PATH_SEP + sMonth + PATH_SEP + sDay + PATH_SEP;
}

int MSGCommand::iDaysInMonth(int iMonth, int iYear)
{
    int iDays;

    if ((iMonth == 4) || (iMonth == 6) || (iMonth == 9) || (iMonth == 11))
        iDays = 30;
    else if (iMonth == 2)
    {
        iDays = 28;
        if (iYear % 100 == 0)  // century year
        {
            if (iYear % 400 == 0)  // century leap year
                ++iDays;
        }
        else
        {
            if (iYear % 4 == 0)  // normal leap year
                ++iDays;
        }
    }
    else
        iDays = 31;

    return iDays;
}

std::string MSGCommand::sCycle(int iCycle)
{
    // find nth full quarter
    // e.g. for n = 1, 200405311114 should result in 200405311115
    // 200405311115 should result in 200405311130
    // 200405311101 should result in 200405311115
    // 200412312345 should result in 200501010000

    std::string sYear(sTimeStamp.substr(0, 4));
    std::string sMonth(sTimeStamp.substr(4, 2));
    std::string sDay(sTimeStamp.substr(6, 2));
    std::string sHours(sTimeStamp.substr(8, 2));
    std::string sMins(sTimeStamp.substr(10, 2));

    int iYear = atoi(sYear.c_str());
    int iMonth = atoi(sMonth.c_str());
    int iDay = atoi(sDay.c_str());
    int iHours = atoi(sHours.c_str());
    int iMins = atoi(sMins.c_str());

    iMins += (iCycle - 1) * 15 * iStep;

    // round off the mins found down to a multiple of 15 mins
    iMins = ((int)(iMins / 15)) * 15;
    // now handle the whole chain back to the year ...
    while (iMins >= 60)
    {
        iMins -= 60;
        ++iHours;
    }
    while (iHours >= 24)
    {
        iHours -= 24;
        ++iDay;
    }
    while (iDay > iDaysInMonth(iMonth, iYear))
    {
        iDay -= iDaysInMonth(iMonth, iYear);
        ++iMonth;
    }
    while (iMonth > 12)
    {
        iMonth -= 12;
        ++iYear;
    }

    char sRet[100];
    snprintf(sRet, sizeof(sRet), "%.4d%.2d%.2d%.2d%.2d", iYear, iMonth, iDay,
             iHours, iMins);

    return sRet;
}

std::string MSGCommand::sFileName(int iSatellite, int iSequence, int iStrip)
{
    int iNr = iNrChannels();
    int iChannelNumber = 1 + (iSequence - 1) % iNr;
    int iCycle = 1 + (iSequence - 1) / iNr;
    char sRet[4096];
    std::string siThCycle(sCycle(iCycle));
    if (fUseTimestampFolder)
        snprintf(sRet, sizeof(sRet),
                 "%s%sH-000-MSG%d__-MSG%d________-%s-%.6d___-%s-C_",
                 sRootFolder.c_str(), sTimeStampToFolder(siThCycle).c_str(),
                 iSatellite, iSatellite,
                 sChannel(iChannel(iChannelNumber)).c_str(), iStrip,
                 siThCycle.c_str());
    else
        snprintf(sRet, sizeof(sRet),
                 "%sH-000-MSG%d__-MSG%d________-%s-%.6d___-%s-C_",
                 sRootFolder.c_str(), iSatellite, iSatellite,
                 sChannel(iChannel(iChannelNumber)).c_str(), iStrip,
                 siThCycle.c_str());
    return sRet;
}

std::string MSGCommand::sPrologueFileName(int iSatellite, int iSequence)
{
    int iCycle = 1 + (iSequence - 1) / iNrChannels();
    char sRet[4096];
    std::string siThCycle(sCycle(iCycle));
    if (fUseTimestampFolder)
        snprintf(sRet, sizeof(sRet),
                 "%s%sH-000-MSG%d__-MSG%d________-_________-PRO______-%s-__",
                 sRootFolder.c_str(), sTimeStampToFolder(siThCycle).c_str(),
                 iSatellite, iSatellite, siThCycle.c_str());
    else
        snprintf(sRet, sizeof(sRet),
                 "%sH-000-MSG%d__-MSG%d________-_________-PRO______-%s-__",
                 sRootFolder.c_str(), iSatellite, iSatellite,
                 siThCycle.c_str());
    return sRet;
}
