/**
 * @file where_provenance.cpp
 * @brief SQL function @c provsql.where_provenance() – column-level provenance.
 *
 * Implements @c provsql.where_provenance(), which evaluates the
 * where-provenance of a query result tuple.  Where-provenance identifies
 * the base-relation cells (table, row, column) from which each output
 * value was copied.
 *
 * The function builds a @c WhereCircuit from the mmap-backed circuit store
 * (using SPI to obtain gate information), evaluates it via
 * @c WhereCircuit::evaluate(), and returns the result as a PostgreSQL
 * text-array value containing @c "table.tid.position" locator strings.
 */
extern "C" {

#include "postgres.h"
#include "fmgr.h"
#include "catalog/pg_type.h"
#include "utils/uuid.h"
#include "executor/spi.h"
#include "utils/builtins.h"

#include "provsql_shmem.h"
#include "provsql_utils.h"

PG_FUNCTION_INFO_V1(where_provenance);
}

#include <algorithm>
#include <utility>
#include <sstream>

#include "WhereCircuit.h"
#include "provsql_utils_cpp.h"

using namespace std;

/**
 * @brief Parse a PostgreSQL text representation of an array of integer pairs.
 * @param s  String in the form @c {{a,b},{c,d},...}.
 * @return   Vector of (first, second) integer pairs.
 */
static vector<pair<int,int> > parse_array(string s)
{
  s=s.substr(1,s.size()-2); // Remove initial '{' and final '}'

  vector<pair<int,int> > result;
  istringstream iss(s);
  string p;

  while(getline(iss, p, '}'))
  {
    if(!p.empty() && p[0]==',')
      p=p.substr(1);

    int k=p.find(",",1);
    string s1=p.substr(1,k-1);
    int i1;
    if(s1=="NULL")
      i1=0;
    else
      i1=stoi(p.substr(1,k-1));
    int i2=stoi(p.substr(k+1,p.size()-k));
    result.push_back(make_pair(i1,i2));
  }

  return result;
}

/**
 * @brief Build a JSON where-provenance description for the circuit rooted at @p token.
 * @param token  Datum containing the root provenance gate UUID.
 * @return       JSON string describing the where-provenance.
 */
static string where_provenance_internal
  (Datum token)
{
  Datum arguments[1]={token};
  const constants_t constants = get_constants(true);
  Oid argtypes[1]={constants.OID_TYPE_UUID};
  char nulls[1] = {' '};

  SPI_connect();

  WhereCircuit c;

  if(SPI_execute_with_args(
       "SELECT * FROM provsql.sub_circuit_for_where($1)", 1, argtypes, arguments, nulls, true, 0)
     == SPI_OK_SELECT) {
    int proc = SPI_processed;
    TupleDesc tupdesc = SPI_tuptable->tupdesc;
    SPITupleTable *tuptable = SPI_tuptable;

    for (int i = 0; i < proc; i++)
    {
      HeapTuple tuple = tuptable->vals[i];

      string f = SPI_getvalue(tuple, tupdesc, 1);
      string type = SPI_getvalue(tuple, tupdesc, 3);
      if(type == "input") {
        char *table = SPI_getvalue(tuple, tupdesc, 4);
        if(table == nullptr)
          provsql_error(
            "where_provenance: input gate %s could not be traced back to a "
            "provenance-tracked relation (it may belong to a table that was "
            "dropped, or to one outside the search_path)", f.c_str());
        int nb_columns = stoi(SPI_getvalue(tuple, tupdesc, 5));

        c.setGateInput(f, table, nb_columns);
      } else {
        auto id=c.getGate(f);

        if(type == "times" || type == "assumed" || type == "annotation") {
          /* assumed and annotation are single-child wrappers; a
           * single-wire TIMES is the identity in WhereCircuit semantics (the
           * Locator-set union of one child is just that child's), so funnel
           * all three through the same case. */
          c.setGate(f, WhereGate::TIMES);
        } else if(type == "plus") {
          c.setGate(f, WhereGate::PLUS);
        } else if(type == "project") {
          vector<pair<int,int> > v = parse_array(SPI_getvalue(tuple, tupdesc, 7));
          sort(v.begin(), v.end(), [](auto &left, auto &right) {
            return left.second < right.second;
          });
          vector<int> infos;
          for(auto p : v) {
            infos.push_back(p.first);
          }
          c.setGateProjection(f, std::move(infos));
        } else if(type == "eq") {
          vector<pair<int,int> > v = parse_array(std::string("{")+SPI_getvalue(tuple, tupdesc, 6)+"}");
          if(v.size()!=1)
            provsql_error("Incorrect extra information on eq gate");
          c.setGateEquality(f, v[0].first, v[0].second);
        } else if(type == "monusr" || type == "monusl" || type == "monus") {
          provsql_error("Where-provenance of non-monotone query not supported");
        } else {
          provsql_error("Where-provenance does not support gates of type %s", type.c_str());
        }
        c.addWire(id, c.getGate(SPI_getvalue(tuple, tupdesc, 2)));
      }
    }
  } else {
    provsql_error("SPI_execute_with_args failed on provsql.sub_circuit_for_where");
  }

  SPI_finish();

  auto gate = c.getGate(UUIDDatum2string(token));

  vector<set<WhereCircuit::Locator> > v = c.evaluate(gate);

  ostringstream os;
  os << "{";
  bool ofirst=true;
  for(auto s : v) {
    if(!ofirst)
      os << ",";
    os << "[";
    bool ifirst=true;
    for(auto l : s) {
      if(!ifirst)
        os << ";";
      os << l.toString();
      ifirst=false;
    }
    os << "]";
    ofirst=false;
  }
  os << "}";

  return os.str();
}

/** @brief PostgreSQL-callable wrapper for where_provenance(). */
Datum where_provenance(PG_FUNCTION_ARGS)
{
  try {
    Datum token = PG_GETARG_DATUM(0);

    PG_RETURN_TEXT_P(cstring_to_text(where_provenance_internal(token).c_str()));
  } catch(const std::exception &e) {
    provsql_error("where_provenance: %s", e.what());
  } catch(...) {
    provsql_error("where_provenance: Unknown exception");
  }
}
