#include "postgres.h"
#include "fmgr.h"
#include "funcapi.h"

#include "catalog/pg_type.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/date.h"
#include "utils/geo_decls.h"
#include "utils/inet.h"
#include "utils/nabstime.h"
#include "utils/numeric.h"
#include "utils/timestamp.h"
#include "utils/varbit.h"
#include "utils/lsyscache.h"

typedef struct {
    Datum	*elements;
    int		num_elements;
    Oid		typelem;
    bool	typbyval;
    int		i;
} UNNEST;

PG_FUNCTION_INFO_V1(unnest);
Datum unnest(PG_FUNCTION_ARGS) {
    FuncCallContext	*funcctx;
    UNNEST		*array;
    
    if(PG_ARGISNULL(0))
	ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),errmsg("null array elements not supported")));

    if (SRF_IS_FIRSTCALL()) {
        MemoryContext	oldcontext;
        funcctx = SRF_FIRSTCALL_INIT();
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

	array = (UNNEST *)palloc(sizeof(UNNEST));

	ArrayType  	*v = PG_GETARG_ARRAYTYPE_P(0);
	int		nitems = ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v));
	ArrayMetaState	*my_extra;

	my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
	if (my_extra == NULL)
	{
    	    fcinfo->flinfo->fn_extra = palloc(sizeof(ArrayMetaState));
    	    my_extra = (ArrayMetaState *) fcinfo->flinfo->fn_extra;
    	    my_extra->element_type = InvalidOid;
	}

        if (my_extra->element_type != ARR_ELEMTYPE(v))
	{
	    get_typlenbyvalalign(ARR_ELEMTYPE(v), &my_extra->typlen, &my_extra->typbyval,&my_extra->typalign);
	    my_extra->element_type = ARR_ELEMTYPE(v);
	}

	array->typelem = my_extra->element_type;
	array->typbyval = my_extra->typbyval;
	array->i = 0;

	deconstruct_array(v,my_extra->element_type,my_extra->typlen,my_extra->typbyval,my_extra->typalign,&array->elements,&array->num_elements);
 
        if(array->num_elements != nitems)
	    elog(WARNING,"array unnests to %d elements but consists of %d items",array->num_elements,nitems);

	funcctx->user_fctx = (void *)array;
        MemoryContextSwitchTo(oldcontext);
    }

    funcctx = SRF_PERCALL_SETUP();

    array = (UNNEST *)funcctx->user_fctx;
 
    if (array->i < array->num_elements) {
	Datum retval = (Datum)array->elements[array->i];
	array->i++;
	SRF_RETURN_NEXT(funcctx,retval);
    } else {
        SRF_RETURN_DONE(funcctx);
    }
}
