/**
 * src/include_diagram/model/diagram.cc
 *
 * Copyright (c) 2021-2023 Bartek Kryza <bkryza@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "diagram.h"

#include "util/error.h"
#include "util/util.h"

namespace clanguml::include_diagram::model {

common::model::diagram_t diagram::type() const
{
    return common::model::diagram_t::kInclude;
}

common::optional_ref<common::model::diagram_element> diagram::get(
    const std::string &full_name) const
{
    return find<source_file>(full_name);
}

common::optional_ref<common::model::diagram_element> diagram::get(
    const common::model::diagram_element::id_t id) const
{
    return find<source_file>(id);
}

void diagram::add_file(std::unique_ptr<common::model::source_file> &&f)
{
    // Don't add the same file more than once
    if (find<source_file>(f->id()))
        return;

    LOG_DBG("Adding source file: {}, {}", f->name(), f->fs_path().string());

    auto &ff = *f;

    assert(!ff.name().empty());
    assert(ff.id() != 0);

    element_view<source_file>::add(ff);

    auto p = ff.path();

    if (!f->path().is_empty()) {
        // If the parent path is not empty, ensure relative parent directories
        // of this source_file are in the diagram
        common::model::filesystem_path parent_path_so_far{
            common::model::path_type::kFilesystem};
        for (const auto &directory : f->path()) {
            auto source_file_path = parent_path_so_far | directory;
            if (parent_path_so_far.is_empty())
                source_file_path = {directory};

            auto dir = std::make_unique<common::model::source_file>(
                std::filesystem::path{source_file_path.to_string()});
            dir->set_type(common::model::source_file_t::kDirectory);

            assert(!dir->name().empty());

            if (!get_element(source_file_path).has_value()) {
                add_file(std::move(dir));

                LOG_DBG("Added directory '{}' at path '{}'", directory,
                    parent_path_so_far.to_string());
            }

            parent_path_so_far.append(directory);
        }
    }

    assert(p.type() == common::model::path_type::kFilesystem);

    add_element(p, std::move(f));
}

std::string diagram::to_alias(const std::string &full_name) const
{
    LOG_DBG("Looking for alias for {}", full_name);

    auto path = common::model::filesystem_path{full_name};

    if (path.is_empty())
        throw error::uml_alias_missing(
            fmt::format("Missing alias for '{}'", path.to_string()));

    auto source_file = get_element<common::model::source_file>(path);

    if (!source_file)
        throw error::uml_alias_missing(
            fmt::format("Missing alias for '{}'", path.to_string()));

    return source_file.value().alias();
}

const common::reference_vector<common::model::source_file> &
diagram::files() const
{
    return element_view<source_file>::view();
}

common::optional_ref<clanguml::common::model::diagram_element>
diagram::get_with_namespace(
    const std::string &name, const common::model::namespace_ &ns) const
{
    // Convert to preferred OS path
    std::filesystem::path namePath{name};
    auto namePreferred = namePath.make_preferred().string();

    auto element_opt = get(namePreferred);

    if (!element_opt) {
        // If no element matches, try to prepend the 'using_namespace'
        // value to the element and search again
        auto fully_qualified_name = ns | namePreferred;
        element_opt = get(fully_qualified_name.to_string());
    }

    return element_opt;
}

inja::json diagram::context() const
{
    inja::json ctx;
    ctx["name"] = name();
    ctx["type"] = "include";

    inja::json::array_t elements{};

    // Add files and directories
    for (const auto &f : files()) {
        elements.emplace_back(f.get().context());
    }

    ctx["elements"] = elements;

    return ctx;
}
} // namespace clanguml::include_diagram::model

namespace clanguml::common::model {
template <>
bool check_diagram_type<include_diagram::model::diagram>(diagram_t t)
{
    return t == diagram_t::kInclude;
}
}
