// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation


#include "precomp.hpp"

#include <string>
#include <sstream> // used in GModel::log


#include <ade/util/zip_range.hpp>   // util::indexed
#include <ade/util/checked_cast.hpp>

#include "opencv2/gapi/gproto.hpp"
#include "api/gnode_priv.hpp"
#include "compiler/gobjref.hpp"
#include "compiler/gmodel.hpp"

namespace cv { namespace gimpl {

ade::NodeHandle GModel::mkOpNode(GModel::Graph &g, const GKernel &k, const std::vector<GArg> &args, const std::string &island)
{
    ade::NodeHandle op_h = g.createNode();
    g.metadata(op_h).set(NodeType{NodeType::OP});
    //These extra empty {} are to please GCC (-Wmissing-field-initializers)
    g.metadata(op_h).set(Op{k, args, {}, {}});
    if (!island.empty())
        g.metadata(op_h).set(Island{island});
    return op_h;
}

ade::NodeHandle GModel::mkDataNode(GModel::Graph &g, const GOrigin& origin)
{
    ade::NodeHandle op_h = g.createNode();
    const auto id = g.metadata().get<DataObjectCounter>().GetNewId(origin.shape);
    g.metadata(op_h).set(NodeType{NodeType::DATA});

    GMetaArg meta;
    Data::Storage storage = Data::Storage::INTERNAL; // By default, all objects are marked INTERNAL

    if (origin.node.shape() == GNode::NodeShape::CONST_BOUNDED)
    {
        auto value = value_of(origin);
        meta       = descr_of(value);
        storage    = Data::Storage::CONST;
        g.metadata(op_h).set(ConstValue{value});
    }
    g.metadata(op_h).set(Data{origin.shape, id, meta, origin.ctor, storage});
    return op_h;
}

void GModel::linkIn(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t in_port)
{
    // Check if input is already connected
    for (const auto& in_e : opH->inEdges())
    {
        GAPI_Assert(g.metadata(in_e).get<Input>().port != in_port);
    }

    auto &op = g.metadata(opH).get<Op>();
    auto &gm = g.metadata(objH).get<Data>();

     // FIXME: check validity using kernel prototype
    GAPI_Assert(in_port < op.args.size());

    ade::EdgeHandle eh = g.link(objH, opH);
    g.metadata(eh).set(Input{in_port});

    // Replace an API object with a REF (G* -> GOBJREF)
    op.args[in_port] = cv::GArg(RcDesc{gm.rc, gm.shape, {}});
}

void GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t out_port)
{
    // FIXME: check validity using kernel prototype

    // Check if output is already connected
    for (const auto& out_e : opH->outEdges())
    {
        GAPI_Assert(g.metadata(out_e).get<Output>().port != out_port);
    }

    auto &op = g.metadata(opH).get<Op>();
    auto &gm = g.metadata(objH).get<Data>();

    GAPI_Assert(objH->inNodes().size() == 0u);

    ade::EdgeHandle eh = g.link(opH, objH);
    g.metadata(eh).set(Output{out_port});

    // TODO: outs must be allocated according to kernel protocol!
    const auto storage_with_port = ade::util::checked_cast<std::size_t>(out_port+1);
    const auto min_out_size = std::max(op.outs.size(), storage_with_port);
    op.outs.resize(min_out_size, RcDesc{-1,GShape::GMAT,{}}); // FIXME: Invalid shape instead?
    op.outs[out_port] = RcDesc{gm.rc, gm.shape, {}};
}

std::vector<ade::NodeHandle> GModel::orderedInputs(Graph &g, ade::NodeHandle nh)
{
    std::vector<ade::NodeHandle> sorted_in_nhs(nh->inEdges().size());
    for (const auto& in_eh : nh->inEdges())
    {
        const auto port = g.metadata(in_eh).get<cv::gimpl::Input>().port;
        GAPI_Assert(port < sorted_in_nhs.size());
        sorted_in_nhs[port] = in_eh->srcNode();
    }
    return sorted_in_nhs;
}

std::vector<ade::NodeHandle> GModel::orderedOutputs(Graph &g, ade::NodeHandle nh)
{
    std::vector<ade::NodeHandle> sorted_out_nhs(nh->outEdges().size());
    for (const auto& out_eh : nh->outEdges())
    {
        const auto port = g.metadata(out_eh).get<cv::gimpl::Output>().port;
        GAPI_Assert(port < sorted_out_nhs.size());
        sorted_out_nhs[port] = out_eh->dstNode();
    }
    return sorted_out_nhs;
}

void GModel::init(Graph& g)
{
    g.metadata().set(DataObjectCounter());
}

void GModel::log(Graph &g, ade::NodeHandle nh, std::string &&msg, ade::NodeHandle updater)
{
    std::string s = std::move(msg);
    if (updater != nullptr)
    {
        std::stringstream fmt;
        fmt << " (via " << updater << ")";
        s += fmt.str();
    }

    if (g.metadata(nh).contains<Journal>())
    {
        g.metadata(nh).get<Journal>().messages.push_back(s);
    }
    else
    {
        g.metadata(nh).set(Journal{{s}});
    }
}

// FIXME:
// Unify with GModel::log(.. ade::NodeHandle ..)
void GModel::log(Graph &g, ade::EdgeHandle eh, std::string &&msg, ade::NodeHandle updater)
{
    std::string s = std::move(msg);
    if (updater != nullptr)
    {
        std::stringstream fmt;
        fmt << " (via " << updater << ")";
        s += fmt.str();
    }

    if (g.metadata(eh).contains<Journal>())
    {
        g.metadata(eh).get<Journal>().messages.push_back(s);
    }
    else
    {
        g.metadata(eh).set(Journal{{s}});
    }
}

ade::NodeHandle GModel::detail::dataNodeOf(const ConstGraph &g, const GOrigin &origin)
{
    // FIXME: Does it still work with graph transformations, e.g. redirectWriter()??
    return g.metadata().get<Layout>().object_nodes.at(origin);
}

void GModel::redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
{
    std::vector<ade::EdgeHandle> ehh(from->outEdges().begin(), from->outEdges().end());
    for (auto e : ehh)
    {
        auto dst = e->dstNode();
        auto input = g.metadata(e).get<Input>();
        g.erase(e);
        linkIn(g, dst, to, input.port);
    }
}

void GModel::redirectWriter(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
{
    GAPI_Assert(from->inEdges().size() == 1);
    auto e = from->inEdges().front();
    auto op = e->srcNode();
    auto output = g.metadata(e).get<Output>();
    g.erase(e);
    linkOut(g, op, to, output.port);
}

GMetaArgs GModel::collectInputMeta(GModel::ConstGraph cg, ade::NodeHandle node)
{
    GAPI_Assert(cg.metadata(node).get<NodeType>().t == NodeType::OP);
    GMetaArgs in_meta_args(cg.metadata(node).get<Op>().args.size());

    for (const auto &e : node->inEdges())
    {
        const auto& in_data = cg.metadata(e->srcNode()).get<Data>();
        in_meta_args[cg.metadata(e).get<Input>().port] = in_data.meta;
    }

    return in_meta_args;
}


ade::EdgeHandle GModel::getInEdgeByPort(const GModel::ConstGraph& cg,
                                        const ade::NodeHandle&    nh,
                                              std::size_t         in_port)
{
    auto inEdges = nh->inEdges();
    const auto& edge = ade::util::find_if(inEdges, [&](ade::EdgeHandle eh) {
        return cg.metadata(eh).get<Input>().port == in_port;
    });
    GAPI_Assert(edge != inEdges.end());
    return *edge;
}

GMetaArgs GModel::collectOutputMeta(GModel::ConstGraph cg, ade::NodeHandle node)
{
    GAPI_Assert(cg.metadata(node).get<NodeType>().t == NodeType::OP);
    GMetaArgs out_meta_args(cg.metadata(node).get<Op>().outs.size());

    for (const auto &e : node->outEdges())
    {
        const auto& out_data = cg.metadata(e->dstNode()).get<Data>();
        out_meta_args[cg.metadata(e).get<Output>().port] = out_data.meta;
    }

    return out_meta_args;
}

bool GModel::isActive(const GModel::Graph &cg, const cv::gapi::GBackend &backend)
{
    return ade::util::contains(cg.metadata().get<ActiveBackends>().backends,
                               backend);
}

}} // cv::gimpl
