/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Author: pgess <v.melnychenko@xreate.org>
 * Created on June 25, 2018, 3:25 PM
 *
 * \file    interpretation.cpp
 * \brief   interpretation
 */

#include "analysis/interpretation.h"

using namespace std;

namespace xreate{
namespace interpretation{

typedef vector<int> InstancePacked;

std::list<Expression>
generateAllInstancesInDomain2(const ExpandedType& domainT) {
    if(!domainT->isValid()) {
        return {Expression()};
    }
    assert(domainT->__operator == TypeOperator::VARIANT);
    std::list<Expression> results;
    int variantId = -1;

    bool flagDomainStateless = std::all_of(domainT->__operands.begin(), domainT->__operands.end(),
        [](const TypeAnnotation & subdomainT) {
            return !subdomainT.isValid();
        });

    for(const TypeAnnotation& subdomainT : domainT->__operands) {
        ++variantId;

        if(flagDomainStateless) {
            Expression result(Operator::VARIANT,{});
            result.setValueDouble(variantId);
            results.push_back(result);
            continue;
        }

        std::list<Expression> subresults = generateAllInstancesInDomain2(ExpandedType(subdomainT));
        for (const Expression& subresult : subresults) {
            Expression result(Operator::VARIANT,{});
            result.setValueDouble(variantId);
            result.operands.push_back(subresult);

            results.push_back(result);
        }
    }

    return results;
}

TypeAnnotation
collapseFnGroup(const std::list<Gringo::Symbol>& symbols) {
    Gringo::Symbol symbolAny = symbols.front();
    size_t operandsCount = symbolAny.args().size;

    TypeAnnotation resultT;
    resultT.__operands.reserve(operandsCount);

    for(size_t operandId = 0; operandId < operandsCount; ++operandId) {
        std::list<Gringo::Symbol> column;

        for(const Gringo::Symbol& row : symbols) {
            column.push_back(row.args()[operandId]);
        }

        TypeAnnotation operandT = collapseColumn(column);
        resultT.__operands.push_back(operandT);
    }

    if(resultT.__operands.size() == 1) {
        return resultT.__operands.front();
    }

    if(resultT.__operands.size() > 1) {
        resultT.__operator = TypeOperator::LIST_RECORD;
        return resultT;
    }

    return resultT;
}

TypeAnnotation
collapseColumn(const std::list<Gringo::Symbol>& symbols) {
    TypeAnnotation resultT;
    if(!symbols.size()) return resultT;

    Gringo::Symbol symbolAny = symbols.front();

    switch(symbolAny.type()) {
    case Gringo::SymbolType::Num:
    {
        return TypeAnnotation(TypePrimitive::Num);
    }

    case Gringo::SymbolType::Str:
    {
        return TypeAnnotation(TypePrimitive::String);
    }

    case Gringo::SymbolType::Fun:
    {
        map<string, list < Gringo::Symbol>> fnGroups;

        for(const Gringo::Symbol& row : symbols) {
            fnGroups[row.name().c_str()].push_back(row);
        }

        TypeAnnotation resultT;
        resultT.__operands.reserve(fnGroups.size());
        resultT.bindings.reserve(fnGroups.size());

        for(const auto& group : fnGroups) {
            if(!group.second.size()) continue;

            TypeAnnotation variantT = collapseFnGroup(group.second);
            Gringo::Symbol symbolAny = group.second.front();
            string variantName = symbolAny.name().c_str();
            resultT.fields.push_back(variantName);
            resultT.__operands.push_back(variantT);
        }

        resultT.__operator = TypeOperator::VARIANT;
        //        if(resultT.__operands.size() == 1) {
        //            return resultT.__operands.front();
        //        }
        return resultT;
    }

    case Gringo::SymbolType::Inf:
    case Gringo::SymbolType::Special:
    case Gringo::SymbolType::Sup:
    {
        break;
    }
    }

    assert(false);
    return TypeAnnotation();
}

ExpandedType
dereferenceSlaveType(ExpandedType t, const TranscendLayer* transcend) {
    assert(t->__operator == TypeOperator::SLAVE);
    const string& domain = t->__valueCustom;
    StaticModel model = transcend->query(domain);
    if(!model.size()) return ExpandedType(TypeAnnotation());

    std::list<Gringo::Symbol> symbols;
    for(auto row : model) {
        symbols.push_back(row.second);
    }
    return ExpandedType(collapseFnGroup(symbols));
}

Expression
representTransExpression(const Gringo::Symbol& atom, ExpandedType schemaT, TranscendLayer* transcend) {
    atom.print(std::cout); std::cout<<endl;
    switch(schemaT->__operator) {
    case TypeOperator::NONE:
    {
        switch(schemaT->__value) {
        case TypePrimitive::I8:
        case TypePrimitive::I32:
        case TypePrimitive::I64:
        case TypePrimitive::Num:
        case TypePrimitive::Int:
        {
            return Expression(Atom<Number_t>(atom.num()));
        }

        case TypePrimitive::String:
        {
            return Expression(Atom<String_t>(atom.string().c_str()));
        }

        case TypePrimitive::Invalid:
        case TypePrimitive::Bool:
        case TypePrimitive::Float:
        {
            assert(false);
            return Expression();
        }
        }
        break;
    }

    case TypeOperator::SLAVE:
    {
        ExpandedType contentT = dereferenceSlaveType(schemaT, transcend);
        return representTransExpression(atom, contentT, transcend);
    }

    case TypeOperator::VARIANT:
    {
        map<string, int> dictVariants;
        for(size_t variantId = 0; variantId < schemaT->fields.size(); ++variantId) {
            dictVariants.emplace(schemaT->fields.at(variantId), variantId);
        }

        string predicateName = atom.name().c_str();
        assert(dictVariants.count(predicateName));

        size_t predicateId = dictVariants.at(predicateName);
        Expression result(Operator::VARIANT,{});
        result.op = Operator::VARIANT;
        result.setValueDouble(predicateId);

        if(!schemaT->__operands.size()) return result;
        ExpandedType contentT = schemaT->__operands.at(predicateId).__operator == TypeOperator::SLAVE
            ? dereferenceSlaveType(ExpandedType(schemaT->__operands.at(predicateId)), transcend)
            : ExpandedType(schemaT->__operands.at(predicateId));

        //edge case, content's type is LIST_NAMED:
        if (contentT->__operator == TypeOperator::LIST_RECORD) {
            result.operands.push_back(representTransExpression(atom, contentT, transcend));

        } else if(!contentT->isValid()) {
            return result;

        } else {
            assert(atom.args().size);
            result.operands.push_back(representTransExpression(atom.args()[0], contentT, transcend));
        }

        return result;
    }

    case TypeOperator::LIST_RECORD:
    {
        const Gringo::SymSpan& operandsRaw = atom.args();
        size_t opCount = operandsRaw.size;
        assert(opCount == schemaT->__operands.size());

        size_t operandId = 0;
        std::vector<Expression> operands;
        operands.reserve(opCount);
        for(const TypeAnnotation operandT : schemaT->__operands) {
            operands.push_back(representTransExpression(operandsRaw[operandId], ExpandedType(operandT), transcend));
            ++operandId;
        }

        Expression result(Operator::LIST,{});
        result.operands = operands;
        result.type = schemaT;
        return result;
    }

    case TypeOperator::LIST_ARRAY:
    case TypeOperator::CALL:
    case TypeOperator::CUSTOM:
    case TypeOperator::ACCESS:
    case TypeOperator::LINK:
    {
        assert(false);
        return Expression();
    }
    }

    assert(false);
    return Expression();
}

}
} //end of xreate namespace
