/*
 * SPDX-FileCopyrightText: 2026 Oak Ridge National Laboratory and Contributors
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "CampaignReader.h"
#include "CampaignReader.tcc"

#include "adios2/helper/adiosFunctions.h" // CSVToVector
#include "adios2/helper/adiosNetwork.h"   // GetFQDN
#include "adios2/helper/adiosSystem.h"    // CreateDirectory
#include "adios2/toolkit/remote/EVPathRemote.h"
#include "adios2/toolkit/remote/XrootdRemote.h"
#include "adios2/toolkit/transportman/TransportMan.h"
#include <adios2-perfstubs-interface.h>
#include <adios2sys/SystemTools.hxx>

#include <algorithm> // std:find in vector
#include <fstream>
#include <future>
#include <iostream>
#include <mutex>
#include <thread>

#include <nlohmann_json.hpp>

namespace adios2
{
namespace core
{
namespace engine
{

CampaignReader::CampaignReader(IO &io, const std::string &name, const Mode mode, helper::Comm comm)
: Engine("CampaignReader", io, name, mode, std::move(comm))
{
    m_ReaderRank = m_Comm.Rank();
    Init();
    m_IsOpen = true;
}

CampaignReader::~CampaignReader()
{
    if (m_IsOpen)
    {
        DestructorClose(m_FailVerbose);
    }
    m_IsOpen = false;
}

StepStatus CampaignReader::BeginStep(const StepMode mode, const float timeoutSeconds)
{
    // step info should be received from the writer side in BeginStep()
    // so this forced increase should not be here
    ++m_CurrentStep;

    if (m_Options.verbose > 1)
    {
        std::cout << "Campaign Reader " << m_ReaderRank << "   BeginStep() new step "
                  << m_CurrentStep << "\n";
    }

    // If we reach the end of stream (writer is gone or explicitly tells the
    // reader)
    // we return EndOfStream to the reader application
    if (m_CurrentStep == 2)
    {
        std::cout << "Campaign Reader " << m_ReaderRank
                  << "   forcefully returns End of Stream at this step\n";

        return StepStatus::EndOfStream;
    }

    // We should block until a new step arrives or reach the timeout

    // m_IO Variables and Attributes should be defined at this point
    // so that the application can inquire them and start getting data

    return StepStatus::OK;
}

void CampaignReader::PerformGets()
{
    if (m_Options.verbose > 1)
    {
        std::cout << "Campaign Reader " << m_ReaderRank << "     PerformGets()\n";
    }

    size_t nEngines = m_Engines.size();
    if (nEngines == 0)
    {
        m_NeedPerformGets = false;
        return;
    }

    size_t nextEngine = 0;
    std::mutex mutexNext;

    auto lf_GetNext = [&]() -> size_t {
        std::lock_guard<std::mutex> lockGuard(mutexNext);
        size_t reqidx = MaxSizeT;
        if (nextEngine < nEngines)
        {
            reqidx = nextEngine;
            ++nextEngine;
        }
        return reqidx;
    };

    auto lf_WaitForPerformGets = [&](const size_t threadID) -> bool {
        while (true)
        {
            const auto engineIdx = lf_GetNext();
            if (engineIdx > nEngines)
            {
                break;
            }
            m_Engines[engineIdx]->PerformGets();
        }
        return true;
    };

    size_t nThreads = std::min(nEngines, (size_t)64);
    std::vector<std::future<bool>> futures(nThreads - 1);

    // launch Threads-1 threads to process subsets of handles,
    // then main thread process the last subset
    for (size_t tid = 0; tid < nThreads - 1; ++tid)
    {
        futures[tid] = std::async(std::launch::async, lf_WaitForPerformGets, tid + 1);
    }

    // main thread runs last subset of reads
    lf_WaitForPerformGets(0);

    // wait for all async threads
    for (auto &f : futures)
    {
        f.get();
    }
    m_NeedPerformGets = false;
}

size_t CampaignReader::CurrentStep() const { return m_CurrentStep; }

void CampaignReader::EndStep()
{
    // EndStep should call PerformGets() if there are unserved GetDeferred()
    // requests
    if (m_NeedPerformGets)
    {
        PerformGets();
    }

    if (m_Options.verbose > 1)
    {
        std::cout << "Campaign Reader " << m_ReaderRank << "   EndStep()\n";
    }
}

// PRIVATE

void CampaignReader::Init()
{
    InitParameters();
    InitTransports();
}

void CampaignReader::InitParameters()
{
    const UserOptions::Campaign &opts = m_UserOptions.campaign;
    m_Options.active = false; // this is really just for Recording
    m_Options.hostname = opts.hostname;
    m_Options.campaignstorepath = opts.campaignstorepath;
    m_Options.cachepath = opts.cachepath;
    m_Options.verbose = opts.verbose;
    for (const auto &pair : m_IO.m_Parameters)
    {
        std::string key(pair.first);
        std::transform(key.begin(), key.end(), key.begin(), ::tolower);

        std::string value(pair.second);
        std::transform(value.begin(), value.end(), value.begin(), ::tolower);

        if (key == "verbose")
        {
            m_Options.verbose = std::stoi(value);
            if (m_Options.verbose < 0 || m_Options.verbose > 5)
                helper::Throw<std::invalid_argument>("Engine", "CampaignReader", "InitParameters",
                                                     "Method verbose argument must be an "
                                                     "integer in the range [0,5], in call to "
                                                     "Open or Engine constructor");
        }
        else if (key == "hostname")
        {
            m_Options.hostname = pair.second;
        }
        else if (key == "campaignstorepath")
        {
            m_Options.campaignstorepath = pair.second;
        }
        else if (key == "cachepath")
        {
            m_Options.cachepath = pair.second;
        }
        else if (key == "include-dataset")
        {
            m_IncludePatterns = helper::StringToVector(pair.second, ';');
            for (auto &s : m_IncludePatterns)
            {
                m_IncludePatternsRe.push_back(std::regex(s));
            }
        }
        else if (key == "exclude-dataset")
        {
            m_ExcludePatterns = helper::StringToVector(pair.second, ';');
            for (auto &s : m_ExcludePatterns)
            {
                m_ExcludePatternsRe.push_back(std::regex(s));
            }
        }
    }

    if (m_Options.hostname.empty())
    {
        m_Options.hostname = helper::GetClusterName();
    }

    m_LocalhostAliases.push_back(m_Options.hostname);
    for (auto &host : ADIOS::GetHostOptions())
    {
        if (host.second.front().protocol == HostAccessProtocol::LocalHost)
            m_LocalhostAliases.push_back(host.first);
    }

    if (m_Options.verbose > 0)
    {
        std::cout << "CampaignReader: \n";
        std::cout << "  Hostname = " << m_Options.hostname << std::endl;
        std::cout << "  Campaign Store Path = " << m_Options.campaignstorepath << std::endl;
        std::cout << "  Cache Path = " << m_Options.cachepath << std::endl;
        if (!m_IncludePatterns.empty())
        {
            std::cout << "  Include patterns = [";
            for (size_t idx = 0; idx < m_IncludePatterns.size(); ++idx)
            {
                std::cout << m_IncludePatterns[idx];
                if (idx < m_IncludePatterns.size() - 1)
                    std::cout << ", ";
            }
            std::cout << "]" << std::endl;
        }
        if (!m_ExcludePatterns.empty())
        {
            std::cout << "  Exclude patterns = [";
            for (size_t idx = 0; idx < m_ExcludePatterns.size(); ++idx)
            {
                std::cout << m_ExcludePatterns[idx];
                if (idx < m_ExcludePatterns.size() - 1)
                    std::cout << ", ";
            }
            std::cout << "]" << std::endl;
        }
        std::cout << "Local host aliases = [";
        for (size_t idx = 0; idx < m_LocalhostAliases.size(); ++idx)
        {
            std::cout << m_LocalhostAliases[idx];
            if (idx < m_LocalhostAliases.size() - 1)
                std::cout << ", ";
        }
        std::cout << "]" << std::endl;
    }
}

std::vector<char> in_memory_object;

void CampaignReader::ExtractInMemoryMetadataToDisk(const std::string name,
                                                   const std::vector<char> &memobj,
                                                   const std::string &cachePath)
{
    auto lf_Save = [&](std::string &path, std::vector<char> &blob) -> void {
        std::ofstream f;
        f.rdbuf()->pubsetbuf(0, 0);
        f.open(path, std::ios::out | std::ios::binary);
        f.write(blob.data(), blob.size());
        f.close();
    };

    if (memobj.size() < 64)
    {
        helper::Throw<std::runtime_error>(
            "Engine", "CampaignReader", "ExtractInMemoryMetadata",
            "The metadata blob is too small to be an ADIOS metadata object for dataset " + name);
    }

    constexpr size_t HEADERLENGTH = 8;
    const std::string BP5HEADER = "BP5     ";
    const std::string BP4HEADER = "BP4     ";
    uint64_t size_mdidx, size_md, size_mmd;
    char header[HEADERLENGTH + 1];
    const char *p = memobj.data();
    memcpy(header, p, HEADERLENGTH);
    header[HEADERLENGTH] = '\0';
    p = p + HEADERLENGTH;
    std::string hs(header);
    std::string path;
    if (hs == BP5HEADER)
    {
        memcpy(&size_md, p, sizeof(uint64_t));
        p = p + sizeof(uint64_t);
        memcpy(&size_mmd, p, sizeof(uint64_t));
        p = p + sizeof(uint64_t);
        memcpy(&size_mdidx, p, sizeof(uint64_t));
        p = p + sizeof(uint64_t);

        std::vector<char> blob;
        blob.resize(size_md);
        std::memcpy(blob.data(), p, size_md);
        p = p + size_md;
        path = cachePath + PathSeparator + "md.0";
        lf_Save(path, blob);

        blob.resize(size_mmd);
        std::memcpy(blob.data(), p, size_mmd);
        p = p + size_mmd;
        path = cachePath + PathSeparator + "mmd.0";
        lf_Save(path, blob);

        blob.resize(size_mdidx);
        std::memcpy(blob.data(), p, size_mdidx);
        p = p + size_mdidx;
        path = cachePath + PathSeparator + "md.idx";
        lf_Save(path, blob);
    }
    else if (hs == BP4HEADER)
    {
        memcpy(&size_md, p, sizeof(uint64_t));
        p = p + sizeof(uint64_t);
        memcpy(&size_mdidx, p, sizeof(uint64_t));
        p = p + sizeof(uint64_t);

        std::vector<char> blob;
        blob.resize(size_md);
        std::memcpy(blob.data(), p, size_md);
        p = p + size_md;
        path = cachePath + PathSeparator + "md.0";
        lf_Save(path, blob);

        blob.resize(size_mdidx);
        std::memcpy(blob.data(), p, size_mdidx);
        p = p + size_mdidx;
        path = cachePath + PathSeparator + "md.idx";
        lf_Save(path, blob);
    }
    else
    {
        helper::Throw<std::runtime_error>(
            "Engine", "CampaignReader", "ExtractInMemoryMetadata",
            "The metadata blob is neither BP4 nor BP5 metadata for dataset " + name);
    }
}

std::string CampaignReader::SaveRemoteMD(size_t dsIdx, size_t repIdx, adios2::core::IO &io,
                                         bool timeseries)
{
    std::string localPath, cachePath; // same for BP, but for HDF5 localpath=cachepath/<filename>
    CampaignDataset &ds = m_CampaignData.datasets[dsIdx];
    CampaignReplica &rep = ds.replicas[repIdx];

    std::string remotePath = m_CampaignData.directory[rep.dirIdx].path + "/" + rep.name;
    std::string taropt;
    CampaignVarInternalInfo *cvii = nullptr;

    if (rep.archiveIdx > 0)
    {
        auto itTarName = m_CampaignData.tarnames.find(rep.archiveIdx);
        if (itTarName != m_CampaignData.tarnames.end())
        {
            std::string tarpath = itTarName->second;
            remotePath = m_CampaignData.directory[rep.dirIdx].path + "/" + tarpath;
            taropt = m_CampaignData.GetTarIdx(dsIdx, repIdx);
            if (taropt.empty())
            {
                std::cout << "ERROR: Remote file " << remotePath
                          << " is in a TAR file but without offset info. Skip" << std::endl;
                return "";
            }
            if (m_Options.verbose > 0)
            {

                std::cout << "Open remote file in TAR file " << remotePath << " with tar indices "
                          << taropt << "\n";
            }
            io.SetParameter("TarInfo", taropt);
            io.SetEngine("BP5");
        }
    }

    const std::string remoteURL = m_CampaignData.hosts[rep.hostIdx].hostname + ":" + remotePath;
    localPath =
        m_Options.cachepath + PathSeparator + ds.uuid.substr(0, 3) + PathSeparator + ds.uuid;
    cachePath = localPath;
    if (m_Options.verbose > 0)
    {
        std::cout << "Open remote file " << remoteURL << " \n";
    }

    std::string keyhex;
    if (rep.hasKey)
    {
        if (m_Options.verbose > 0)
        {
            std::cout << "The dataset is key protected with key id "
                      << m_CampaignData.keys[rep.keyIdx].id << "\n";
        }
#ifdef ADIOS2_HAVE_SODIUM
        if (m_CampaignData.keys[rep.keyIdx].keyHex.empty())
        {
            // Retrieve key
            if (!m_ConnectionManager)
            {
                m_ConnectionManager = std::make_unique<Remote>(core::ADIOS::StaticGetHostOptions());
            }
            m_CampaignData.keys[rep.keyIdx].keyHex =
                m_ConnectionManager->GetKeyFromConnectionManager(
                    m_CampaignData.keys[rep.keyIdx].id);

            if (m_Options.verbose > 0)
            {
                std::cout << "-- Received key " << m_CampaignData.keys[rep.keyIdx].keyHex << "\n";
            }
        }

        if (m_CampaignData.keys[rep.keyIdx].keyHex == "0")
        {
            // We received no key, ignore files encrypted with this key
            std::cerr << "ERROR: don't have the key " << m_CampaignData.keys[rep.keyIdx].id
                      << " to decrypt " << ds.name << ". Ignoring this dataset." << std::endl;
            return "";
        }

        keyhex = m_CampaignData.keys[rep.keyIdx].keyHex;
#else
        helper::Throw<std::runtime_error>("Engine", "CampaignReader", "InitTransports",
                                          "ADIOS needs to be built with libsodium and with SST to "
                                          "be able to process protected campaign files");
#endif
    }

    std::string newLocalPath = localPath;

    // first file is HDF5's file to be opened as path
    // second file is min/max data if available
    // first file if TEXT's file to be opened as path
    bool setHDF5FilePath = true;
    bool cachedirCreated = false;
    if (rep.files.size())
    {
        for (auto &fileid : rep.files)
        {
            CampaignFile &cf = m_CampaignData.files[fileid];
            std::string path =
                localPath + PathSeparator + adios2sys::SystemTools::GetFilenameName(cf.name);
            if (ds.format == FileFormat::TEXT)
            {
                // TEXT -> create a variable, will read from DB directly, no local path
                CreateTextVariable(ds.name, cf.lengthOriginal, dsIdx, repIdx, false);
                newLocalPath = "";
                continue;
            }
            else if (ds.format == FileFormat::IMAGE)
            {
                // IMAGE -> create a variable, will read from DB directly, no local path
                std::string imgName =
                    ds.name + "/" + std::to_string(rep.x) + "x" + std::to_string(rep.y);
                CreateImageVariable(imgName, cf.lengthOriginal, dsIdx, repIdx, false);
                newLocalPath = "";
                continue;
            }
            else if (ds.format == FileFormat::HDF5)
            {
                if (!cachedirCreated)
                {
                    if (m_Options.verbose > 0)
                    {
                        std::cout << "    use local cache for metadata at " << cachePath << " \n";
                    }
                    helper::CreateDirectory(cachePath);
                    cachedirCreated = true;
                }
                m_CampaignData.SaveToFile(path, fileid, keyhex);
                if (setHDF5FilePath)
                {
                    newLocalPath = path;
                    setHDF5FilePath = false;
                }
            }
            else if (ds.format == FileFormat::ADIOS)
            {
                if (cf.name == "metadata")
                {
                    in_memory_object.resize(cf.lengthOriginal);
                    m_CampaignData.ReadToMemory(in_memory_object.data(), fileid, keyhex);
                    if (timeseries)
                    {
                        // time series does not work with in-memory objects, need to save to disk
                        if (!cachedirCreated)
                        {
                            if (m_Options.verbose > 0)
                            {
                                std::cout << "    use local cache for metadata at " << cachePath
                                          << " \n";
                            }
                            helper::CreateDirectory(cachePath);
                            cachedirCreated = true;
                        }
                        ExtractInMemoryMetadataToDisk(ds.name, in_memory_object, cachePath);
                    }
                    else
                    {
                        newLocalPath = "";
                    }
                }
                else if (cf.name != "profiling.json")
                {
                    if (!cachedirCreated)
                    {
                        if (m_Options.verbose > 0)
                        {
                            std::cout << "    use local cache for metadata at " << cachePath
                                      << " \n";
                        }
                        helper::CreateDirectory(cachePath);
                        cachedirCreated = true;
                    }
                    m_CampaignData.SaveToFile(path, fileid, keyhex);
                }
            }
        }
    }
    else
    {
        // a remote dataset without any embedded file
        if (ds.format == FileFormat::TEXT)
        {
            // TEXT -> create a variable, will read from remote
            cvii = CreateTextVariable(ds.name, rep.size, dsIdx, repIdx, false, remotePath, taropt);
        }
        else if (ds.format == FileFormat::IMAGE)
        {
            // IMAGE -> create a variable, will read from remote
            std::string imgName =
                ds.name + "/" + std::to_string(rep.x) + "x" + std::to_string(rep.y);
            cvii = CreateImageVariable(imgName, rep.size, dsIdx, repIdx, false, remotePath, taropt);
        }
        else
        {
            helper::Throw<std::invalid_argument>(
                "Engine", "CampaignReader", "Open",
                "ADIOS/HDF5 replicas must have embedded metadata file(s) but found none for " +
                    rep.name);
        }
    }

    // Handle https and s3 differently from other host accesses
    if (!m_CampaignData.hosts[rep.hostIdx].defaultProtocol.empty())
    {
        // https or s3 protocols handled here
        if (m_CampaignData.hosts[rep.hostIdx].defaultProtocol == "s3")
        {
            auto it = ADIOS::GetHostOptions().find(m_CampaignData.hosts[rep.hostIdx].hostname);
            if (it != ADIOS::GetHostOptions().end())
            {
                const HostConfig &ho = (it->second).front();
                if (ho.protocol == HostAccessProtocol::S3)
                {
                    const std::string endpointURL = ho.endpoint;
                    const std::string objPath =
                        m_CampaignData.directory[rep.dirIdx].path + "/" + rep.name;
                    Params p;
                    p.emplace("library", "awssdk");
                    p.emplace("endpoint", endpointURL);
                    p.emplace("cache", cachePath);
                    p.emplace("verbose", std::to_string(ho.verbose));
                    p.emplace("recheck_metadata", (ho.recheckMetadata ? "true" : "false"));
                    if (cvii)
                    {
                        cvii->params = p;
                        cvii->path = objPath;
                    }
                    else
                    {
                        io.AddTransport("File", p);
                        io.SetParameter("UUID", ds.uuid);
                        io.SetEngine("BP5");
                    }
                    newLocalPath = remotePath;

                    if (ho.isAWS_EC2)
                    {
                        adios2sys::SystemTools::PutEnv("AWS_EC2_METADATA_DISABLED=false");
                    }
                    else
                    {
                        adios2sys::SystemTools::PutEnv("AWS_EC2_METADATA_DISABLED=true");
                    }

                    if (ho.awsProfile.empty())
                    {
                        adios2sys::SystemTools::PutEnv("AWS_PROFILE=default");
                    }
                    else
                    {
                        std::string es = "AWS_PROFILE=" + ho.awsProfile;
                        adios2sys::SystemTools::PutEnv(es);
                    }
                }
            }
        }
        else if (m_CampaignData.hosts[rep.hostIdx].defaultProtocol == "https")
        {
            std::string url =
                "https://" + m_CampaignData.hosts[rep.hostIdx].longhostname + "/" + remotePath;
            Params p;
            p.emplace("library", "https");
            // p.emplace("hostname", m_CampaignData.hosts[rep.hostIdx].longhostname);
            // p.emplace("path", m_CampaignData.directory[rep.dirIdx].path + "/" + tarpath +
            // rep.name);
            p.emplace("cache", cachePath);
            p.emplace("verbose", std::to_string(m_Options.verbose));
            p.emplace("recheck_metadata", "false");
            if (cvii)
            {
                cvii->params = p;
                cvii->path = url;
            }
            else
            {
                io.AddTransport("File", p);
                io.SetParameter("UUID", ds.uuid);

                if (ds.format == FileFormat::HDF5)
                {
                    io.SetEngine("HDF5");
                }
                else
                {
                    io.SetEngine("BP5");
                }
            }
            if (m_Options.verbose > 0)
            {
                std::cout << "Open remote URL " << url << " \n";
            }
            newLocalPath = url;
        }
    }
    else
    {
        io.SetParameter("RemoteDataPath", remotePath);
        io.SetParameter("RemoteHost", m_CampaignData.hosts[rep.hostIdx].hostname);
        io.SetParameter("UUID", ds.uuid);
    }
    // Save info in cache directory for cache manager and for humans
    if (cachedirCreated)
    {
        std::ofstream f(localPath + PathSeparator + "info.txt");
        if (f.is_open())
        {
            f << "Campaign = " << m_Name << "\n";
            f << "Dataset = " << ds.name << "\n";
            f << "Replica idx = " << repIdx << "\n";
            f << "Replica = " << rep.name << "\n";
            f << "RemoteHost = " << m_CampaignData.hosts[rep.hostIdx].hostname << "\n";
            f << "RemoteDataPath = " << remotePath << "\n";
            f.close();
        }
    }

    // HDF5/TEXT point to the first file under the uuid folder in cache
    localPath = newLocalPath;

    return localPath;
}

void CampaignReader::InitTransports()
{
    std::string path = m_Name;
    if (!adios2sys::SystemTools::FileExists(path) && path[0] != '/' && path[0] != '\\' &&
        !m_Options.campaignstorepath.empty())
    {
        std::string path2 = m_Options.campaignstorepath + PathSeparator + m_Name;
        if (adios2sys::SystemTools::FileExists(path2))
        {
            path = path2;
        }
    }

    m_CampaignData.Open(path);
    m_CampaignData.ReadDatabase();

    if (m_Options.verbose > 0)
    {
        std::cout << "Database result:\n  version = " << m_CampaignData.version.version
                  << "\n  hosts:\n";

        for (size_t hostidx = 0; hostidx < m_CampaignData.hosts.size(); ++hostidx)
        {
            CampaignHost &h = m_CampaignData.hosts[hostidx];
            std::cout << "    " << hostidx + 1 << ". host = " << h.hostname
                      << "  long name = " << h.longhostname << "  directories: \n";
            for (size_t diridx = 0; diridx < h.dirIdx.size(); ++diridx)
            {
                std::cout << "      " << h.dirIdx[diridx] + 1
                          << ". dir = " << m_CampaignData.directory[h.dirIdx[diridx]].path;
                if (m_CampaignData.directory[h.dirIdx[diridx]].archive)
                {
                    std::cout << "  - Archive: "
                              << m_CampaignData.directory[h.dirIdx[diridx]].archiveSystemName;
                    for (size_t archiveidx : m_CampaignData.directory[h.dirIdx[diridx]].archiveIDs)
                    {
                        std::cout << "\n        " << h.dirIdx[diridx] + 1 << "." << archiveidx
                                  << " " << m_CampaignData.tarnames[archiveidx];
                    }
                }
                std::cout << "\n";
            }
        }
        std::cout << "  keys:\n";
        for (size_t keyidx = 0; keyidx < m_CampaignData.keys.size(); ++keyidx)
        {
            CampaignKey &k = m_CampaignData.keys[keyidx];
            std::cout << "    key = " << k.id << "\n";
        }
        std::cout << "  datasets:\n";
        for (auto &itDS : m_CampaignData.datasets)
        {
            CampaignDataset &ds = itDS.second;
            if (ds.tsid && (ds.format == FileFormat::HDF5 || ds.format == FileFormat::ADIOS))
                continue;
            std::cout << "    " << ds.name << "\n";
            std::cout << "      uuid: " << ds.uuid << "\n";
            std::cout << "      fmt : " << ds.format.ToString() << "\n";
            for (auto &itRep : ds.replicas)
            {
                CampaignReplica &rep = itRep.second;
                std::string tarpath;
                if (rep.archiveIdx > 0)
                {
                    auto itTarName = m_CampaignData.tarnames.find(rep.archiveIdx);
                    if (itTarName != m_CampaignData.tarnames.end())
                    {
                        tarpath = itTarName->second + "/";
                    }
                }
                std::cout << "      " << m_CampaignData.hosts[rep.hostIdx].hostname << ":"
                          << m_CampaignData.directory[rep.dirIdx].path << "/" << tarpath << rep.name
                          << "\n";
                if (rep.hasKey)
                {
                    std::cout << "          key: " << rep.keyIdx << "\n";
                }
                for (auto &fileid : rep.files)
                {
                    std::cout << "          file: " << m_CampaignData.files[fileid].name << "\n";
                }
            }
        }
        std::cout << "\n\n  time series:\n";
        for (auto &itTS : m_CampaignData.timeseries)
        {
            size_t tsid = itTS.first;
            CampaignTimeSeries &ts = itTS.second;
            if (ts.datasets.empty())
                continue;
            auto &ds = m_CampaignData.datasets[ts.datasets.begin()->second];
            if (ds.format == FileFormat::IMAGE || ds.format == FileFormat::TEXT)
                continue;
            std::cout << "    " << tsid << ". " << ts.name << "\n";
            for (auto &itDS : ts.datasets)
            {
                size_t tsorder = itDS.first;
                size_t dsid = itDS.second;
                CampaignDataset &ds = m_CampaignData.datasets[dsid];
                std::cout << "    " << ds.name << "\n";
                std::cout << "      tsorder: " << tsorder << "\n";
                std::cout << "      uuid: " << ds.uuid << "\n";
                std::cout << "      fmt : " << ds.format.ToString() << "\n";
                for (auto &itRep : ds.replicas)
                {
                    CampaignReplica &rep = itRep.second;
                    std::cout << "      " << m_CampaignData.hosts[rep.hostIdx].hostname << ":"
                              << m_CampaignData.directory[rep.dirIdx].path << "/" << rep.name
                              << "\n";
                    if (rep.hasKey)
                    {
                        std::cout << "          key: " << rep.keyIdx << "\n";
                    }
                    for (auto &fileid : rep.files)
                    {
                        std::cout << "          file: " << m_CampaignData.files[fileid].name
                                  << "\n";
                    }
                }
            }
        }
    }

    // process ADIOS/HDF5 datasets per time-series first
    for (auto &itTS : m_CampaignData.timeseries)
    {
        size_t tsIdx = itTS.first;
        CampaignTimeSeries &ts = itTS.second;

        if (!ts.datasets.size())
            continue;

        auto &ds = m_CampaignData.datasets[ts.datasets.begin()->second];
        if (ds.format == FileFormat::IMAGE || ds.format == FileFormat::TEXT)
            continue;

        std::string localCachePath =
            m_Options.cachepath + PathSeparator + ds.uuid.substr(0, 3) + PathSeparator + ds.uuid;
        std::string atsFilePath = localCachePath + PathSeparator + ts.name + ".ats";
        auto atsDir = adios2sys::SystemTools::GetFilenamePath(atsFilePath);
        helper::CreateDirectory(atsDir);
        if (m_Options.verbose > 0)
        {
            std::cout << "    " << tsIdx << ". " << ts.name << "  --> " << atsFilePath << std::endl;
        }
        std::ofstream atsfile(atsFilePath);
        size_t atsLines = 0;

        adios2::core::IO &io = m_IO.m_ADIOS.DeclareIO("CampaignReader-TS-" + std::to_string(tsIdx));
        for (auto &itDS : ts.datasets)
        {
            size_t tsorder = itDS.first;
            size_t dsIdx = itDS.second;
            CampaignDataset &ds = m_CampaignData.datasets[dsIdx];
            if (!Matches(ds.name))
                continue;
            std::string localPath;
            size_t repIdx = m_CampaignData.FindReplicaOnHost(dsIdx, m_LocalhostAliases);
            if (!repIdx)
            {
                auto reps = m_CampaignData.FindRemoteReplicas(dsIdx, ADIOS::GetHostOptions());
                size_t repIdx = reps.front();
                std::string localPath = SaveRemoteMD(dsIdx, repIdx, io, true);
                if (!localPath.empty())
                {
                    if (m_Options.verbose > 0)
                    {
                        std::cout << "      " << tsorder << ". " << ds.name << " local file "
                                  << localPath << "\n";
                    }
                    CampaignDataset &ds = m_CampaignData.datasets[dsIdx];
                    CampaignReplica &rep = ds.replicas[repIdx];
                    atsfile << "- localpath: " << localPath << "\n  remotepath: "
                            << m_CampaignData.directory[rep.dirIdx].path + PathSeparator + rep.name
                            << "\n  remotehost: " << m_CampaignData.hosts[rep.hostIdx].hostname
                            << "\n  uuid: " << ds.uuid << std::endl;
                    ++atsLines;
                }
                else if (!in_memory_object.size())
                {
                    if (m_Options.verbose > 0)
                    {
                        std::cout << "      " << tsorder << ". " << ds.name << " Skipping \n";
                    }
                }
            }
            else
            {
                CampaignReplica &rep = ds.replicas[repIdx];
                localPath = m_CampaignData.directory[rep.dirIdx].path + PathSeparator + rep.name;
                if (m_Options.verbose > 0)
                {
                    std::cout << "      " << ds.tsorder << ". " << ds.name << " local file "
                              << localPath << "\n";
                }
                atsfile << "- " << localPath << std::endl;
                ++atsLines;
            }
        }
        atsfile << "- end" << std::endl;
        atsfile.close();
        if (atsLines > 0)
            OpenDatasetWithADIOS(ts.name, ds.format, io, atsFilePath);
    }

    // process individual datasets not in any time-series (and all images/texts)
    for (auto &it : m_CampaignData.datasets)
    {
        size_t dsIdx = it.first;
        CampaignDataset &ds = it.second;
        if (ds.tsid && (ds.format == FileFormat::HDF5 || ds.format == FileFormat::ADIOS))
            continue;
        if (ds.format == FileFormat::IMAGE)
            continue;
        if (!Matches(ds.name))
            continue;

        adios2::core::IO &io = m_IO.m_ADIOS.DeclareIO("CampaignReader" + std::to_string(dsIdx));
        std::string localPath;
        std::string taropt;
        size_t repIdx = m_CampaignData.FindReplicaOnHost(dsIdx, m_LocalhostAliases);
        if (!repIdx)
        {
            auto reps = m_CampaignData.FindRemoteReplicas(dsIdx, ADIOS::GetHostOptions());
            size_t repIdx = reps.front();
            localPath = SaveRemoteMD(dsIdx, repIdx, io, false);
            if (!localPath.empty())
            {
                if (m_Options.verbose > 0)
                {
                    std::cout << "    " << ds.name << " local file " << localPath << "\n";
                }
            }
            else if (!in_memory_object.size())
            {
                if (m_Options.verbose > 0)
                {
                    std::cout << "    " << ds.name << " Skipping \n";
                }
            }
        }
        else
        {
            CampaignReplica &rep = ds.replicas[repIdx];
            if (rep.archiveIdx == 0)
            {
                localPath = m_CampaignData.directory[rep.dirIdx].path + PathSeparator + rep.name;
                if (m_Options.verbose > 0)
                {
                    std::cout << "Open local file " << localPath << "\n";
                }
            }
            else
            {
                auto itTarName = m_CampaignData.tarnames.find(rep.archiveIdx);
                if (itTarName != m_CampaignData.tarnames.end())
                {
                    std::string tarpath = itTarName->second;
                    localPath = m_CampaignData.directory[rep.dirIdx].path + PathSeparator + tarpath;
                    taropt = m_CampaignData.GetTarIdx(dsIdx, repIdx);
                    if (taropt.empty())
                    {
                        std::cout << "ERROR: Local file " << localPath
                                  << " is in a TAR file but without offset info. Skip" << std::endl;
                        continue;
                    }
                    if (m_Options.verbose > 0)
                    {

                        std::cout << "Open local file in TAR file " << localPath
                                  << " with tar indices " << taropt << "\n";
                    }
                    io.SetParameter("TarInfo", taropt);
                    io.SetEngine("BP5");
                }
                else
                {
                    localPath =
                        m_CampaignData.directory[rep.dirIdx].path + PathSeparator + rep.name;
                    if (m_Options.verbose > 0)
                    {
                        std::cout << "Open local file in archive dir " << localPath << "\n";
                    }
                }
            }

            if (ds.format == FileFormat::TEXT)
            {
                // TEXT -> create a variable
                CreateTextVariable(ds.name, adios2sys::SystemTools::FileLength(localPath), dsIdx,
                                   repIdx, true, localPath, taropt);
            }
        }

        if (ds.format == FileFormat::HDF5)
        {
            io.SetEngine("HDF5");
        }
        else if (ds.format == FileFormat::TEXT || ds.format == FileFormat::IMAGE)
        {
            continue;
        }
        else if (ds.format == FileFormat::Unknown)
        {
            continue;
        }
        OpenDatasetWithADIOS(ds.name, ds.format, io, localPath);
    }

    // process images separately as all resolutions are presented as different variables
    for (auto &it : m_CampaignData.datasets)
    {
        size_t dsIdx = it.first;
        CampaignDataset &ds = it.second;
        if (ds.format != FileFormat::IMAGE)
            continue;
        if (!Matches(ds.name))
            continue;
        std::string localPath;
        if (m_Options.verbose > 1)
        {
            std::cout << "-- Image " << ds.name << ":\n";
        }

        size_t localRepIdx = m_CampaignData.FindReplicaOnHost(dsIdx, m_LocalhostAliases);
        auto reps = m_CampaignData.FindRemoteReplicas(dsIdx, ADIOS::GetHostOptions());
        if (localRepIdx)
        {
            reps.insert(reps.begin(), localRepIdx);
        }

        for (auto repIdx : reps)
        {
            CampaignReplica &rep = ds.replicas[repIdx];
            if (m_Options.verbose > 1)
            {
                std::cout << "      " << m_CampaignData.hosts[rep.hostIdx].hostname << ":"
                          << m_CampaignData.directory[rep.dirIdx].path << PathSeparator << rep.name
                          << "\n";
            }

            // need to check if we already defined this image with this resolution in this loop
            std::string imgName =
                ds.name + "/" + std::to_string(rep.x) + "x" + std::to_string(rep.y);
            auto v = m_IO.InquireVariable<uint8_t>(imgName);
            if (v)
            {
                continue;
            }

            if (rep.files.size())
            {
                // this replica has embedded image
                // IMAGE -> create a variable, will read from DB directly, no local path
                if (m_Options.verbose > 1)
                {
                    std::cout << "        --- embedded image " << imgName << "\n";
                }
                auto fileid = *rep.files.begin();
                CreateImageVariable(imgName, m_CampaignData.files[fileid].lengthOriginal, dsIdx,
                                    repIdx, true);
            }
            else if (std::find(m_LocalhostAliases.begin(), m_LocalhostAliases.end(),
                               m_CampaignData.hosts[rep.hostIdx].hostname) !=
                     m_LocalhostAliases.end())
            {
                // this replica is local
                std::string taropt;
                if (rep.archiveIdx == 0)
                {
                    localPath =
                        m_CampaignData.directory[rep.dirIdx].path + PathSeparator + rep.name;
                    if (m_Options.verbose > 0)
                    {
                        std::cout << "Open local file " << localPath << "\n";
                    }
                }
                else
                {
                    auto itTarName = m_CampaignData.tarnames.find(rep.archiveIdx);
                    if (itTarName != m_CampaignData.tarnames.end())
                    {
                        std::string tarpath = itTarName->second;
                        localPath =
                            m_CampaignData.directory[rep.dirIdx].path + PathSeparator + tarpath;
                        taropt = m_CampaignData.GetTarIdx(dsIdx, repIdx);
                        if (taropt.empty())
                        {
                            std::cout << "ERROR: Local image " << localPath
                                      << " is in a TAR file but without offset info. Skip"
                                      << std::endl;
                            continue;
                        }
                        if (m_Options.verbose > 0)
                        {

                            std::cout << "Open local image in TAR file " << localPath
                                      << " with tar indices " << taropt << "\n";
                        }
                    }
                    else
                    {
                        localPath =
                            m_CampaignData.directory[rep.dirIdx].path + PathSeparator + rep.name;
                        if (m_Options.verbose > 1)
                        {
                            std::cout << "        --- local image " << localPath
                                      << " and resolution " << rep.x << "x" << rep.y << std::endl;
                        }
                    }
                }
                CreateImageVariable(imgName, adios2sys::SystemTools::FileLength(localPath), dsIdx,
                                    repIdx, true, localPath, taropt);
            }
            else
            {
                // this is a remote image
                adios2::core::IO &io =
                    m_IO.m_ADIOS.DeclareIO("CampaignReader" + std::to_string(dsIdx));
                localPath = SaveRemoteMD(dsIdx, repIdx, io, false);
                if (!localPath.empty())
                {
                    if (m_Options.verbose > 0)
                    {
                        std::cout << "    " << ds.name << " local file " << localPath << "\n";
                    }
                }
                else
                {
                    if (m_Options.verbose > 0)
                    {
                        std::cout << "    " << ds.name << " Skipping \n";
                    }
                }
                /*std::string imgName =
                    ds.name + "/" + std::to_string(rep.x) + "x" + std::to_string(rep.y);
                if (m_Options.verbose > 1)
                {
                    std::cout << "        --- remote image " << imgName << "\n";
                }
                CreateImageVariable(imgName, rep.size, dsIdx, repIdx, false);*/
            }
        }
    }
}

void CampaignReader::OpenDatasetWithADIOS(std::string prefixName, FileFormat format,
                                          adios2::core::IO &io, std::string &localPath)
{
    auto lf_Open = [&]() -> adios2::core::Engine & {
        if (localPath.empty())
        {
            std::string engineName(in_memory_object.data(), 8);
            helper::RightTrim(engineName);
            // in memory ADIOS metadata
            if (m_Options.verbose > 0)
            {
                std::cout << "     Open in-memory for " << prefixName << " using engine ["
                          << engineName << "] \n";
            }
            io.SetEngine(engineName);
            auto &e = io.Open(prefixName, m_OpenMode, m_Comm.Duplicate(), in_memory_object.data(),
                              in_memory_object.size());
            in_memory_object.clear();
            return e;
        }
        else
        {
            return io.Open(localPath, m_OpenMode, m_Comm.Duplicate());
        }
    };

    adios2::core::Engine &e = lf_Open();

    m_IOs.push_back(&io);
    m_Engines.push_back(&e);

    auto vmap = io.GetAvailableVariables();
    auto amap = io.GetAvailableAttributes();
    VarInternalInfo internalInfo(nullptr, m_IOs.size() - 1, m_Engines.size() - 1);

    for (auto &vr : vmap)
    {
        auto vname = vr.first;
        std::string fname = prefixName;
        std::string newname;
        if (format == FileFormat::HDF5)
        {
            newname = fname + vname;
        }
        if (!vname.empty() && vname[0] == '/')
        {
            newname = fname + vname;
        }
        else
        {
            newname = fname + "/" + vname;
        }

        const DataType type = io.InquireVariableType(vname);

        if (type == DataType::Struct)
        {
        }
#define declare_type(T)                                                                            \
    else if (type == helper::GetDataType<T>())                                                     \
    {                                                                                              \
        Variable<T> *vi = io.InquireVariable<T>(vname);                                            \
        Variable<T> v = DuplicateVariable(vi, m_IO, newname, internalInfo);                        \
    }

        ADIOS2_FOREACH_STDTYPE_1ARG(declare_type)
#undef declare_type
    }

    for (auto &ar : amap)
    {
        auto aname = ar.first;
        std::string fname = prefixName;
        std::string newname = fname + "/" + aname;

        const DataType type = io.InquireAttributeType(aname);

        if (type == DataType::Struct)
        {
        }
#define declare_type(T)                                                                            \
    else if (type == helper::GetDataType<T>())                                                     \
    {                                                                                              \
        Attribute<T> *ai = io.InquireAttribute<T>(aname);                                          \
        Attribute<T> v = DuplicateAttribute(ai, m_IO, newname);                                    \
    }

        ADIOS2_FOREACH_STDTYPE_1ARG(declare_type)
#undef declare_type
    }
}

void CampaignReader::DoClose(const int transportIndex)
{
    if (m_Options.verbose > 1)
    {
        std::cout << "Campaign Reader " << m_ReaderRank << " Close(" << m_Name << ")\n";
    }
    PerformGets();
    for (auto ep : m_Engines)
    {
        ep->Close();
    }
    m_CampaignData.Close();
    m_IsOpen = false;
}

void CampaignReader::CreateDatasetAttributes(const std::string type, const std::string &name,
                                             const size_t dsIdx, const size_t repIdx,
                                             const std::string localPath)
{
    m_IO.DefineAttribute<std::string>("__dataset_type__", type, name);
    bool isLocal = false;
    if (!localPath.empty())
        isLocal = true; // file is on current host

    if (m_CampaignData.datasets[dsIdx].replicas[repIdx].files.size() > 0)
        isLocal = true; // file is embedded in archive

    // FIXME: in cache?

    m_IO.DefineAttribute<std::string>("__dataset_location__", (isLocal ? "local" : "remote"), name);
}

CampaignReader::CampaignVarInternalInfo *
CampaignReader::CreateTextVariable(const std::string &name, const size_t len, const size_t dsIdx,
                                   const size_t repIdx, const bool local, const std::string &path,
                                   const std::string &taropt)
{
    // embedded TEXT -> create a variable, will read from DB directly, no local path
    // local TEXT in file or in a TAR file
    // remote TEXT not in DB: read it from remote place, either file or from TAR
    size_t taroffset = 0;
    size_t tarsize = 0;
    if (!taropt.empty())
    {
        helper::TarInfoMap tim = helper::StringToTarInfo(taropt);
        for (const auto &ti : tim)
        {
            taroffset = std::get<0>(ti.second);
            tarsize = std::get<1>(ti.second);
        }
    }
    size_t fileSize = (tarsize > 0 ? tarsize : len);
    Variable<char> &v =
        m_IO.DefineVariable<char>(name, Dims{fileSize}, Dims{0ULL}, Dims{fileSize}, false);
    v.m_AvailableStepsCount = 1;
    v.m_AvailableStepsStart = 0;
    CampaignVarInternalInfo internalInfo(&v, dsIdx, repIdx, local, path);
    internalInfo.taroffset = taroffset;
    internalInfo.tarsize = tarsize;
    auto pair = m_CampaignVarInternalInfo.emplace(v.m_Name, internalInfo);
    CreateDatasetAttributes("text", name, dsIdx, repIdx, (local ? path : ""));
    return &(pair.first->second);
}

CampaignReader::CampaignVarInternalInfo *
CampaignReader::CreateImageVariable(const std::string &name, const size_t len, const size_t dsIdx,
                                    const size_t repIdx, const bool local, const std::string &path,
                                    const std::string &taropt)
{
    // IMAGE -> create a variable, will read from DB directly, no local path
    // local IMAGE in file or in a TAR file
    // remote IMAGE not in DB: read it from remote place, either file or from TAR
    size_t taroffset = 0;
    size_t tarsize = 0;
    if (!taropt.empty())
    {
        helper::TarInfoMap tim = helper::StringToTarInfo(taropt);
        for (const auto &ti : tim)
        {
            taroffset = std::get<0>(ti.second);
            tarsize = std::get<1>(ti.second);
        }
    }
    size_t fileSize = (tarsize > 0 ? tarsize : len);
    Variable<uint8_t> &v =
        m_IO.DefineVariable<uint8_t>(name, Dims{fileSize}, Dims{0ULL}, Dims{fileSize}, false);
    v.m_AvailableStepsCount = 1;
    v.m_AvailableStepsStart = 0;
    CampaignVarInternalInfo internalInfo(&v, dsIdx, repIdx, local, path);
    internalInfo.taroffset = taroffset;
    internalInfo.tarsize = tarsize;
    auto pair = m_CampaignVarInternalInfo.emplace(v.m_Name, internalInfo);
    CreateDatasetAttributes("image", name, dsIdx, repIdx, (local ? path : ""));
    return &(pair.first->second);
}

void CampaignReader::DestructorClose(bool Verbose) noexcept { m_CampaignData.Close(); }

bool CampaignReader::Matches(const std::string &dsname)
{
    for (const auto &re : m_ExcludePatternsRe)
    {
        if (std::regex_match(dsname, re))
        {
            return false;
        }
    }

    if (!m_IncludePatternsRe.empty())
    {
        for (const auto &re : m_IncludePatternsRe)
        {
            if (std::regex_match(dsname, re))
            {
                return true;
            }
        }
        // If no match in include patterns and the list is not empty, return false
        return false;
    }

    // If no match in exclude list while include list is empty, return true
    return true;
}

// Remove the engine name from the var name, which must be of pattern
// <engineName>/<original var name>
/*static std::string RemoveEngineName(const std::string &varName,
                                    const std::string &engineName)
{
    auto le = engineName.size() + 1;
    auto v = varName.substr(le);
    return v;
}*/

MinVarInfo *CampaignReader::MinBlocksInfo(const VariableBase &Var, size_t Step) const
{
    auto it = m_VarInternalInfo.find(Var.m_Name);
    if (it != m_VarInternalInfo.end())
    {
        VariableBase *vb = reinterpret_cast<VariableBase *>(it->second.originalVar);
        Engine *e = m_Engines[it->second.engineIdx];
        MinVarInfo *MV = e->MinBlocksInfo(*vb, Step);
        if (MV)
        {
            return MV;
        }
    }
    else
    {
        auto it = m_CampaignVarInternalInfo.find(Var.m_Name);
        if (it != m_CampaignVarInternalInfo.end())
        {
            int ndims = static_cast<int>(Var.m_Shape.size());
            MinVarInfo *MV = new MinVarInfo(ndims, Var.m_Shape.data());
            MV->Step = 0;
            MV->Dims = ndims;
            MV->Shape = Var.m_Shape.data();
            MV->WasLocalValue = false;
            MV->IsValue = false;
            MV->IsReverseDims = false;
            MV->BlocksInfo.reserve(1);
            MinBlockInfo Blk;
            Blk.WriterID = 0;
            Blk.BlockID = 0;
            Blk.Start = Var.m_Start.data();
            Blk.Count = Var.m_Count.data();
            switch (m_CampaignData.datasets.at(it->second.dsIdx).format)
            {
            case FileFormat::TEXT:
                Blk.MinMax.Init(adios2::DataType::Char);
                Blk.MinMax.MinUnion.field_int8 = (int8_t)'a';
                Blk.MinMax.MaxUnion.field_int8 = (int8_t)'z';
                MV->BlocksInfo.push_back(Blk);
                break;
            case FileFormat::IMAGE:
                Blk.MinMax.Init(adios2::DataType::UInt8);
                Blk.MinMax.MinUnion.field_uint8 = 0;
                Blk.MinMax.MaxUnion.field_uint8 = 255;
                MV->BlocksInfo.push_back(Blk);
                break;
            default:
                break;
            }
            return MV;
        }
    }
    return nullptr;
}

bool CampaignReader::VarShape(const VariableBase &Var, const size_t Step, Dims &Shape) const
{
    auto it = m_VarInternalInfo.find(Var.m_Name);
    if (it != m_VarInternalInfo.end())
    {
        VariableBase *vb = reinterpret_cast<VariableBase *>(it->second.originalVar);
        Engine *e = m_Engines[it->second.engineIdx];
        return e->VarShape(*vb, Step, Shape);
    }
    else
    {
        auto it = m_CampaignVarInternalInfo.find(Var.m_Name);
        if (it != m_CampaignVarInternalInfo.end())
        {
            Shape = Var.m_Shape;
            return true;
        }
    }
    return false;
}

bool CampaignReader::VariableMinMax(const VariableBase &Var, const size_t Step,
                                    MinMaxStruct &MinMax)
{
    auto it = m_VarInternalInfo.find(Var.m_Name);
    if (it != m_VarInternalInfo.end())
    {
        VariableBase *vb = reinterpret_cast<VariableBase *>(it->second.originalVar);
        Engine *e = m_Engines[it->second.engineIdx];
        return e->VariableMinMax(*vb, Step, MinMax);
    }
    else
    {
        auto it = m_CampaignVarInternalInfo.find(Var.m_Name);
        if (it != m_CampaignVarInternalInfo.end())
        {
            switch (m_CampaignData.datasets[it->second.dsIdx].format)
            {
            case FileFormat::TEXT:
                MinMax.Init(adios2::DataType::Char);
                MinMax.MinUnion.field_int8 = (int8_t)'A';
                MinMax.MaxUnion.field_int8 = (int8_t)'Z';
                return true;
            case FileFormat::IMAGE:
                MinMax.Init(adios2::DataType::UInt8);
                MinMax.MinUnion.field_uint8 = 1;
                MinMax.MaxUnion.field_uint8 = 254;
                return true;
            default:
                return false;
            }
        }
    }
    return false;
}

std::string CampaignReader::VariableExprStr(const VariableBase &Var)
{
    auto it = m_VarInternalInfo.find(Var.m_Name);
    if (it != m_VarInternalInfo.end())
    {
        VariableBase *vb = reinterpret_cast<VariableBase *>(it->second.originalVar);
        Engine *e = m_Engines[it->second.engineIdx];
        return e->VariableExprStr(*vb);
    }
    return "";
}

void CampaignReader::GetVariableFromDB(std::string name, size_t dsIdx, size_t repIdx, DataType type,
                                       void *data)
{
    if (type != DataType::Char && type != DataType::UInt8)
    {
        helper::Throw<std::invalid_argument>(
            "Engine", "CampaignReader", "GetVariableFromDB",
            "Expected char/uint8_t variable but was called with a type " + ToString(type));
    }

    CampaignReplica &rep = m_CampaignData.datasets[dsIdx].replicas[repIdx];
    const size_t fileid = rep.files.at(0);
    std::string keyhex;
    if (rep.hasKey)
        keyhex = m_CampaignData.keys[rep.keyIdx].keyHex;
    m_CampaignData.ReadToMemory((char *)data, fileid, keyhex);
}

void CampaignReader::ReadRemoteFile(const std::string &remoteHost, const std::string &remotePath,
                                    const size_t offset, const size_t size, void *data)
{
    std::unique_ptr<Remote> remote = nullptr;
#ifdef ADIOS2_HAVE_XROOTD
    if (getenv("DoXRootD"))
    {
        remote = std::make_unique<XrootdRemote>(ADIOS::GetHostOptions());
        remote->Open("localhost", 1094, m_Name, m_OpenMode, true);
    }
    else
#endif
#ifdef ADIOS2_HAVE_SST
    {
        remote = std::make_unique<EVPathRemote>(ADIOS::GetHostOptions());
        int localPort = remote->LaunchRemoteServerViaConnectionManager(remoteHost);
        remote->OpenSimpleFile("localhost", localPort, remotePath);
    }
#endif
    // evaluate validity of object, not just that the pointer is non-NULL
    if (remote == nullptr || !(*remote))
    {
        helper::Throw<std::ios_base::failure>(
            "Engine", "CampaignReader", "ReadRemoteFile",
            "Cannot get remote connection to read file " + remoteHost + ":" + remotePath +
                ". Make sure connection manager is running, and the server specification for the "
                "host is valid.");
    }
    remote->Read(offset, size, data);
}

void CampaignReader::ReadRemoteFile(const std::string &remoteHost, const std::string &remotePath,
                                    const Params &params, const size_t offset, const size_t size,
                                    char *data)
{
    std::string library = params.at("library");
    helper::Comm comm;
    transportman::TransportMan tm(m_IO, comm);
    auto transport = tm.OpenFileTransport(remotePath, Mode::Read, params, false, false, comm);
    transport->Read(data, size, offset);
    transport->Close();
}

#define declare_type(T)                                                                            \
    void CampaignReader::DoGetSync(Variable<T> &variable, T *data) { GetSyncTCC(variable, data); } \
    void CampaignReader::DoGetDeferred(Variable<T> &variable, T *data)                             \
    {                                                                                              \
        GetDeferredTCC(variable, data);                                                            \
    }                                                                                              \
                                                                                                   \
    std::map<size_t, std::vector<typename Variable<T>::BPInfo>>                                    \
    CampaignReader::DoAllStepsBlocksInfo(const Variable<T> &variable) const                        \
    {                                                                                              \
        PERFSTUBS_SCOPED_TIMER("CampaignReader::AllStepsBlocksInfo");                              \
        auto it = m_VarInternalInfo.find(variable.m_Name);                                         \
        if (it == m_VarInternalInfo.end())                                                         \
        {                                                                                          \
            std::map<size_t, std::vector<typename core::Variable<T>::BPInfo>> allStepsBlocksInfo;  \
            /*allStepsBlocksInfo[0] = ;*/                                                          \
            return allStepsBlocksInfo;                                                             \
        }                                                                                          \
        Variable<T> *v = reinterpret_cast<Variable<T> *>(it->second.originalVar);                  \
        Engine *e = m_Engines[it->second.engineIdx];                                               \
        return e->AllStepsBlocksInfo(*v);                                                          \
    }                                                                                              \
                                                                                                   \
    std::vector<std::vector<typename Variable<T>::BPInfo>>                                         \
    CampaignReader::DoAllRelativeStepsBlocksInfo(const Variable<T> &variable) const                \
    {                                                                                              \
        PERFSTUBS_SCOPED_TIMER("CampaignReader::AllRelativeStepsBlocksInfo");                      \
        auto it = m_VarInternalInfo.find(variable.m_Name);                                         \
        Variable<T> *v = reinterpret_cast<Variable<T> *>(it->second.originalVar);                  \
        Engine *e = m_Engines[it->second.engineIdx];                                               \
        return e->AllRelativeStepsBlocksInfo(*v);                                                  \
    }                                                                                              \
                                                                                                   \
    std::vector<typename Variable<T>::BPInfo> CampaignReader::DoBlocksInfo(                        \
        const Variable<T> &variable, const size_t step) const                                      \
    {                                                                                              \
        PERFSTUBS_SCOPED_TIMER("CampaignReader::BlocksInfo");                                      \
        auto it = m_VarInternalInfo.find(variable.m_Name);                                         \
        Variable<T> *v = reinterpret_cast<Variable<T> *>(it->second.originalVar);                  \
        Engine *e = m_Engines[it->second.engineIdx];                                               \
        return e->BlocksInfo(*v, step);                                                            \
    }

ADIOS2_FOREACH_STDTYPE_1ARG(declare_type)
#undef declare_type

} // end namespace engine
} // end namespace core
} // end namespace adios2
