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

#include "gtest/gtest.h"
#include "mbase/MEnv.h"
#include "mbase/MLog.h"
#include "../mbase/MParContainer.h"
#include "mfileio/MMatrix.h"

using namespace std;

class testMMatrix : public ::testing::Test {
 protected:
  // You can remove any or all of the following functions if its body
  // is empty.
  const std::string path2test = "./mtest/MMatrixTEST/";
	
  testMMatrix() {
    // You can do set-up work for each test here.
  }

  virtual ~testMMatrix() {
    // You can do clean-up work that doesn't throw exceptions here.
  }

  // If the constructor and destructor are not enough for setting up
  // and cleaning up each test, you can define the following methods:

  virtual void SetUp() {
    // Code here will be called immediately after the constructor (right
    // before each test).
  }

  virtual void TearDown() {
    // Code here will be called immediately after each test (right
    // before the destructor).
  }

  // Objects declared here can be used by all tests in the test case for Foo.
};
//------------------------------------------------------------------------------
TEST_F(testMMatrix, ParseCorrectFile) {

  // The MMatrix will parse a text file specified in the configuration file
  // e.g. ceres.rc. The Data can then be used in the simulation.
  // For example it is used to parse and contain the 1440 temporal offsets of
  // the telkscope pixels.
  try{

    // first we need an Menv to parse in the config file
    MEnv env( 
      (path2test + "config_where_the_path_to_MyData_is_defined.rc" ).c_str() 
    );

    // now we create the data container itself. The constructor has two 
    // parameters:
    // 1) A name
    // 2) I dont know... its called titel internal in the MParContainer.
    MMatrix A("Any name related with A","titel");

    // the MMatrix container is still empty yet. We now call the ReadEnv() 
    // method inherited by MParContainer. 
    // Parameters:
    // 1) the MEnv class which has parsed in the config file
    // 2) the keyword used in the configfile for this data container
    // 3) A verbosity flag which tells you wether the file defined in the config
    //    has been found or not. 
    // the integer return value is ignored here
    A.ReadEnv(env,"MyData",kFALSE);

    // The file specified in the config file is supposed to hold a 3 x 3 Matrix
    // Since the data matrix called 'fM' of the MMatrix is public we can 
    // directly access the data and check.
    ASSERT_EQ( 3, A.fM.size() );
    ASSERT_EQ( 3, A.fM[0].size() );
    ASSERT_EQ( 3, A.fM[1].size() );
    ASSERT_EQ( 3, A.fM[2].size() );

    EXPECT_EQ( 1.1, A.fM[0][0] );
    EXPECT_EQ(   2, A.fM[0][1] );
    EXPECT_EQ(   3, A.fM[0][2] );
    EXPECT_EQ(   4, A.fM[1][0] );
    EXPECT_EQ( 5.5, A.fM[1][1] );
    EXPECT_EQ(   6, A.fM[1][2] );
    EXPECT_EQ(   7, A.fM[2][0] );
    EXPECT_EQ(   8, A.fM[2][1] );
    EXPECT_EQ( 9.9, A.fM[2][2] );
  }
  catch (std::exception &error){
    cout << error.what();
  } 
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, LineBreak_CRLF) {

  // Check the CRLF linebreak as it is used by several Computers as Mac and 
  // Windows.
  // For example Dominik and Sebastian exchanged emails with data.csv. Both had
  // Ubuntu 14 machines. The file was mangeled from LF only, as it is common on 
  // Linux to CRLF !
  try{

    // CR and LF i.e. \r\n i.e. 10 and 13
    MEnv env( (path2test + "LineBreak_CRLF.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    A.ReadEnv(env,"MyData",kFALSE);

    ASSERT_EQ( 3, A.fM.size() );
    ASSERT_EQ( 3, A.fM[0].size() );
    ASSERT_EQ( 3, A.fM[1].size() );
    ASSERT_EQ( 3, A.fM[2].size() );

    EXPECT_EQ( 1.1, A.fM[0][0] );
    EXPECT_EQ(   2, A.fM[0][1] );
    EXPECT_EQ(   3, A.fM[0][2] );
    EXPECT_EQ(   4, A.fM[1][0] );
    EXPECT_EQ( 5.5, A.fM[1][1] );
    EXPECT_EQ(   6, A.fM[1][2] );
    EXPECT_EQ(   7, A.fM[2][0] );
    EXPECT_EQ(   8, A.fM[2][1] );
    EXPECT_EQ( 9.9, A.fM[2][2] );
  }
  catch (std::exception &error){
    cout << error.what();
  } 
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, LineBreak_CR) {

  // CR only linebreak is hopefully not common anymore. It was used by Mac.
  try{

    // CR and LF i.e. \r\n i.e. 10 and 13
    MEnv env( (path2test + "LineBreak_CR.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    A.ReadEnv(env,"MyData",kFALSE);

    ASSERT_EQ( 3, A.fM.size() );
    ASSERT_EQ( 3, A.fM[0].size() );
    ASSERT_EQ( 3, A.fM[1].size() );
    ASSERT_EQ( 3, A.fM[2].size() );

    EXPECT_EQ( 1.1, A.fM[0][0] );
    EXPECT_EQ(   2, A.fM[0][1] );
    EXPECT_EQ(   3, A.fM[0][2] );
    EXPECT_EQ(   4, A.fM[1][0] );
    EXPECT_EQ( 5.5, A.fM[1][1] );
    EXPECT_EQ(   6, A.fM[1][2] );
    EXPECT_EQ(   7, A.fM[2][0] );
    EXPECT_EQ(   8, A.fM[2][1] );
    EXPECT_EQ( 9.9, A.fM[2][2] );
  }
  catch (std::exception &error){
    cout << error.what();
  } 
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, CommentOnly) {

  // We read in a csv file with only comments line in there. The resulting
  // Matrix shall be empty.
  try{

    MEnv env( (path2test + "comment_only.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    A.ReadEnv(env,"MyData",kFALSE);

    ASSERT_EQ( 0, A.fM.size() );
  }
  catch (std::exception &error){
    cout << error.what();
  } 
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, DataOnly) {

  // The file has no comments at all. 3x3 Matrix data only
  try{

    MEnv env( (path2test + "data_only.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    A.ReadEnv(env,"MyData",kFALSE);

    ASSERT_EQ( 3, A.fM.size() );
    ASSERT_EQ( 3, A.fM[0].size() );
    ASSERT_EQ( 3, A.fM[1].size() );
    ASSERT_EQ( 3, A.fM[2].size() );

    EXPECT_EQ( 1.1, A.fM[0][0] );
    EXPECT_EQ(   2, A.fM[0][1] );
    EXPECT_EQ(   3, A.fM[0][2] );
    EXPECT_EQ(   4, A.fM[1][0] );
    EXPECT_EQ( 5.5, A.fM[1][1] );
    EXPECT_EQ(   6, A.fM[1][2] );
    EXPECT_EQ(   7, A.fM[2][0] );
    EXPECT_EQ(   8, A.fM[2][1] );
    EXPECT_EQ( 9.9, A.fM[2][2] );
  }
  catch (std::exception &error){
    cout << error.what();
  } 
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, data_and_comments_mixed) {

  try{

    MEnv env( (path2test + "data_and_comments_mixed.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    A.ReadEnv(env,"MyData",kFALSE);

    ASSERT_EQ( 3, A.fM.size() );
    ASSERT_EQ( 3, A.fM[0].size() );
    ASSERT_EQ( 3, A.fM[1].size() );
    ASSERT_EQ( 3, A.fM[2].size() );

    EXPECT_EQ( 1.1, A.fM[0][0] );
    EXPECT_EQ(   2, A.fM[0][1] );
    EXPECT_EQ(   3, A.fM[0][2] );
    EXPECT_EQ(   4, A.fM[1][0] );
    EXPECT_EQ( 5.5, A.fM[1][1] );
    EXPECT_EQ(   6, A.fM[1][2] );
    EXPECT_EQ(   7, A.fM[2][0] );
    EXPECT_EQ(   8, A.fM[2][1] );
    EXPECT_EQ( 9.9, A.fM[2][2] );
  }
  catch (std::exception &error){
    cout << error.what();
  } 
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, missing_delimiter) {
  // when a delimiter is missing, a fatal exception must be thrown
  bool error_detected = false;
  try{

    MEnv env( (path2test + "missing_delimiter.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    
    // right here an exception shall be thrown because of the missing delimiter
    A.ReadEnv(env,"MyData",kFALSE);

  }
  catch (std::exception error){
    error_detected = true;
  }
  EXPECT_TRUE(error_detected);
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, bad_floating_point_number) {
  // when a floating point number is bad, a fatal exception must be thrown
  bool error_detected = false;
  try{

    MEnv env( (path2test + "bad_floating_point_number.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    
    // expect exception because of bad floating point number
    A.ReadEnv(env,"MyData",kFALSE);

  }
  catch (std::exception error){
    error_detected = true;
  }
  EXPECT_TRUE(error_detected);
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, NaN_Inf_exponent) {

  // +Inf, -Inf, Inf
  // NaN, 1e3, 42e13.37
  try{

    MEnv env( (path2test + "NaN_Inf_exponent.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    
    A.ReadEnv(env,"MyData",kFALSE);

    // check dimension
    ASSERT_EQ( 2, A.fM.size() );
    ASSERT_EQ( 3, A.fM[0].size() );
    ASSERT_EQ( 3, A.fM[1].size() ); 

    // +Inf
    EXPECT_FALSE( std::signbit( A.fM[0][0]) );
    EXPECT_TRUE(  std::isinf(   A.fM[0][0]) );
    // -Inf
    EXPECT_TRUE(  std::signbit( A.fM[0][1]) );
    EXPECT_TRUE(  std::isinf(   A.fM[0][1]) );
    // Inf
    EXPECT_FALSE( std::signbit( A.fM[0][2]) );
    EXPECT_TRUE(  std::isinf(   A.fM[0][2]) );

    EXPECT_TRUE( std::isnan(    A.fM[1][0]) );
    EXPECT_EQ( 1e3,             A.fM[1][1]  );
    EXPECT_EQ( 13.37e42,        A.fM[1][2]  );
  }
  catch (std::exception &error){
    cout << error.what();
  } 
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, whitespaces_and_tabs) {
  // whitespaces are allowd and must not effect the parsing
  try{

    MEnv env( (path2test + "whitespaces_and_tabs.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    
    A.ReadEnv(env,"MyData",kFALSE);

    // check dimension
    // we expect 9 row and 3 columns each with 1,2,3 in there

    ASSERT_EQ( 9, A.fM.size() );
    for(int row=0; row<9; row++){

      ASSERT_EQ( 3, A.fM[row].size() );
      for(int col=0; col<3; col++){

        EXPECT_EQ( (col+1) , A.fM[row][col]  );
      }
    }
  }
  catch (std::exception &error){
    cout << error.what();
  } 
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, csv_file_does_not_exist) {
  // There must be a fatal exception when the csv file does not exist
  bool error_detected = false;
  try{

    MEnv env( (path2test + "csv_file_does_not_exist.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    
    A.ReadEnv(env,"MyData",kFALSE);
  }
  catch (std::exception &error){
    error_detected = true;
  } 
  EXPECT_TRUE(error_detected);
}
//------------------------------------------------------------------------------
TEST_F(testMMatrix, key_does_not_exist_in_config_file) {
  // There must be a fatal exception when the key can not be found in the 
  // config file.
  bool error_detected = false;
  try{

    MEnv env( (path2test + "key_does_not_exist_in_config_file.rc" ).c_str() );
    MMatrix A("Any name related with A","titel");
    
    A.ReadEnv(env,"KeyToDataThatDoesNotExistInTheConfigFile",kFALSE);
  }
  catch (std::exception &error){
    error_detected = true;
  } 
  EXPECT_TRUE(error_detected);
}
//------------------------------------------------------------------------------