/* r4total.c   (c)Copyright Sequiter Software Inc., 1991-1994.  All rights reserved. */

#include "d4all.h"

#include <math.h>

/* resets the totals values */
void S4FUNCTION   total4value_reset( TOTAL4 *t4 )
{

  t4->low = 1.7 * pow(10.0, 308.0);
  t4->high = -1.7 * pow(10.0, 308.0);
  t4->count = 0;
  t4->total = 0.0;

}

/* frees the TOTAL4 related memory, then deletes the calculation used to
   create the total */
void S4FUNCTION   total4free( TOTAL4 *total )
{
  if( total->last_reset_value )
  {
    u4free( total->last_reset_value );
    total->last_reset_value = NULL;
  }

  if( total->reset_expression )
  {
    expr4free( total->reset_expression );
    total->reset_expression = NULL;
  }

  if( total->last_add_value )
  {
    u4free( total->last_add_value );
    total->last_add_value = NULL;
  }

  if( total->add_condition )
  {
    expr4free( total->add_condition );
    total->add_condition = NULL;
  }

  expr4calc_delete( total->calc_ptr );

}

/* creates a total */
TOTAL4 * S4FUNCTION   total4create( REPORT4 *r4, char *name, char *expr_src, int total_type, char *reset_expr_src )
{
  EXPR4 *expr, *rexpr;
  TOTAL4 *t4;

  if( !r4 )
    return NULL;

  if( !name || name[0] == '\0' || !expr_src )
  {
    e4describe( r4->code_base, e4parm, E4_REP_TCREATEVAL, 0, 0 );
    return NULL;
  }

  /* parse the expression */
  expr = expr4parse( r4->relate->data, expr_src );
  if( !expr )
  {
    e4describe( r4->code_base, e4total_create, E4_REP_TEXPR, 0, 0 );
    return NULL;
  }

  /* allocate the TOTAL4 structure */
  t4 = (TOTAL4 *)mem4create_alloc( r4->code_base, &r4->code_base->total_memory,
                                  5, sizeof(TOTAL4), 5, 0 );
  if( !t4 )
  {
    e4describe( r4->code_base, e4total_create, E4_REP_TMEM, 0, 0 );
    expr4free( expr );
    return NULL;
  }

  /* create the calculation based on the expression */
  t4->calc_ptr = expr4calc_create( r4->code_base, expr, name );
  if( !t4->calc_ptr )
  {
    e4describe( r4->code_base, e4total_create, E4_REP_TCALC, 0, 0 );
    u4free( t4 );
    expr4free( expr );
    return NULL;
  }

  /* set the flags and pointers */
  t4->calc_ptr->total = t4;
  t4->report = r4;
  t4->total_type = total_type;

  /* set the totals reset expression */
  if( reset_expr_src && reset_expr_src[0] != '\0' )
  {
    rexpr = expr4parse( r4->relate->data, reset_expr_src );
    if( !rexpr )
    {
      e4describe( r4->code_base, e4total_create, E4_REP_TREXPR, 0, 0 );
      expr4free( expr );
      u4free( t4 );
      return NULL;
    }
    t4->reset_expression = rexpr;
  }

  l4add( &r4->code_base->total_list, t4 );

  total4value_reset( t4 );

  return t4;
}

/* This function is used for evaluating look ahead totals, it skips ahead
   through the records incrementing the total appropriately until the reset
   condition is met */
void total4evaluate_lookahead( PTOTAL4 total )
{
  long reccount = 0;
  int  flag, len, addflag;
  char *ptr;
  double tvalue;

  if( !total->reset_expression && total->donce )
    return;

  total->donce = 1;
  addflag = 1;

  /* check the conditional add */
  if( total->add_condition )
  {
    addflag = 0;
    if( total->logcon == 0 )
    {
      len = expr4vary( total->add_condition, &ptr );
      if( memcmp( total->last_add_value, ptr, len ) != 0 )
      {
        memset( total->last_add_value, 0, len+1 );
        memcpy( total->last_add_value, ptr, len );
        addflag = 1;
      }
    }
    else
    {
      if( expr4true( total->add_condition ) )
        addflag = 1;
    }
  }

  /* increment the total for the current record */
  if( addflag == 1 )
  {
    tvalue = expr4double( total->calc_ptr->expr ) ;
    total->total += tvalue ;
    total->count += 1 ;
    if ( tvalue < total->low )
      total->low = tvalue ;

    if( tvalue > total->high )
      total->high = tvalue ;
  }

  /* start going through the records */
  flag = 1;
  while( flag )
  {
    if( relate4skip( total->report->relate, 1L ) != 0 )
    {
      reccount++;
      break;
    }

    reccount++;

    /* if this total has a reset expression */
    if( total->reset_expression )
    {
      len = expr4vary( total->reset_expression, &ptr );
      /* if the reset condition is not met */
      if( memcmp( ptr, total->last_reset_value, len ) == 0 )
      {
        addflag = 1;

        /* check for a conditional total */
        if( total->add_condition )
        {
          addflag = 0;
          if( total->logcon == 0 )
          {
            len = expr4vary( total->add_condition, &ptr );
            if( memcmp( total->last_add_value, ptr, len ) != 0 )
            {
              memset( total->last_add_value, 0, len+1 );
              memcpy( total->last_add_value, ptr, len );
              addflag = 1;
            }
          }
          else
          {
            if( expr4true( total->add_condition ) )
              addflag = 1;
          }
        }

        /* increment the total */
        if( addflag == 1 )
        {
          tvalue = expr4double( total->calc_ptr->expr ) ;
          total->total += tvalue ;
          total->count += 1 ;
          if ( tvalue < total->low )
            total->low = tvalue ;

          if( tvalue > total->high )
            total->high = tvalue ;
        }
      }
      else /* if the reset condition is met */
      {
        flag = 0;
      }
    }
    else /* if no reset condition */
    {
      addflag = 1;

      /* check for a conditional total */
      if( total->add_condition )
      {
        addflag = 0;
        if( total->logcon == 0 )
        {
          len = expr4vary( total->add_condition, &ptr );
          if( memcmp( total->last_add_value, ptr, len ) != 0 )
          {
            memset( total->last_add_value, 0, len+1 );
            memcpy( total->last_add_value, ptr, len );
            addflag = 1;
          }
        }
        else
        {
          if( expr4true( total->add_condition ) )
            addflag = 1;
        }
      }

      /* increment the total for the current record */
      if( addflag == 1 )
      {
        tvalue = expr4double( total->calc_ptr->expr ) ;
        total->total += tvalue ;
        total->count += 1 ;
        if ( tvalue < total->low )
          total->low = tvalue ;

        if( tvalue > total->high )
          total->high = tvalue ;
      }
    }

  }

  /* skip back the appropriate number of records */
  relate4skip( total->report->relate, (long)(-1 * reccount) );

}

/* updates the totals internals */
void total4value_update( TOTAL4 *total )
{
  double tvalue;
  int len, flag = 1;
  char *ptr;
  int addflag;

  /* check for a reset on the total */
  if( total->reset_expression )
  {
    len = expr4vary( total->reset_expression, &ptr );
    if( (flag = memcmp(total->last_reset_value, ptr, len)) != 0 )
    {
      memset( total->last_reset_value, 0, len+1 );
      memcpy( total->last_reset_value, ptr, len );
      total->low = 1.7 * pow( 10,308 ) ;
      total->high = -1.7 * pow( 10,308 ) ;
      total->total = 0.0 ;
      total->count = 0;
    }
  }

  /* for non-lookahead totals */
  if( !total->lookahead )
  {
    addflag = 1;

    /* check for conditional total */
    if( total->add_condition )
    {
      addflag = 0;
      if( total->logcon == 0 )
      {
        len = expr4vary( total->add_condition, &ptr );
        if( memcmp( total->last_add_value, ptr, len ) != 0 )
        {
          memset( total->last_add_value, 0, len+1 );
          memcpy( total->last_add_value, ptr, len );
          addflag = 1;
        }
      }
      else
      {
        if( expr4true( total->add_condition ) )
          addflag = 1;
      }
    }

    /* increment the total */
    if( addflag )
    {
      tvalue = expr4double( total->calc_ptr->expr ) ;
      total->total += tvalue ;
      total->count += 1 ;
      if( tvalue < total->low )
        total->low = tvalue ;

      if( tvalue > total->high )
        total->high = tvalue ;
    }
  }

  /* evaluate the lookahead */
  if( total->lookahead && flag != 0 )
  {
    total4evaluate_lookahead( total );
  }
}

/* returns a pointer to the TOTAL4 with the given name */
TOTAL4 * S4FUNCTION total4lookup( REPORT4 *report, char *name )
{
  TOTAL4 *total_on ;

  if( !report )
    return NULL;

  if( !name || name[0] == '\0' )
  {
    e4describe( report->code_base, e4parm, E4_REP_TLKP, 0, 0 );
    return NULL;
  }

  total_on = (TOTAL4 *) l4first( &report->code_base->total_list );
  while(total_on)
  {
    if ( strcmp( name, total_on->calc_ptr->name ) == 0 )
      return total_on ;

    total_on = (TOTAL4 *) l4next( &report->code_base->total_list,total_on);
  }
  return NULL ;
}


/* this function is used when checking whether objects are dependant on a
   certain total or calculation, also in deleting any dependant objects.
   If the function is being used to check for dependancies (delflag = 0) it
   simply returns 1 at the first dependant object found.  If delflag = 1 any
   dependant objects are delted */
int report4checkObjExpr( POBJ4 obj, int delflag )
{
  EXPR4CALC *calc_on;
  POBJ4     obj_on, obj_next;
  EXPR4     *expr;
  CODE4     *cb;
  int       rc;

  cb = obj->area->report->code_base;

  obj_on = (POBJ4)l4first( &obj->contained );
  while( obj_on )
  {
    obj_next = (POBJ4)l4next( &obj->contained, obj_on );
    rc = report4checkObjExpr( obj_on, delflag );
    if( rc == 1 )
      return rc;
    obj_on = obj_next;
  }

  switch( obj->obj_type_num )
  {
  case obj4type_expr:
    expr = expr4parse( obj->area->report->relate->data, expr4source((EXPR4 *)obj->data) );
    if( expr )
    {
      expr4free( expr );
    }
    else
    {
      if( delflag )
        obj4delete( obj );
      else
        return 1;
    }
    break;

  case obj4type_total:
    rc = 0;
    calc_on = (EXPR4CALC *)l4first( &cb->calc_list );
    while( calc_on )
    {
      if( calc_on == ((PTOTAL4)obj->data)->calc_ptr )
      {
        rc = 1;
        calc_on = NULL;
      }
      else
        calc_on = (EXPR4CALC *)l4next( &cb->calc_list, calc_on );
    }
    if( rc == 0 )
    {
      if( delflag )
      {
        obj->data = NULL;
        obj4free( obj );
      }
      else
        return 1;
    }
    break;

  case obj4type_calc:
    rc = 0;
    calc_on = (EXPR4CALC *)l4first( &cb->calc_list );
    while( calc_on )
    {
      if( calc_on == (EXPR4CALC *)obj->data )
      {
        rc = 1;
        calc_on = NULL;
      }
      else
        calc_on = (EXPR4CALC *)l4next( &cb->calc_list, calc_on );
    }
    if( rc == 0 )
    {
      if( delflag )
        obj4delete( obj );
      else
        return 1;
    }
    break;
  }

  return 0;
}


/* USED to delete a calc/total from the report */
int S4FUNCTION report4deleteCalc( PREPORT4 report, EXPR4CALC S4PTR *del_calc )
{
  POBJ4 obj_on, obj_next;
  EXPR4 *expr;
  LIST4 temp_calc_list;
  CODE4 *code_base;
  PGROUP4 group_on;
  PAREA4  area_on;
  EXPR4CALC *calc_on, *calc_next;
  int   calc_transfered = 1, expr_error, rc = 0;

  if( report == NULL || del_calc == NULL )
  {

    return -1;
  }

  code_base = report->code_base;

  /* separate the calculations so that a temporary list contains all
     calculations dependant on the calculation to be deleted */

  memcpy( &temp_calc_list, &code_base->calc_list, sizeof(temp_calc_list) );
  memset( &code_base->calc_list, 0, sizeof(code_base->calc_list) );

  if( l4seek( &temp_calc_list, del_calc ) != 0 )
    l4remove( &temp_calc_list, del_calc );

  expr_error = code_base->expr_error;

  code_base->expr_error = 0;

  while( calc_transfered )
  {
    calc_transfered = 0;
    calc_on = (EXPR4CALC *)l4first( &temp_calc_list );
    while( calc_on )
    {
      calc_next = (EXPR4CALC *)l4next( &temp_calc_list, calc_on );

      expr = expr4parse( calc_on->expr->data, expr4source(calc_on->expr) );
      if( expr )
      {
        expr4free( expr );
        l4remove( &temp_calc_list, calc_on );
        l4add( &code_base->calc_list, calc_on );
        calc_transfered = 1;
      }

      calc_on = calc_next;
    }
  }

  l4add( &code_base->calc_list, del_calc );
  expr4calc_delete( del_calc );

  calc_on = (EXPR4CALC *)l4pop( &temp_calc_list );
  while( calc_on )
  {
    l4add( &code_base->calc_list, calc_on ) ;
    expr4calc_delete( calc_on ) ;
    calc_on = (EXPR4CALC *)l4pop( &temp_calc_list );
  }


  /* Now check on all the objects in the report */
  /* start with the page header and footer */
  group_on = report->page_header_footer;
  if( group_on )
  {
    area_on = (PAREA4)l4first( &group_on->header_areas );
    while( area_on )
    {
      obj_on = (POBJ4)l4first( &area_on->objects );
      while( obj_on )
      {
        obj_next = (POBJ4)l4next( &area_on->objects, obj_on );
        rc = report4checkObjExpr( obj_on, 1 );
        if( rc == 1 )
          obj_on = NULL;
        else
          obj_on = obj_next;
      }
      if( rc == 1 )
        area_on = NULL;
      else
        area_on = (PAREA4)l4next( &group_on->header_areas, area_on );
    }

    area_on = (PAREA4)l4first( &group_on->footer_areas );
    while( area_on )
    {
      obj_on = (POBJ4)l4first( &area_on->objects );
      while( obj_on )
      {
        obj_next = (POBJ4)l4next( &area_on->objects, obj_on );
        rc = report4checkObjExpr( obj_on, 1 );
        if( rc == 1 )
          obj_on = NULL;
        else
          obj_on = obj_next;
      }
      if( rc == 1 )
        area_on = NULL;
      else
        area_on = (PAREA4)l4next( &group_on->footer_areas, area_on );
    }
  }

  group_on = (PGROUP4)l4first( &report->groups );
  if( rc == 1 )
    group_on = NULL;
  while( group_on )
  {
    area_on = (PAREA4)l4first( &group_on->header_areas );
    while( area_on )
    {
      obj_on = (POBJ4)l4first( &area_on->objects );
      while( obj_on )
      {
        obj_next = (POBJ4)l4next( &area_on->objects, obj_on );
        rc = report4checkObjExpr( obj_on, 1 );
        if( rc == 1 )
          obj_on = NULL;
        else
          obj_on = obj_next;
      }
      if( rc == 1 )
        area_on = NULL;
      else
        area_on = (PAREA4)l4next( &group_on->header_areas, area_on );
    }

    area_on = (PAREA4)l4first( &group_on->footer_areas );
    while( area_on )
    {
      obj_on = (POBJ4)l4first( &area_on->objects );
      while( obj_on )
      {
        obj_next = (POBJ4)l4next( &area_on->objects, obj_on );
        rc = report4checkObjExpr( obj_on, 1 );
        if( rc == 1 )
          obj_on = NULL;
        else
          obj_on = obj_next;
      }
      if( rc == 1 )
        area_on = NULL;
      else
        area_on = (PAREA4)l4next( &group_on->footer_areas, area_on );
    }

    if( rc == 1 )
      group_on = NULL;
    else
      group_on = (PGROUP4)l4next( &report->groups, group_on );
  }



  report->code_base->expr_error = expr_error;

  return 0;

}


/* SEE THE CODEREPORTER MANUAL */
int S4FUNCTION total4addCondition( PTOTAL4 total, char *add_condition_src, int logcon )
{
  EXPR4 *expr;

  if( !total || !add_condition_src || logcon < 0 )
    return -1;

  if( total->report == NULL )
    return -1;

  expr = expr4parse( total->report->relate->data, add_condition_src );
  if( expr )
  {
    if( logcon && expr4type(expr) != r4log )
      return -1;

    if( total->add_condition )
      expr4free( total->add_condition );
    total->add_condition = expr;
    if( total->last_add_value )
    {
      u4free( total->last_add_value );
      total->last_add_value = NULL;
    }
    total->logcon = logcon;
    return 0;
  }
  else
    e4set( total->report->code_base, 0 );

  return -1;
}