// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#include "trace.h"
#include "servicing_index.h"

static const pal::char_t* DOTNET_SERVICING_INDEX_TXT = _X("dotnet_servicing_index.txt");

servicing_index_t::servicing_index_t(const pal::string_t& svc_dir)
{
    m_patch_root = svc_dir;
    if (!m_patch_root.empty())
    {
        m_index_file.assign(m_patch_root);
        append_path(&m_index_file, DOTNET_SERVICING_INDEX_TXT);
    }
    m_parsed = m_index_file.empty() || !pal::file_exists(m_index_file);
}

bool servicing_index_t::find_redirection(
        const pal::string_t& package_name,
        const pal::string_t& package_version,
        const pal::string_t& package_relative,
        pal::string_t* redirection)
{
    ensure_redirections();

    redirection->clear();

    if (m_redirections.empty())
    {
        return false;
    }

    pal::stringstream_t stream;
    stream << package_name << _X("|") << package_version << _X("|") << package_relative;

    auto iter = m_redirections.find(stream.str());
    if (iter != m_redirections.end())
    {
        pal::string_t full_path = m_patch_root;
        append_path(&full_path, iter->second.c_str());
        if (pal::file_exists(full_path))
        {
            *redirection = full_path;
            trace::verbose(_X("Servicing %s with %s"), stream.str().c_str(), redirection->c_str());
            return true;
        }
        trace::verbose(_X("Serviced file %s doesn't exist"), full_path.c_str());
    }

    trace::verbose(_X("Entry %s not serviced or file doesn't exist"), stream.str().c_str());
    return false;
}

void servicing_index_t::ensure_redirections()
{
    if (m_parsed)
    {
        return;
    }

    pal::ifstream_t fstream(m_index_file);
    if (!fstream.good())
    {
        return;
    }

    pal::stringstream_t sstream;
    std::string line;
    while (std::getline(fstream, line))
    {
        pal::string_t str;
        pal::to_palstring(line.c_str(), &str);

        // Can interpret line as "package"?
        pal::string_t prefix = _X("package|");
        if (str.find(prefix) != 0)
        {
            continue;
        }

        pal::string_t name, version, relative;
        pal::string_t* tokens[] = { &name, &version, &relative };
        pal::string_t delim[] = { pal::string_t(_X("|")), pal::string_t(_X("|")), pal::string_t(_X("=")) };

        bool bad_line = false;

        size_t from = prefix.length();
        for (size_t cur = 0; cur < (sizeof(delim) / sizeof(delim[0])); ++cur)
        {
            size_t pos = str.find(delim[cur], from);
            if (pos == pal::string_t::npos)
            {
                bad_line = true;
                break;
            }

            tokens[cur]->assign(str.substr(from, pos - from));
            from = pos + 1;
        }

        if (bad_line)
        {
            trace::error(_X("Invalid line in servicing index. Skipping..."));
            continue;
        }

        // Save redirection for this package.
        sstream.str(_X(""));
        sstream << name << _X("|") << version << _X("|") << relative;

        if (trace::is_enabled())
        {
            trace::verbose(_X("Adding servicing entry %s => %s"), sstream.str().c_str(), str.substr(from).c_str());
        }

        // Store just the filename.
        pal::string_t redir = str.substr(from);
        if (_X('/') != DIR_SEPARATOR)
        {
            replace_char(&redir, _X('/'), DIR_SEPARATOR);
        }
        m_redirections.emplace(sstream.str(), redir);
    }

    m_parsed = true;
}
