/* 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/.
 *
 * aux.cpp
 *
 * Author: pgess <v.melnychenko@xreate.org>
 */

/**
 *  \file src/analysis/utils.h
 *  \brief Various reasoning related utilities
 */

#include "utils.h"
#include "resources.h"
#include "aux/kludges.h"
#include <boost/format.hpp>

using namespace std;

namespace xreate { namespace analysis {

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::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::Int);
    }

    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));
}

ASTSitePacked
getSite(const Expression& e, TranscendLayer* transcend){
  Attachments::put<ExprId_A>(e, e);
  return transcend->pack(ASTSite{e.id});
}

Expression
recognizeSite(const Gringo::Symbol& atom, const ExpandedType& schemaT, TranscendLayer* transcend)
{
  if(atom.type()!=Gringo::SymbolType::Fun) return Expression();
  string predicateName = atom.name().c_str();
  if (predicateName != SITE_SYMBOL_PREDICATE && predicateName != SITE_ANON_PREDICATE)
    return Expression();

  const ASTSitePacked& siteP = TranscendLayer::parseAtom<ASTSitePacked>(atom);
  const ASTSite& site = transcend->unpack(siteP);
  return site.getDefinition();
}

Expression
representTransExpression(const Gringo::Symbol& atom, ExpandedType schemaT, TranscendLayer* transcend) {
  const Expression siteExpr = recognizeSite(atom, schemaT, transcend);
  if (siteExpr.isValid()) return siteExpr;

  switch(schemaT->__operator) {
    case TypeOperator::NONE:
    {
      switch(schemaT->__value) {
        case TypePrimitive::I8:
        case TypePrimitive::I32:
        case TypePrimitive::I64:
        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 schemeOperands = schemaT->__operands.at(predicateId).__operator == TypeOperator::SLAVE
                                    ? dereferenceSlaveType(ExpandedType(schemaT->__operands.at(predicateId)), transcend)
                                    : ExpandedType(schemaT->__operands.at(predicateId));

      if (schemeOperands->__operator == TypeOperator::RECORD){
        Expression operandsList = representTransExpression(atom, schemeOperands, transcend);
        result.operands = move(operandsList.operands);

      } else if(!schemeOperands->isValid()) {
        assert(atom.args().size == 0);
        return result;

      } else {
        assert(false);
      }

      return result;
    }

    case TypeOperator::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.get();
      return result;
    }

    case TypeOperator::ARRAY:
    case TypeOperator::ALIAS:
    case TypeOperator::ACCESS:
    case TypeOperator::REF:
    case TypeOperator::VOID:
    case TypeOperator::GUARD:
    {
      assert(false);
      return Expression();
    }
  }

  assert(false);
  return Expression();
}

Expression
representVecStr(const std::vector<std::string>& srcList){
  Expression dstE(Operator::LIST, {});

  for(const string op: srcList){
    dstE.operands.push_back(Atom<String_t>(string(op)));
  }

  return dstE;
}

Expression
findAnnById(unsigned annId, const ExpandedType& annT, const std::list<Expression>& annotations){
  assert(annT->fields.size() > annId);
  const string& annExpectedS = annT->fields.at(annId);

  for(const Expression& e: annotations){
    if (e.type.fields.size() <= annId) continue;
    const string& annTestS = e.type.fields.at(annId);

    if (annTestS == annExpectedS) return e;
  }

  return Expression();
}

Expression
findAnnByType(const Expression &expr, const ExpandedType &annExpectedT, AST *ast){
  std::list<Expression> tagsL = kludges::getAnnotations(expr);

  for (const Expression& annActualE: tagsL){
    if (isSameVariantType(ast->expandType(annActualE.type), annExpectedT)){
      return annActualE;
    }
  }

  return Expression();
}

bool
isSameVariantType(const ExpandedType& a, const ExpandedType& b){
  assert(a->__operator == TypeOperator::VARIANT);
  assert(b->__operator == TypeOperator::VARIANT);

  if (a->fields.size()!= b->fields.size()) return false;
  for (size_t idx = 0; idx < a->fields.size(); ++idx){
    if (a->fields.at(idx) != b->fields.at(idx)) return false;
  }

  return true;
}

}} //end of xreate::analysis


