//
// Created by pgess on 3/14/15.
//

#include <clasplayer.h>
#include "query/containers.h"

using namespace std;
using namespace xreate::containers;
using namespace xreate;

const std::string ID_IMPLEMENTATIONS = "impl_fulfill_cluster";
const std::string ID_CLUSTERS = "var_cluster";
const std::string ID_PROTOTYPES = "proto_cluster";

ImplementationData
Query::queryImplementation(xreate::Symbol const &s) {

    typedef SymbolAttachments attachs;
    if (attachs::exists<ImplementationData>(s))
    {
        return SymbolAttachments::get<ImplementationData>(s);
    }

    return ImplementationData(s);
}

void
Query::init(ClaspLayer* clasp)
{
    if (flagIsDataLoaded) return;

    map<Symbol, Symbol> prototypes;
    map<Symbol, string> roots;

        //read all proto data
    auto range = clasp->query(ID_PROTOTYPES);
    for(ClaspLayer::ModelIterator atom = range.first; atom != range.second; ++atom) {
        auto data =  ClaspLayer::parse<SymbolPacked, SymbolPacked>(atom->second);
        Symbol root = clasp->dfgData.unpack(get<0> (data));
        Symbol prototype = clasp->dfgData.unpack(get<1> (data));

        prototypes[root] = prototype;
    }

        // fill implementation data for a data sources:
    range = clasp->query(ID_IMPLEMENTATIONS);
    for(ClaspLayer::ModelIterator atom = range.first; atom != range.second; ++atom)
    {
        auto data = ClaspLayer::parse<SymbolPacked, string>(atom->second);

        Symbol var  = clasp->dfgData.unpack(get<0>(data));
        string implSerialized  = get<1>(data);

            //data source, has no prototypes:
        if (!prototypes.count(var))
        {
            ImplementationData impl(var);
            SymbolAttachments::put<ImplementationData>(var, move(impl));
            continue;
        }

        roots.emplace(move(var), move(implSerialized));
    }

        //fill implementation dara for a cluster roots
    for (const pair<Symbol, string> &  root: roots)
    {
        Symbol prototype = prototypes[root.first];

        while (prototypes.count(prototype))        {
            prototype = prototypes.at(prototype);
        }

        ImplementationData impl(root.first, root.second, SymbolAttachments::get<ImplementationData>(prototype));
        SymbolAttachments::put<ImplementationData>(root.first, move(impl));
    }

    // read cluster data and fill implementation data for cluster members
    range = clasp->query(ID_CLUSTERS);
    for(ClaspLayer::ModelIterator atom = range.first; atom != range.second; ++atom)
    {
        auto info = ClaspLayer::parse<SymbolPacked, SymbolPacked>(atom->second);

        Symbol root =  clasp->dfgData.unpack(get<0>(info));
        Symbol child =  clasp->dfgData.unpack(get<1>(info));

        if (!(child == root)) {
            ImplementationData rootImpl = SymbolAttachments::get<ImplementationData>(root);
            SymbolAttachments::put<ImplementationData>(child, move(rootImpl));
        }

    }

    flagIsDataLoaded = true;
}

ImplementationData::ImplementationData(Symbol var)
{

    Expression varDecl = CodeScope::findDeclaration(var);
    switch (varDecl.op)
    {
        case Operator::LIST_RANGE:
            range=var;
            impl = ON_THE_FLY;
            break;

        case Operator::LIST:
            impl = LLVM_CONST_ARRAY;
            size = varDecl.getOperands().size();
            break;

        default:
            assert(false && "unable to determine proper implementation for the symbol");
            break;
    };
}

ImplementationData::ImplementationData(Symbol var, std::string implSerialized, const ImplementationData& implPrototype)
{
    if (implSerialized == "llvm_array")
    {
        impl = LLVM_ARRAY;
        size = implPrototype.size;

    } else if (implSerialized == "llvm_const_array") {
        impl = LLVM_CONST_ARRAY;
        size = implPrototype.size;

    } else if (implSerialized == "on_the_fly") {
        impl = ON_THE_FLY;
        range = implPrototype.range;

    } else {
        assert(false && "unable to determine proper implementation for the symbol");
    }
}

std::pair<xreate::Expression, xreate::Expression>
ImplementationData::getRange()
{
    switch (impl) {
        case ON_THE_FLY: {
            Expression decl = CodeScope::findDeclaration(range);

            assert(decl.getOperands().size() == 2);
            return make_pair(decl.getOperands()[0], decl.getOperands()[1]);
        }

        default: {
            return make_pair(Expression(Atom<Number_t>(0)), Expression(Atom<Number_t>(size - 1)));
        }
    };
}
