
extern "C" {

#include "postgres.h"

#include "funcapi.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "catalog/pg_type_d.h"

#include <stdlib.h>
#include <unistd.h>

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(set_of_kpos);

}

#define MAX_KPOS  10

typedef struct s_KPos {
   long long  Betrag;
   int        Konto;
   short      Sign;
   short      BTyp;
} t_KPos;

long long fast_atoi( const char *&b ) {
    long long res  = 0;
    bool      sign = false;
    unsigned  d;

    while ( *b ) {
      if ( (d = *b - '0') <= 9 )
        res = (res << 1) + (res << 3) + d;
      else if ( *b == '-' )
        sign = true;
      else if ( *b != '.' && *b != '+' )      // Punkt und Plus ignorieren
        break;                                // Bei anderen Zeichen Abbruch

      b++;
    }

    if ( *b == ',' || *b == ';' )             // Trenner am Ende überspringen
      b++;

    return sign ? -res : res;
}

int split_kpos( t_KPos kpos[], short allBTyp[], const int kpMax, const char s[] ) {
  int         pos = 0;
  const char *p   = s;

  while ( *p && pos < kpMax ) {

    kpos[pos].Betrag  = fast_atoi( p );
    kpos[pos].Konto   = fast_atoi( p );
    kpos[pos].Sign    = fast_atoi( p );
    kpos[pos].BTyp    = fast_atoi( p );

    pos++;
  }

  return pos;
}

#define N_ATTS    6

typedef struct s_TypInfo {
  int16        typlen;
  bool         typbyval;
  char         typalign;
  int          attOids[N_ATTS];
  const char  *attNames[N_ATTS];
  bool         nulls[N_ATTS];
} t_TypInfo;

const t_TypInfo& init_tinfo( FunctionCallInfo &fcinfo )
{
  static  volatile bool isInit  = false;
  static  t_TypInfo     tinfo;

  // Initialisierung der Typinformation einmal innerhalb einer Session
  if ( ! isInit ) {
    TupleDesc tupleDesc;
    Oid       outTypeId;

    get_call_result_type( fcinfo, &outTypeId, &tupleDesc );

    elog( INFO, "pid: %i, outTypeId: %i", getpid(), outTypeId );

    Oid outTypRelId = get_typ_typrelid( outTypeId );

    get_typlenbyvalalign( outTypeId, &tinfo.typlen, &tinfo.typbyval, &tinfo.typalign );
    elog( INFO, "outTypRelId: %i, typlen: %i, typbyval: %i, typalign: %c"
        , outTypRelId, tinfo.typlen, tinfo.typbyval, tinfo.typalign );

    for ( int n = 0; n < N_ATTS; n++ ) {
      tinfo.attNames[n] = get_attname ( outTypRelId, n+1, true );
      tinfo.attOids[n]  = get_atttype ( outTypRelId, n+1 );
      tinfo.nulls[n]    = false;
//       elog(INFO, "attributeName/OID: %10s, %3i", tinfo.attNames[n], tinfo.attOids[n] );
    }

    isInit = true;
  }

  return tinfo;
}

int create_result_from_kpos( Datum result[], const t_TypInfo &tinfo, const char txt[] )
{
  t_KPos  kpos   [MAX_KPOS];
  short   allBtyp[MAX_KPOS];
  bool    nulls  [N_ATTS]; memcpy( nulls, tinfo.nulls, sizeof(nulls) );
  Datum   values [N_ATTS];

  int       count     = split_kpos( kpos, allBtyp, MAX_KPOS, txt );
  TupleDesc tupleDesc = CreateTemplateTupleDesc( N_ATTS );

  for ( int n = 0; n < N_ATTS; n++ )
    TupleDescInitEntry( tupleDesc, n+1, tinfo.attNames[n], tinfo.attOids[n], -1, 0 );
  tupleDesc = BlessTupleDesc( tupleDesc );

  for ( int i = 0; i < count; i++ ) {
//     elog(INFO, "%2i: %8i %5i %5i %10lli", i+1, kpos[i].Konto, kpos[i].Sign, kpos[i].BTyp, kpos[i].Betrag );

    values[0] = Int16GetDatum( (short)i+1      );
    values[1] = Int16GetDatum( (short)count    );
    values[2] = Int32GetDatum( kpos[i].Konto   );
    values[3] = Int16GetDatum( kpos[i].Sign    );
    values[4] = Int16GetDatum( kpos[i].BTyp    );
    values[5] = Int64GetDatum( kpos[i].Betrag  );

    result[i] = HeapTupleGetDatum( heap_form_tuple( tupleDesc, values, nulls ) );
  }

  return count;
}

Datum set_of_kpos(PG_FUNCTION_ARGS)
{
  typedef struct {
    int     nextelem, numelems;
    Datum   result[MAX_KPOS];
  } t_user_ctx;

  FuncCallContext *funcctx;
  t_user_ctx      *userctx;

  // stuff done only on the first call of the function
  if ( SRF_IS_FIRSTCALL() ) {
    // create a function context for cross-call persistence
    funcctx = SRF_FIRSTCALL_INIT();

    // switch to memory context appropriate for multiple function calls
    MemoryContext oldcontext  = MemoryContextSwitchTo( funcctx->multi_call_memory_ctx );

    const t_TypInfo  &tinfo   = init_tinfo( fcinfo );
    const char       *txt     = TextDatumGetCString( PG_GETARG_TEXT_PP( 0 ) );

    // allocate memory for user context
    funcctx->user_fctx = userctx = (t_user_ctx*) palloc(sizeof(t_user_ctx));

    userctx->nextelem = 0;
    userctx->numelems = create_result_from_kpos( userctx->result, tinfo, txt );

    MemoryContextSwitchTo( oldcontext );
  }

  // stuff done on every call of the function
  funcctx  = SRF_PERCALL_SETUP();
  userctx  = (t_user_ctx*) funcctx->user_fctx;

  if ( userctx->nextelem < userctx->numelems ) {
    SRF_RETURN_NEXT( funcctx, userctx->result[ userctx->nextelem++ ] );
  } else {
    SRF_RETURN_DONE( funcctx );
  }
}

/*
# user postgres
umask 0022
cd ~/lib/src/
g++ -g -O3 -fPIC -I/usr/include/postgresql/12/server -c test.c && g++ -shared -o ~/lib/12/test.so test.o

umask 0022
cd ~/lib/src/
g++ -g -O3 -fPIC -I/usr/include/postgresql/13/server -c test.c && g++ -shared -o ~/lib/13/test.so test.o
rm -f test.o
*/

