// Copyright (C) 2018  INRIA
//
// Treerecs is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Treerecs 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

#include <cassert>
#include <gtest/gtest.h>
#include <treerecs/containers/Grid.h>
#include <treerecs/containers/Table.h>

using namespace treerecs;

class grids_test : public testing::Test {
protected:
  virtual void SetUp() {
  }

  virtual void TearDown() {
  }

  treerecs::Grid<int> square = treerecs::Grid<int>(3,3);
};

TEST_F(grids_test, gridTest) {
  // TEST CONSTRUCTION
  ASSERT_EQ(3, square.nrow());
  ASSERT_EQ(3, square.ncol());
  treerecs::Grid<int> square_copy(square);

  // TEST operator ==
  ASSERT_EQ(square, square_copy);
  ASSERT_TRUE(square == square_copy);

  // TEST SETTERS
  square[1][2] = 1989;
  ASSERT_NE(square[1][2], square_copy[1][2]);
  square_copy.set(1, 2, 1989);
  ASSERT_EQ(square[1][2], square_copy[1][2]);

  // TEST operator== with no equality
  ASSERT_EQ(square, square_copy);
  ASSERT_TRUE(square == square_copy);
  square_copy.set(1, 1, 1988);
  ASSERT_NE(square, square_copy);
  ASSERT_FALSE(square == square_copy);
  square_copy.set(1, 1, 0);

  // TEST GETTERS
  ASSERT_EQ(square.get(1, 2), square_copy(1, 2));
  ASSERT_EQ(square.get(0, 0), square_copy(0, 0));

  // TEST min and max methods
  square_copy(2, 2) = -1;
  auto min = square_copy.min();
  ASSERT_EQ(-1, square_copy(min.first, min.second));
  auto max = square_copy.max();
  ASSERT_EQ(1989, square_copy(max.first, max.second));

  // TEST building with std::vector
  std::vector<std::vector<int>> vect = {{0, 0, 0}, {0, 0, 1989}, {0, 0, 0}};
  treerecs::Grid<int> built_with_vectors = vect;
  ASSERT_TRUE(square == built_with_vectors);

  // TEST building from empty grid
  treerecs::Grid<int> empty_grid;
  empty_grid.addCol({174536, 212, 9546});
  ASSERT_EQ(1, empty_grid.ncol());
  ASSERT_EQ(3, empty_grid.nrow());
  empty_grid.addRow({31});
  ASSERT_EQ(1, empty_grid.ncol());
  ASSERT_EQ(4, empty_grid.nrow());
  empty_grid.addCol({65174, 415, 786, 1215});
  ASSERT_EQ(2, empty_grid.ncol());
  ASSERT_EQ(4, empty_grid.nrow());

  // TEST removing column
  empty_grid.removeCol(0);
  ASSERT_EQ(4, empty_grid.nrow());
  ASSERT_EQ(1, empty_grid.ncol());

  // TEST removing row
  empty_grid.removeRow(0);
  ASSERT_EQ(3, empty_grid.nrow());
  ASSERT_EQ(1, empty_grid.ncol());

  // TEST Shift
  // Because we are removing the first column and the first row, we are
  // expecting a shift of values in the grid.
  std::vector<int> row0 = empty_grid.getRow(0);
  ASSERT_EQ(415, row0[0]);

  // TEST clearing
  square_copy.clear();
  ASSERT_EQ(0, square_copy.nrow());
  ASSERT_EQ(0, square_copy.ncol());

  // TEST toSTL
  auto square_stl = square.toStl();
  for (decltype(square_stl.size()) i =0 ;
      i < square_stl.size() ;
      i++) {
    for (decltype(square_stl.front().size()) j = 0 ;
        j < square_stl.front().size() ;
        j++) {
      ASSERT_EQ(square_stl[i][j], square(i, j));
    }
  }

}

TEST_F(grids_test, tableTest) {
  // TEST CONSTRUCTION
  typedef std::size_t T;
  typedef std::string K;

  treerecs::Table<T, K, K> tab0(3, 3);
  tab0.setRowIndexes({"row0", "row1", "row2"});
  tab0.setColIndexes({"col0", "col1", "col2"});

  treerecs::Table<T, K, K> tab1(tab0);

  // TEST CONSTRUCTION
  ASSERT_EQ(3, tab0.nrow());
  ASSERT_EQ(3, tab0.ncol());

  // TEST operator ==
  ASSERT_EQ(tab0, tab1);
  ASSERT_TRUE(tab0 == tab1);

  // TEST SETTERS
  tab0["row1"][2] = 1989;
  ASSERT_NE(tab0("row1", "col2"), tab1("row1", "col2"));
  tab1.set("row1", "col2", 1989);
  ASSERT_EQ(tab0("row1", "col2"), tab1("row1", "col2"));

  // TEST operator== with no equality
  ASSERT_EQ(tab0, tab1);
  ASSERT_TRUE(tab0 == tab1);
  tab1.set("row1", "col1", 1988);
  ASSERT_NE(tab0, tab1);
  ASSERT_FALSE(tab0 == tab1);
  tab1.set("row1", "col1", 0);

  // TEST GETTERS
  ASSERT_EQ(tab0.get("row1", "col2"), tab1("row1", "col2"));
  ASSERT_EQ(tab0.get("row0", "col0"), tab1("row0", "col0"));

  // TEST building from empty grid
  treerecs::Table<T, K, K> empty_tab;
  empty_tab.addCol({174536, 212, 9546}, "col0");
  ASSERT_EQ(1, empty_tab.ncol());
  ASSERT_EQ(3, empty_tab.nrow());
  empty_tab.addRow({31}, "row3");
  ASSERT_EQ(1, empty_tab.ncol());
  ASSERT_EQ(4, empty_tab.nrow());
  empty_tab.addCol({65174, 415, 786, 1215}, "col1");
  ASSERT_EQ(2, empty_tab.ncol());
  ASSERT_EQ(4, empty_tab.nrow());

  // TEST removing column
  empty_tab.removeCol("col0");
  ASSERT_EQ(4, empty_tab.nrow());
  ASSERT_EQ(1, empty_tab.ncol());

  // TEST removing row
  empty_tab.removeRow("row3");
  ASSERT_EQ(3, empty_tab.nrow());
  ASSERT_EQ(1, empty_tab.ncol());

  //TEST clearing
  tab1.clear();
  ASSERT_EQ(0, tab1.nrow());
  ASSERT_EQ(0, tab1.ncol());

  // TEST writing in stream
  empty_tab.setRowIndexes({"row0", "row1", "row2"});
  std::stringstream ss;
  empty_tab.write(ss);
  auto rows = utils::splitString(ss.str(), "\n");
  auto coln = std::find(rows.begin(), rows.end(), "     \tcol1 ");
  auto row0 = std::find(rows.begin(), rows.end(), "row0 \t65174");
  auto row1 = std::find(rows.begin(), rows.end(), "row1 \t415  ");
  auto row2 = std::find(rows.begin(), rows.end(), "row2 \t786  ");
  ASSERT_FALSE(coln == rows.end());
  ASSERT_NE(row0, rows.end());
  ASSERT_NE(row1, rows.end());
  ASSERT_NE(row2, rows.end());
  ASSERT_EQ(4, rows.size());
  //ASSERT_STREQ("     \tcol1 \nrow2 \t786  \nrow0 \t65174\nrow1 \t415  \n",
  //             ss.str().c_str());

  ss.str("");  // clear stringstream
  empty_tab.write(ss, ",", true, true); // csv equivalent
  rows = utils::splitString(ss.str(), "\n");
  coln = std::find(rows.begin(), rows.end(), "     ,col1 ");
  row0 = std::find(rows.begin(), rows.end(), "row0 ,65174");
  row1 = std::find(rows.begin(), rows.end(), "row1 ,415  ");
  row2 = std::find(rows.begin(), rows.end(), "row2 ,786  ");
  ASSERT_NE(coln, rows.end());
  ASSERT_NE(row0, rows.end());
  ASSERT_NE(row1, rows.end());
  ASSERT_NE(row2, rows.end());
  ASSERT_EQ(4, rows.size());
  //ASSERT_STREQ("     ,col1 \nrow2 ,786  \nrow0 ,65174\nrow1 ,415  \n",
  //             ss.str().c_str());

  ss.str(""); // clear stringstream
  empty_tab.write(ss, ",", false, true); // test without rownames
  rows = utils::splitString(ss.str(), "\n");
  coln = std::find(rows.begin(), rows.end(), "col1 ");
  row0 = std::find(rows.begin(), rows.end(), "65174");
  row1 = std::find(rows.begin(), rows.end(), "415  ");
  row2 = std::find(rows.begin(), rows.end(), "786  ");
  ASSERT_NE(coln, rows.end());
  ASSERT_NE(row0, rows.end());
  ASSERT_NE(row1, rows.end());
  ASSERT_NE(row2, rows.end());
  ASSERT_EQ(4, rows.size());
  //ASSERT_STREQ("     ,col1 \n786  \n65174\n415  \n",
  //             ss.str().c_str());

  ss.str(""); // clear stringstream
  empty_tab.write(ss, ",", true, false); // test without colnames
  rows = utils::splitString(ss.str(), "\n");
  coln = std::find(rows.begin(), rows.end(), "     ,col1 ");
  row0 = std::find(rows.begin(), rows.end(), "row0 ,65174");
  row1 = std::find(rows.begin(), rows.end(), "row1 ,415  ");
  row2 = std::find(rows.begin(), rows.end(), "row2 ,786  ");
  ASSERT_EQ(coln, rows.end());
  ASSERT_NE(row0, rows.end());
  ASSERT_NE(row1, rows.end());
  ASSERT_NE(row2, rows.end());
  ASSERT_EQ(3, rows.size());
  //ASSERT_STREQ("row2 ,786  \nrow0 ,65174\nrow1 ,415  \n",
  //             ss.str().c_str());

  ss.str(""); // clear stringstream
  empty_tab.write(ss, ",", false, false); // test without col- and row-names
  rows = utils::splitString(ss.str(), "\n");
  coln = std::find(rows.begin(), rows.end(), "     ,col1 ");
  row0 = std::find(rows.begin(), rows.end(), "65174");
  row1 = std::find(rows.begin(), rows.end(), "415  ");
  row2 = std::find(rows.begin(), rows.end(), "786  ");
  ASSERT_EQ(coln, rows.end());
  ASSERT_NE(row0, rows.end());
  ASSERT_NE(row1, rows.end());
  ASSERT_NE(row2, rows.end());
  ASSERT_EQ(3, rows.size());
  //ASSERT_STREQ("786  \n65174\n415  \n",
  //             ss.str().c_str());
}