/*
   Copyright 2019 Equinor ASA.

   This file is part of the Open Porous Media project (OPM).

   OPM 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 3 of the License, or
   (at your option) any later version.

   OPM is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with OPM.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "EclRegressionTest.hpp"

#include <fmt/format.h>

#include <opm/io/eclipse/EGrid.hpp>
#include <opm/io/eclipse/ERft.hpp>
#include <opm/io/eclipse/ERst.hpp>
#include <opm/io/eclipse/ESmry.hpp>
#include <opm/io/eclipse/ERsm.hpp>
#include <opm/io/eclipse/RestartFileView.hpp>
#include <opm/io/eclipse/rst/state.hpp>

#include <opm/output/eclipse/RestartIO.hpp>

#include <opm/common/ErrorMacros.hpp>
#include <opm/common/utility/numeric/cmp.hpp>

#include <algorithm>
#include <cmath>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <set>
#include <typeinfo>
#include <unordered_set>
#include <vector>

// helper macro to handle error throws or not
#define HANDLE_ERROR(type, message)                     \
    do {                                                \
        if (throwOnError) {                             \
            OPM_THROW((type), (message));               \
        }                                               \
        else {                                          \
            std::cerr << (message) << std::endl;        \
            ++this->num_errors;                         \
        }                                               \
    } while (false)

#define HANDLE_ERROR_OR_RETURN(type, message)           \
    do {                                                \
        if (throwOnError) {                             \
            OPM_THROW((type), (message));               \
        }                                               \
        else {                                          \
            std::cerr << (message) << std::endl;        \
            ++this->num_errors;                         \
            return;                                     \
        }                                               \
    } while (false)

namespace {

template <typename T>
std::vector<T> sorted(std::vector<T> v) {
    std::ranges::sort(v);
    return v;
}

}

using namespace Opm::EclIO;

ECLRegressionTest::~ECLRegressionTest()
{
    delete grid1;
    delete grid2;
}


static bool fileExists(const std::string& fileName) {
    std::ifstream f(fileName);
    return f.good();
}


bool ECLRegressionTest::checkFileName(const std::string &rootName, const std::string &extension, std::string &filename) {

    if (fileExists(rootName + "." + extension)) {
        filename = rootName + "." + extension;
        return true;
    } else if (fileExists(rootName + ".F" + extension)) {
        filename = rootName + ".F" + extension;
        return true;
    } else {
        filename.clear();
        return false;
    }
}


template <typename T>
bool operator==(const std::vector<T> & t1, const std::vector<T> & t2)
{
    return std::ranges::equal(t1, t2);
}


template <typename T>
void ECLRegressionTest::compareFloatingPointVectors(const std::vector<T>& t1, const std::vector<T>& t2, const std::string& keyword, const std::string& reference) {

    if (typeid(T) != typeid(float) && typeid(T) != typeid(double)) {
        HANDLE_ERROR(std::runtime_error, "\nMember function compareVectors should be used with floating point vectors ");
    }

    if (t1.size() != t2.size()) {
        HANDLE_ERROR_OR_RETURN(std::runtime_error,
                               fmt::format("\nError trying to compare two vectors "
                                           "with different size {} - {}"
                                           "\n > size of first vector : {}"
                                           "\n > size of second vector: {}",
                                           keyword, reference, t1.size(), t2.size()));
    }

    auto it = std::ranges::find(keywordDisallowNegatives, keyword);
    bool allowNegatives = it == keywordDisallowNegatives.end() ? true : false;

    it = std::ranges::find(keywordsStrictTol, keyword);
    bool strictTol = it != keywordsStrictTol.end() ? true : false;

    for (size_t i = 0; i < t1.size(); i++) {
        deviationsForCell(static_cast<double>(t1[i]),
                          static_cast<double>(t2[i]),
                          keyword, reference, t1.size(),
                          i, allowNegatives, strictTol);
    }
}


template <typename T>
void ECLRegressionTest::compareVectors(const std::vector<T>& t1, const std::vector<T>& t2, const std::string& keyword, const std::string& reference) {

    if (t1.size() != t2.size()) {
        HANDLE_ERROR_OR_RETURN(std::runtime_error,
                               fmt::format("\nError trying to compare two vectors "
                                           "with different size {} - {}"
                                           "\n > size of first vector : {}"
                                           "\n > size of second vector: {}",
                                           keyword, reference, t1.size(), t2.size()));

    }

    if (typeid(T) == typeid(float) || typeid(T) == typeid(double)) {
        HANDLE_ERROR(std::runtime_error,
                     "\nMember function compareVectors should not be used "
                     "with floating point vectors");
    }

    bool result = t1 == t2 ? true : false ;

    if (!result) {
        for (size_t i = 0; i < t1.size(); i++) {
            deviationsForNonFloatingPoints(t1[i], t2[i], keyword, reference, t1.size(), i);
        }
    }
}


template <typename T>
void ECLRegressionTest::deviationsForNonFloatingPoints(T val1, T val2, const std::string& keyword, const std::string& reference, size_t kw_size, size_t cell)
{
    if (val1 != val2) {
        printValuesForCell(keyword, reference, kw_size, cell, grid1, val1, val2);
        HANDLE_ERROR(std::runtime_error, "Non floating point values not identical ");
    }
}


void ECLRegressionTest::deviationsForCell(double val1, double val2, const std::string& keyword, const std::string& reference, size_t kw_size, size_t cell, bool allowNegativeValues, bool useStrictTol)
{
    double absToleranceLoc = useStrictTol ? strictAbsTol : getAbsTolerance();
    double relToleranceLoc = useStrictTol ? strictAbsTol : getRelTolerance();

    if (!allowNegativeValues) {
        if (val1 < 0) {
            if (std::abs(val1) > absToleranceLoc) {
                printValuesForCell(keyword, reference, kw_size, cell, grid1, val1, val2);
                HANDLE_ERROR(std::runtime_error,
                             fmt::format("Negative value in first file which in absolute value "
                                          "exceeds the absolute tolerance of {}.", absToleranceLoc));
            }
            val1 = 0;
        }

        if (val2 < 0) {
            if (std::abs(val2) > absToleranceLoc) {
                printValuesForCell(keyword, reference, kw_size, cell, grid1, val1, val2);
                HANDLE_ERROR(std::runtime_error,
                             fmt::format("Negative value in second file which in absolute value "
                                         "exceeds the absolute tolerance of {}.", absToleranceLoc));
            }
            val2 = 0;
        }
    }

    Deviation dev = calculateDeviations(val1, val2);
    if (dev.abs > absToleranceLoc && (dev.rel > relToleranceLoc || dev.rel == -1)) {
        if (analysis) {
            std::string keywref = keyword + ": " + reference;
            deviations[keywref].push_back(dev);
        } else {
            printValuesForCell(keyword, reference, kw_size, cell, grid1, val1, val2);

            if (useStrictTol) {
                std::cout << "Keyword: " << keyword << " requires strict tolerances.\n" << std::endl;
            }

            HANDLE_ERROR(std::runtime_error,
                         fmt::format("Deviations exceed tolerances."
                                     "\nThe absolute deviation is {}, "
                                     "and the tolerance limit is {}."
                                     "\nThe relative deviation is {}, "
                                     "and the tolerance limit is {}.",
                                     dev.abs, absToleranceLoc,
                                     dev.rel, relToleranceLoc));
        }
    }

    if (dev.abs != -1) {
        absDeviation.push_back(dev.abs);
    }

    if (dev.rel != -1) {
        relDeviation.push_back(dev.rel);
    }
}


void ECLRegressionTest::printDeviationReport()
{
    if (analysis) {
        std::cout << " \n" << deviations.size() << " keyword"
                  << (deviations.size() > 1 ? "s":"") << " exhibit failures" << std::endl;
        for (const auto& iter : deviations) {
            std::cout << "\t" << iter.first << std::endl;
            std::cout << "\t\tFails for " << iter.second.size() << " entries" << std::endl;
            std::cout.precision(7);
            const double absErr =
                std::ranges::max_element(iter.second,
                                         [](const Deviation& a, const Deviation& b)
                                        { return a.abs < b.abs;})->abs;
            const double relErr =
                std::ranges::max_element(iter.second,
                                         [](const Deviation& a, const Deviation& b)
                                         { return a.rel < b.rel; })->rel;
            std::cout << "\t\tLargest absolute error: "
                      <<  std::scientific << absErr << std::endl;
            std::cout << "\t\tLargest relative error: "
                      <<  std::scientific << relErr << std::endl;
        }
    }
}


void ECLRegressionTest::compareKeywords(const std::vector<std::string> &keywords1, const std::vector<std::string> &keywords2, const std::string &reference) {

    if (!(acceptExtraKeywords or acceptExtraKeywordsBoth)) {
        if (keywords1 != keywords2) {
            std::cout << "not same keywords in " << reference << std::endl;

            if (keywords1.size() > 50) {
                printMissingKeywords(keywords1,keywords2);
            } else {
                printComparisonForKeywordLists(keywords1,keywords2);
            }
            OPM_THROW(std::runtime_error,
                      "\nKeywords not identical in " + reference);
        }
    } else {
        int extraKeywordsFirstFile = 0;
        for (auto& keyword : keywords1) {
            const auto it1 = std::ranges::find(keywords2, keyword);
            if (it1 == keywords2.end()) {
                extraKeywordsFirstFile++;
                std::cout << "Keyword " << keyword << " missing in second file " << std::endl;

                if (keywords1.size() > 50) {
                    printMissingKeywords(keywords1, keywords2);
                } else {
                    printComparisonForKeywordLists(keywords1, keywords2);
                }
                if (!acceptExtraKeywordsBoth) {
                    OPM_THROW(std::runtime_error,
                              "\nKeyword " + keyword + " missing in second file ");
                }
            }
        }

        if (keywords2.size() > keywords1.size() - extraKeywordsFirstFile) {
            std::cout << "\nExtra keywords ("
                      << std::to_string(keywords2.size() - keywords1.size() + extraKeywordsFirstFile)
                      << ") accepted in second file " << std::endl;
        }
        if (extraKeywordsFirstFile > 0) {
            std::cout << "\nExtra keywords ("
                      << extraKeywordsFirstFile
                      << ") accepted in first file " << std::endl;
        }
    }
}


void ECLRegressionTest::checkSpecificKeyword(std::vector<std::string>& keywords1,
                                             std::vector<std::string>& keywords2,
                                             std::vector<eclArrType>& arrayType1,
                                             std::vector<eclArrType>& arrayType2,
                                             const std::string& reference)
{
    const auto search1 = std::ranges::find(keywords1, specificKeyword);
    const auto search2 = std::ranges::find(keywords2, specificKeyword);

    if (search1 == keywords1.end() && search2 == keywords2.end()) {
        const std::string msg =
            fmt::format("Testing specific keyword \"{}\" in {}. "
                        "Keyword not found in any of the cases.",
                        specificKeyword, reference);
        std::cout << msg << std::endl;
        OPM_THROW(std::runtime_error, "\n" + msg);
    }

    if (search1 != keywords1.end()) {
        int ind = std::distance(keywords1.begin(), search1);
        const eclArrType arrType = arrayType1[ind];

        if (search2 == keywords2.end()) {
            const std::string msg =
                fmt::format("Testing specific keyword in {}. "
                            "Keyword found in first case but "
                            "not in second case.", reference);
            std::cout << msg << std::endl;
            OPM_THROW(std::runtime_error, "\n" + msg);
        }

        keywords1.clear();
        arrayType1.clear();
        keywords1.push_back(specificKeyword);
        arrayType1.push_back(arrType);

        keywords2.clear();
        arrayType2.clear();
        keywords2.push_back(specificKeyword);
        arrayType2.push_back(arrType);
    } else {
        if (search2 != keywords2.end()) {
            const std::string msg =
                fmt::format("Testing specific keyword in {}. "
                            "Keyword not found in first case but "
                            "found in second case.", reference);
            std::cout << msg << std::endl;
            OPM_THROW(std::runtime_error, "\n "+ msg);
        }

        keywords1.clear();
        arrayType1.clear();

        keywords2.clear();
        arrayType2.clear();
    }
}


void ECLRegressionTest::loadGrids()
{
    bool foundEGrid1, foundEGrid2;
    std::string fileName1, fileName2;

    foundEGrid1 = checkFileName(rootName1, "EGRID", fileName1);
    foundEGrid2 = checkFileName(rootName2, "EGRID", fileName2);

    if (foundEGrid1) {
        std::cout << "\nLoading EGrid " << fileName1 << "  .... ";
        grid1 = new EGrid(fileName1);
        std::cout << " done." << std::endl;
    }

    if (foundEGrid2) {
        std::cout << "Loading EGrid " << fileName2 << "  .... ";
        grid2 = new EGrid(fileName2);
        std::cout << " done." << std::endl;
    }

    if ((not foundEGrid1) || (not foundEGrid2)) {
        std::cout << "\nWarning! Both grids could not be loaded. Not possible to reference cell values to grid indices." << std::endl;
        std::cout << "Grid compare may also fail. SMRY, RFT, UNRST and INIT files can be checked \n" << std::endl;
    }
}


void ECLRegressionTest::gridCompare()
{
    deviations.clear();

    if ((grid1) && (not grid2)){
        std::string message ="test case egrid file " + rootName2 + ".EGRID could not be loaded";
	std::cout << message << std::endl;
        OPM_THROW(std::runtime_error, message);
    }

    if (grid1 && grid2) {

        std::cout << "comparing grids " << std::endl;

        const auto& dim1 = grid1->dimension();
        const auto& dim2 = grid2->dimension();

        if (printKeywordOnly) {

            auto arrayList1 = grid1->getList();
            auto arrayList2 = grid2->getList();

            std::vector<std::string> keywords1;
            std::vector<eclArrType> arrayType1;

            for (auto& array : arrayList1) {
                keywords1.push_back(std::get<0>(array));
                arrayType1.push_back(std::get<1>(array));
            }

            std::vector<std::string> keywords2;
            std::vector<eclArrType> arrayType2;

            for (auto& array : arrayList2) {
                keywords2.push_back(std::get<0>(array));
                arrayType2.push_back(std::get<1>(array));
            }

            printComparisonForKeywordLists(keywords1, keywords2, arrayType1, arrayType2);
            return;
        }

        std::cout << "\nComparing egrid files \n" << std::endl;

        std::cout << "Dimensions             " << " ... ";

        if (dim1[0] != dim2[0]  || dim1[1] != dim2[1] || dim1[2] != dim2[2]) {
            OPM_THROW(std::runtime_error,
                      fmt::format("\n Grid1 and grid2 have different dimensions. "
                                  "\n grid1 : {}x{}x{} "
                                  "\n grid2 : {}x{}x{}",
                                  dim1[0], dim1[1], dim1[2],
                                  dim2[0], dim2[1], dim2[2]));
        }

        std::cout << " done." << std::endl;

        std::cout << "Active cells           " << " ... ";

        for (int k = 0; k < dim1[2]; k++) {
            for (int j=0; j < dim1[1]; j++) {
                for (int i = 0; i < dim2[0]; i++) {
                    if (grid1->active_index(i,j,k) != grid2->active_index(i,j,k)) {
                        OPM_THROW(std::runtime_error,
                                  fmt::format("\nGrid1 and grid2 have different definition of active cells. "
                                              "First difference found for cell i={} j={} k={}",
                                              i+1, j+1, k+1));
                    }
                }
            }
        }

        std::cout << " done." << std::endl;

        std::cout << "X, Y and Z coordinates " << " ... ";

        std::array<double,8> X1 = {0.0};
        std::array<double,8> Y1 = {0.0};
        std::array<double,8> Z1 = {0.0};

        std::array<double,8> X2 = {0.0};
        std::array<double,8> Y2 = {0.0};
        std::array<double,8> Z2 = {0.0};

        for (int k = 0; k < dim1[2]; k++) {
            for (int j = 0; j < dim1[1]; j++) {
                for (int i = 0; i < dim1[0]; i++) {
                    if (grid1->active_index(i,j,k) > -1) {
                        grid1->getCellCorners({i,j,k}, X1, Y1, Z1);
                        grid2->getCellCorners({i,j,k}, X2, Y2, Z2);

                        for (int n = 0; n < 8; n++) {
                            Deviation devX = calculateDeviations(X1[n], X2[n]);
                            Deviation devY = calculateDeviations(Y1[n], Y2[n]);
                            Deviation devZ = calculateDeviations(Z1[n], Z2[n]);

                            if (devX.abs > strictAbsTol) {
                                if (analysis) {
                                    deviations["xcoordinate"].push_back(devX);
                                } else {
                                    OPM_THROW(std::runtime_error,
                                              fmt::format("\nGrid1 and grid2 have different X coordinates. "
                                                          "First difference found for cell i={} j={} k={}",
                                                          i+1, j+1, k+1));
                                }
                            }

                            if (devY.abs > strictAbsTol) {
                                if (analysis) {
                                    deviations["ycoordinate"].push_back(devY);
                                } else {
                                    OPM_THROW(std::runtime_error,
                                              fmt::format("\nGrid1 and grid2 have different Y coordinates. "
                                                          "First difference found for cell i={} j={} k={}",
                                                          i+1, j+1, k+1));
                                }
                            }

                            if (devZ.abs > strictAbsTol) {
                                if (analysis) {
                                    deviations["zcoordinate"].push_back(devZ);
                                } else {
                                    OPM_THROW(std::runtime_error,
                                              fmt::format("\nGrid1 and grid2 have different Z coordinates. "
                                                          "First difference found for cell i={} j={} k={}",
                                                          i+1, j+1, k+1));
                                }
                            }
                        }
                    }
                }
            }
        }

        std::cout << " done." << std::endl;

        std::cout << "NNC indices            " << " ... ";

        // check / compare NNC definitions

        if (grid1->hasKey("NNC1")) {
            std::vector<int> NNC11 = grid1->get<int>("NNC1");
            std::vector<int> NNC21 = grid1->get<int>("NNC2");

            if (!grid2->hasKey("NNC1")) {
                OPM_THROW(std::runtime_error, "\nFirst Grid have NNC1 keyword but not second grid  ");
            }

            std::vector<int> NNC12 = grid2->get<int>("NNC1");
            std::vector<int> NNC22 = grid2->get<int>("NNC2");

            if (NNC11.size() != NNC12.size() || NNC21.size() != NNC22.size()) {
                OPM_THROW(std::runtime_error,
                          fmt::format("\n Grid1 and grid2 have different number of NNCs. "
                                      "\n Grid1: {}, Grid2: {}",
                                      NNC11.size(), NNC12.size()));
            }

            for (size_t n = 0; n < NNC11.size(); n++) {
                if (NNC11[n] != NNC12[n] || NNC21[n] != NNC22[n]) {
                    std::cout << "Differences in NNCs. First found for " << NNC11[n] << " -> " <<  NNC21[n];
                    std::cout << " not same as " << NNC12[n] << " -> " <<  NNC22[n] << std::endl;

                    auto ijk1 = grid1->ijk_from_global_index(NNC11[n]-1);
                    auto ijk2  = grid1->ijk_from_global_index(NNC21[n]-1);

                    std::cout << "In grid1 " << ijk1[0]+1 << "," << ijk1[1]+1 <<"," << ijk1[2]+1  << " -> " << ijk2[0]+1 << "," << ijk2[1]+1 <<"," << ijk2[2]+1 << std::endl;

                    ijk1 = grid2->ijk_from_global_index(NNC12[n]-1);
                    ijk2 = grid2->ijk_from_global_index(NNC22[n]-1);

                    std::cout << "In grid2 " << ijk1[0]+1 << "," << ijk1[1]+1 <<"," << ijk1[2]+1  << " -> " << ijk2[0]+1 << "," << ijk2[1]+1 <<"," << ijk2[2]+1 << std::endl;

                    OPM_THROW(std::runtime_error, "\n Grid1 and grid2 have different definitions of NNCs. ");
                }
            }
        }

        std::cout << " done." << std::endl;

        if (!deviations.empty()) {
            printDeviationReport();
        }

    } else {
        std::cout << "\n!Warning, grid files not found, hence not compared. \n" << std::endl;
    }

}


void ECLRegressionTest::results_init()
{
    std::string fileName1, fileName2;

    bool foundInit1 = checkFileName(rootName1, "INIT", fileName1);
    bool foundInit2 = checkFileName(rootName2, "INIT", fileName2);

    if ((foundInit1) && (not foundInit2)){
        std::string message ="test case init file " + rootName2 + ".INIT not found";
	std::cout << message << std::endl;
        OPM_THROW(std::runtime_error, message);
    }

    if (foundInit1 && foundInit2) {
        EclFile init1(fileName1);
        std::cout << "\nLoading INIT file " << fileName1 << "  .... done" << std::endl;

        EclFile init2(fileName2);
        std::cout << "Loading INIT file " << fileName2 << "  .... done\n" << std::endl;

        deviations.clear();

        init1.loadData();
        init2.loadData();

        auto arrayList1 = init1.getList();
        auto arrayList2 = init2.getList();

        std::vector<std::string> keywords1;
        std::vector<eclArrType> arrayType1;

        for (const auto& array : arrayList1) {
            keywords1.push_back(std::get<0>(array));
            arrayType1.push_back(std::get<1>(array));
        }

        std::vector<std::string> keywords2;
        std::vector<eclArrType> arrayType2;

        for (const auto& array : arrayList2) {
            keywords2.push_back(std::get<0>(array));
            arrayType2.push_back(std::get<1>(array));
        }

        if (printKeywordOnly) {
            printComparisonForKeywordLists(keywords1, keywords2, arrayType1, arrayType2);
        } else {
            std::cout << "\nComparing init files \n" << std::endl;
            std::string reference = "Init file";

            if (specificKeyword.empty()) {
                if (keywords1.size() == keywords2.size() && keywords1 != keywords2) {
                    /*
                      If the keywords come in different order in the two files
                      we mark that as a test failure, but all comparisons are
                      still performed, and the keyword reordering can be
                      identified by the error message.
                    */
                    auto kw1 = sorted(keywords1);
                    auto kw2 = sorted(keywords2);
                    compareKeywords(kw1, kw2, reference);
                    std::cerr << "Keyword reordering detected in INIT file" << std::endl;
                    /*
                      The keyword reordering should eventually be marked as a an
                      error, but temporarily during the refactoring of 3D
                      properties it is only marked with a message to stderr.
                      This is to avoid deviations between ci builds and
                      developers builds. December 2019 - Joakim Hove
                    */
                    //HANDLE_ERROR(std::runtime_error, "Keyword reordering detected in INIT file");
                } else
                    compareKeywords(keywords1, keywords2, reference);
            } else {
                checkSpecificKeyword(keywords1, keywords2, arrayType1, arrayType2, reference);
            }

            for (size_t i = 0; i < keywords1.size(); i++) {
                const auto it1 = std::ranges::find(keywords2, keywords1[i]);
                if (it1 == keywords2.end() && acceptExtraKeywordsBoth) {
                    continue;
                }
                int ind2 = std::distance(keywords2.begin(),it1);

                if (arrayType1[i] != arrayType2[ind2]) {
                    printComparisonForKeywordLists(keywords1, keywords2, arrayType1, arrayType2);
                    OPM_THROW(std::runtime_error,
                              fmt::format("\nArray with same name '{}', "
                                          "but of different type. Init file",
                                          keywords1[i]));
                }

                const auto it = std::ranges::find(keywordsBlackList, keywords1[i]);

                if (it != keywordsBlackList.end()){
                    std::cout << "Skipping  " << keywords1[i] << std::endl;
                } else {
                    std::cout << "Comparing " << keywords1[i] << " ... ";

                    if (arrayType1[i] == INTE) {
                        auto vect1 = init1.get<int>(keywords1[i]);
                        auto vect2 = init2.get<int>(keywords2[ind2]);
                        compareVectors(vect1, vect2, keywords1[i],reference);
                    } else if (arrayType1[i] == REAL) {
                        auto vect1 = init1.get<float>(keywords1[i]);
                        auto vect2 = init2.get<float>(keywords2[ind2]);
                        compareFloatingPointVectors(vect1, vect2, keywords1[i], reference);
                    } else if (arrayType1[i] == DOUB) {
                        auto vect1 = init1.get<double>(keywords1[i]);
                        auto vect2 = init2.get<double>(keywords2[ind2]);
                        compareFloatingPointVectors(vect1, vect2, keywords1[i], reference);
                    } else if (arrayType1[i] == LOGI) {
                        auto vect1 = init1.get<bool>(keywords1[i]);
                        auto vect2 = init2.get<bool>(keywords2[ind2]);
                        compareVectors(vect1, vect2, keywords1[i], reference);
                    } else if (arrayType1[i] == CHAR) {
                        auto vect1 = init1.get<std::string>(keywords1[i]);
                        auto vect2 = init2.get<std::string>(keywords2[ind2]);
                        compareVectors(vect1, vect2, keywords1[i], reference);
                    } else if (arrayType1[i] == MESS) {
                        // shold not be any associated data
                    } else {
                        std::cout << "unknown array type " << std::endl;
                        exit(1);
                    }

                    std::cout << " done." << std::endl;
                }
            }

            if (!deviations.empty()) {
                printDeviationReport();
            }
        }
    } else {
        std::cout << "\n!Warning, init files not found, hence not compared. \n" << std::endl;
    }

}


void ECLRegressionTest::results_rst()
{
    std::string fileName1, fileName2;
    bool foundRst1 = checkFileName(rootName1,"UNRST",fileName1);
    bool foundRst2 = checkFileName(rootName2,"UNRST",fileName2);

    if ((foundRst1) && (not foundRst2)){
        std::string message ="test case restart file " + rootName2 + ".UNRST not found";
	std::cout << message << std::endl;
        OPM_THROW(std::runtime_error, message);
    }

    if (foundRst1 && foundRst2) {
        auto rst1 = std::make_shared<ERst>(fileName1);
        std::cout << "\nLoading restart file " << fileName1 << "  .... done" << std::endl;

        auto rst2 = std::make_shared<ERst>(fileName2);
        std::cout << "Loading restart file " << fileName2 << "  .... done\n" << std::endl;

        std::vector<int> seqnums1 = rst1->listOfReportStepNumbers();
        std::vector<int> seqnums2 = rst2->listOfReportStepNumbers();

        deviations.clear();

        if (specificSequence > -1) {
            const auto search1 = std::ranges::find(seqnums1, specificSequence);
            const auto search2 = std::ranges::find(seqnums2, specificSequence);

            if (search1 == seqnums1.end()) {
                OPM_THROW(std::runtime_error,
                          fmt::format("\nSpecified sequence {} "
                                      "not found in restart files for case 1",
                                      specificSequence));
            }

            if (search2 == seqnums2.end()) {
                OPM_THROW(std::runtime_error,
                          fmt::format("\nSpecified sequence {} "
                                      "not found in restart files for case 2",
                                      specificSequence));
            }

            seqnums1.clear();
            seqnums1.push_back(specificSequence);

            seqnums2.clear();
            seqnums2.push_back(specificSequence);

        } else if (onlyLastSequence) {

            if (seqnums1.back() != seqnums2.back()) {
                OPM_THROW(std::runtime_error, "\nLast sequence not same for for case 1 and case 2");
            }

            seqnums1.clear();
            seqnums1.push_back(seqnums2.back());

            seqnums2.clear();
            seqnums2.push_back(seqnums1.back());
        }

        if (seqnums1 != seqnums2) {
            std::vector<std::string> seqnStrList1;
            std::vector<std::string> seqnStrList2;

            std::ranges::transform(seqnums1, std::back_inserter(seqnStrList1),
                                   [](const auto& val) { return std::to_string(val); });

            std::ranges::transform(seqnums2, std::back_inserter(seqnStrList2),
                                   [](const auto& val) { return std::to_string(val); });

            std::cout << "\nrestart sequences " << std::endl;
            printComparisonForKeywordLists(seqnStrList1, seqnStrList2);
            OPM_THROW(std::runtime_error, "\nRestart files not having the same report steps: ");
        }

        for (int& seqn : seqnums1) {
            std::cout << "\nUnified restart files, sequence  " << std::to_string(seqn) << "\n" << std::endl;

            std::string reference = "Restart, sequence "+std::to_string(seqn);

            rst1->loadReportStepNumber(seqn);
            rst2->loadReportStepNumber(seqn);

            auto arrays1 = rst1->listOfRstArrays(seqn);
            auto arrays2 = rst2->listOfRstArrays(seqn);

            std::vector<std::string> keywords1;
            std::vector<eclArrType> arrayType1;
            for (const auto& array : arrays1) {
                keywords1.push_back(std::get<0>(array));
                arrayType1.push_back(std::get<1>(array));
            }

            std::vector<std::string> keywords2;
            std::vector<eclArrType> arrayType2;

            for (const auto& array : arrays2) {
                keywords2.push_back(std::get<0>(array));
                arrayType2.push_back(std::get<1>(array));
            }

            if (integrationTest) {
                std::vector<std::string> keywords;

                for (size_t i = 0; i < keywords1.size(); i++) {
                    if (keywords1[i] == "PRESSURE" ||
                        keywords1[i] == "SWAT" ||
                        keywords1[i] =="SGAS")
                    {
                        const auto search2 = std::ranges::find(keywords2, keywords1[i]);
                        if (search2 != keywords2.end()) {
                            keywords.push_back(keywords1[i]);
                        }
                        else if (acceptExtraKeywordsBoth) {
                            continue;
                        }
                    }
                }

                keywords1 = keywords2 = keywords;

                int nKeys = keywords.size();
                arrayType1.assign(nKeys, REAL);
                arrayType2.assign(nKeys, REAL);
            }

            if (printKeywordOnly) {
                printComparisonForKeywordLists(keywords1, keywords2, arrayType1, arrayType2);
            } else {
                if (specificKeyword.empty()) {
                    compareKeywords(keywords1, keywords2, reference);
                } else {
                    checkSpecificKeyword(keywords1, keywords2, arrayType1, arrayType2, reference);
                }

                for (size_t i = 0; i < keywords1.size(); i++) {
                    //if (keywords.count(keywords1[i]) == 0)
                    //    continue;

                    const auto it1 = std::ranges::find(keywords2, keywords1[i]);
                    if (it1 == keywords2.end() and acceptExtraKeywordsBoth) {
                        continue;
                    }
                    int ind2 = std::distance(keywords2.begin(), it1);

                    if (arrayType1[i] != arrayType2[ind2]) {
                        printComparisonForKeywordLists(keywords1, keywords2, arrayType1, arrayType2);
                        OPM_THROW(std::runtime_error,
                                  fmt::format("\nArray with same name '{}', "
                                              "but of different type. "
                                              "Restart file sequence {}",
                                              keywords1[i], seqn));
                    }

                    const auto it = std::ranges::find(keywordsBlackList, keywords1[i]);

                    if (it != keywordsBlackList.end()){
                        std::cout << "Skipping  " << keywords1[i] << std::endl;
                    } else {

                        std::cout << "Comparing " << keywords1[i] << " ... ";

                        if (arrayType1[i] == INTE) {
                            auto vect1 = rst1->getRestartData<int>(keywords1[i], seqn, 0);
                            auto vect2 = rst2->getRestartData<int>(keywords2[ind2], seqn, 0);
                            compareVectors(vect1, vect2, keywords1[i], reference);
                        } else if (arrayType1[i] == REAL) {
                            auto vect1 = rst1->getRestartData<float>(keywords1[i], seqn, 0);
                            auto vect2 = rst2->getRestartData<float>(keywords2[ind2], seqn, 0);
                            compareFloatingPointVectors(vect1, vect2, keywords1[i], reference);
                        } else if (arrayType1[i] == DOUB) {
                            auto vect1 = rst1->getRestartData<double>(keywords1[i], seqn, 0);
                            auto vect2 = rst2->getRestartData<double>(keywords2[ind2], seqn, 0);

                            // hack in order to not test doubhead[1], dependent on simulation results
                            // All ohter items in DOUBHEAD are tested with strict tolerances
                            if (keywords1[i]=="DOUBHEAD"){
                                vect2[1] = vect1[1];
                            }
                            compareFloatingPointVectors(vect1, vect2, keywords1[i], reference);
                        } else if (arrayType1[i] == LOGI) {
                            auto vect1 = rst1->getRestartData<bool>(keywords1[i], seqn, 0);
                            auto vect2 = rst2->getRestartData<bool>(keywords2[ind2], seqn, 0);
                            compareVectors(vect1, vect2, keywords1[i], reference);
                        } else if (arrayType1[i] == CHAR) {
                            auto vect1 = rst1->getRestartData<std::string>(keywords1[i], seqn, 0);
                            auto vect2 = rst2->getRestartData<std::string>(keywords2[ind2], seqn, 0);
                            compareVectors(vect1, vect2, keywords1[i], reference);
                        } else if (arrayType1[i] == MESS) {
                            // shold not be any associated data
                        } else {
                            std::cout << "unknown array type " << std::endl;
                            exit(1);
                        }

                        std::cout << " done." << std::endl;
                    }
                }
            }
        }

        if (!deviations.empty()) {
            printDeviationReport();
        }
    } else {
        std::cout << "\n!Warning, restart files not found, hence not compared. \n" << std::endl;
    }

}


void ECLRegressionTest::results_smry()
{
    std::string fileName1, fileName2;
    bool foundSmspec1 = checkFileName(rootName1, "SMSPEC", fileName1);
    bool foundSmspec2 = checkFileName(rootName2, "SMSPEC", fileName2);

    if ((foundSmspec1) && (not foundSmspec2)){
        std::string message ="test case summary file " + rootName2 + ".SMSPEC not found";
	std::cout << message << std::endl;
        OPM_THROW(std::runtime_error, message);
    }

    if (foundSmspec1 && foundSmspec2) {
        ESmry smry1(fileName1, loadBaseRunData);
        smry1.loadData();
        std::cout << "\nLoading summary file " << fileName1 << "  .... done" << std::endl;

        ESmry smry2(fileName2, loadBaseRunData);
        smry2.loadData();
        std::cout << "Loading summary file " << fileName2 << "  .... done" << std::endl;

        deviations.clear();

        std::string reference = "Summary file";

        std::cout << "\nComparing summary files " << std::endl;

        if (reportStepOnly){
            std::cout << " -- Values at report steps will be compared. Time steps in between reports are ignored " << std::endl;
        }

        std::vector<std::string> keywords1 = smry1.keywordList();
        std::vector<std::string> keywords2 = smry2.keywordList();

        std::vector<eclArrType> arrayType1(keywords1.size(), REAL);
        std::vector<eclArrType> arrayType2 (keywords1.size(), REAL);

        if (integrationTest) {

            auto isIntegrationKw = [](const std::string& kw)
            {
                static const auto kwlist = std::vector<std::string> {
                   "WOPR", "WWPR", "WGPR", "WBHP",
                   "SPR", "SPRD", "SPRDH", "SPRDF", "SPRDA",
                };

                const auto p = kw.find_first_of(':');
                return std::ranges::find(kwlist, kw.substr(0, p)) != kwlist.end();
            };

            std::vector<std::string> keywords;

            for (const auto& kw : keywords1) {
                if (! isIntegrationKw(kw)) { continue; }

                const auto search2 = std::ranges::find(keywords2, kw);
                if (search2 != keywords2.end()) {
                    keywords.push_back(kw);
                } else if (acceptExtraKeywordsBoth) {
                    continue;
                }
            }

            keywords1 = keywords2 = keywords;

            int nKeys = keywords.size();

            arrayType1.assign(nKeys, REAL);
            arrayType2.assign(nKeys, REAL);
        }

        if (printKeywordOnly) {
            if (keywords1.size() < 50) {
                printComparisonForKeywordLists(keywords1, keywords2);
            } else {
                printMissingKeywords(keywords1, keywords2);
            }
        } else {
            if (specificKeyword.empty()) {
                compareKeywords(keywords1, keywords2, reference);
            } else {
                checkSpecificKeyword(keywords1, keywords2, arrayType1, arrayType2, reference);
            }

            // Remove blacklisted keywords
            std::vector<std::string> blackListed;
            auto make_remover = [&blackListed](const std::vector<std::string>& _blacklist) {
                                    return [&blacklist = _blacklist, &blackListed](const auto& kw)
                                            {
                                                const auto it = std::ranges::find(blacklist, kw);
                                                if (it != blacklist.end()) {
                                                    blackListed.push_back(kw);
                                                }
                                                return it != blacklist.end();
                                            };
                                };

            std::erase_if(keywords1, make_remover(keywordsBlackList));
            if (loadBaseRunData) {
                std::erase_if(keywords1, make_remover(keywordsBlackListExtraRestart));
            }

            std::cout << "\nChecking " << keywords1.size() << "  vectors  ... ";

            for (size_t i = 0; i < keywords1.size(); i++) {
                const auto it1 = std::ranges::find(keywords2, keywords1[i]);
                if (it1 == keywords2.end() and acceptExtraKeywordsBoth) {
                    std::cout << "\nSkipping comparison for kw " << keywords1[i];
                    continue;
                }

                std::vector<float> vect1;
                std::vector<float> vect2;

                if (reportStepOnly){
                    vect1 = smry1.get_at_rstep( keywords1[i]);
                    vect2 = smry2.get_at_rstep( keywords1[i]);
                } else {
                    vect1 = smry1.get( keywords1[i]);
                    vect2 = smry2.get( keywords1[i]);
                }

                if (vect1.size() != vect2.size()) {
                    OPM_THROW(std::runtime_error,
                              fmt::format("\nKeyword {} summary vector of "
                                          "different length ({} != {})",
                                          keywords1[i], vect1.size(), vect2.size()));
                }

                compareFloatingPointVectors(vect1, vect2, keywords1[i], reference);
            }

            std::cout << " done." << std::endl;

            if (blackListed.size()>0){
                std::cout << "Number of black listed vectors " << blackListed.size() << " (not compared) " << std::endl;
            }

            if (!deviations.empty()) {
                printDeviationReport();
            }
        }

    } else {
        std::cout << "\n!Warning, summary files not found, hence not compared. \n" << std::endl;
    }

}

void ECLRegressionTest::results_rsm()
{
    std::string fileName1,fileName2;
    bool foundRsm2 = checkFileName(rootName2, "RSM", fileName2);
    bool foundSmspec1 = checkFileName(rootName1, "SMSPEC", fileName1);

    if ((foundRsm2) && (foundSmspec1)) {

        ESmry smry2(fileName1, loadBaseRunData);
        smry2.loadData();
        std::cout << "\nLoading summary file " << fileName1 << "  .... done" << std::endl;

        namespace fs = std::filesystem;
        std::string rsm_file = rootName2 + ".RSM";
        if (fs::is_regular_file(fs::path(rsm_file))) {
            std::cout << "\nLoading RSM file " << rsm_file << "  .... " << std::flush;
            auto rsm = ERsm(rsm_file);
            std::cout << " done " << std::endl << std::flush;;

            std::cout << "\nComparing RSM file against SMRY file  .... " << std::flush;

            if (!cmp(smry2, rsm))
                HANDLE_ERROR(std::runtime_error, "The RSM file did not compare equal to the summary file");

            std::cout << " done " << std::endl << std::flush;;
        }

    } else {
        std::cout << "\n!Warning, summary and/or RSM - file not found, hence not compared. \n" << std::endl;
    }
}


void ECLRegressionTest::results_rft()
{
    std::string fileName1, fileName2;
    bool foundRft1 = checkFileName(rootName1, "RFT", fileName1);
    bool foundRft2 = checkFileName(rootName2, "RFT", fileName2);

    if ((!foundRft1 && foundRft2) || (foundRft1 && !foundRft2)) {
        std::string message ="test case rft file " + (foundRft1 ? rootName1 : rootName2) + ".RFT not found";
        std::cout << message << std::endl;
        OPM_THROW(std::runtime_error, message);
    }

    if (foundRft1 && foundRft2) {
        ERft rft1(fileName1);
        std::cout << "\nLoading rft file " << fileName1 << "  .... done" << std::endl;

        ERft rft2(fileName2);
        std::cout << "Loading rft file " << fileName2 << "  .... done\n" << std::endl;

        auto rftReportList1 = rft1.listOfRftReports();
        auto rftReportList2 = rft2.listOfRftReports();

        deviations.clear();

        if (rftReportList1 != rftReportList2) {
            std::vector<std::string> rftList1;
            for (auto& report : rftReportList1) {
                std::string well =  std::get<0>(report);
                std::tuple<int, int, int> date =  std::get<1>(report);
                std::string str1 = well +" (" + std::to_string(std::get<0>(date)) + "/" + std::to_string(std::get<1>(date)) + "/"  +   std::to_string(std::get<2>(date)) + ")";
                rftList1.push_back(str1);
            }

            std::vector<std::string> rftList2;
            for (auto& report : rftReportList2) {
                std::string well =  std::get<0>(report);
                std::tuple<int, int, int> date =  std::get<1>(report);
                std::string str2 = well +" (" + std::to_string(std::get<0>(date)) + "/" + std::to_string(std::get<1>(date)) + "/"  +   std::to_string(std::get<2>(date)) + ")";
                rftList2.push_back(str2);
            }

            printComparisonForKeywordLists(rftList1, rftList2);

            OPM_THROW(std::runtime_error, "\nNot same RFTs in in RFT file ");
        }

        for (auto& report : rftReportList2) {
            std::string well =  std::get<0>(report);
            std::tuple<int, int, int> date =  std::get<1>(report);

            std::string dateStr = std::to_string(std::get<0>(date)) + "/" + std::to_string(std::get<1>(date)) + "/" + std::to_string(std::get<2>(date));

            std::cout << "Well: " << well << " date: " << dateStr << std::endl;

            std::string reference = "RFT: " + well + ", " + dateStr;

            auto vectList1 = rft1.listOfRftArrays(well, date);
            auto vectList2 = rft2.listOfRftArrays(well, date);

            std::vector<std::string> keywords1;
            std::vector<eclArrType> arrayType1;
            for (auto& array : vectList1 ) {
                keywords1.push_back(std::get<0>(array)) ;
                arrayType1.push_back(std::get<1>(array)) ;
            }

            std::vector<std::string> keywords2;
            std::vector<eclArrType> arrayType2;
            for (auto& array : vectList2 ) {
                keywords2.push_back(std::get<0>(array)) ;
                arrayType2.push_back(std::get<1>(array)) ;
            }

            if (printKeywordOnly) {
                printComparisonForKeywordLists(keywords1, keywords2, arrayType1, arrayType2);
            } else {
                if (specificKeyword.empty()) {
                    compareKeywords(keywords1, keywords2, reference);
                } else {
                    checkSpecificKeyword(keywords1, keywords2, arrayType1, arrayType2, reference);
                }

                for (auto& array : vectList1 ) {
                    std::string keyword = std::get<0>(array);
                    eclArrType arrayType = std::get<1>(array);

                    const auto it = std::ranges::find(keywordsBlackList, keyword);

                    if (it != keywordsBlackList.end()){
                        std::cout << "Skipping  " << keyword << std::endl;
                    } else {
                        std::cout << "Comparing: " << keyword << " ... ";

                        if (arrayType == INTE) {
                            auto vect1 = rft1.getRft<int>(keyword, well, date);
                            auto vect2 = rft2.getRft<int>(keyword, well, date);
                            compareVectors(vect1, vect2, keyword, reference);
                        } else if (arrayType == REAL) {
                            auto vect1 = rft1.getRft<float>(keyword, well, date);
                            auto vect2 = rft2.getRft<float>(keyword, well, date);
                            compareFloatingPointVectors(vect1, vect2, keyword, reference);
                        } else if (arrayType == DOUB) {
                            auto vect1 = rft1.getRft<double>(keyword, well, date);
                            auto vect2 = rft2.getRft<double>(keyword, well, date);
                            compareFloatingPointVectors(vect1, vect2, keyword, reference);
                        } else if (arrayType == LOGI) {
                            auto vect1 = rft1.getRft<bool>(keyword, well, date);
                            auto vect2 = rft2.getRft<bool>(keyword, well, date);
                            compareVectors(vect1, vect2, keyword, reference);
                        } else if (arrayType == CHAR) {
                            auto vect1 = rft1.getRft<std::string>(keyword, well, date);
                            auto vect2 = rft2.getRft<std::string>(keyword, well, date);
                            compareVectors(vect1, vect2, keyword, reference);
                        } else if (arrayType == MESS) {
                            // shold not be any associated data
                        } else {
                            std::cout << "unknown array type " << std::endl;
                            exit(1);
                        }

                        std::cout << " done." << std::endl;
                    }
                }
            }
            std::cout << std::endl;
        }

        if (!deviations.empty()) {
            printDeviationReport();
        }
    } else {
        std::cout << "\n!Warning, rft files not found, hence not compared. \n" << std::endl;
    }
}


void ECLRegressionTest::printComparisonForKeywordLists(const std::vector<std::string>& arrayList1,
                                                       const std::vector<std::string>& arrayList2,
                                                       const std::vector<eclArrType>& arrayType1,
                                                       const std::vector<eclArrType>& arrayType2) const
{
    unsigned int maxLen = 0;

    std::vector<std::string> arrTypeStrList = {"INTE", "REAL", "DOUB", "CHAR", "LOGI", "MESS"};

    std::set<std::string> commonList;

    for (auto& key : arrayList1) {
        commonList.insert(key);
    }

    for (auto& key : arrayList2) {
        commonList.insert(key);
    }

    for (const auto& key : commonList) {
        if (key.size() > maxLen) {
            maxLen = key.size();
        }
    }

    maxLen += 4;

    std::cout << std::endl;

    for (auto& it : commonList) {
        const auto it1 = std::ranges::find(arrayList1, it);
        int ind1 = std::distance(arrayList1.begin(), it1);

        const auto it2 = std::ranges::find(arrayList2, it);
        int ind2 = std::distance(arrayList2.begin(),it2);

        if (arrayType1[ind1] != arrayType2[ind2]) {
            std::cout << "\033[1;31m";
        }

        if (std::ranges::find(arrayList1, it) != arrayList1.end()) {
            std::cout <<  std::setw(maxLen) << it << " (" <<  arrTypeStrList[arrayType1[ind1]] << ") | ";
        }
        else {
            std::cout <<  std::setw(maxLen) << "" << "        | ";
        }

        if (std::ranges::find(arrayList2, it) != arrayList2.end()) {
            std::cout <<  std::setw(maxLen) << it << " (" <<  arrTypeStrList[arrayType2[ind2]] << ") ";
        }
        else {
            std::cout <<  std::setw(maxLen) << "";
        }

        if (arrayType1[ind1] != arrayType2[ind2]) {
            std::cout << " !" << "\033[0m";
        }

        std::cout << std::endl;
    }

    std::cout << std::endl << std::endl;
}


void ECLRegressionTest::printMissingKeywords(const std::vector<std::string>& arrayList1,
                                             const std::vector<std::string>& arrayList2) const
{
    std::set<std::string> commonList;

    for (auto& key : arrayList1) {
        commonList.insert(key);
    }

    for (auto& key : arrayList2) {
        commonList.insert(key);
    }

    std::cout << "\nKeywords found in second case, but missing in first case: \n" << std::endl;

    for (auto& it : commonList) {
        if (std::ranges::find(arrayList1, it) == arrayList1.end()) {
            std::cout << "  > '" << it  << "'" << std::endl;
        }
    }

    std::cout << "\nKeywords found in first case, but missing in second case: \n" << std::endl;

    for (auto& it : commonList) {
        if (std::ranges::find(arrayList2, it) == arrayList2.end()) {
            std::cout << "  > '" << it  << "'" << std::endl;
        }
    }
}


void ECLRegressionTest::printComparisonForKeywordLists(const std::vector<std::string>& arrayList1,
                                                       const std::vector<std::string>& arrayList2) const
{
    std::set<std::string> commonList;
    unsigned int maxLen = 0;

    for (auto& key : arrayList1) {
        commonList.insert(key);
    }

    for (auto& key : arrayList2) {
        commonList.insert(key);
    }

    for (const auto& key : commonList) {
        if (key.size() > maxLen) {
            maxLen = key.size();
        }
    }

    maxLen += 2;

    std::cout << std::endl;

    for (auto& it : commonList) {
        if (std::ranges::find(arrayList1, it) != arrayList1.end()) {
            std::cout <<  std::setw(maxLen) << it  << " | ";
        }
        else {
            std::cout <<  std::setw(maxLen) << "" << " | ";
        }

        if (std::ranges::find(arrayList2, it) != arrayList2.end()) {
            std::cout <<  std::setw(maxLen) << it << "";
        }
        else {
            std::cout <<  std::setw(maxLen) << "" ;
        }

        std::cout << std::endl;
    }

    std::cout << std::endl;
}
