/*
 * Copyright (C) 1996-2011 Daniel Waggoner
 *
 * This 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 3 of the License, or
 * (at your option) any later version.
 *
 * It 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.
 *
 * If you did not received a copy of the GNU General Public License
 * with this software, see <http://www.gnu.org/licenses/>.
 */

#include "matrix_test.hpp"
#include "matrix_test_instances.hpp"
#include "dw_error.h"
#include "dw_parse_cmd.h"
#include "dw_ascii.h"
#include "dw_std.h"

#include <fstream>

#include <string.h>
#include <time.h>
#include <sys/time.h>

//===============================================================================
//===============================================================================
//===============================================================================

void PrintMessage(ostream &out, TFunction *fnct, char *message)
{
  out << fnct->GetName();
  fnct->PrintArgList(out);
  out << message << "\n";
  out << flush;
}

int EvaluateErr(char *msg, PRECISION err)
{
  if ((err < 0.0) || (err > SQRT_MACHINE_EPSILON))
    {
      sprintf(msg," - Not OK - Error : %le",err);
      return 1;
    }
  else
    {
      if (err > MACHINE_EPSILON)
	sprintf(msg," - OK - Correct to square root machine epsilon : %le",err);
      else
	sprintf(msg," - OK - Correct to machine epsilon : %le",err);
      return 0;
    }
}

char* GetErrorString(int arg_err, char *arg_string)
{
  arg_string[0]='\0';
  if (arg_err & ARG_ERR_SIZE) strcat(arg_string,"size");
  if (arg_err & ARG_ERR_NULL) strcat(arg_string,arg_string[0] ? "/null" : "null");
  if (arg_err & ARG_ERR_GENERAL) strcat(arg_string,arg_string[0] ? "/general" : "general");
  return arg_string;
}

int Test(TFunction *fnct, int verbose, ostream &out)
{  
  PRECISION err;
  int VerboseErrors, TerminalErrors, rtrn, arg_err, fnct_err, total_err=0;
  char msg[512], err_string[128];

  VerboseErrors=dw_SetVerboseErrors(0);
  TerminalErrors=dw_SetTerminalErrors(0);

  do
    {
      rtrn=fnct->CallFunction();
      if (fnct->ArgChanged())
	{
	  sprintf(msg," - Not OK - pointer argument improperly changed");
	  fnct_err=1;
	}
      else
	if (rtrn)
	  fnct_err=EvaluateErr(msg,err=fnct->ResultOK());
	else
	  if (arg_err=fnct->ArgumentsError())
	    {
	      sprintf(msg," - OK - Argument Error : %s : %s",GetErrorString(arg_err,err_string),dw_GetErrorMessage());
	      fnct_err=0;
	    }
	  else
	    {
	      sprintf(msg," - Not OK - function should have valid return : %s",dw_GetErrorMessage());
	      fnct_err=1;
	    }

      if (verbose || fnct_err) PrintMessage(out,fnct,msg);

      fnct->CleanCall();

      total_err|=fnct_err;
    }
  while (fnct->Next());

  dw_SetVerboseErrors(VerboseErrors);
  dw_SetTerminalErrors(TerminalErrors);

  return total_err;
}

int TestFunction(TFunction *fnct, int min, int max, int verbose, ostream &out)
{
  int total_err=0;

  // Basic Test
  if (verbose) out << "=== Basic Test ====================\n";
  if (fnct->TestBasic(min,max))
    total_err|=Test(fnct,verbose,out);

  // Equal Pointers Test
  if (verbose) out << "=== Equal Pointers Test ====================\n";
  if (fnct->TestEqualPointers(min,max))
    total_err|=Test(fnct,verbose,out);

  // Invalid Sizes Test
  if (verbose) out << "=== Invalid Sizes Test ====================\n";
  if (fnct->TestInvalidSizes(min,max))
    total_err|=Test(fnct,verbose,out);

  // Invalid Nulls Test
  if (verbose) out << "=== Invalid Nulls Test ====================\n";
  if (fnct->TestInvalidNulls(min,max))
    total_err|=Test(fnct,verbose,out);

  cout << fnct->GetName() << (total_err ? " - Not OK\n" : " - OK\n");

  return total_err;
}

int TestFunctionAll(TFunction_Node *FunctionList, int min, int max, int verbose, ostream &out)
{
  TFunction *fnct;
  int total_err=0;
  if (FunctionList)
    do 
      {
	fnct=FunctionList->GetNode();
	total_err+=TestFunction(fnct,min,max,verbose,out);
      }
    while (FunctionList=FunctionList->Next());
  return total_err;
}

int TestFunctionSingle(TFunction *fnct, int min, int max, int verbose, ostream &out)
{
  return TestFunction(fnct,min,max,verbose,out);
}

int SpeedTest(TFunction *fnct, int size, int count, ostream &out) 
{
  int btime, etime, TerminalErrors;
  timeval btimeval, etimeval;
  if (size <= 0) size=10;
  out << "Speed test " << count << " - " << fnct->GetName() << endl;
  if (fnct->TestSpeed(size))
    {
      TerminalErrors=dw_SetTerminalErrors(0);
      gettimeofday(&btimeval,NULL);
      btime=clock();
      for (int i=0; i < count; i++)
	{
	  fnct->CallFunction();
	  fnct->CleanCall();
	}
      etime=clock();
      gettimeofday(&etimeval,NULL);
      dw_SetTerminalErrors(TerminalErrors);
      out << "Elapsed CPU time: " << (double)(etime - btime)/(double)CLOCKS_PER_SEC << " seconds\n";
      out << "Elapsed time: " << (etimeval.tv_sec+etimeval.tv_usec*1e-6)-(btimeval.tv_sec+btimeval.tv_usec*1e-6) << " seconds\n";
    }
}

//===============================================================================
//===============================================================================
//===============================================================================

void PrintUntested(TFunction_Node *FunctionList, char *filename, ostream &out)
{
  TFunction_Node *node;
  TFunction *fnct;
  FILE *f_in;
  char *buffer1=(char*)NULL, *buffer2=(char*)NULL, *sbuffer, ch;
  int n1=0, n2=0, sn, j, k;

  f_in=dw_OpenTextFile(filename);

  buffer2=dw_ReadLine(f_in,buffer2,&n2);

  while (buffer1=dw_ReadLine(f_in,buffer1,&n1))
    {
      if (buffer1[0] == '{') 
	{
	  for (k=0; buffer2[k] && !isspace(buffer2[k]); k++);
	  for (  ; isspace(buffer2[k]); k++);
	  for (j=k; buffer2[j] && (buffer2[j] != '('); j++);
	  ch=buffer2[j];
	  buffer2[j]='\0';
	  for (node=FunctionList; node; node=node->Next()) 
	    {
	      fnct=node->GetNode();
	      if (!strcmp(buffer2+k,fnct->GetName())) break;
	    }
	  buffer2[j]=ch;
	  if (!node) out << buffer2;
	}
      sbuffer=buffer1;
      buffer1=buffer2;
      buffer2=sbuffer;
      sn=n1;
      n1=n2;
      n2=sn;
    }
      
  fclose(f_in);
}

void PrintAll(TFunction_Node *FunctionList, char *filename, ostream &out)
{
  FILE *f_in;
  char *buffer1=(char*)NULL, *buffer2=(char*)NULL, *sbuffer;
  int n1=0, n2=0, sn, k;

  f_in=dw_OpenTextFile(filename);

  buffer2=dw_ReadLine(f_in,buffer2,&n2);

  while (buffer1=dw_ReadLine(f_in,buffer1,&n1))
    {
      if (buffer1[0] == '{') out << buffer2;
      sbuffer=buffer1;
      buffer1=buffer2;
      buffer2=sbuffer;
      sn=n1;
      n1=n2;
      n2=sn;
    }

  fclose(f_in);
}

//===============================================================================
//===============================================================================
//===============================================================================

int main(int nargs, char **args)
{
  TFunction_Node *FunctionList, *Node;
  TFunction *fnct;
  ofstream out;
  char ch[256], *filename, *fnct_name, *cfilename;
  int min, max, verbose, i, count, total_err;

  if (dw_FindArgument(nargs,args,'h') != -1)
    {
      printf("\nOptions"
             "\n  -b  <size>  minimum size to test (default: 1,2,3,7,10 or maximum)"
             "\n  -t  <size>  maximum size to test (default: 1,2,3,7,10 or minimum)"
             "\n  -f  <function name> function to test (default: all functions)"
             "\n  -o  <file name>  output file (default: test.dat)"
             "\n  -v  verbose mode (default: off)"
             "\n  -l  make list of untested functions in file"
             "\n  -a  make list of all functions in file"
             "\n  -s  <count>  speed test (default: 100 | must be used with -f | test size is minimum)"
             "\n  -h  this help screen\n");
      return 0;
    }
   
  filename=dw_ParseString(nargs,args,'o',"test.dat");
  out.open(filename);
  if (!out.is_open())
    {
      cerr << "Unable to open " << filename << endl;
      dw_exit(0);
    }

  min=dw_ParseInteger(nargs,args,'b',-1);
  if ((max=dw_ParseInteger(nargs,args,'t',min)) < min) max=min;

  verbose=(dw_FindArgument(nargs,args,'v') != -1) ? 1 : 0;
 
  dw_initialize_generator(30);

  FunctionList=CreateFunctionList();

  if (cfilename=dw_ParseString(nargs,args,'l',(char*)NULL))
    PrintUntested(FunctionList,cfilename,out);
  else if (cfilename=dw_ParseString(nargs,args,'a',(char*)NULL))
    PrintAll(FunctionList,cfilename,out);
  else if (fnct_name=dw_ParseString(nargs,args,'f',(char*)NULL))
    {
      for (Node=FunctionList; Node; Node=Node->Next()) 
	{
	  fnct=Node->GetNode();
	  if (!strcmp(fnct_name,fnct->GetName()))
	    {
	      if (dw_FindArgument(nargs,args,'s') != -1)
		SpeedTest(fnct,min,dw_ParseInteger(nargs,args,'s',100),cout);
	      else
		do
		  {
		    cout << '\n';
		    TestFunctionSingle(fnct,min,max,verbose,out);
		    cout << "Enter 'q' to quit ";
		    cin.getline(ch,256);
		  }
		while (toupper(ch[0]) != 'Q');
	      break;
	    }
	}
      if (!Node)
	cout << "Function not found: " << fnct_name << endl;
    }
  else
    do
      {
	cout << '\n';
	if (total_err=TestFunctionAll(FunctionList,min,max,verbose,out))
	  cout << "\nAn error occured in " << total_err << " of the functions\n";
	else
	  cout << "\nAll OK\n";
	cout << "Enter 'q' to quit ";
	cin.getline(ch,256);
      }
    while (toupper(ch[0]) != 'Q');

  out.close();

  delete FunctionList;

  cout << "Done!\n";
  return 0;
}
