/* fxpstres -  A program to stress test the fixed point math ('fxp') API.
   Rick Smereka, Copyright (C) 2002-2004.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, get a copy via the Internet at
   http://gnu.org/copyleft/gpl.html or write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
   MA 02111-1307 USA

   You can contact the author via email at rsmereka@future-lab.com

   This program contains an infinite loop and will have to be
   forced to terminate. The program works by picking a random
   math operation/comparison, generating random floating point
   numbers (in string form) and using the fixed point API ('fxp')
   to carry out the operation/comparison.

   Program syntax:

      fxpstres

   Output will be sent to 'stdout'. The personal logger may also
   be used as the 'fxp' API is coded to write log messages if the
   personal logger is enabled.

   Original Linux version. Apr/2002, Rick Smereka

   Ported to 32-bit Windows. May/2002, Rick Smereka

   Ported to DOS and QNX 4.x. Jul/2002, Rick Smereka

   Ported to Debian Linux. Jan/2003, Rick Smereka

   Changed all logging calls to use the 'logman' API.
   Changed all requests for a random number to use the
   'rnd' function (in 'misc.c'). Dec/2004, Rick Smereka */

#include "stdhead.h"

/* global data */

long trans_no;

/* operation codes */

#define FXPS_ADD 1
#define FXPS_SUB 2
#define FXPS_MUL 3
#define FXPS_DIV 4
#define FXPS_EQ 5
#define FXPS_LT 6
#define FXPS_GT 7
#define FXPS_LE 8
#define FXPS_GE 9
#define FXPS_MAXCOM 9

#define VERSION "1.01.01-2004.12.21"
#define APNAME "fxpstres"

/* function prototypes */

int main(void);
int pick_com(void);
void do_com(int);
void do_add(struct fxp *, struct fxp *);
void do_sub(struct fxp *, struct fxp *);
void do_mul(struct fxp *, struct fxp *);
void do_div(struct fxp *, struct fxp *);
void do_eq(struct fxp *, struct fxp *);
void do_lt(struct fxp *, struct fxp *);
void do_gt(struct fxp *, struct fxp *);
void do_le(struct fxp *, struct fxp *);
void do_ge(struct fxp *, struct fxp *);
void report_math(struct fxp *, struct fxp *, struct fxp *, char);
void report_comp(struct fxp *, struct fxp *, int, char *);
int get_num(char *);
void app_out(char *,...);

int main(void)
{
   int fxpcom;

   /* if (logman_start("/work/logs/fxpstres.log", APNAME))
      {
      printf("error starting log\n");
      return(0);
      } */

   srand((unsigned)time(NULL));                /* seed rnd gen */
   trans_no = 1L;

   /* build logo string based on platform */

#ifndef OS_UNIX
   app_out("%s for %s Version %s", APNAME, PLATFORM_STRING, VERSION);
#else
   app_out("%s for %s Version %s", APNAME, SUB_PLATFORM_STRING, VERSION);
#endif

   app_out("By Rick Smereka, Copyright (c) 2002-2004");
   app_out("%s comes with ABSOLUTELY NO WARRANTY", APNAME);
   app_out("This is free software, and you are welcome to redistribute it");
   app_out("under certain conditions; see 'gpl.txt' for information.");

   /* main loop, infinite */

   while(1)
      {
      fxpcom = pick_com();
      trans_no++;
      do_com(fxpcom);
      }

   if (logman_is_active())
      logman_end();

   return(0);
}

int pick_com(void)
{
   /* Pick a random operation/comparison. */

   int rnd_num;

   rnd_num = rnd(1, FXPS_MAXCOM);
   return(rnd_num);
}

void do_com(int com_num)
{
   /* Dispatch command. */

   struct fxp *fxp1, *fxp2;
   char mname[] = "do_com", n1[10], n2[10];

   logman("%s:enter", mname);

   if (!get_num(n1))
      {
      logman("%s:exit:bad rc from get_num[n1]", mname);
      return;
      }
   
   if (!get_num(n2))
      {
      logman("%s:exit:bad rc from get_num[n2]", mname);
      return;
      }

   if ((fxp1 = fxp_iconv(n1)) == FXP_OT_NULL)
      {
      logman("%s:bad rc from fxp_iconv[fxp1]", mname);
      return;
      }

   if ((fxp2 = fxp_iconv(n2)) == FXP_OT_NULL)
      {
      logman("%s:bad rc from fxp_iconv[fxp2]", mname);
      fxp_free(fxp1);
      return;
      }

   switch(com_num)
      {
      case FXPS_ADD:
         do_add(fxp1, fxp2);
         break;

      case FXPS_SUB:
         do_sub(fxp1, fxp2);
         break;

      case FXPS_MUL:
         do_mul(fxp1, fxp2);
         break;

      case FXPS_DIV:
         do_div(fxp1, fxp2);
         break;

      case FXPS_EQ:
         do_eq(fxp1, fxp2);
         break;

      case FXPS_LT:
         do_lt(fxp1, fxp2);
         break;

      case FXPS_GT:
         do_gt(fxp1, fxp2);
         break;

      case FXPS_LE:
         do_le(fxp1, fxp2);
         break;

      case FXPS_GE:
         do_ge(fxp1, fxp2);
         break;

      default:
         app_out("%s:unknown command[%d],ignoring", mname, com_num);
      };

   fxp_free(fxp1);
   fxp_free(fxp2);
   logman("%s:normal exit", mname);
}

void do_add(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point addition on two numbers. */

   struct fxp *result;
   char mname[] = "do_add";
   
   logman("%s:enter", mname);

   if ((result = fxp_add(fxp1, fxp2)) == FXP_OT_NULL)
      {
      app_out("%s:exit:bad rc from fxp_add[fxp1,fxp2]", mname);
      return;
      }

   report_math(fxp1, fxp2, result, '+');
   fxp_free(result);
   logman("%s:normal exit", mname);
}

void do_sub(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point subtraction on two numbers. */

   struct fxp *result;
   char mname[] = "do_sub";
   
   logman("%s:enter", mname);

   if ((result = fxp_sub(fxp1, fxp2)) == FXP_OT_NULL)
      {
      app_out("%s:exit:bad rc from fxp_sub[fxp1,fxp2]", mname);
      return;
      }

   report_math(fxp1, fxp2, result, '-');
   fxp_free(result);
   logman("%s:normal exit", mname);
}

void do_mul(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point multiplication on two numbers. */

   struct fxp *result;
   char mname[] = "do_mul";
   
   logman("%s:enter", mname);

   if ((result = fxp_mul(fxp1, fxp2)) == FXP_OT_NULL)
      {
      app_out("%s:exit:bad rc from fxp_mul[fxp1,fxp2]", mname);
      return;
      }

   report_math(fxp1, fxp2, result, '*');
   fxp_free(result);
   logman("%s:normal exit", mname);
}

void do_div(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point division on two numbers. */

   struct fxp *result;
   char mname[] = "do_div";
   
   logman("%s:enter", mname);

   if ((result = fxp_div(fxp1, fxp2)) == FXP_OT_NULL)
      {
      app_out("%s:exit:bad rc from fxp_div[fxp1,fxp2]", mname);
      return;
      }

   report_math(fxp1, fxp2, result, '/');
   fxp_free(result);
   logman("%s:normal exit", mname);
}

void do_eq(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point equality test. */

   char mname[] = "do_eq";
   int result;
   
   logman("%s:enter", mname);
   result = fxp_eq(fxp1, fxp2);
   report_comp(fxp1, fxp2, result, "=");
   logman("%s:normal exit", mname);
}

void do_lt(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point less than comparison. */

   char mname[] = "do_lt";
   int result;
   
   logman("%s:enter", mname);
   result = fxp_lt(fxp1, fxp2);
   report_comp(fxp1, fxp2, result, "<");
   logman("%s:normal exit", mname);
}

void do_gt(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point greater than comparison. */

   char mname[] = "do_gt";
   int result;
   
   logman("%s:enter", mname);
   result = fxp_gt(fxp1, fxp2);
   report_comp(fxp1, fxp2, result, ">");
   logman("%s:normal exit", mname);
}

void do_le(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point less than or equal comparison. */

   char mname[] = "do_le";
   int result;
   
   logman("%s:enter", mname);
   result = fxp_le(fxp1, fxp2);
   report_comp(fxp1, fxp2, result, "<=");
   logman("%s:normal exit", mname);
}

void do_ge(struct fxp *fxp1, struct fxp *fxp2)
{
   /* Perform a fixed point greater than or equal comparison. */

   char mname[] = "do_ge";
   int result;
   
   logman("%s:enter", mname);
   result = fxp_ge(fxp1, fxp2);
   report_comp(fxp1, fxp2, result, ">=");
   logman("%s:normal exit", mname);
}

void report_math(struct fxp *f1, struct fxp *f2, struct fxp *result, char op)
{
   /* Report the result of an math operation. */

   char mname[] = "report_math", cf1[10], cf2[10], cr[10];
   int out_scale;

   logman("%s:enter", mname);

   if (f1 == FXP_OT_NULL)
      {
      logman("%s:exit:null[f1]", mname);
      return;
      }

   if (f2 == FXP_OT_NULL)
      {
      logman("%s:exit:null[f2]", mname);
      return;
      }

   if (result == FXP_OT_NULL)
      {
      logman("%s:exit:null[result]", mname);
      return;
      }

   if (!fxp_oconv(f1, FXP_DEFAULT, cf1))
      {
      logman("%s:exit:bad rc from fxp_oconv[f1]", mname);
      return;
      }

   if (!fxp_oconv(f2, FXP_DEFAULT, cf2))
      {
      logman("%s:exit:bad rc from fxp_oconv[f2]", mname);
      return;
      }

   /* set result scale to largest of the two numbers except
      for divide where we set the scale to the smallest */

   if (op == '/')
      out_scale = (f2->scale < f1->scale) ? f2->scale : f1->scale;
   else
      out_scale = (f1->scale > f2->scale) ? f1->scale : f2->scale;

   if (!fxp_oconv(result, out_scale, cr))
      {
      logman("%s:exit:bad rc from fxp_oconv[result]", mname);
      return;
      }

   app_out("%ld:%s%c%s=%s", trans_no, cf1, op, cf2, cr);
}

void report_comp(struct fxp *f1, struct fxp *f2, int result, char *op)
{
   /* Report the result of a comparison. */

   char mname[] = "report_comp", cf1[10], cf2[10];

   logman("%s:enter", mname);

   if (f1 == FXP_OT_NULL)
      {
      logman("%s:exit:null[f1]", mname);
      return;
      }

   if (f2 == FXP_OT_NULL)
      {
      logman("%s:exit:null[f2]", mname);
      return;
      }

   if (result != TRUE && result != FALSE)
      {
      logman("%s:exit:out of range[result]", mname);
      return;
      }

   if (!fxp_oconv(f1, FXP_DEFAULT, cf1))
      {
      logman("%s:exit:bad rc from fxp_oconv[f1]", mname);
      return;
      }

   if (!fxp_oconv(f2, FXP_DEFAULT, cf2))
      {
      logman("%s:exit:bad rc from fxp_oconv[f2]", mname);
      return;
      }

   app_out("%ld:%s%s%s=%d", trans_no, cf1, op, cf2, result);
}

int get_num(char *numout)
{
   /* Generate a random floating point number in string form.
      Function returns 'TRUE' upon success, 'FALSE' otherwise. */

   char mname[] = "get_num";
   int ival, is_frac, fval;

   logman("%s:enter", mname);

   if (numout == (char *)NULL)
      {
      logman("%s:null[numout]", mname);
      return(FALSE);
      }

   numout[0] = EOS;

   /* first, generate integer portion */

   ival = rnd(0, 99);

   /* decide whether number should have fractional portion */

   is_frac = rnd(0, 1);

   if (is_frac)
      fval = rnd(0, 99);
   else
      fval = 0;

   if (is_frac)
      sprintf(numout, "%d.%d", ival, fval);
   else
      sprintf(numout, "%d", ival);

   return(TRUE);
}

void app_out(char *fmt,...)
{
   /* Display a message and if the logger is active, log
      the message also. */

   va_list argptr;
   char *mes, mname[] = "app_out";
   int ret;

   va_start(argptr, fmt);

   if ((mes = (char *)malloc(2048)) == (char *)NULL)
      {
      logman("%s:alloc fail[mes]", mname);
      return;
      }

   // format message

   memset(mes, 0, 2048);
   vsprintf(mes, fmt, argptr);
   printf("%s\n", mes);

   if (logman_is_active())
      logman_nf(mes);

   free(mes);
}
