#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 (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;


	if (nitems == 0) {
	    elog(ERROR,"Empty Array");
	    PG_RETURN_NULL();
	}

	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);
	
	funcctx->user_fctx = (void *)array;
        MemoryContextSwitchTo(oldcontext);
    }

    funcctx = SRF_PERCALL_SETUP();

    array = (UNNEST *)funcctx->user_fctx;
 
    if (array->i < array->num_elements) {
	Datum retval;
	
        switch(array->typelem) {
	    case BOOLOID:
	        retval = BoolGetDatum(array->elements[array->i]);
		break;
    	    case CHAROID:
	        retval = CharGetDatum(array->elements[array->i]);
		break;
    	    case INT2OID:
	        retval = Int16GetDatum(array->elements[array->i]);
		break;
    	    case INT4OID:
	        retval = Int32GetDatum(array->elements[array->i]);
		break;
	    case OIDOID:
	        retval = ObjectIdGetDatum(array->elements[array->i]);
		break;
    	    case FLOAT4OID:
		retval = Float4GetDatum(array->elements[array->i]);
		break;
    	    case FLOAT8OID:
	        retval = Float8GetDatum(array->elements[array->i]);
		break;
    	    case ABSTIMEOID:
	        retval = AbsoluteTimeGetDatum(array->elements[array->i]);
		break;
	    case RELTIMEOID:
	        retval = RelativeTimeGetDatum(array->elements[array->i]);
		break;
    	    case TINTERVALOID:
	        retval = TimeIntervalGetDatum(array->elements[array->i]);
		break;
    	    case DATEOID:
	        retval = DateADTGetDatum(array->elements[array->i]);
		break;
    	    case TIMEOID:
	        retval = TimeADTGetDatum(array->elements[array->i]);
		break;
    	    case NUMERICOID:
	        retval = NumericGetDatum(array->elements[array->i]);
		break;
    	    case TIMESTAMPOID:
    	    case TIMESTAMPTZOID:
    	    case INT8OID:
	        retval = Int64GetDatum(array->elements[array->i]);
		break;
    	    case BYTEAOID:
    	    case TEXTOID:
    	    case MACADDROID:
    	    case INETOID:
    	    case CIDROID:
    	    case BPCHAROID:
    	    case VARCHAROID:
    	    case INTERVALOID:
    	    case TIMETZOID:
    	    case POINTOID:
    	    case LSEGOID:
    	    case PATHOID:
    	    case BOXOID:
    	    case POLYGONOID:
    	    case LINEOID:
    	    case CIRCLEOID:
    	    case CASHOID:
    	    case VARBITOID:
	        retval = PointerGetDatum(array->elements[array->i]);
		break;
    	    default:
		elog(ERROR,"Unsupported Array Type");
		SRF_RETURN_DONE(funcctx);
	}
	
	array->i++;
	SRF_RETURN_NEXT(funcctx,retval);
    } else {
        SRF_RETURN_DONE(funcctx);
    }
}
