/* This file is part of the KDE project
   Copyright 2004 Ariya Hidayat <ariya@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include "tester.h"
#include "value_tester.h"

#include <math.h>

#include <kspread_value.h>

#define CHECK(x,y)  check(__FILE__,__LINE__,#x,x,y)
#define CHECK_DATE(d,x) check(__FILE__,__LINE__,TQString(d->asDate().toString()).latin1(),d->asFloat(),x)

using namespace KSpread;

ValueTester::ValueTester(): Tester()
{
}

TQString ValueTester::name()
{
  return TQString("Value");
}

template<typename T>
void ValueTester::check( const char *file, int line, const char* msg, const T& result, 
  const T& expected )
{
  testCount++;
  if( result != expected )
  {
    TQString message;
    TQTextStream ts( &message, IO_WriteOnly );
    ts << msg;
    ts << "  Result:";
    ts << result;
    ts << ", ";
    ts << "Expected:";
    ts << expected;
    fail( file, line, message );
  }
}

void ValueTester::check( const char *file, int line, const char* msg, bool result, bool expected )
{
  testCount++;
  if( result != expected )
  {
    TQString message;
    TQTextStream ts( &message, IO_WriteOnly );
    ts << msg;
    ts << "  Result: ";
    if( result ) ts << "True"; else ts << "False";
    ts << ", ";
    ts << "Expected: ";
    if( expected ) ts << "True"; else ts << "False";
    fail( file, line, message );
  }
}

void ValueTester::run()
{
  testCount = 0;
  errorList.clear();

  Value* v1;
  Value* v2;
  
  // empty value
  v1 = new Value();
  CHECK( v1->type(), Value::Empty );
  delete v1;

  // boolean value (true)
  v1 = new Value( true );
  CHECK( v1->type(), Value::Boolean );
  CHECK( v1->asBoolean(), true );
  v1->setValue( 1 ); // dummy
  v1->setValue( true );
  CHECK( v1->type(), Value::Boolean );
  CHECK( v1->asBoolean(), true );
  delete v1;
  
  // boolean value (false)
  v1 = new Value( false );
  CHECK( v1->type(), Value::Boolean );
  CHECK( v1->asBoolean(), false );
  v1->setValue( 4 ); // dummy
  v1->setValue( false );
  CHECK( v1->type(), Value::Boolean );
  CHECK( v1->asBoolean(), false );
  delete v1;

  // integer value
  v1 = new Value( 1977 );
  CHECK( v1->type(), Value::Integer );
  CHECK( v1->asInteger(), (long)1977 );
  v1->setValue( false ); // dummy
  v1->setValue( 14 );
  CHECK( v1->type(), Value::Integer );
  CHECK( v1->isInteger(), true );
  CHECK( v1->isFloat(), false );
  CHECK( v1->isString(), false );
  CHECK( v1->isNumber(), true );
  CHECK( v1->asInteger(), (long)14 );
  delete v1;
  
  // floating-point value
  v1 = new Value( M_PI );
  CHECK( v1->type(), Value::Float );
  CHECK( v1->asFloat(), M_PI );
  v1->setValue( false ); // dummy
  v1->setValue( 14.03 );
  CHECK( v1->type(), Value::Float );
  CHECK( v1->isInteger(), false );
  CHECK( v1->isFloat(), true );
  CHECK( v1->isString(), false );
  CHECK( v1->isNumber(), true );
  CHECK( v1->asFloat(), 14.03 );
  delete v1;
  
  // string value
  v1 = new Value( TQString("Ailinon" ) );
  CHECK( v1->type(), Value::String );
  CHECK( v1->asString(), TQString("Ailinon" ) );
  v1->setValue( 7 ); // dummy
  v1->setValue( TQString("spreadsheet" ) );
  CHECK( v1->type(), Value::String );
  CHECK( v1->isInteger(), false );
  CHECK( v1->isFloat(), false );
  CHECK( v1->isString(), true );
  CHECK( v1->isNumber(), false );
  CHECK( v1->asString(), TQString("spreadsheet" ) );
  delete v1;

  // check all (valid) dates from 1900 to 2050
  // note: bail on first error immediately
  TQDate refDate( 1899, 12, 31 );
  v1 = new Value();
  bool date_error = 0;
  for( unsigned y = 1900; !date_error && y < 2050; y++ )
  for( unsigned m = 1; !date_error && m <= 12; m++ )
  for( unsigned d = 1; !date_error && d <= 31; d++ )
  {
    TQDate dv1 = TQDate( y, m, d );
    if( !dv1.isValid() ) continue;
    double serialNo = -dv1.daysTo( refDate ) + 1.0;
    v1->setValue( dv1 );
    CHECK_DATE(v1,serialNo);
    date_error = v1->asFloat() != serialNo;
  } 
  
  // time value
  v1 = new Value();
  v1->setValue( TQTime( 0, 0, 0 ) );
  CHECK( v1->type(), Value::Integer );
  int time_error = 0; // used to  save time, bail on first error
  for( unsigned h = 0; !time_error && h < 24; h++ )
  for( unsigned m = 0; !time_error && m < 60; m++ )
  for( unsigned s = 0; !time_error && s < 60; s++ )
  {
    TQTime t1 = TQTime( h, m, s );
    v1->setValue( t1 );
    TQTime t2 = v1->asTime();
    if( t1.hour() != t2.hour() ) time_error++;
    if( t1.minute() != t2.minute() ) time_error++;
    if( t1.second() != t2.second() ) time_error++;
    if( t1.msec() != t2.msec() ) time_error++;
  }
  CHECK( time_error, 0 );
  delete v1;

  // time value (msec)
  v1 = new Value();
  v1->setValue( TQTime( 0, 0, 0 ) );
  CHECK( v1->type(), Value::Integer );
  int msec_error = 0; // used to  save time, bail on first error
  for( unsigned ms= 0;ms < 1000;ms++ )
  {
    TQTime t1 = TQTime( 1, 14, 2, ms );
    v1->setValue( t1 );
    TQTime t2 = v1->asTime();
    if( t1.hour() != t2.hour() ) msec_error++;
    if( t1.minute() != t2.minute() ) msec_error++;
    if( t1.second() != t2.second() ) msec_error++;
    if( t1.msec() != t2.msec() ) msec_error++;
    if( msec_error ) break;
  }
  CHECK( msec_error, 0 );
  delete v1;
  
    
  // TODO error values
  
  // TODO compare values
  // TODO add, sub, mul, div values
  // TODO pow
  
  // array
  v1 = new Value( 10, 3 ); // 10 columns, 3 rows
  CHECK( v1->type(), Value::Array );
  CHECK( v1->columns(), (unsigned)10 );
  CHECK( v1->rows(), (unsigned)3 );
  delete v1;
  
  // check empty value in array
  v1 = new Value( 1, 1 );
  CHECK( v1->type(), Value::Array );
  v2 = new Value( v1->element( 0, 0 ) );
  CHECK( v2->type(), Value::Empty );
  delete v1;
  
  // fill simple 1x1 array
  v1 = new Value( 1, 1 );
  CHECK( v1->type(), Value::Array );
  v2 = new Value( 14.3 );
  v1->setElement( 0, 0, *v2 );
  delete v2;
  v2 = new Value( v1->element( 0, 0 ) );
  CHECK( v2->type(), Value::Float );
  CHECK( v2->asFloat(), 14.3 );
  delete v2;
  delete v1;
  
  // stress test, array of 1000x1000
  v1 = new Value( 1000, 1000 );
  CHECK( v1->type(), Value::Array );
  for( unsigned c=0; c<1000; c++ )
  for( unsigned r=0; r<1000; r++ )
  {
    int index = 1000*r + c;
    v1->setElement( c, r, Value( index ) );
  }
  int array_error = 0;
  for( unsigned c=0; !array_error && c<1000; c++ )
  for( unsigned r=0; !array_error && r<1000; r++ )
  {
    int index = 1000*r + c;
    v2 = new Value( v1->element( c, r ) );
    if( v2->type() != Value::Integer ) array_error++;
    if( v2->asInteger() != index ) array_error++;
    delete v2;
  }
  CHECK( array_error, (int)0 );
  delete v1;
  
  // assignment of array value
  v1 = new Value( 1, 1 );
  CHECK( v1->type(), Value::Array );
  v1->setElement( 0, 0, Value( 14.3) );
  v2 = new Value( *v1 ); // v2 is now also an array
  delete v1;
  v1 = new Value( v2->element( 0, 0 ) );
  CHECK( v1->type(), Value::Float );
  CHECK( v1->asFloat(), 14.3 );
  delete v1;
  delete v2;
    
  // copy value
  v1 = new Value();
  v1->setValue( 14.3 );
  v2 = new Value( *v1 );
  CHECK( v1->type(), Value::Float );
  CHECK( v2->type(), Value::Float );
  CHECK( v1->asFloat(), 14.3 );
  CHECK( v2->asFloat(), 14.3 );
  delete v1;
  delete v2;
  
  // value assignment
  v1 = new Value( 14.3 );
  v2 = new Value( true );
  v2->assign( *v1 );
  CHECK( v1->type(), Value::Float );
  CHECK( v2->type(), Value::Float );
  CHECK( v1->asFloat(), 14.3 );
  CHECK( v2->asFloat(), 14.3 );
  delete v1;
  delete v2;
  
  // verify detachment
  v1 = new Value( 14.3 );
  v2 = new Value( *v1 );
  v2->detach(); // v1 and v2 don't share data anymore
  CHECK( v1->type(), Value::Float );
  CHECK( v2->type(), Value::Float );
  CHECK( v1->asFloat(), 14.3 );
  CHECK( v2->asFloat(), 14.3 );
  delete v1;
  delete v2;
}
