/*
 *-----------------------------------------------------------------------------
 * Copyright 1994 DeltaLink bv
 *-----------------------------------------------------------------------------
 * Module Name : av_task.c
 * Version/Rev : 1.00
 * Author/Date : Leon Westervoort
 * Description : Main Task for DeltaLink Alarm Viewer
 *
 *-----------------------------------------------------------------------------
 */

/*
 *-----------------------------------------------------------------------------
 *  Includes
 *-----------------------------------------------------------------------------
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <flib.h>
#include <hdt_all.h>

#include "fl_utils.h"
#include "g_utils.h"
#include "vr_tools.h"
#include "vr_brwse.h"

/*
 *-----------------------------------------------------------------------------
 *  Local Variables
 *-----------------------------------------------------------------------------
 */

DBSM  *dbsm;
DBS    *dbs;

static void build_where_clause  ( VIEW *);
static void fill_alarm          ( INF *, VIEW *, uint );
static void fill_group          ( GINF *, VIEW *, uint );
static void fill_active          ( ACTIVE *, VIEW *, uint );

static char strchar              ( char *, const char *);
static u32  string_2_sectime    ( char *, char *);

/*-----------------------------------------------------------------------------
 *
 * function:                 init_browser
 *   
 *
 * arguments:
 *   none
 *
 * purpose:
 *   This routine initializes a number of parameters.
 *
 * returns:
 *   GOOD or ERROR
 *
 *-----------------------------------------------------------------------------
 */

int init_browser( void)
{

  /*
   * initialize the time formats
   */
  tfmt_init( Xlate_fmt);

  /*
   * If no valid alias is present do nothing
   */
  if( strlen( Db_info.dbalias) == 0)
    return GOOD;

  al_status( &Task, "DBINIT", FLS_ACTIVE, Db_info.dbalias);

  /*
   * In order to Logon to the Historian we need to do the following:
   *
   * 1 : Create a Database Session Manager
   * 2 : Attach a session to the Session Manager
   * 3 : Prepare the Statements
   */

  if (( dbsm = dbsm_create()) == NULL)
  {
    al_status( &Task, "%s", FLS_INACTIVE, Dbe_errmsg);
    return ERROR;
  }

  if (( dbs = dbsm_attach( dbsm, Db_info.dbalias,
                                 &Db_info.histmbx, &Db_info.rplymbx,
                                 Task.id)) == NULL)
  {
    al_status( &Task, "%s", FLS_INACTIVE, Dbe_errmsg);
    return ERROR;
  }

  return GOOD;
}

/*-----------------------------------------------------------------------------
 *
 * function:                 exit_browser
 *   
 *
 * arguments:
 *   none
 *
 * purpose:
 *   This routine sends a log off command to the logger.
 *
 * returns:
 *   GOOD or ERROR
 *
 *-----------------------------------------------------------------------------
 */

int exit_browser( void)
{

  /*
   * Close all the statements
   */
  if ( strlen( Db_info.dbalias) == 0)
    return ERROR;

  dbsm_destroy( dbsm);

  return GOOD;
}

/*-----------------------------------------------------------------------------
 *
 * function:                 check_views
 *   
 *
 * arguments:
 *   none
 *
 * purpose:
 *   This functions checks the filters of all views on changes etc.
 *   Each view with changes is then marked for further processing.
 *
 * returns:
 *   none
 *
 *-----------------------------------------------------------------------------
 */

void check_views( void)
{

uint    vw;
uint     index;

  /*
   * check every View for change on filters, update triggers and sort tags
   */
  for ( vw = 0; vw < Nr_views; vw++)
  {

    /*
     * check if the View has been scrolled
     */
    index = 0;

    if ( fl_trigger( &Task, &View[ vw].select_tag) == AL_CHANGE)
    {
      print_dbg( &Task, 2, "Browse[%d] Select Trigger", vw);
      View[ vw].operation |= W_SELECT | W_UPDATE;
    }

    /*
     * check if the View has been scrolled
     */
    index = 0;

    if ( fl_gettype( &View[ vw].offset_tag) == FL_ANALOG)
      if ( fl_chread( &Task, &View[ vw].offset_tag, 1, &index, &View[ vw].offset) == CHANGED)
      {
        print_dbg( &Task, 2, "Browse[%d] Offset changed to %d", vw, View[ vw].offset);
        View[ vw].operation |= W_UPDATE;
      }

    /*
     * Check if the View has to be sorted different. If the tag is a message we use
     * the kmap lookup function to translate the data from a string to a number
     * If the user supplies an unknown string we receive the default value 0
     */
    index = 0;

    if (fl_gettype( &View[ vw].selack_tag) == FL_ANALOG)
      if (fl_chread( &Task, &View[ vw].selack_tag, 1, &index, &View[ vw].selack) == CHANGED)
      {
        print_dbg( &Task, 2, "Browse[%d] Selectline changed to %d", vw, View[ vw].selack);
        View[ vw].operation |= W_UPDATE;
      }
  }

  return;
}


/*-----------------------------------------------------------------------------
 *
 * function:                 browse_select
 *   
 *
 * arguments:
 *   VIEW        *v                     Pointer to the current View
 *
 * purpose:
 *   build_view builds a new View from the active alarm list using the current
 *   Filters. It does not do any kind of sorting.
 *
 * returns:
 *   none
 *
 *-----------------------------------------------------------------------------
 */

void browse_select( VIEW *v)
{

char  select_stmt[] = "SELECT seq, name, grp, area, prio, itime, dur, msg, var1, var2, var3, var4, opr FROM %s WHERE %s";
char  select_2[] = "SELECT seq, name, grp, area, prio, itime, dur, msg, var1, var2, var3, var4, opr FROM %s";
char  sql_buf[1024];
HS_DA  *hsda;

  /* reset the build flag of this View */

  v->operation &= ~W_SELECT;

  /* generate the filters on this View */

  build_where_clause( v);

  /*
   * Remove the old cursor from the system.
   */
  if ( v->dbc != NULL )
  {
    dbc_close_cursor( v->dbc, DBS_SYNC);
    dbc_destroy( v->dbc);
    v->dbc = NULL;
  }

  if (strlen(v->where) != 0)
    sprintf( sql_buf, select_stmt, Db_info.atable, v->where);
  else
    sprintf( sql_buf, select_2, Db_info.atable);
  
  print_dbg( &Task, 1, "SQL: %s", sql_buf);

  if(( v->dbc = dbs_init_cursor( dbs, sql_buf, DBC_READER, 0)) == NULL)
  {
    al_status( &Task, Dbe_errmsg, FLS_ERROR);
    return;
  }

  hsda = dbc_get_select( v->dbc);
  hsda_create_data( hsda, 50);
  hsda->data_rows = 50;

  if( dbc_prepare_cursor( v->dbc) != DBE_GOOD)
  {
    al_status( &Task, Dbe_errmsg, FLS_ERROR);
    return;
  }

  /*
   * Fill out the message command's
   */

  if( dbc_exec_cursor( v->dbc, DBS_SYNC) != GOOD)
  {
    al_status( &Task, Dbe_errmsg, FLS_ERROR);
    return;
  }

  if( dbc_fetch_cursor( v->dbc, 500, &v->rows) != GOOD)
  {
    al_status( &Task, Dbe_errmsg, FLS_ERROR);
    return;
  }

  print_dbg( &Task, 1, "SQL: Retrieved %d lines", v->rows);
  
  return;
}

/*-----------------------------------------------------------------------------
 *
 * function:                 update_view
 *   
 *
 * arguments:
 *   VIEW        *v                     Pointer to the current View
 *
 * purpose:
 *   Updates the actual View on the srceen. This function checks
 *   first if the user has scrolled through the View and if
 *   necessary updates the View also depending on the update trigger
 *
 * returns:
 *   none
 *
 *-----------------------------------------------------------------------------
 */

void update_view( VIEW *v)
{

uint     line;
uint    g;                        /* group index */
ACTIVE  active;
INF     alarm_info;
GINF    group_info;
char    alarm_name[ 48];
char    alarm_msg[ MAX_ALINE +1];
char    alarm_area[ 16];
char    group_name[ 16];

  /* reset the UPDATE flag of this View */

  v->operation &= ~W_UPDATE;

  print_dbg( &Task, 4, "UPD: Update view with Scroll %d and Ack %d", v->offset, v->selack);

  /*
   * check first if the scroll tag lies within the View range
   */

  if ( fl_gettype( &v->offset_tag) != FL_UNDEFINED)
  {

    /*
     * If a scroll tag has been defined we check to see if the value it
     * contains is still valid. If not the value is changed to either the
     * top or bottom value. The value is then written to the RTDB
     */
    if ( (v->offset >= v->rows) || (v->offset < 0))
    {
      if( v->offset < 0)
        v->offset = 0;
      else
      {
        if(  v->rows)
          v->offset = v->rows -1;
        else
          v->offset = 0;
      }

      /*
       * Write the corrected offset to the tag and clear the flag
       * to prevent a reread on change
       */
      print_dbg( &Task, 4, "UPD: Changed OFFSET to %d", v->offset);

      fl_write( Task.id, &v->offset_tag, 1, &v->offset);

      fl_clear_chng( Task.id, &v->offset_tag, 1);
    }
  }

  /*
   * Update the lines in this View with the new user variables. If there is an
   * alarm left to present in the lines, the line is shown else we fill the
   * line with default values and no text
   */
  for ( line = 0, v->rows_displayed = 0; line < v->nr_lines; line++)
  {
    if( ( ANA)(v->offset + line) < ( ANA)v->rows)
    {
      /* Fill the line with an alarm info */

      alarm_info.name = alarm_name;
      alarm_info.msg  = alarm_msg;
      alarm_info.area = alarm_area;
      group_info.name = group_name;
      
      fill_active( &active, v, v->offset + line);
      fill_alarm( &alarm_info, v, v->offset + line);
      fill_group( &group_info, v, v->offset + line);
      
      fmt_line( &Task,
                v->line_data[ line].msg.m_ptr,
                v->format,
                Xlate_fmt,
                &active,
                &alarm_info,
                &group_info);
                
      print_dbg( &Task, 8, "UPD: line[%d] = '%s'", line, v->line_data[ line].msg.m_ptr);
                      
      v->line_data[ line].msg.m_len  = strlen( v->line_data[ line].msg.m_ptr);

      /*
       * set the colors and blinking state of this alarm line depending on the status
       */
      for( g = Nr_groups; g != 0; g--)
        if( strcmp( Group[g].grpnam, group_info.name) == 0)
          break;

      v->line_data[ line].f_clr    = Group[ g].fclr;
      v->line_data[ line].b_clr    = Group[ g].bclr;
      v->line_data[ line].blinking = Group[ g].blnk;

      v->rows_displayed++;
      
     }
     else
     {
       /* no text will be displayed on these lines */

       *v->line_data[ line].msg.m_ptr = '\0';
       v->line_data[ line].msg.m_len  = 0;

       /*
        * set these lines to the default colors
        */
       v->line_data[ line].f_clr    = v->def_bgclr;   /* set foreground color to background */
       v->line_data[ line].b_clr    = v->def_bgclr;   /* set default background color */
       v->line_data[ line].blinking = 0;              /* no blinking */
     }
  }

  /*
   * update the line of the View in the RTDB
   */
  for( line = 0; line < v->nr_lines; line++)
    if (fl_write( Task.id,
                  (TAG *)&v->line[ line],
                  TAGS_PER_LINE,
                  ( void *)&v->line_data[ line] ) == ERROR)
      al_status( &Task, "FL_WRITE", FLS_ERROR, fl_errno( Task.id), "updating lines");

  /*
   * update the selected alarm line if a selector tag has been defined
   */
  if( fl_gettype( &v->selack_tag) == FL_ANALOG) 
    update_selector( v);

  return;
}

/*-----------------------------------------------------------------------------
 *
 * function:                 fill_alarm
 *   
 *
 * arguments:
 *   VIEW        *v                     Pointer to the current View
 *
 * returns:
 *   none
 *
 *-----------------------------------------------------------------------------
 */
static void fill_alarm( INF *a, VIEW *v, uint offset)
{

HS_DA    *hsda;
char    buf[ MAX_ALINE + 1];
short   i;

  /*
   * Get the Data structure from the cursor
   */
  print_dbg( &Task, 9, "INF: Fill from record %d", offset);
  
  i = (short)offset;
  hsda = dbc_get_select( v->dbc);

  hsda_to_string( hsda, DBT_NAME, i, buf, MAX_ALINE);
  strncpy( a->name, buf, 48);
  print_dbg( &Task, 8, "INF: name  %s", a->name);
  
  hsda_to_string( hsda, DBT_MSG, i, buf, MAX_ALINE);
  strncpy( a->msg, buf, MAX_ALINE);
  print_dbg( &Task, 8, "     msg   %s", a->msg);
  
  a->prio =  hsda_to_ana( hsda, DBT_PRIO, i);
  print_dbg( &Task, 8, "     prio  %d", a->prio);
  
  hsda_to_string( hsda, DBT_AREA, i, buf, MAX_ALINE);
  strncpy( a->area, buf, 16);
  print_dbg( &Task, 8, "     area  %s", a->area);

  undef_tag( &a->var[0]);
  undef_tag( &a->var[1]);
  undef_tag( &a->var[2]);
  undef_tag( &a->var[3]);
 
}

/*-----------------------------------------------------------------------------
 *
 * function:                 fill_group
 *   
 *
 * arguments:
 *   VIEW        *v                     Pointer to the current View
 *
 * returns:
 *   none
 *
 *-----------------------------------------------------------------------------
 */

static void fill_group( GINF *g, VIEW *v, uint offset)
{

HS_DA    *hsda;
char    buf[ MAX_ALINE + 1];
short   i;

  /*
   * Get the Data structure from the cursor
   */
  print_dbg( &Task, 9, "GRP: Fill from record %d", offset);
  
  i = (short)offset;
  hsda = dbc_get_select( v->dbc);

  hsda_to_string( hsda, DBT_GRP, i, buf, MAX_ALINE);
  strncpy( g->name, buf, 16);

  g->ack = 0;
  
  print_dbg( &Task, 8, "GRP: name %s", g->name);

}
/*-----------------------------------------------------------------------------
 *
 * function:                 fill_active
 *   
 *
 * arguments:
 *   VIEW        *v                     Pointer to the current View
 *
 * returns:
 *   none
 *
 *-----------------------------------------------------------------------------
 */
static void fill_active( ACTIVE *a, VIEW *v, uint offset)
{

HS_DA    *hsda;
char    buf[ MAX_ALINE + 1];
short   i;
u32      dur;

  /*
   * Get the Data structure from the cursor
   */
  print_dbg( &Task, 9, "ACT: Fill from record %d", offset);
  
  i = (short)offset;
  hsda = dbc_get_select( v->dbc);

  a->seq = 0;

  dur = hsda_to_ana( hsda, DBT_DUR, i);

  hsda_to_string( hsda, DBT_ITIME, i, buf, MAX_ALINE);

  fl_xlate_set_tree( Xlate_fmt);
  a->itime = string_2_sectime( buf, fl_xlate( "LOGTIME"));
  fl_xlate_set_tree( Xlate_view);

  a->ntime = a->itime - dur;
  a->atime = 0;

  a->ndx = 0;
  a->status[0] = 0;
  a->status[1] = 0;

  a->var[0] = hsda_to_float( hsda, DBT_VAR1, i);
  a->var[1] = hsda_to_float( hsda, DBT_VAR2, i);
  a->var[2] = hsda_to_float( hsda, DBT_VAR3, i);
  a->var[3] = hsda_to_float( hsda, DBT_VAR4, i);

  hsda_to_string( hsda, DBT_OPR, i, buf, MAX_ALINE);
  strncpy( a->opr, buf, MAX_OPR);

  print_dbg( &Task, 7, "ACT: itime %ld", a->itime);
  print_dbg( &Task, 7, "     ntime %ld", a->ntime);
  print_dbg( &Task, 7, "     dur   %ld", dur);
  print_dbg( &Task, 7, "     var1  %.3f", a->var[0]);
  print_dbg( &Task, 7, "     var2  %.3f", a->var[1]);
  print_dbg( &Task, 7, "     var3  %.3f", a->var[2]);
  print_dbg( &Task, 7, "     var4  %.3f", a->var[3]);
  print_dbg( &Task, 7, "     opr   %s", a->opr);
  
}

/*-----------------------------------------------------------------------------
 *
 * function:                 build_where_clause
 *   
 *
 * arguments:
 *   VIEW        *v                     Pointer to the current View
 *
 * purpose:
 *   builds the group filter array with indexes in the group array from the user
 *   filter string. The string looks like 'ALARMS;WARNINGS;CRITICAL' It is
 *   translated into an array of grp index numbers.
 *
 * returns:
 *   none
 *
 *-----------------------------------------------------------------------------
 */

static void build_where_clause( VIEW *v)
{

uint   i;
char  buf[ MAX_MSG];
VAL    val;
char  clause[ MAX_ALINE];
  /*
   * Notify for DEBUG
   */
  print_dbg( &Task, 7, "Build WHERE clause for view");

  /*
   * Get all the expressions and tags from the view
   */
  memset(v->where, 0x00, 1024);

  for( i = 0; i < v->nr_query; i++)
  {
    memset( buf, 0x00, MAX_MSG);

    print_dbg( &Task, 5, "Adding Expr: '%s' to clause", v->query[i].expr);

    strcat( v->where, " ");
    
    strcat( v->where, v->query[i].expr);

    if ( fl_gettype( &v->query[i].tag) != FL_UNDEFINED)
    {
      val.msg.m_ptr = buf;
      val.msg.m_max = MAX_MSG;
      val.msg.m_len = 0;

      fl_read( Task.id, &v->query[i].tag, 1, &val);

      switch( fl_gettype( &v->query[i].tag))
      {
      case FL_ANALOG :
        sprintf( clause, "%d", val.ana);
        break;

      case FL_LANALOG :
        sprintf( clause, "%ld", val.lana);
        break;

      case FL_MESSAGE :
        sprintf( clause, "%s", buf);
        break;         

      default:
        print_dbg( &Task, 1, "Unsupported Tag Type for filter !");
        break;
      }
      
      print_dbg( &Task, 5, "Adding Tag: '%s' to clause", clause);

      strcat( v->where, clause);
    }

  }

  print_dbg( &Task, 1, "WHERE: '%s'", v->where);

  return;
}

/*-----------------------------------------------------------------------------
 *
 * function:                 update_selector
 *   
 *
 * arguments:
 *   VIEW        *v                     Pointer to the current View
 *
 * purpose:
 *   Reads the operator namen from the RTDB
 *
 * returns:
 *   The operator name
 *
 *-----------------------------------------------------------------------------
 */

void update_selector( VIEW *v)
{

  /*
   * check first if there are any rows on this View
   */
  if ( v->rows == 0)
    return;

  /*
   * check first if the select value lies within the window and the active alarm list
   */
  if (  (v->selack < 0)
     || (v->selack >= v->rows_displayed))
  {

    /*
     * check if the selector is always in range
     */
    if( v->selack < 0)
      v->selack = 0;
    else
    {
      if( v->selack >= v->rows_displayed)
        v->selack = v->rows_displayed - 1;
    }

    print_dbg( &Task, 5, "SEL: Moved Selection to %d", v->selack);

    fl_write( Task.id, &v->selack_tag, 1, &v->selack);
    fl_clear_chng( Task.id, &v->selack_tag, 1);
  }

  /*
   * If a different background color has been configured for the selected line
   * we need to reset the color of the old line and set the color of the new
   * line.
   */
  if( v->sel_bgclr != 0xFFFF)
  {
    /*
     * update the background of the old selected line only if it is displayed at
     * the moment
     */
    v->line_data[ v->selack].b_clr = v->sel_bgclr;

    fl_write( Task.id, &v->line[ v->selack].b_clr,
                  1,
                  &v->line_data[ v->selack].b_clr);
  }

  return;
}

/*-----------------------------------------------------------------------------
 *
 * function:                 update_selector
 *   
 *
 * arguments:
 *   VIEW        *v                     Pointer to the current View
 *
 * purpose:
 *   Reads the operator namen from the RTDB
 *
 * returns:
 *   The operator name
 *
 *-----------------------------------------------------------------------------
 */

static u32 string_2_sectime( char *date, char *fmt)
{

struct tm   t;
char        b[3];

  
  print_dbg( &Task, 9, "SEC: Convering '%s' using '%s'", date, fmt);

  memset( b, 0x00, 3);

  t.tm_sec   = atoi((char *)memcpy( b, date + strchar( fmt, "sc"), 2));
  t.tm_min   = atoi((char *)memcpy( b, date + strchar( fmt, "mi"), 2));
  t.tm_hour  = atoi((char *)memcpy( b, date + strchar( fmt, "hr"), 2));
  t.tm_mday  = atoi((char *)memcpy( b, date + strchar( fmt, "dy"), 2));

  t.tm_mon   = atoi((char *)memcpy( b, date + strchar( fmt, "mo"), 2));

  if ( strchar( fmt, "year") == - 1)
  {
    t.tm_year   = atoi((char *)memcpy( b, date + strchar( fmt, "yr"), 2));
  }
  else
  {
    t.tm_year   = atoi((char *)memcpy( b, date + strchar( fmt, "year"), 4)) - 1900;
  }

  t.tm_isdst = 0;

  /*
   * The month starts counting on 0: JAN = 0
   */
  t.tm_mon--;

  return ((u32)(mktime( &t) - timezone -DIFFTIME));
}

/*-----------------------------------------------------------------------------
 *
 * function:                 strchar
 *   
 *
 * arguments:
 *   char        *src                     Source string
 *   char        *token                   Token to find
 *
 * purpose:
 *   Get the location of a token in offset
 *
 * returns:
 *   the offset in char
 *
 *-----------------------------------------------------------------------------
 */

static char strchar( char *src, const char *token)
{

char    *token_pos;

  if (( token_pos = strstr( src, token)) != NULL)
    return (char)( token_pos - src);
  else
    return -1;                               
}
