// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// 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 "src/buildtool/execution_engine/dag/dag.hpp"

#include <algorithm>
#include <filesystem>
#include <iterator>

auto DependencyGraph::CreateOutputArtifactNodes(
    std::string const& action_id,
    std::vector<std::string> const& file_paths,
    std::vector<std::string> const& dir_paths,
    bool is_tree_action,
    bool is_tree_overlay_action)
    -> std::pair<std::vector<DependencyGraph::NamedArtifactNodePtr>,
                 std::vector<DependencyGraph::NamedArtifactNodePtr>> {
    if (is_tree_overlay_action) {  // create tree-overlay artifact
        auto artifact =
            ArtifactDescription::CreateTreeOverlay(action_id).ToArtifact();
        auto const node_id = AddArtifact(std::move(artifact));
        return std::make_pair(std::vector<NamedArtifactNodePtr>{},
                              std::vector<NamedArtifactNodePtr>{
                                  {{}, &(*artifact_nodes_[node_id])}});
    }

    if (is_tree_action) {  // create tree artifact
        auto artifact = ArtifactDescription::CreateTree(action_id).ToArtifact();
        auto const node_id = AddArtifact(std::move(artifact));
        return std::make_pair(std::vector<NamedArtifactNodePtr>{},
                              std::vector<NamedArtifactNodePtr>{
                                  {{}, &(*artifact_nodes_[node_id])}});
    }

    // create action artifacts
    auto node_creator = [this, &action_id](auto* nodes, auto const& paths) {
        for (auto const& artifact_path : paths) {
            auto artifact = ArtifactDescription::CreateAction(
                                action_id, std::filesystem::path{artifact_path})
                                .ToArtifact();
            auto const node_id = AddArtifact(std::move(artifact));
            nodes->emplace_back(NamedArtifactNodePtr{
                artifact_path, &(*artifact_nodes_[node_id])});
        }
    };

    std::vector<NamedArtifactNodePtr> file_nodes{};
    file_nodes.reserve(file_paths.size());
    node_creator(&file_nodes, file_paths);

    std::vector<NamedArtifactNodePtr> dir_nodes{};
    dir_nodes.reserve(dir_paths.size());
    node_creator(&dir_nodes, dir_paths);

    return std::make_pair(std::move(file_nodes), std::move(dir_nodes));
}

auto DependencyGraph::CreateInputArtifactNodes(
    ActionDescription::inputs_t const& inputs)
    -> std::optional<std::vector<DependencyGraph::NamedArtifactNodePtr>> {
    std::vector<NamedArtifactNodePtr> nodes{};

    for (auto const& [local_path, artifact_desc] : inputs) {
        auto artifact = artifact_desc.ToArtifact();
        auto const node_id = AddArtifact(std::move(artifact));
        nodes.push_back({local_path, &(*artifact_nodes_[node_id])});
    }
    return nodes;
}

auto DependencyGraph::CreateActionNode(Action const& action) noexcept
    -> DependencyGraph::ActionNode* {
    if (action.IsTreeAction() or action.IsTreeOverlayAction() or
        not action.Command().empty()) {
        auto const node_id = AddAction(action);
        return &(*action_nodes_[node_id]);
    }
    return nullptr;
}

auto DependencyGraph::LinkNodePointers(
    std::vector<NamedArtifactNodePtr> const& output_files,
    std::vector<NamedArtifactNodePtr> const& output_dirs,
    gsl::not_null<ActionNode*> const& action_node,
    std::vector<NamedArtifactNodePtr> const& input_nodes) noexcept -> bool {
    for (auto const& named_file : output_files) {
        if (not named_file.node->AddBuilderActionNode(action_node) or
            not action_node->AddOutputFile(named_file)) {
            return false;
        }
    }
    for (auto const& named_dir : output_dirs) {
        if (not named_dir.node->AddBuilderActionNode(action_node) or
            not action_node->AddOutputDir(named_dir)) {
            return false;
        }
    }

    for (auto const& named_node : input_nodes) {
        if (not named_node.node->AddConsumerActionNode(action_node) or
            not action_node->AddDependency(named_node)) {
            return false;
        }
    }

    action_node->NotifyDoneLinking();

    return true;
}

auto DependencyGraph::Add(std::vector<ActionDescription> const& actions)
    -> bool {
    return std::all_of(
        actions.begin(), actions.end(), [this](auto const& action) {
            return AddAction(action);
        });
}

auto DependencyGraph::Add(std::vector<ActionDescription::Ptr> const& actions)
    -> bool {
    return std::all_of(
        actions.begin(), actions.end(), [this](auto const& action) {
            return AddAction(*action);
        });
}

auto DependencyGraph::AddArtifact(ArtifactDescription const& description)
    -> ArtifactIdentifier {
    auto artifact = description.ToArtifact();
    auto id = artifact.Id();
    [[maybe_unused]] auto const node_id = AddArtifact(std::move(artifact));
    return id;
}

auto DependencyGraph::AddAction(ActionDescription const& description) -> bool {
    auto output_nodes = CreateOutputArtifactNodes(
        description.Id(),
        description.OutputFiles(),
        description.OutputDirs(),
        description.GraphAction().IsTreeAction(),
        description.GraphAction().IsTreeOverlayAction());
    auto* action_node = CreateActionNode(description.GraphAction());
    auto input_nodes = CreateInputArtifactNodes(description.Inputs());
    if (action_node == nullptr or not input_nodes.has_value() or
        (output_nodes.first.empty() and output_nodes.second.empty())) {
        return false;
    }

    return LinkNodePointers(
        output_nodes.first, output_nodes.second, action_node, *input_nodes);
}

auto DependencyGraph::AddAction(Action const& a) noexcept
    -> DependencyGraph::ActionNodeIdentifier {
    auto const& id = a.Id();
    auto const action_it = action_ids_.find(id);
    if (action_it != action_ids_.end()) {
        return action_it->second;
    }
    action_nodes_.emplace_back(ActionNode::Create(a));
    ActionNodeIdentifier node_id{action_nodes_.size() - 1};
    action_ids_[id] = node_id;
    return node_id;
}

auto DependencyGraph::AddAction(Action&& a) noexcept
    -> DependencyGraph::ActionNodeIdentifier {
    auto const& id = a.Id();
    auto const action_it = action_ids_.find(id);
    if (action_it != action_ids_.end()) {
        return action_it->second;
    }
    action_nodes_.emplace_back(ActionNode::Create(std::move(a)));
    ActionNodeIdentifier node_id{action_nodes_.size() - 1};
    action_ids_[id] = node_id;
    return node_id;
}

auto DependencyGraph::AddArtifact(Artifact const& a) noexcept
    -> DependencyGraph::ArtifactNodeIdentifier {
    auto const& id = a.Id();
    auto const artifact_it = artifact_ids_.find(id);
    if (artifact_it != artifact_ids_.end()) {
        return artifact_it->second;
    }
    artifact_nodes_.emplace_back(ArtifactNode::Create(a));
    ArtifactNodeIdentifier node_id{artifact_nodes_.size() - 1};
    artifact_ids_[id] = node_id;
    return node_id;
}

auto DependencyGraph::AddArtifact(Artifact&& a) noexcept
    -> DependencyGraph::ArtifactNodeIdentifier {
    auto id = a.Id();
    auto const artifact_it = artifact_ids_.find(id);
    if (artifact_it != artifact_ids_.end()) {
        return artifact_it->second;
    }
    artifact_nodes_.emplace_back(ArtifactNode::Create(std::move(a)));
    ArtifactNodeIdentifier node_id{artifact_nodes_.size() - 1};
    artifact_ids_[id] = node_id;
    return node_id;
}

auto DependencyGraph::ArtifactIdentifiers() const noexcept
    -> std::unordered_set<ArtifactIdentifier> {
    std::unordered_set<ArtifactIdentifier> ids;
    std::transform(
        std::begin(artifact_ids_),
        std::end(artifact_ids_),
        std::inserter(ids, std::begin(ids)),
        [](auto const& artifact_id_pair) { return artifact_id_pair.first; });
    return ids;
}

auto DependencyGraph::ArtifactNodeWithId(ArtifactIdentifier const& id)
    const noexcept -> DependencyGraph::ArtifactNode const* {
    auto it_to_artifact = artifact_ids_.find(id);
    if (it_to_artifact == artifact_ids_.end()) {
        return nullptr;
    }
    return &(*artifact_nodes_[it_to_artifact->second]);
}

auto DependencyGraph::ActionNodeWithId(ActionIdentifier const& id)
    const noexcept -> DependencyGraph::ActionNode const* {
    auto it_to_action = action_ids_.find(id);
    if (it_to_action == action_ids_.end()) {
        return nullptr;
    }
    return &(*action_nodes_[it_to_action->second]);
}
