/*
 * 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 "dw_matrix_rand.h"
#include "dw_std.h"

#include <string.h>
#include <iostream>
#include <ostream>

int CheckArgumentString(char*);
int CreateArguments(char*,TArg**,int);


//=== TArgInc ========================================================= begin ===
TArgInc::TArgInc(TArgList* p_args) 
{
  args=p_args; 
  args->DefaultTestSizes();
}

TArgInc::TArgInc(TArgList* p_args, int** ts) 
{
  args=p_args; 
  args->SetTestSizes(ts);
}

TArgInc::TArgInc(TArgList* p_args, int nts, int* ts) 
{
  args=p_args;
  args->SetTestSizes(nts,ts);
}

TArgInc::TArgInc(TArgList* p_args, int min, int max)
{
  args=p_args;
  args->SetTestSizes(min,max);
}

void TArgInc::ComputeActualSizes(void)
{
  args->ComputeCorrectSizes();
  memcpy(args->GetActualSizes(),args->GetCorrectSizes(),args->GetNumberSizes()*sizeof(int));
}
//=== TArgInc =========================================================== end ===


//=== TArgInc_Basic =================================================== begin ===
TArgInc_Basic::~TArgInc_Basic()
{
  if (valid_null) dw_free(valid_null);
}

int TArgInc_Basic::First(void)
{
  for (int i=args->number_args()-1; i >= 0; i--) valid_null[i]=0;
  args->ResetMinimalIndexes();
  ComputeActualSizes();
  args->Allocate();
  args->Initialize();
  return 1;
}

int TArgInc_Basic::Next(void)
{
  TArg_pointer *p;
  if (!IncrementValidNull())
    if (!args->IncrementMinimalIndexes())
      return 0;

  ComputeActualSizes();
  args->Allocate();
  for (int i=args->number_args()-1; i >= 0; i--)
    if (valid_null[i] && (p=dynamic_cast<TArg_pointer*>(args->arg(i))))
      p->Free();
  args->Initialize();
  return 1;
}

int TArgInc_Basic::IncrementValidNull(void)
{
  TArg_pointer *p;
  for (int i=args->number_args()-1; i >= 0; i--)
    if (valid_null[i] == 1)
      valid_null[i]=0;
    else
      if ((p=dynamic_cast<TArg_pointer*>(args->arg(i))) && p->IsNullOK())
	{
	  valid_null[i]=1;
	  return 1;
	}
  return 0;
}
//=== TArgInc_Basic ===================================================== end ===


//=== TArgInc_InvalidSizes ============================================ begin ===
void TArgInc_InvalidSizes::ComputeActualSizes(void)
{
  TArgInc::ComputeActualSizes();
  args->MakeSizeInvalid(i);
}

int TArgInc_InvalidSizes::First(void)
{
  i=0;
  args->ResetMinimalIndexes();
  ComputeActualSizes();
  args->Allocate();
  args->Initialize();
  return 1;
}

int TArgInc_InvalidSizes::Next(void)
{
  if (i < args->GetNumberSizes()-1)
    i++;
  else
    if (args->IncrementMinimalIndexes())
      i=0;
    else
      return 0;
 
  ComputeActualSizes();
  args->Allocate();
  args->Initialize();
  return 1;
}
//=== TArgInc_InvalidSizes ============================================== end ===


//=== TArgInc_InvalidNulls ============================================ begin ===
int TArgInc_InvalidNulls::First(void)
{
  i=-1;
  args->ResetMinimalIndexes();
  return Next();
}

int TArgInc_InvalidNulls::Next(void)
{
  TArg_pointer *p;
  while (++i < args->number_args())
    if ((p=dynamic_cast<TArg_pointer*>(args->arg(i))) && !p->IsNullOK()) break;

  while (i == args->number_args())
    if (!args->IncrementMinimalIndexes())
      return 0;
    else
      for (i=0; i < args->number_args(); i++)
	if ((p=dynamic_cast<TArg_pointer*>(args->arg(i))) && !p->IsNullOK()) break;

  ComputeActualSizes();
  args->Allocate();
  p->Free();
  args->Initialize();
  return 1;
}
//=== TArgInc_InvalidNulls ============================================== end ===


//=== TArgInc_EqualPointers =========================================== begin ===
void TArgInc_EqualPointers::SetupEqualPointers(void)
{ 
  actual=(int*)NULL;
  master=(int**)NULL;
  if (args->number_args() > 0)
    {
      if (!(actual=(int*)dw_malloc(args->number_args()*sizeof(int)))) throw ERR_MEM_FAILURE;
      if (!(master=(int**)dw_malloc(args->number_args()*sizeof(int*)))) throw ERR_MEM_FAILURE;
      for (int i=0; i < args->number_args(); i++) master[i]=(int*)NULL;
      for (int i=0; i < args->number_args(); i++)
	if (!(master[i]=(int*)dw_malloc((args->number_args()+1)*sizeof(int)))) throw ERR_MEM_FAILURE;
    }
}

TArgInc_EqualPointers::~TArgInc_EqualPointers()
{
  if (actual) dw_free(actual);
  if (master)
    {
      for (int i=0; i < args->number_args(); i++)
	if (master[i]) dw_free(master[i]);
      dw_free(master);
    }
}

int TArgInc_EqualPointers::First(void)
{
  args->ResetMinimalIndexes();
  while (!SetMaster())
    if (!args->IncrementMinimalIndexes())
      return 0;
  return Next();
}

int TArgInc_EqualPointers::Next(void)
{
  if (IncrementActual())
    {
      Initialize();
      return 1;
    }
  else
    while (args->IncrementMinimalIndexes())
      if (SetMaster())
	if (IncrementActual())
	  {
	    Initialize();
	    return 1;
	  }
  return 0;
}

int TArgInc_EqualPointers::SetMaster(void)
{
  TArg_sized *sp, *s;
  int i, j, k, primary, found, n=args->number_args(), *row, *actual_sizes=args->GetActualSizes();

  ComputeActualSizes();
  for (i=n-1; i >= 0; i--)
    {
      actual[i]=-1;
      master[i][n]=-1;
      for (j=n-1; j >= 0; j--)
	master[i][j]=0;
    }

  for (threads=0, primary=0; primary < n; primary++)
    if (dynamic_cast<TArg_pointer*>(args->arg(primary)))
      {
	for (i=0; i < n; i++)
	  if ((i != primary) && (dynamic_cast<TArg_pointer*>(args->arg(i))))
	    {
	      found=0;
	      switch (args->arg(i)->type())
		{
		case dw_TVector_type:
		  if (dynamic_cast<TArg_TVector*>(args->arg(primary))) found=1;
		  break;
		case dw_TMatrix_S_type:
		  if (dynamic_cast<TArg_TMatrix_S*>(args->arg(primary))) found=1;
		  break;
		case dw_TMatrix_SPD_type:
		  if (dynamic_cast<TArg_TMatrix_SPD*>(args->arg(primary))) found=1;
		  break;
		case dw_TMatrix_U_type:
		  if (dynamic_cast<TArg_TMatrix_U*>(args->arg(primary))) found=1;
		  break;
		case dw_TMatrix_L_type:
		  if (dynamic_cast<TArg_TMatrix_L*>(args->arg(primary))) found=1;
		  break;
		case dw_TMatrix_type:
		  if (dynamic_cast<TArg_TMatrix*>(args->arg(primary))) found=1;
		  break;
		case dw_TPermutation_type:
		  if (dynamic_cast<TArg_TPermutation*>(args->arg(primary))) found=1;
		  break;
		case dw_pInteger_type:
		  if (dynamic_cast<TArg_pInteger*>(args->arg(primary))) found=1;
		  break;
		default:
		  throw ERR_ARG_TYPE;
		}
	      if (found)
		if ((sp=dynamic_cast<TArg_sized*>(args->arg(primary))) && (s=dynamic_cast<TArg_sized*>(args->arg(i))))
		  {
		    for (j=sp->GetNumberSizes()-1; j >= 0; j--)
		      if (sp->GetSizeReference(j) != s->GetSizeReference(j))
			{
			  found=0;
			  break;
			}
		  }
	      if (found)
		{
		  master[threads][i]=1;
		  master[threads][n]=primary;
		}
	    }
	if (master[threads][n] != -1) 
	  {
	    master[threads][primary]=1;
	    threads++;
	  }
      }

  for (i=0; i < threads; i++)
    for (j=i+1; j < threads; j++)
      {
	for (k=0; k < n; k++)
	  if (master[i][k] != master[j][k]) break;
	if (k == n) master[j][master[i][n]]=0;
      }

  for (i=0; i < threads; i++)
    master[i][master[i][n]]=0;

  /**
  printf("\n");
  for (i=0; i < n; i++)
    {
      for (j=0; j <= n; j++) printf("%d ",master[i][j]);
      printf("\n");
    }
  printf("\n");
  /**/

  return threads ? 1 : 0;
}

int TArgInc_EqualPointers::IncrementActual(void)
{
  int i, k, primary, n=args->number_args();
  for (k=0; k < threads; k++)
    {
      primary=master[k][n];
      if ((actual[primary] == -1) || (actual[primary] == primary))
	{
	  actual[primary]=-1;
	  for (i=0; i < n; i++)
	    if ((i != primary) && master[k][i])
	      if (actual[i] == primary)
		actual[i]=-1;
	      else
		if (actual[i] == -1)
		  {
		    actual[i]=actual[primary]=primary;
		    return 1;
		  }
	}
    }
  return 0;
}

void TArgInc_EqualPointers::Initialize(void)
{
  int i, k, primary, n=args->number_args();
  args->Allocate();
  for (k=0; k < threads; k++)
    for (primary=master[k][n], i=0; i < n; i++)
      if ((i != primary) && (actual[i] == primary))
	(dynamic_cast<TArg_pointer*>(args->arg(i)))->EquatePointers(dynamic_cast<TArg_pointer*>(args->arg(primary)));
  args->Initialize();
  //for (i=0; i < args->number_args(); i++) printf("%d ",actual[i]); printf("\n"); args->PrintArgList(cout);printf("\n"); getchar();
}
//=== TArgInc_EqualPointers ============================================= end ===


//=== TArgList ======================================================== begin ===
/*
   arg_string syntax

    pI(m)    - integer array
    I        - integer
    I(m)     - integer as a size parameter
    F        - PRECISION
    V(m)     - TVector : general
    M(m,n)   - TMatrix : general
    U(m,n)   - TMatrix : upper triangular
    L(m,n)   - TMatrix : lower triangular
    S(m,n)   - TMatrix : symmetric
    SPD(m,n) - TMatrix : symmetric and positive definite
    P(m)     - TPermutation : general

   Arguments must be separated by a semicolon and the last argument is not 
   followed by a semicolon.

   If any of the pointer argments is followed by an '!', then that argument is 
   allowed to be null.

   If any of the pointer arguments is followd by an '+', then that argument is 
   allowed to change.  If an argument is allowed to be null and change, then
   the '!' must preceed the '+'.

   The sizes can be any lower case letter.  A '?' signifies that the size is not
   determined. 

   If any of these argument is preceeded by a 'p', then it is a pointer to an
   argument of that type. (not yet implemented)

   Returns the number of arguments or -1 upon error.
*/
int CheckArgString(char *s)
{
  int nargs=0, j=0;
  
  if (!s || !s[0]) return nargs;

  while (1)
    {
      switch (s[j])
	{
	case 'p':
	  switch (s[++j])
	    {
	    case 'I':
	      if ((s[j+1] != '(') || ((s[j+2] != '?') && ((s[j+2] < 'a') || ('z' < s[j+2]))) || (s[j+3] != ')'))
		return -1;
	      if (s[j+=4] == '!') j+=1;
	      if (s[j] == '+') j+=1;
	      break;
	    default:
	      cerr << "Invalid argument string\n";
	      return -1;
	    }
	  break;
	case 'I':
	  if (s[j+1] != '(')
	    j+=1;
	  else
	    {
	      if (((s[j+2] != '?') && ((s[j+2] < 'a') || ('z' < s[j+2]))) || (s[j+3] != ')'))
		return -1;
	      j+=4;
	    }
	  break;
	case 'F':
	  j+=1;
	  break;
	case 'V': 
	  if ((s[j+1] != '(') || ((s[j+2] != '?') && ((s[j+2] < 'a') || ('z' < s[j+2]))) || (s[j+3] != ')'))
	    return -1;
	  if (s[j+=4] == '!') j+=1;
	  if (s[j] == '+') j+=1;
	  break;
	case 'S': 
	  if ((s[j+1] == 'P') && (s[j+2] == 'D')) j+=2;
	case 'M':	 
	case 'U':
	case 'L':
	  if ((s[j+1] != '(') || ((s[j+2] != '?') && ((s[j+2] < 'a') || ('z' < s[j+2]))) || 
	      (s[j+3] != ',') || ((s[j+4] != '?') && ((s[j+4] < 'a') || ('z' < s[j+4]))) || (s[j+5] != ')'))
	    return -1;
	  if (s[j+=6] == '!') j+=1;
	  if (s[j] == '+') j+=1;
	  break;
        case 'P':
	  if ((s[j+1] != '(') || ((s[j+2] != '?') && ((s[j+2] < 'a') || ('z' < s[j+2]))) || (s[j+3] != ')'))
	    return -1;
	  if (s[j+=4] == '!') j+=1;
	  if (s[j] == '+') j+=1;
	  break;
	default:
	  cerr << "Invalid argument string\n";
	  return -1;
	}

      nargs++;

      if (!s[j]) return nargs;

      if (s[j] == ';') 
	j+=1;
      else
	return -1;
    }
}

void SetSizeCharacter(char *s, int *j, TArg_sized *arg)
{
  int i, n=arg->GetNumberSizes();
  for (i=0; i < n; i++)
    arg->SetSizeChar(s[(*j)+=2],i);
  (*j)+=2;
}

void SetPointerInfo(char *s, int *j, TArg_pointer *arg)
{
  if (s[*j] == '!')
    {
      arg->SetNullOK(1);
      (*j)++;
    }
  if (s[*j] == '+') 
    {
      arg->SetChangeOK(1);
      (*j)++;
    }
}

int CreateArgs(char *s, TArg **args, int nargs)
{
  int i, j;

  TArg_TVector *pV;
  TArg_TMatrix *pM;
  TArg_TMatrix_U *pU;
  TArg_TMatrix_L *pL;
  TArg_TMatrix_S *pS;
  TArg_TMatrix_SPD *pSPD;
  TArg_TPermutation *pP;
  TArg_int_size *pIS;
  TArg_pInteger *ppI;

  // Create each argument
  for (j=i=0; i < nargs; i++)
    {
      switch (s[j])
	{
	case 'p':
	  switch (s[++j])
	    {
	    case 'I':
	      ppI=new TArg_pInteger();
	      args[i]=static_cast<TArg*>(ppI);
	      SetSizeCharacter(s,&j,static_cast<TArg_sized*>(ppI));
	      SetPointerInfo(s,&j,static_cast<TArg_pointer*>(ppI));
	      break;
	    default:
	      cerr << "Invalid argument string\n";
	      return 0;
	    }
	  break;
	case 'I':
	  if (s[j+1] != '(')
	    {
	      args[i]=static_cast<TArg*>(new TArg_int());
	      j++;
	    }
	  else
	    {
	      pIS=new TArg_int_size();
	      args[i]=static_cast<TArg*>(pIS);
	      SetSizeCharacter(s,&j,static_cast<TArg_sized*>(pIS));
	    }
	  break;
	case 'F':
	  args[i]=static_cast<TArg*>(new TArg_PRECISION());
	  j++;
	  break;
	case 'V': 
	  pV=new TArg_TVector();
	  args[i]=static_cast<TArg*>(pV);
	  SetSizeCharacter(s,&j,static_cast<TArg_sized*>(pV));
	  SetPointerInfo(s,&j,static_cast<TArg_pointer*>(pV));
	  break;
	case 'M':
	  pM=new TArg_TMatrix();
	  args[i]=static_cast<TArg*>(pM);
	  SetSizeCharacter(s,&j,static_cast<TArg_sized*>(pM));
	  SetPointerInfo(s,&j,static_cast<TArg_pointer*>(pM));
	  break;	 
	case 'U':
	  pU=new TArg_TMatrix_U();
	  args[i]=static_cast<TArg*>(pU);
	  SetSizeCharacter(s,&j,static_cast<TArg_sized*>(pU));
	  SetPointerInfo(s,&j,static_cast<TArg_pointer*>(pU));
	  break;
	case 'L':
	  pL=new TArg_TMatrix_L();
	  args[i]=static_cast<TArg*>(pL);
	  SetSizeCharacter(s,&j,static_cast<TArg_sized*>(pL));
	  SetPointerInfo(s,&j,static_cast<TArg_pointer*>(pL));
	  break;
	case 'S': 
	  if ((s[j+1] == 'P') && (s[j+2] == 'D'))
	    {
	      j+=2;
	      pSPD=new TArg_TMatrix_SPD();
	      args[i]=static_cast<TArg*>(pSPD);
	      SetSizeCharacter(s,&j,static_cast<TArg_sized*>(pSPD));
	      SetPointerInfo(s,&j,static_cast<TArg_pointer*>(pSPD));
	    }
	  else
	    {
	      pS=new TArg_TMatrix_S();
	      args[i]=static_cast<TArg*>(pS);
	      SetSizeCharacter(s,&j,static_cast<TArg_sized*>(pS));
	      SetPointerInfo(s,&j,static_cast<TArg_pointer*>(pS));
	    }
	  break;;
        case 'P':
	  pP=new TArg_TPermutation();
	  args[i]=static_cast<TArg*>(pP);
	  SetSizeCharacter(s,&j,static_cast<TArg_sized*>(pP));
	  SetPointerInfo(s,&j,static_cast<TArg_pointer*>(pP));
	  break;
	default:
	  cerr << "Invalid argument string\n";
	  return 0;
	}

      for (j++; s[j] && (s[j-1] != ';'); j++);
    }

  return 1;
}

TArgList::TArgList(char *string)
{
  int ch[256], i, j, k;
  TArg_sized *p;

  // default values
  nargs=0;
  args=(TArg**)NULL;
  arg_string=(char*)NULL;
  n_minimal_idx=0;
  minimal_idx=(int*)NULL;
  n_sizes=0;
  sizes_idx=(int*)NULL;
  correct_sizes=(int*)NULL;
  actual_sizes=(int*)NULL;

  // No arguments 
  if (!string || !string[0]) return;

  // Check and copy argument string
  if ((nargs=CheckArgString(string)) == -1) throw ERR_STRING_SYNTAX;
  if (!(arg_string=(char*)dw_malloc((strlen(string)+1)*sizeof(char)))) throw ERR_MEM_FAILURE;
  strcpy(arg_string,string);

  // Setup Arguments
  if (!(args=(TArg**)dw_malloc(nargs*sizeof(TArg*)))) throw ERR_MEM_FAILURE;
  for (i=0; i < nargs; i++) args[i]=(TArg*)NULL;
  if (!CreateArgs(arg_string,args,nargs)) throw ERR_STRING_SYNTAX;

  // Set n_sizes, sizes_idx, correct_sizes, and actual_sizes
  for (n_sizes=i=0; i < nargs; i++)
    if ((p=dynamic_cast<TArg_sized*>(args[i]))) n_sizes+=p->GetNumberSizes();
  if (!(sizes_idx=(int*)dw_malloc(n_sizes*sizeof(int)))) throw ERR_MEM_FAILURE;
  if (!(correct_sizes=(int*)dw_malloc(n_sizes*sizeof(int)))) throw ERR_MEM_FAILURE;
  if (!(actual_sizes=(int*)dw_malloc(n_sizes*sizeof(int)))) throw ERR_MEM_FAILURE;

  // Set n_mininal_idx, minimal_idx 
  for (i=0; i < 256; i++) ch[i]=0;

  for (i=nargs-1; i >= 0; i--)
    if (p=dynamic_cast<TArg_sized*>(args[i]))
      for (j=p->GetNumberSizes()-1; j >= 0; j--)
	if (p->GetSizeChar(j) != '?') ch[p->GetSizeChar(j)]++;

  for (n_minimal_idx=i=0; i < 256; i++)
    ch[i]=(ch[i] > 0) ? n_minimal_idx++ : -1;

  if (!(minimal_idx=(int*)dw_malloc(n_minimal_idx*sizeof(int)))) throw ERR_MEM_FAILURE; 
  for (i=0; i < n_minimal_idx; i++) minimal_idx[i]=0;

  // Set size_idx values and size reference
  for (k=i=0; i < nargs; i++)
    if (p=dynamic_cast<TArg_sized*>(args[i]))
      for (j=0; j < p->GetNumberSizes(); j++)
	{
	  p->SetSizeReference(actual_sizes+k,j);
	  sizes_idx[k++]=ch[p->GetSizeChar(j)];
	}

  // Set tests_sizes
  test_sizes=(int**)dw_malloc(n_minimal_idx*sizeof(int*));
  for (i=0; i < n_minimal_idx; i++) test_sizes[i]=(int*)NULL;
  DefaultTestSizes();
}

TArgList::~TArgList()
{
  if (minimal_idx) dw_free(minimal_idx);
  if (sizes_idx) dw_free(sizes_idx);
  if (correct_sizes) dw_free(correct_sizes);
  if (actual_sizes) dw_free(actual_sizes);
  if (test_sizes)
    {
      FreeTestSizes();
      dw_free(test_sizes);
    }
  if (args)
    {
      for (int i=0; i < nargs; i++)
	if (args[i]) delete args[i];
      dw_free(args);
    }
  if (arg_string) dw_free(arg_string);
}

void TArgList::FreeTestSizes(void)
{
  for (int i=0; i < n_minimal_idx; i++)
    if (test_sizes[i]) 
      {
	dw_free(test_sizes[i]);
	test_sizes[i]=(int*)NULL;
      }
}

void TArgList::DefaultTestSizes(void)
{
  int ts[5]={1,2,3,7,10};
  SetTestSizes(5,ts);
}

void TArgList::SetTestSizes(int **ts)
{
  if (ts)
    {
      FreeTestSizes();
      for (int i=0; i < n_minimal_idx; i++)
	{
	  if (!(test_sizes[i]=(int*)dw_malloc((ts[i][0]+1)*sizeof(int)))) throw ERR_MEM_FAILURE;
	  memcpy(test_sizes[i],ts[i],(ts[i][0]+1)*sizeof(int));
	}
    }
  else
    throw ERR_NULL_TEST_SIZES;
}

void TArgList::SetTestSizes(int nts, int* ts)
{
  if ((nts > 0) && ts)
    {
      FreeTestSizes();
      for (int i=0; i < n_minimal_idx; i++)
	{
	  if (!(test_sizes[i]=(int*)dw_malloc((nts+1)*sizeof(int)))) throw ERR_MEM_FAILURE;
	  test_sizes[i][0]=nts;
	  memcpy(test_sizes[i]+1,ts,nts*sizeof(int));
	}
    }
  else
    throw ERR_NULL_TEST_SIZES;
}

void TArgList::SetTestSizes(int min, int max)
{
  if ((min <= 0) || (max < min)) throw ERR_NULL_TEST_SIZES;
  int *ts=(int*)dw_malloc((max-min+1)*sizeof(int));
  if (!ts) throw ERR_MEM_FAILURE;
  for (int i=min; i <= max; i++) ts[i-min]=i;
  SetTestSizes(max-min+1,ts);
  dw_free(ts);
}

int TArgList::ResetMinimalIndexes(void)
{
  for (int i=0; i < n_minimal_idx; i++) minimal_idx[i]=1;
  return 1;
}

int TArgList::IncrementMinimalIndexes(void)
{
  for (int i=0; i < n_minimal_idx; i++)
    if (minimal_idx[i] == test_sizes[i][0])
      minimal_idx[i]=1;
    else
      {
	minimal_idx[i]++;
	return 1;
      }
  return 0;
}

void TArgList::ComputeCorrectSizes()
{
  for (int i=0; i < n_sizes; i++)
    correct_sizes[i]=(sizes_idx[i] >= 0) ? test_sizes[sizes_idx[i]][minimal_idx[sizes_idx[i]]] : -1;
}

void TArgList::MakeSizeInvalid(int i)
{
  if (actual_sizes[i] == 1)
    actual_sizes[i]=(rand() % 2) + 2;
  else
    if (actual_sizes[i] == 2)
      actual_sizes[i]=(rand() % 2) ? 1 : 3;
    else
      actual_sizes[i]+=(rand() % 2) ? -((rand() % 2) + 1) : (rand() % 2) + 1;
}

void TArgList::Allocate(void)
{
  for (int i=nargs-1; i >= 0; i--) args[i]->Allocate();
}

void TArgList::Initialize(void)
{
  for (int i=nargs-1; i >= 0; i--) args[i]->Initialize();
}

int TArgList::SizesOK(void)
{
  int i, j;
  for (i=n_sizes-1; i >= 0; i--)
    if (correct_sizes[i] != actual_sizes[i])
      if (sizes_idx[i] != -1)
	for (j=n_sizes-1; j >= 0; j--)
	  if ((sizes_idx[i] == sizes_idx[j]) && (actual_sizes[i] != actual_sizes[j]))
	    return 0;
  return 1;
}

int TArgList::NullOK(void)
{
  TArg_pointer *p;
  for (int i=nargs-1; i >= 0; i--)
    if ((p=dynamic_cast<TArg_pointer*>(args[i])) && !p->address() && !p->IsNullOK())
      return 0;
  return 1;
}

int TArgList::ArgChanged(void)
{
  int i, j;
  TArg_pointer *p, *q;
  for (i=nargs-1; i >= 0; i--)
    if ((p=dynamic_cast<TArg_pointer*>(args[i])) && p->address() && !p->IsChangeOK() && p->Owner())
      {
	for (j=nargs-1; j >= 0; j--)
	  if ((j != i) && (q=dynamic_cast<TArg_pointer*>(args[j])) && (q->address() == p->address()) && q->IsChangeOK()) break;
	if ((j < 0) && p->Changed()) return 1;
      }
  return 0;
}

#define ARG1(i,j) dynamic_cast<TArg_sized*>(args[i])->GetSize(j)
#define ARG2(type,i) dynamic_cast<type*>(args[i])->GetArg()
#define ARG3(i) dynamic_cast<TArg_pointer*>(args[i])
void TArgList::PrintArgList(ostream &out)
{
  TArg_pointer *p, *q;
  int i, j;
  out << '(';
  for (i=0; i < nargs; i++)
    {
      switch(args[i]->type())
	{
	case dw_pInteger_type:
	  p=ARG3(i);
	  if (!p->address())
	    out << "null";
	  else
	    if (p->Owner())
	      out << "pI" << i << '(' << ARG1(i,0) << ')';
	    else
	      {
		for (j=0; j < nargs; j++)
		  if ((j != i) && (q=ARG3(j)) && (p->address() == q->address()))
		    {
		      out << "pI" << j << '(' << ARG1(i,0) << ')';
		      break;
		    }
		if (j == nargs)
		  out << "pI?(" << ARG1(i,0) << ')';
	      }
	  break;
	case dw_int_type:
	  out << ARG2(TArg_int,i);
	  break;
	case dw_int_size_type:
	  out << ARG2(TArg_int_size,i);
	  break;
	case dw_PRECISION_type:
	  out << ARG2(TArg_PRECISION,i);
	  break;
	case dw_TVector_type:
	  p=ARG3(i);
	  if (!p->address())
	    out << "null";
	  else
	    if (p->Owner())
	      out << 'V' << i << '(' << ARG1(i,0) << ')';
	    else
	      {
		for (j=0; j < nargs; j++)
		  if ((j != i) && (q=ARG3(j)) && (p->address() == q->address()))
		    {
		      out << 'V' << j << '(' << ARG1(i,0) << ')';
		      break;
		    }
		if (j == nargs)
		  out << "V?(" << ARG1(i,0) << ')';
	      }
	  break;
	case dw_TMatrix_type:
	  p=ARG3(i);
	  if (!p->address())
	    out << "null";
	  else
	    if (p->Owner())
	      out << 'M' << i << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	    else
	      {
		for (j=0; j < nargs; j++)
		  if ((q=ARG3(j)) && q->Owner() && (p->address() == q->address()))
		    {
		      out << 'M' << j << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
		      break;
		    }
		if (j == nargs)
		  out << "M?(" << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	      }
	  break;
	case dw_TMatrix_U_type:
	  p=ARG3(i);
	  if (!p->address())
	    out << "null";
	  else
	    if (p->Owner())
	      out << 'U' << i << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	    else
	      {
		for (j=0; j < nargs; j++)
		  if ((j != i) && (q=ARG3(j)) && (p->address() == q->address()))
		    {
		      out << 'U' << j << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
		      break;
		    }
		if (j == nargs)
		  out << "U?(" << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	      }
	  break;
	case dw_TMatrix_L_type:
	  p=ARG3(i);
	  if (!p->address())
	    out << "null";
	  else
	    if (p->Owner())
	      out << 'L' << i << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	    else
	      {
		for (j=0; j < nargs; j++)
		  if ((j != i) && (q=ARG3(j)) && (p->address() == q->address()))
		    {
		      out << 'L' << j << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
		      break;
		    }
		if (j == nargs)
		  out << "L?(" << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	      }
	  break;
	case dw_TMatrix_SPD_type:
	  p=ARG3(i);
	  if (!p->address())
	    out << "null";
	  else
	    if (p->Owner())
	      out << "SPD" << i << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	    else
	      {
		for (j=0; j < nargs; j++)
		  if ((j != i) && (q=ARG3(j)) && (p->address() == q->address()))
		    {
		      out << 'L' << j << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
		      break;
		    }
		if (j == nargs)
		  out << "L?(" << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	      }
	  break;
	case dw_TMatrix_S_type:
	  p=ARG3(i);
	  if (!p->address())
	    out << "null";
	  else
	    if (p->Owner())
	      out << 'S' << i << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	    else
	      {
		for (j=0; j < nargs; j++)
		  if ((j != i) && (q=ARG3(j)) && (p->address() == q->address()))
		    {
		      out << 'L' << j << '(' << ARG1(i,0) << ',' << ARG1(i,1) << ')';
		      break;
		    }
		if (j == nargs)
		  out << "L?(" << ARG1(i,0) << ',' << ARG1(i,1) << ')';
	      }
	  break;
	case dw_TPermutation_type:
	  p=ARG3(i);
	  if (!p->address())
	    out << "null";
	  else
	    if (p->Owner())
	      out << 'P' << i << '(' << ARG1(i,0) << ')';
	    else
	      {
		for (j=0; j < nargs; j++)
		  if ((j != i) && (q=ARG3(j)) && (p->address() == q->address()))
		    {
		      out << 'P' << j << '(' << ARG1(i,0) << ')';
		      break;
		    }
		if (j == nargs)
		  out << "P?(" << ARG1(i,0) << ')';
	      }
	  break;
	default:
	  throw ERR_ARG_TYPE;
	}
      if (i < nargs-1) out << ',';
    }
  out << ')';
}
#undef ARG1
#undef ARG2
#undef ARG3

//=== TArgList ========================================================== end ===


//=== TArg_sized ====================================================== begin ===
TArg_sized::TArg_sized(int n)
{
  n_sizes=n;
  if (!(size=(int**)dw_malloc(n*sizeof(int*))))
    throw ERR_MEM_FAILURE;
  if (!(size_char=(unsigned char*)dw_malloc(n*sizeof(unsigned char))))
    throw ERR_MEM_FAILURE;
  for (int i=0; i < n; i++) 
    {
      size[i]=(int*)NULL;
      size_char[i]='?';
    }
}

TArg_sized::~TArg_sized()
{
  if (size) dw_free(size);
  if (size_char) dw_free(size_char);
}
//=== TArg_sized ======================================================== end ===


//=== TArg_TMatrix ==================================================== begin ===
void TArg_TMatrix::Free(void)
{
  if (owner)
    {
      if (arg) FreeMatrix(arg);
      if (copy) FreeMatrix(copy);
    }
  arg=(TMatrix)NULL;
  copy=(TMatrix)NULL;
  owner=0;
}

void TArg_TMatrix::CreateMatrices(int row, int col)
{
  if (owner && arg)
    {
      if ((RowM(arg) != row) || (ColM(arg) != col))
	{
	  FreeMatrix(arg);
	  FreeMatrix(copy);
	  arg=CreateMatrix(row,col);
	  copy=CreateMatrix(row,col);
	}
    }
  else
    {
      arg=CreateMatrix(row,col);
      copy=CreateMatrix(row,col);
    }
  owner=1;
}

void TArg_TMatrix::EquatePointers(TArg_pointer* p_arg) 
{
  TArg_TMatrix *p=dynamic_cast<TArg_TMatrix*>(p_arg);
  if (p)
    { 
      Free();
      arg=p->arg; 
      copy=p->copy; 
      owner=0;
    }
}

int TArg_TMatrix::Changed(void)
{
  if (arg)
    {
      int i;
      PRECISION *a=pElementM(arg), *c=pElementM(copy);
      for (i=RowM(arg)*ColM(arg)-1; i >= 0; i--)
	if (a[i] != c[i]) return 1;
    }
  return 0;
}

int TArg_TMatrix::GetSize(int i)
{ 
  if (i == 0) 
    return arg ? RowM(arg) : 0; 
  else 
    if (i == 1) 
      return arg ? ColM(arg) : 0; 
    else 
      throw ERR_INDEX_OUT_OF_RANGE; 
}
//=== TArg_TMatrix ====================================================== end ===

//=== TArg_TMatrix_U ================================================== begin ===
void TArg_TMatrix_U::Initialize(void)
{
  if (arg && owner)
    {
      int i, j;
      dw_NormalMatrix(arg);
      for (i=RowM(arg)-1; i > 0; i--)
	for (j=(ColM(arg) < i) ? ColM(arg)-1 : i-1; j >= 0; j--)
	  ElementM(arg,i,j)=0.0;
      EquateMatrix(copy,arg);
    }
}

void TArg_TMatrix_U::EquatePointers(TArg_pointer* p_arg) 
{
  TArg_TMatrix_U *p=dynamic_cast<TArg_TMatrix_U*>(p_arg);
  if (p)
    { 
      Free();
      arg=p->arg; 
      copy=p->copy; 
      owner=0;
    }
}
//=== TArg_TMatrix_U ==================================================== end ===

//=== TArg_TMatrix_L ================================================== begin ===
void TArg_TMatrix_L::Initialize(void)
{
  if (arg && owner)
    {
      int i, j;
      dw_NormalMatrix(arg);
      for (j=ColM(arg)-1; j > 0; j--)
	for (i=(RowM(arg) < j) ? RowM(arg)-1 : j-1; i >= 0; i--)
	  ElementM(arg,i,j)=0.0;
      EquateMatrix(copy,arg);
    }
}

void TArg_TMatrix_L::EquatePointers(TArg_pointer* p_arg) 
{
  TArg_TMatrix_L *p=dynamic_cast<TArg_TMatrix_L*>(p_arg);
  if (p)
    { 
      Free();
      arg=p->arg; 
      copy=p->copy; 
      owner=0;
    }
}
//=== TArg_TMatrix_L ==================================================== end ===

//=== TArg_TMatrix_SPD ================================================ begin ===
void TArg_TMatrix_SPD::Initialize(void)
{
  if (arg && owner)
    {
      dw_NormalMatrix(arg);
      if (RowM(arg) == ColM(arg))
	TransposeProductMM(arg,arg,arg);
      EquateMatrix(copy,arg);
    }
}

void TArg_TMatrix_SPD::EquatePointers(TArg_pointer* p_arg) 
{
  TArg_TMatrix_SPD *p=dynamic_cast<TArg_TMatrix_SPD*>(p_arg);
  if (p)
    { 
      Free();
      arg=p->arg; 
      copy=p->copy; 
      owner=0;
    }
}
//=== TArg_TMatrix_SPD ================================================== end ===

//=== TArg_TMatrix_S ================================================== begin ===
void TArg_TMatrix_S::Initialize(void)
{
  if (arg && owner)
    {
      int i, j;
      dw_NormalMatrix(arg);
      if (RowM(arg) == ColM(arg))
	for (j=ColM(arg)-1; j > 0; j--)
	  for (i=j-1; i >= 0; i--)
	    ElementM(arg,i,j)=ElementM(arg,j,i);
      EquateMatrix(copy,arg);
    }
}

void TArg_TMatrix_S::EquatePointers(TArg_pointer* p_arg) 
{
  TArg_TMatrix_S *p=dynamic_cast<TArg_TMatrix_S*>(p_arg);
  if (p)
    { 
      Free();
      arg=p->arg; 
      copy=p->copy; 
      owner=0;
    }
}
//=== TArg_TMatrix_S ==================================================== end ===

//=== TArg_TVector ==================================================== begin ===
void TArg_TVector::Free(void)
{
  if (owner)
    {
      if (arg) FreeVector(arg);
      if (copy) FreeVector(copy);

    }
  arg=(TVector)NULL;
  copy=(TVector)NULL;
  owner=0;
}

void TArg_TVector::CreateVectors(int dim)
{
  if (owner && arg)
    {
      if (DimV(arg) != dim)
	{
	  FreeVector(arg);
	  FreeVector(copy);
	  arg=CreateVector(dim);
	  copy=CreateVector(dim);
	}
    }
  else
    {
      arg=CreateVector(dim);
      copy=CreateVector(dim);
    }
  owner=1;
}

void TArg_TVector::EquatePointers(TArg_pointer* p_arg) 
{
  TArg_TVector *p=dynamic_cast<TArg_TVector*>(p_arg);
  if (p)
    { 
      Free();
      arg=p->arg; 
      copy=p->copy; 
      owner=0;
    }
}

int TArg_TVector::Changed(void)
{
  if (arg)
    {
      int i;
      PRECISION *a=pElementV(arg), *c=pElementV(copy);
      for (i=DimV(arg)-1; i >= 0; i--)
	if (a[i] != c[i]) return 1;
    }
  return 0;
}

int TArg_TVector::GetSize(int i)
{ 
  if (i == 0) 
    return arg ? DimV(arg) : 0; 
  else 
    throw ERR_INDEX_OUT_OF_RANGE;
}
//=== TArg_TVector ====================================================== end ===


//=== TArg_TPermutation =============================================== begin ===
void TArg_TPermutation::Free(void)
{
  if (owner)
    {
      if (arg) FreePermutation(arg);
      if (copy) FreePermutation(copy);

    }
  arg=(TPermutation)NULL;
  copy=(TPermutation)NULL;
  owner=0;
}

void TArg_TPermutation::CreatePermutations(int dim)
{
  if (owner && arg)
    {
      if (DimP(arg) != dim)
	{
	  FreePermutation(arg);
	  FreePermutation(copy);
	  arg=CreatePermutation(dim);
	  copy=CreatePermutation(dim);
	}
    }
  else
    {
      arg=CreatePermutation(dim);
      copy=CreatePermutation(dim);
    }
  owner=1;
}

void TArg_TPermutation::Initialize(void) 
{
  if (arg && owner)
    {
      UseP(arg)=rand() % DimP(arg);
      for (int i=UseP(arg)-1; i >= 0; i--)
	ElementP(arg,i)=i + (rand() % (DimP(arg)-i));
      EquatePermutation(copy,arg);
    }
}

void TArg_TPermutation::EquatePointers(TArg_pointer* p_arg) 
{
  TArg_TPermutation *p=dynamic_cast<TArg_TPermutation*>(p_arg);
  if (p)
    { 
      Free();
      arg=p->arg; 
      copy=p->copy; 
      owner=0;
    }
}

int TArg_TPermutation::Changed(void)
{
  if (arg)
    {
      if (UseP(arg) != UseP(copy)) return 1;
      int i, *a=pElementP(arg), *c=pElementP(copy);
      for (i=UseP(arg)-1; i >= 0; i--)
      	if (a[i] != c[i]) return 1;
    }
  return 0;
}

int TArg_TPermutation::GetSize(int i)
{ 
  if (i == 0) 
    return arg ? DimP(arg) : 0; 
  else 
    throw ERR_INDEX_OUT_OF_RANGE;
}
//=== TArg_TPermutation ================================================= end ===


//=== TArg_pInteger =================================================== begin ===
void TArg_pInteger::Free(void)
{
  if (owner)
    {
      if (arg) dw_free(arg);
      if (copy) dw_free(copy);
    }
  arg=(int*)NULL;
  copy=(int*)NULL;
  n=0;
  owner=0;
}

void TArg_pInteger::CreateIntegerArray(int dim)
{
  if (owner && arg)
    {
      if (n != dim)
	{
	  dw_free(arg);
	  dw_free(copy);
	  arg=(int*)dw_malloc(dim*sizeof(int));
	  copy=(int*)dw_malloc(dim*sizeof(int));	
	}
    }
  else
    {
      arg=(int*)dw_malloc(dim*sizeof(int));
      copy=(int*)dw_malloc(dim*sizeof(int));
    }
  n=dim;
  owner=1;
}

void TArg_pInteger::Initialize(void)
{
  if (arg && owner)
    {
      for (int i=0; i < n; i++)
	copy[i]=arg[i]=(rand() % 10)+1;
    }
}

void TArg_pInteger::EquatePointers(TArg_pointer* p_arg) 
{
  TArg_pInteger *p=dynamic_cast<TArg_pInteger*>(p_arg);
  if (p)
    { 
      Free();
      arg=p->arg; 
      copy=p->copy; 
      n=p->GetSize(0);
      owner=0;
    }
}

int TArg_pInteger::Changed(void)
{
  if (arg)
    {
      for (int i=n-1; i >= 0; i--)
	if (arg[i] != copy[i]) return 1;
    }
  return 0;
}
//=== TArg_pInteger ===================================================== end ===


//=== TFunction ======================================================= begin ===
TFunction::TFunction(char* name_string, char *rtrn_string, char* arg_string)
  : TArgList(arg_string)
{
  rtrn=(TReturn*)NULL;
  inc=(TArgInc*)NULL;
  name=(char*)NULL;
  default_test_sizes=(int**)NULL;

  // Setup rtrn 
  if (!rtrn_string || !rtrn_string[0]) 
    rtrn=new TReturn_void();
  else
    switch (rtrn_string[0])
      {
      case 'p':
	cerr << "Not yet implemented\n";
	throw ERR_STRING_SYNTAX;
      case 'I':
	rtrn=static_cast<TReturn*>(new TReturn_int());
	break;
      case 'F':
	rtrn=static_cast<TReturn*>(new TReturn_PRECISION());
	break;
      case 'V': 
	rtrn=static_cast<TReturn*>(new TReturn_TVector());
	break;
      case 'M':	 
	rtrn=static_cast<TReturn*>(new TReturn_TMatrix());
	break;
      case 'P':
	rtrn=static_cast<TReturn*>(new TReturn_TPermutation());
	break;
      default:
	cerr << "Invalid return string\n";
	throw ERR_STRING_SYNTAX;
      }

  // Copy name
  if (!(name=(char*)dw_malloc((strlen(name_string)+1)*sizeof(char)))) throw ERR_MEM_FAILURE;
  strcpy(name,name_string);
}

TFunction::~TFunction()
{
  if (name) dw_free(name);
  if (rtrn) delete rtrn;
  if (inc) delete inc;
}

/*
   Returns 0 if all arguments are OK
   Returns 1 if there is a size error
   Returns 2 if there is a NULL error
*/
int TFunction::ArgumentsError(void)
{
  int err=0;

  if (args)
    {
      // Sizes OK
      if (!SizesOK()) err|=ARG_ERR_SIZE;

      // NULL pointers OK
      if (!NullOK()) err|=ARG_ERR_NULL;
    }

  return err;
}

void TFunction::SetReturnOwner(void)
{
  TReturn_pointer *p=dynamic_cast<TReturn_pointer*>(rtrn);
  if (p)
    {
      void* ptr=p->address();
      if (ptr)
	{
	  TArg_pointer *q;
	  if (args)
	    for (int i=nargs-1; i >= 0; i--)
	      if ((q=dynamic_cast<TArg_pointer*>(arg(i))) && (ptr == q->address()))
		{
		  p->SetOwner(0);
		  return;
		}
	  p->SetOwner(1);
	}
      else
	p->SetOwner(0);
    }
}

int TFunction::TestBasic(int min, int max)
{
  if (inc) delete inc;
  inc=((min <= 0) || (max < min)) ? new TArgInc_Basic(this) : new TArgInc_Basic(this,min,max);
  return First();
}

int TFunction::TestInvalidSizes(int min, int max)
{
  if (inc) delete inc;
  inc=((min <= 0) || (max < min)) ? new TArgInc_InvalidSizes(this) : new TArgInc_InvalidSizes(this,min,max);
  return First();
}

int TFunction::TestInvalidNulls(int min, int max)
{
  if (inc) delete inc;
  inc=((min <= 0) || (max < min)) ? new TArgInc_InvalidNulls(this) : new TArgInc_InvalidNulls(this,min,max);
  return First();
}

int TFunction::TestEqualPointers(int min, int max)
{
  if (inc) delete inc;
  inc=((min <= 0) || (max < min)) ? new TArgInc_EqualPointers(this) : new TArgInc_EqualPointers(this,min,max);
  return First();
}

int TFunction::TestSpeed(int size)
{
  if (inc) delete inc;
  if (size <= 0) size=10;
  inc=new TArgInc_Speed(this,1,&size);
  return First();
}
//=== TFunction ========================================================= end ===

