#include "executor/spi.h"       /* For GetAttributeByName */
#include "commands/trigger.h"   /* for trigger stuff */
#include "commands/sequence.h"  /* for nextval() */

//#define TASSIV_DEBUG

//#define STDDEV( n, s, s2 ) (sqrt( fabs( (n * s2 - s * s) / (n * (n - 1) ) )))
float8 STDDEV( int4, float8, float8 );

float8
STDDEV( int4 count, float8 sum, float8 sum_square )
   {
   float8 result;

   result = sqrt( fabs(  (count * sum_square - sum * sum) )
                      / (count * (count - 1) ) );
#ifdef TASSIV_DEBUG
   elog( NOTICE, "STDDEV count: %d sum: %f sum_square: %f result: %f",
                 count, sum, sum_square, result );
#endif

   return result;
   }

typedef struct
   {
      bool exists;
      Datum inserted_star_id;

      int4 loc_count;

      float4 ra;
      float8 ra_sum;
      float4 ra_sigma;
      float8 ra_sum_square;

      float4 dec;
      float8 dec_sum;
      float4 dec_sigma;
      float8 dec_sum_square;

      int4 obs_count;
      float4 mag;
      float8 mag_sum;
      float4 mag_sigma;
      float8 mag_sum_square;
   }
Table_catalog;

extern Datum observations_trigger( PG_FUNCTION_ARGS );
extern void remove_influence( HeapTuple,
                              TupleDesc,
                              char );
extern Datum add_influence( HeapTuple,
                            TupleDesc,
                            char );

extern Table_catalog get_catalog_info( Datum,
                                       char );

extern void update_star( Datum,
                         char,
                         Table_catalog * );

extern Datum get_bin_value( char *,
                            char *,
                            HeapTuple,
                            TupleDesc );

PG_FUNCTION_INFO_V1( observations_trigger );

Datum
observations_trigger( PG_FUNCTION_ARGS )
{
   char func_name[] = "obs_trigger";
   TriggerData *trig_data = ( TriggerData * ) fcinfo->context;
   HeapTuple old_tuple = NULL;
   HeapTuple new_tuple = NULL;
   HeapTuple ret_tuple = NULL;
   TupleDesc tup_desc;
   Datum star_id;
   bool old_star_used = false;
   bool new_star_used = false;
   char band;
   char *relname;
   int itemp;

#ifdef TASSIV_DEBUG
   elog( NOTICE, "entering %s", func_name );
#endif

   if ( !CALLED_AS_TRIGGER( fcinfo ) )
      elog( ERROR, "%s: not fired by trigger manager", func_name );

   if ( TRIGGER_FIRED_FOR_STATEMENT( trig_data->tg_event ) )
      elog( ERROR, "%s: can't process STATEMENT events", func_name );

   if ( TRIGGER_FIRED_AFTER( trig_data->tg_event ) )
      elog( ERROR, "%s: must be fired before event", func_name );

   if ( TRIGGER_FIRED_BY_UPDATE( trig_data->tg_event ) )
      {
         ret_tuple = trig_data->tg_newtuple;
         old_tuple = trig_data->tg_trigtuple;
         new_tuple = trig_data->tg_newtuple;
      }
   else if ( TRIGGER_FIRED_BY_INSERT( trig_data->tg_event ) )
      {
         ret_tuple = trig_data->tg_trigtuple;
         new_tuple = trig_data->tg_trigtuple;
      }
   else if ( TRIGGER_FIRED_BY_DELETE( trig_data->tg_event ) )
      {
         ret_tuple = trig_data->tg_trigtuple;
         old_tuple = trig_data->tg_trigtuple;
      }
   else
      elog( ERROR, "%s: unknown event", func_name );

   if ( SPI_ERROR_CONNECT == SPI_connect(  ) )
      elog( ERROR, "%s: cannot connect to backend", func_name );

   relname = SPI_getrelname( trig_data->tg_relation );

   sscanf( relname, "obs_%c", &band );

   tup_desc = trig_data->tg_relation->rd_att;

   if ( old_tuple != NULL )
      old_star_used = get_bin_value( func_name, "use", old_tuple, tup_desc );
   if ( new_tuple != NULL )
      new_star_used = get_bin_value( func_name, "use", new_tuple, tup_desc );

   if ( old_star_used == true )
      remove_influence( old_tuple, tup_desc, band );
   if ( new_star_used == true )
      {
         star_id = add_influence( new_tuple, tup_desc, band );

         if ( DatumGetInt32( star_id ) != -1 )
            {
               if ( SPI_ERROR_NOATTRIBUTE
                    == ( itemp = SPI_fnumber( tup_desc, "star_id" ) ) )
                  {
                     elog( ERROR, "%s: cannot find attribute %s",
                           func_name, "star_id" );
                  }
               ret_tuple = SPI_modifytuple( trig_data->tg_relation, ret_tuple,
                                            1, &itemp, &star_id, NULL );
               if ( ret_tuple == NULL )
                  {
                     elog( ERROR, "%s: modify tuple %s", func_name,
                           "star_id" );
                  }
            }
      }

   pfree( relname );

   SPI_finish(  );

   return ( PointerGetDatum( ret_tuple ) );
}

void
remove_influence( HeapTuple old_tuple,
                  TupleDesc desc,
                  char band )
{
   char func_name[] = "remove_influence";
   Datum file_id;
   Datum star_id;
   Table_catalog catalog_info;
   float mag, ra, dec;

#ifdef TASSIV_DEBUG
   elog( NOTICE, "entering %s", func_name );
#endif

   file_id = get_bin_value( func_name, "file_id", old_tuple, desc );
   star_id = get_bin_value( func_name, "star_id", old_tuple, desc );

   catalog_info = get_catalog_info( star_id, band );

   if ( catalog_info.obs_count > 1 )
      {
         mag =
            DatumGetFloat4( get_bin_value
                            ( func_name, "mag", old_tuple, desc ) );

         --catalog_info.obs_count;
         catalog_info.mag_sum -= mag;
         catalog_info.mag_sum_square -= mag * mag;
         catalog_info.mag = catalog_info.mag_sum / catalog_info.obs_count;
         if ( catalog_info.obs_count >= 2 )
            {
               catalog_info.mag_sigma = STDDEV( catalog_info.obs_count,
                                                catalog_info.mag_sum,
                                                catalog_info.mag_sum_square );
            }
         else
            {
               catalog_info.mag_sigma = 0;
            }
      }
   else
      {
         catalog_info.obs_count = 0;
         catalog_info.mag = 99;
         catalog_info.mag_sum = 0;
         catalog_info.mag_sigma = 0;
         catalog_info.mag_sum_square = 0;
      }

   if ( catalog_info.loc_count > 1 )
      {
         ra =
            DatumGetFloat4( get_bin_value
                            ( func_name, "ra", old_tuple, desc ) );
         dec =
            DatumGetFloat4( get_bin_value
                            ( func_name, "dec", old_tuple, desc ) );

         --catalog_info.loc_count;
         catalog_info.ra_sum -= ra;
         catalog_info.ra_sum_square -= ra * ra;
         catalog_info.ra = catalog_info.ra_sum / catalog_info.loc_count;
         catalog_info.dec_sum -= dec;
         catalog_info.dec_sum_square -= dec * dec;
         catalog_info.dec = catalog_info.dec_sum / catalog_info.loc_count;

         if ( catalog_info.loc_count >= 2 )
            {
               catalog_info.ra_sigma = STDDEV( catalog_info.loc_count,
                                               catalog_info.ra_sum,
                                               catalog_info.ra_sum_square );
               catalog_info.dec_sigma = STDDEV( catalog_info.loc_count,
                                                catalog_info.dec_sum,
                                                catalog_info.dec_sum_square );
            }
         else
            {
               catalog_info.ra_sigma = 0;
               catalog_info.dec_sigma = 0;
            }
      }
   else
      {
         catalog_info.loc_count = 0;
         // catalog_info.ra = 0; - don't change 'single' value
         catalog_info.ra_sum = 0;
         catalog_info.ra_sigma = 0;
         catalog_info.ra_sum_square = 0;
         // catalog_info.dec = 0; - don't change 'single' value
         catalog_info.dec_sum = 0;
         catalog_info.dec_sigma = 0;
         catalog_info.dec_sum_square = 0;
      }

   update_star( star_id, band, &catalog_info );

   return;
}

Datum
add_influence( HeapTuple new_tuple,
               TupleDesc desc,
               char band )
{
   char func_name[] = "add_influence";
   Datum file_id;
   Datum star_id;
   Table_catalog catalog_info;
   float mag, ra, dec;

#ifdef TASSIV_DEBUG
   elog( NOTICE, "entering %s %c", func_name, band );
#endif

   file_id = get_bin_value( func_name, "file_id", new_tuple, desc );
   star_id = get_bin_value( func_name, "star_id", new_tuple, desc );

   if ( DatumGetInt32( star_id ) == 0 )
      {
         elog( ERROR, "%s: Need a valid star_id before setting 'use'\n",
               func_name );
      }

   catalog_info = get_catalog_info( star_id, band );

   if ( catalog_info.obs_count == 0 )
      {
         catalog_info.mag = 0;
      }

   mag = DatumGetFloat4( get_bin_value( func_name, "mag", new_tuple, desc ) );

   ++catalog_info.obs_count;
   catalog_info.mag_sum += mag;
   catalog_info.mag_sum_square += mag * mag;
   catalog_info.mag = catalog_info.mag_sum / catalog_info.obs_count;

   if ( catalog_info.obs_count >= 2 )
      {
         catalog_info.mag_sigma = STDDEV( catalog_info.obs_count,
                                          catalog_info.mag_sum,
                                          catalog_info.mag_sum_square );
      }
   else
      {
         catalog_info.mag_sigma = 0;
      }

#ifdef TASSIV_DEBUG
   elog( NOTICE, "getting ra/dec" );
#endif
   ra = DatumGetFloat4( get_bin_value( func_name, "ra", new_tuple, desc ) );
   dec = DatumGetFloat4( get_bin_value( func_name, "dec", new_tuple, desc ) );
#ifdef TASSIV_DEBUG
   elog( NOTICE, "got ra %f and dec %f", ra, dec );
#endif

   ++catalog_info.loc_count;
   catalog_info.ra_sum += ra;
   catalog_info.ra_sum_square += ra * ra;
   catalog_info.ra = catalog_info.ra_sum / catalog_info.loc_count;

   catalog_info.dec_sum += dec;
   catalog_info.dec_sum_square += dec * dec;
   catalog_info.dec = catalog_info.dec_sum / catalog_info.loc_count;

   if ( catalog_info.loc_count >= 2 )
      {
         catalog_info.ra_sigma = STDDEV( catalog_info.loc_count,
                                         catalog_info.ra_sum,
                                         catalog_info.ra_sum_square );
         catalog_info.dec_sigma = STDDEV( catalog_info.loc_count,
                                          catalog_info.dec_sum,
                                          catalog_info.dec_sum_square );
      }
   else
      {
         catalog_info.ra_sigma = 0;
         catalog_info.dec_sigma = 0;
      }

   update_star( star_id, band, &catalog_info );

#ifdef TASSIV_DEBUG
   elog( NOTICE, "leaving %s", func_name );
#endif
   return ( catalog_info.inserted_star_id );
}

Table_catalog
get_catalog_info( Datum star_id,
                  char band )
{
   char func_name[] = "get_band_info";
   Table_catalog catalog_info;
   char buff[100];

#ifdef TASSIV_DEBUG
   elog( NOTICE, "entering %s", func_name );
#endif

   sprintf( buff, "SELECT * FROM catalog WHERE star_id = %d",
            DatumGetInt32( star_id ) );

   if ( SPI_exec( buff, 1 ) < 0 )
      {
         elog( ERROR, "%s: could not select star_id %d from catalog",
               func_name, DatumGetInt32( star_id ) );
      }

   if ( SPI_processed > 0 )
      {
         catalog_info.exists = true;
         catalog_info.loc_count
            = DatumGetInt32( get_bin_value( func_name, "loc_count",
                                            SPI_tuptable->vals[0],
                                            SPI_tuptable->tupdesc ) );
         catalog_info.ra
            = DatumGetFloat4( get_bin_value( func_name, "ra",
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         catalog_info.ra_sum
            = DatumGetFloat8( get_bin_value( func_name, "ra_sum",
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         catalog_info.ra_sigma
            = DatumGetFloat4( get_bin_value( func_name, "ra_sigma",
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         catalog_info.ra_sum_square
            = DatumGetFloat8( get_bin_value( func_name, "ra_sum_square",
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         catalog_info.dec
            = DatumGetFloat4( get_bin_value( func_name, "dec",
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         catalog_info.dec_sum
            = DatumGetFloat8( get_bin_value( func_name, "dec_sum",
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         catalog_info.dec_sigma
            = DatumGetFloat4( get_bin_value( func_name, "dec_sigma",
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         catalog_info.dec_sum_square
            = DatumGetFloat8( get_bin_value( func_name, "dec_sum_square",
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         sprintf( buff, "mag_%c_count", band );
         catalog_info.obs_count
            = DatumGetInt32( get_bin_value( func_name, buff,
                                            SPI_tuptable->vals[0],
                                            SPI_tuptable->tupdesc ) );
         sprintf( buff, "mag_%c", band );
         catalog_info.mag
            = DatumGetFloat4( get_bin_value( func_name, buff,
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         sprintf( buff, "mag_%c_sum", band );
         catalog_info.mag_sum
            = DatumGetFloat8( get_bin_value( func_name, buff,
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         sprintf( buff, "mag_%c_sigma", band );
         catalog_info.mag_sigma
            = DatumGetFloat4( get_bin_value( func_name, buff,
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
         sprintf( buff, "mag_%c_sum_square", band );
         catalog_info.mag_sum_square
            = DatumGetFloat8( get_bin_value( func_name, buff,
                                             SPI_tuptable->vals[0],
                                             SPI_tuptable->tupdesc ) );
      }
   else
      {
         catalog_info.exists = false;
         catalog_info.loc_count = 0;
         catalog_info.ra = 0;
         catalog_info.ra_sum = 0;
         catalog_info.ra_sigma = 0;
         catalog_info.ra_sum_square = 0;
         catalog_info.dec = 0;
         catalog_info.dec_sum = 0;
         catalog_info.dec_sigma = 0;
         catalog_info.dec_sum_square = 0;
         catalog_info.obs_count = 0;
         catalog_info.mag = 0;
         catalog_info.mag_sum = 0;
         catalog_info.mag_sigma = 0;
         catalog_info.mag_sum_square = 0;
      }

   return ( catalog_info );
}

void
update_star( Datum star_id,
             char band,
             Table_catalog * catalog_info )
{
   char func_name[] = "update_star";
   char buff[500];

#ifdef TASSIV_DEBUG
   elog( NOTICE, "entering %s", func_name );
#endif

   if ( catalog_info->exists == true )
      {
         catalog_info->inserted_star_id = Int32GetDatum( -1 );
         sprintf( buff,
                  "UPDATE catalog\n"
                  "SET loc_count=%d,\n"
                  "    ra=%f, ra_sum=%f, ra_sigma=%f, ra_sum_square=%lf,\n"
                  "    dec=%f, dec_sum=%f, dec_sigma=%f, dec_sum_square=%lf,\n"
                  "    mag_%c_count=%d,\n"
                  "    mag_%c=%f, mag_%c_sum=%f,\n"
                  "    mag_%c_sigma=%f, mag_%c_sum_square=%lf\n"
                  "WHERE star_id=%d",
                  catalog_info->loc_count,
                  catalog_info->ra,
                  catalog_info->ra_sum,
                  catalog_info->ra_sigma,
                  catalog_info->ra_sum_square,
                  catalog_info->dec,
                  catalog_info->dec_sum,
                  catalog_info->dec_sigma,
                  catalog_info->dec_sum_square,
                  band, catalog_info->obs_count,
                  band, catalog_info->mag,
                  band, catalog_info->mag_sum,
                  band, catalog_info->mag_sigma,
                  band, catalog_info->mag_sum_square,
                  DatumGetInt32( star_id ) );
      }
   else
      {

/*
      Datum temp;
                Datum seqname;
                char sequence[] = "star_id_seq";

                seqname = DirectFunctionCall1( textin, CStringGetDatum( sequence ) );
      temp = DirectFunctionCall1( nextval, seqname );
*/
         /* nextval now returns int64; coerce down to int32 */
//              temp = Int32GetDatum( (int32) DatumGetInt64( temp ) );

         catalog_info->inserted_star_id = star_id;

         sprintf( buff,
                  "INSERT INTO catalog( star_id, loc_count,\n"
                  "   ra, ra_sum, ra_sigma, ra_sum_square,\n"
                  "   dec, dec_sum, dec_sigma, dec_sum_square,\n"
                  "   mag_%c_count,\n"
                  "   mag_%c, mag_%c_sum, mag_%c_sigma, mag_%c_sum_square )\n"
                  "VALUES( %d, %d,\n"
                  "        %f, %f, %f, %lf,\n"
                  "        %f, %f, %f, %lf,\n"
                  "        %d, %f, %f, %f, %lf )\n",
                  band, band, band, band, band,
                  DatumGetInt32( catalog_info->inserted_star_id ),
                  catalog_info->loc_count,
                  catalog_info->ra,
                  catalog_info->ra_sum,
                  catalog_info->ra_sigma,
                  catalog_info->ra_sum_square,
                  catalog_info->dec,
                  catalog_info->dec_sum,
                  catalog_info->dec_sigma,
                  catalog_info->dec_sum_square,
                  catalog_info->obs_count,
                  catalog_info->mag,
                  catalog_info->mag_sum,
                  catalog_info->mag_sigma, catalog_info->mag_sum_square );
      }
#ifdef TASSIV_DEBUG
   elog( NOTICE, "%s: %s", func_name, buff );
#endif

   if ( ( SPI_exec( buff, 1 ) < 0 ) || ( SPI_processed < 1 ) )
      {
         elog( ERROR, "%s: failed insert/update\n%s", func_name, buff );
      }
}

Datum
get_bin_value( char *calling_func_name,
               char *field_name,
               HeapTuple tuple,
               TupleDesc desc )
{
   Datum data;
   int itemp;
   bool is_null;
   char func_name[ ] = "get_bin_value";

#ifdef TASSIV_DEBUG
   elog( NOTICE, "entering %s from %s %s",
         func_name, calling_func_name, field_name );
#endif

   if ( SPI_ERROR_NOATTRIBUTE == ( itemp = SPI_fnumber( desc, field_name ) ) )
      {
         elog( ERROR, "%s - %s: cannot find attribute %s",
                      func_name, calling_func_name, field_name );
      }

   if ( SPI_ERROR_NOATTRIBUTE
        == ( data = SPI_getbinval( tuple, desc, itemp, &is_null ) ) )
      {
         elog( ERROR, "%s: cannot retrieve %s from query",
               calling_func_name, field_name );
      }

#ifdef TASSIV_DEBUG
   elog( NOTICE, "leaving %s", func_name );
#endif
   return ( data );
}


