/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include "ecflow/core/LogVerification.hpp"

#include <sstream>

#include "ecflow/core/File.hpp"
#include "ecflow/core/NState.hpp"
#include "ecflow/core/Str.hpp"

using namespace std;

namespace ecf {

bool LogVerification::extractNodePathAndState(const std::string& logfile,
                                              std::vector<std::pair<std::string, std::string>>& pathStateVec,
                                              std::string& errorMsg) {
    // The log file format we are interested is :
    //       0          1               2                 3
    // LOG:[14:37:00 20.5.2010]    submitted: /test_abort_cmd/family0/abort duration_(00:00:00) initTime_(2010-May-20
    // 14:37:00) suiteTime_(2010-May-20 14:37:00)

    // Open log file, and collate of the node paths and corresponding states
    std::vector<std::string> lines;
    if (!File::splitFileIntoLines(logfile, lines)) {
        errorMsg = "Could not open log file " + logfile + " for test verification (" + strerror(errno) + ")";
        return false;
    }

    // int line_number = 0;
    for (auto& line : lines) {

        // line_number++;
        if (line.find("LOG:") == std::string::npos) {
            continue; // State changes have type Log::LOG
        }

        std::vector<std::string> lineTokens;
        Str::split(line, lineTokens);
        if (lineTokens.size() < 3) {
            continue;
        }

        std::string theState = lineTokens[2];
        theState.erase(theState.size() - 1); // remove ':' at the end
        if (!NState::isValid(theState)) {
            continue;
        }
        // cout << line_number << Str::COLON() << *i << "\n";

        pathStateVec.emplace_back(lineTokens[3], theState);
    }
    return true;
}

static bool searchVec(const std::vector<std::pair<std::string, std::string>>& goldenLines,
                      const std::pair<std::string, std::string>& line,
                      size_t start_index) {
    for (size_t i = start_index; i < goldenLines.size(); i++) {
        if (goldenLines[i] == line) {
            return true;
        }
    }
    return false;
}

bool LogVerification::compareNodeStates(const std::string& logfile,
                                        const std::string& goldenRefLogFile,
                                        std::string& errorMsg) {
    // Open log files, and extract just the state changes
    std::vector<std::pair<std::string, std::string>> lines, goldenLines;
    if (!extractNodePathAndState(logfile, lines, errorMsg))
        return false;
    if (!extractNodePathAndState(goldenRefLogFile, goldenLines, errorMsg))
        return false;

    if (lines != goldenLines) {
        std::stringstream ss;
        ss << "Log file " << logfile << " does not match golden reference file " << goldenRefLogFile << "\n";
        if (lines.size() != goldenLines.size())
            ss << "Expected log file size " << goldenLines.size() << " but found " << lines.size() << "\n";

        bool errorFnd = false;
        for (size_t i = 0; i < lines.size() || i < goldenLines.size(); ++i) {

            std::string theLine, theGoldenLine;
            if (i < lines.size())
                theLine = lines[i].second + Str::COLON() + lines[i].first;
            if (i < goldenLines.size())
                theGoldenLine = goldenLines[i].second + Str::COLON() + goldenLines[i].first;

            if (i < lines.size() && i < goldenLines.size()) {

                if (lines[i] != goldenLines[i]) {
                    // Please note that we can't do an exact comparison for certain state changes:
                    //   - submitted --> active
                    //   - active    --> complete
                    // Since this can be OS/scheduler dependent and hence order dependent
                    // To compensate, we search for a Golden file
                    if (lines[i].second == "submitted" || lines[i].second == "active" ||
                        lines[i].second == "complete") {
                        // search for line in golden file
                        if (searchVec(goldenLines, lines[i], 0)) {
                            continue;
                        }
                    }

                    ss << "Mismatch at " << i << " log(" << theLine << ")   golden(" << theGoldenLine << ")\n";
                    errorFnd = true;
                }
                else {
                    ss << "            " << i << " log(" << theLine << ")   golden(" << theGoldenLine << ")\n";
                }
            }
            else {
                errorFnd = true;
                ss << "Mismatch at " << i;
                if (i < lines.size())
                    ss << " log(" << theLine << ")   ";
                else
                    ss << " log( ---- )   ";

                if (i < goldenLines.size())
                    ss << "golden(" << theGoldenLine << ")\n";
                else
                    ss << "golden( --- )\n";
            }
        }
        if (errorFnd)
            errorMsg = ss.str();
    }

    return errorMsg.empty();
}

} // namespace ecf
