/*
// Copyright (c) 2017 Intel Corporation
//
// 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 "program_node.h"
#include "program_impl.h"
#include "primitive_inst.h"
#include "to_string_utils.h"
#include "json_object.h"


using namespace cldnn;

program_node::program_node(std::shared_ptr<primitive> prim, program_impl & prog) : desc(prim), myprog(prog), org_id(prim->id)
{
    if (prim)
        output_layout.data_padding = prim->output_padding;
}

void program_node::replace_dependency(size_t idx, program_node& new_dep)
{
    if (idx >= dependencies.size())
        return;
    if (dependencies[idx] == &new_dep)
        return;

    dependencies[idx]->users.remove(this);
    myprog.remove_if_dangling(*dependencies[idx]);

    dependencies[idx] = &new_dep;
    new_dep.users.push_back(this);
}

void program_node::replace_dependency(program_node const& old_dep, program_node& new_dep)
{
    for (size_t i = 0; i < dependencies.size(); ++i)
        if (dependencies[i] == &old_dep)
            return replace_dependency(i, new_dep);
}

std::vector<primitive_id> program_node::get_dependencies_ids() const
{
    std::vector<primitive_id> dep_ids;
    for (auto& dependency : dependencies)
        dep_ids.push_back(dependency->get_primitive()->id);
    return dep_ids;
}

void program_node::remove_dependency(size_t idx)
{
    if (idx >= dependencies.size())
        return;

    dependencies[idx]->users.remove(this);
    myprog.remove_if_dangling(*dependencies[idx]);
    dependencies.erase(dependencies.begin() + idx);
}

std::set<primitive_id> program_node::get_memory_dependencies() const
{
    return memory_dependencies;
}

void program_node::add_memory_dependency(primitive_id prim)
{
    memory_dependencies.insert(prim);
}

void program_node::add_memory_dependency(std::vector<primitive_id> prim_list)
{
    memory_dependencies.insert(prim_list.begin(),prim_list.end());
}

std::unique_ptr<json_composite> program_node::desc_to_json() const
{
    std::unique_ptr<json_composite> node_info = std::unique_ptr<json_composite>(new json_composite());
    node_info->add("ptr", "node_" + std::to_string(reinterpret_cast<uintptr_t>(this)));
    node_info->add("id", id());
    node_info->add("type", get_extr_type(typeid(*this).name()));
    node_info->add("internal", bool_to_str(this->is_type<internal_primitive>()));
    node_info->add("valid output layout", bool_to_str(valid_output_layout));

    json_composite output_layout_info;
    output_layout_info.add("data type", dt_to_str(output_layout.data_type));
    output_layout_info.add("format", fmt_to_str(output_layout.format));
    output_layout_info.add("size", output_layout.size.to_string());

    json_composite padding_info;
    padding_info.add("lower size", output_layout.data_padding.lower_size().to_string());
    padding_info.add("upper size", output_layout.data_padding.upper_size().to_string());
    output_layout_info.add("padding info", padding_info);

    node_info->add("output layout", output_layout_info);

    node_info->add("in data flow", bool_to_str(data_flow));
    node_info->add("constant", bool_to_str(constant));
    node_info->add("in data flow", bool_to_str(data_flow));
    node_info->add("output", bool_to_str(output));


    std::vector<std::string> deps_ptrs;
    {
        bool empty = true;
        auto itr = dependencies.begin();
        while (itr != dependencies.end())
        {
            if (empty)
            {
                empty = false;
            }
            deps_ptrs.push_back(std::to_string(reinterpret_cast<uintptr_t>(*itr++)));
        }
        if (deps_ptrs.empty())
        {
            deps_ptrs.push_back("null");
        }
    }
    node_info->add("dependencies", deps_ptrs);

    std::vector<std::string> users_ptrs;
    {
        bool empty = true;
        auto itr = users.begin();
        while (itr != users.end())
        {
            if (empty)
            {
                empty = false;
            }
            users_ptrs.push_back(std::to_string(reinterpret_cast<uintptr_t>(*itr++)));
        }
        if (users_ptrs.empty())
        {
            users_ptrs.push_back("null");
        }
    }
    node_info->add("users", users_ptrs);
    std::vector<std::string> impls;
    if (!selected_impl)
    {
        impls.push_back("null");
    }
    else
    {
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpotentially-evaluated-expression"
#endif
        impls.push_back(selected_impl->get_kernel_name());
#ifdef __clang__
#pragma clang diagnostic pop
#endif
    }
    node_info->add("implementation", impls);
    return node_info;
}

void program_node::remove_dependency(program_node & node)
{
    for (size_t i = 0; i < dependencies.size(); ++i)
        if (dependencies[i] == &node)
            remove_dependency(i);
}

bool program_node::is_detached(bool whole_branch)
{
    if (!users.empty())
        return false;
    if (!whole_branch && !dependencies.empty())
        return false;
    return true;
}

layout program_node::calc_output_layout() const
{
    return type()->calc_output_layout(*this);
}

layout program_node::get_output_layout(bool invalidate_users_if_changed)
{
    if (valid_output_layout)
        return output_layout;

    auto new_layout = calc_output_layout();
    set_output_layout(new_layout, invalidate_users_if_changed);
    return new_layout;
}

layout program_node::get_output_layout() const
{
    if (!valid_output_layout)
        throw std::runtime_error("Output layout not calculated");

    return output_layout;
}

layout program_node::get_non_padded_output_layout(bool invalidate_users_if_changed)
{
    auto out_layout = get_output_layout(invalidate_users_if_changed);
    auto result = layout({ out_layout.data_type, out_layout.format, out_layout.size });
    return result;
}

bool program_node::set_output_layout(layout new_layout, bool invalidate_users_if_changed)
{
    merge_output_padding(new_layout.data_padding);
    new_layout.data_padding = output_layout.data_padding;
    bool changed = (new_layout != output_layout);
    if (changed && invalidate_users_if_changed) //output_layout has changed! invalidate users
        invalidate_users();

    output_layout = new_layout;
    valid_output_layout = true;
    return changed;
}

bool program_node::recalc_output_layout(bool invalidate_users_if_changed)
{
    return set_output_layout(calc_output_layout(), invalidate_users_if_changed);
}

bool program_node::has_padded_dependency()
{
    return std::any_of(get_dependencies().begin(), get_dependencies().end(), [](program_node* node) { return node->is_padded(); });
}

bool program_node::has_padded_dependency() const
{
    return std::any_of(get_dependencies().begin(), get_dependencies().end(), [](const program_node* node) { return node->is_padded(); });
}

void program_node::invalidate_users() const
{
    for (auto& user : users)
    {
        if (user->valid_output_layout)
        {
            user->valid_output_layout = false;
            user->invalidate_users();
        }
    }
}

primitive_id details::internal_program_node_base::get_next_internal_id()
{
    static std::atomic<uint64_t> counter{ 0 };
    auto idx = counter++;
    return primitive_id("_cldnn_internal_") + std::to_string(idx);
}

details::internal_program_node_base::internal_program_node_base(program_impl & prog) : program_node(nullptr, prog), internal_id(get_next_internal_id())
{
}

void details::internal_program_node_base::set_implementation(std::unique_ptr<primitive_impl>&& impl)
{
    selected_impl = std::move(impl);
}

