/*
 * --- GSMP-COPYRIGHT-NOTE-BEGIN ---
 * 
 * This copyright note is auto-generated by ./scripts/Create-CopyPatch.
 * Please add additional copyright information _after_ the line containing
 * the GSMP-COPYRIGHT-NOTE-END tag. Otherwise it might get removed by
 * the ./scripts/Create-CopyPatch script. Do not edit this copyright text!
 * 
 * GSMP: pcm/tests/ArithmeticTest.cc
 * General Sound Manipulation Program is Copyright (C) 2000 - 2004
 *   Valentin Ziegler and René Rebe
 * 
 * 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; version 2. A copy of the GNU General
 * Public License can be found in the file LICENSE.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANT-
 * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * 
 * --- GSMP-COPYRIGHT-NOTE-END ---
 */
//#define GSMP_SINUS

#include "Types.hh"
#include "Timer.hh"

#include <iostream>
#include <string>
//#include <math.h>

using std::string;
using std::cout;
using std::endl;
using namespace GSMP;

template <typename T>
class OutputResult
{
 public:
  static void Out (const T& t)
  {
    std::cout << std::fixed << t;
  }
};

template <> class OutputResult<int8_t>
{
 public:
  static void Out (const int8_t& t)
  {
    std::cout << std::fixed << (int)t;
  }
};



template <typename T>
class ArithmeticTest
{
public:
  typedef PcmTraits <T> Traits;
  typedef PcmArithmetic <T> Arithmetic;  
  typedef OutputResult <T> Output;

  T One () { return Traits::dBfs (); }
  T Half () { return Arithmetic::Fraction (Traits::dBfs (), 2); }
  T Quater () { return Arithmetic::Fraction (Traits::dBfs (), 4); }
  T Zero () { return Traits::Zero (); }

  void Compare (const T& is, const T& should)
    {
      double fis = (double) is;
      double fshl = ((double) should) * 0.999;
      double fshh = ((double) should) * 1.001;
      
      if (fis >= fshl && fis <= fshh)
	cout << "ok." << endl;
      else
	{
	  cout << "failed ! ( is: ";
	  Output::Out (is);
	  cout << " , should be: ";
	  Output::Out (should);
	  cout << " )" << endl;
	}
    }

  ArithmeticTest (const string& type_name)
    {
      cout << type_name << " : Sum ........";
      T is = Arithmetic::Sum (Half (), Half ());
      Compare (is, One ());

      cout << type_name << " : Prod I .....";
      is = Arithmetic::Prod (One (), One ());
      Compare (is, One ());
     
      cout << type_name << " : Prod II ....";
      is = Arithmetic::Prod (Half (), Zero ());
      Compare (is, Zero ());

      cout << type_name << " : Prod III ...";
      is = Arithmetic::Prod (Half (), Half ());
      Compare (is, Quater ());

      cout << type_name << " : Macc .......";
      T sum = Zero ();
      Arithmetic::Macc (sum, Half (), One ());
      Arithmetic::Macc (sum, Half (), Half ());
      Arithmetic::Macc (sum, Quater (), One ());
      Compare (sum, One ());
    }
};


template <typename T>
class SinusTest
{
public:
  typedef PcmTraits <T> Traits;
  typedef PcmArithmetic <T> Arithmetic;  
  typedef OutputResult <T> Output;

  double Result (double phase)
  {
    T res = Arithmetic::Sinus (phase);
    double dres = res;
    dres /= (double) Traits::dBfs ();
    return dres;
  }

  void Cmp (double phase)
  {
    double lib = sin (phase);
    double own = Result (phase);
    cout << phase << "\t" << lib << "\t" << own << "\t" << lib-own << endl;
  }


  SinusTest ()
  {
    cout << "phi\t\tlibc\t\tgsmp\t\tdifference" << endl;
    for (double phase = -3.2; phase <= 3.2; phase += 0.2)
      Cmp (phase);
  }
};


template <typename T, typename TestOp>
class SpeedBench
{
public:
  typedef OutputResult <T> Output;
  typedef PcmTraits <T> Traits;

  static const int runs = 0x01000000;
  static const int small_buff_size = 128;
  static const int large_buff_size = 4096*64;
  
  T* buffer;

  SpeedBench (const string& test_name, const string& type_name)
  {
    buffer = new T  [large_buff_size];

    Utility::TickTimer timer;
    TestOp op;
    long long delta;

    std::fill (buffer, buffer + large_buff_size, Traits::dBfs ());

    //    for (int i = 0; i < 2; i++)
    //  {
    //	if (i == 0)
	  cout << "----------------------------------------------------------" << endl;
	  //	else
	  //  cout << "------------------[Second Run]----------------------------" << endl;
	cout << type_name << " : " << test_name << " register     : ";
	cout.flush ();
	timer.Reset ();
	
	for (int r = 0; r < runs; r++)
	  op(buffer[0]);

	delta = timer.Delta ();
	cout << delta << " Ticks" << endl;
	
	cout << type_name << " : " << test_name << " small buffer : ";
	cout.flush ();
	timer.Reset ();
	
	for (int r = 0; r < runs; r++)
	  op(buffer[r % small_buff_size]);

	delta = timer.Delta ();
	cout << delta << " Ticks" << endl;

	cout << type_name << " : " << test_name << " large buffer : ";
	cout.flush ();
	timer.Reset ();

	for (int r = 0; r < runs; r++)
	  op(buffer[r % large_buff_size]);

	delta = timer.Delta ();
	cout << delta << " Ticks" << endl;
	//      }

	volatile T sum = Traits::Zero ();	
	for (int i = 0; i < large_buff_size; i++)
	  sum += buffer [i];

	delete buffer;
  };
};


template <typename T>
class MaccOp
{
public:
  typedef PcmTraits <T> Traits;
  typedef PcmArithmetic <T> Arithmetic;  

  MaccOp ()
  {
    multiplier = Arithmetic::Fraction (Traits::dBfs (), 2000);
  }

  void operator () (T& x)
  {
    Arithmetic::Macc (x, multiplier, x);
  }

  T multiplier;
};

template <typename T>
class LoMaccOp
{
public:
  typedef PcmTraits <T> Traits;
  typedef PcmArithmetic <T> Arithmetic;  

  LoMaccOp ()
  {
    multiplier = Arithmetic::Fraction (Traits::dBfs (), 2000);
  }

  void operator () (T& x)
  {
    Arithmetic::LoMacc (x, multiplier, x);
  }

  T multiplier;
};




template <typename T>
class SinusOp
{
public:
  typedef PcmTraits <T> Traits;
  typedef PcmArithmetic <T> Arithmetic;  

  SinusOp ()
  {
    phase = -2.0;
  }

  void operator () (T& x)
  {
    x = Arithmetic::Sinus (phase);
    phase += 0.0000001;
  }

  double phase;
};


template <typename T>
class MaccSpeed : public SpeedBench <T, MaccOp <T> >
{
public:
  MaccSpeed (const string& type_name)
    : SpeedBench <T, MaccOp <T> > ("Macc speed", type_name) {};
};

template <typename T>
class LoMaccSpeed : public SpeedBench <T, LoMaccOp <T> >
{
public:
  LoMaccSpeed (const string& type_name)
    : SpeedBench <T, LoMaccOp <T> > ("LoMacc speed", type_name) {};
};



template <typename T>
class SinusSpeed : public SpeedBench <T, SinusOp <T> >
{
public:
  SinusSpeed (const string& type_name)
    : SpeedBench <T, SinusOp <T> > ("Sinus speed", type_name) {};
};



// For disassemblation purpose ****************

int TestFixPt32 (int x, int y)
{ 
  return PcmArithmetic<int32_t>::Prod (x, y);
}

float TestFloat (float x, float y)
{ 
  return PcmArithmetic<float>::Prod (x, y);
}

double TestDouble (double x, double y)
{ 
  return PcmArithmetic<double>::Prod (x, y);
}

double TestDouble2 (double x, double y)
{ 
  return PcmArithmetic<double>::LoProd (x, y);
}


// ********************************************

int main ()
{
  cout << "Testing basic arithmetic operations" << endl
       << "-> expect some int8 failures due to lousy precission ;)" << endl;
  ArithmeticTest <int8_t> ("int8  ");
  ArithmeticTest <int16_t> ("int16 ");
  ArithmeticTest <int32_t> ("int32 ");
  ArithmeticTest <int64_t> ("int64 ");
  ArithmeticTest <float> ("float ");
  ArithmeticTest <double> ("double");

#ifdef GSMP_SINUS
  cout << endl << "Checking Sinus quality <double>" << endl;
  SinusTest <double> ();

  cout << endl << "Checking Sinus quality <int32>" << endl;
  SinusTest <int32_t> ();
#endif

  cout << endl << "some Benchmarks (each one is crunching "
       << SpeedBench <double, MaccOp <double> >::runs / (1024 * 1024)
       << " megawords of data)" << endl;

  MaccSpeed <double> ("double");
  MaccSpeed <float> ("float");

  MaccSpeed <int16_t> ("int16");
  MaccSpeed <int32_t> ("int32");
  MaccSpeed <int64_t> ("int64");

  LoMaccSpeed <double> ("double");
  LoMaccSpeed <float> ("float");

  LoMaccSpeed <int16_t> ("int16");
  LoMaccSpeed <int32_t> ("int32");
  LoMaccSpeed <int64_t> ("int64");

  SinusSpeed <double> ("double");
  SinusSpeed <int32_t> ("int32");

  return 0;
};
