#include <iostream>
#include <set>
#include <cassert>
#include <cstdlib>
#include <sstream>
#include <fstream>
#include "sudoku.h"
using namespace std;

void testConstructor();
void testCopyConstructor();
void testSetType();
void testGetSetAt();
void testIO();


const char *str4x4 = "ABCD";
const char *str9x9 = "123456789";
const char *str16x16 = "123456789ABCDEFG";
  
const set<char> set4x4 = set<char>(str4x4, str4x4 + 4);
const set<char> set9x9 = set<char>(str9x9, str9x9 + 9);
const set<char> set16x16 = set<char>(str16x16, str16x16 + 16);


int main() {
  cout << "Testovaci program pro tridu Sudoku. Jestli je vsechno v poradku,\n"
  "program to oznami a vrati 0, v opacnem pripade vypise chybovou hlasku" << endl;

  testConstructor();
  testSetType();
  testCopyConstructor();
  testGetSetAt();
  testIO();
  
  cout << "Blahopreji, test probehl uspesne" << endl;
  return 0;
}  


/*
 * Test implicitniho konstruktoru
 */
void testConstructor() {
  cout << "Test konstruktoru..." << endl;
  try {
    Sudoku s_default_constructor;
    assert(s_default_constructor.getSizeOfSide() == 9);
    assert(s_default_constructor.getSymbols() == set9x9);
  } catch (int error) {
    cerr << "CHYBA: Konstruktor vyhodil vyjimku, kdyz nemel.\n"
      << "Volani: 'Sudoku s_default_constructor;'" << endl; 
    exit(1);
  }  

  try {
    Sudoku s_4x4(4, str4x4);
    assert(s_4x4.getSizeOfSide() == 4);
    assert(s_4x4.getSymbols() == set4x4);
  } catch (int error) {
    cerr << "CHYBA: Konstruktor vyhodil vyjimku, kdyz nemel.\n"
      << "Volani: 'Sudoku s_4x4(4, \"" << str4x4 << "\");'" << endl;
    exit(1);
  }  

  try {
    Sudoku s_9x9(9, str9x9);
    assert(s_9x9.getSizeOfSide() == 9);
    assert(s_9x9.getSymbols() == set9x9);
  } catch (int error) {
    cerr << "CHYBA: Konstruktor vyhodil vyjimku, kdyz nemel.\n"
      << "Volani: 'Sudoku s_9x9(9, \"" << str9x9 << "\");'" << endl; 
    exit(1);
  }  
  
  try {
    Sudoku s_16x16(16, str16x16);
    assert(s_16x16.getSizeOfSide() == 16);
    assert(s_16x16.getSymbols() == set16x16);
  } catch (int error) {
    cerr << "CHYBA: Konstruktor vyhodil vyjimku, kdyz nemel.\n"
      << "Volani: 'Sudoku s_16x16(16, \"" << str16x16 << "\");'" << endl; 
    exit(1);
  }

  try {
    Sudoku s_duplicated_input(9, "1123433567798");
    assert(s_duplicated_input.getSizeOfSide() == 9);
    assert(s_duplicated_input.getSymbols() == set9x9);
  } catch (int error) {
    cerr << "CHYBA: Konstruktor vyhodil vyjimku, kdyz nemel.\n"
      << "Volani: 'Sudoku s_duplicated_input(9, \"1123433567798\");'" << endl; 
    exit(1);
  }
  
  try {
    Sudoku s_negative_size(-5, str4x4);
    cerr << "CHYBA: Argument sizeOfSide byl zaporny a konstruktor nevyhodil "
      "vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    Sudoku s_zero_size(0, str4x4);
    cerr << "CHYBA: Argument sizeOfSide byl rovny 0 a konstruktor nevyhodil "
      "vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    Sudoku s_size_equal_1(1, str4x4);
    cerr << "CHYBA: Argument sizeOfSide byl rovny 1 a konstruktor nevyhodil "
      "vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    Sudoku s_bad_size(3, "123");
    cerr << "CHYBA: Argument sizeOfSide neni mocninou zadneho prirozeneho "
      "cisla a konstruktor nevyhodil vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    Sudoku s_short_symbols(16, "123");
    cerr << "CHYBA: Delka retezce symbols neni rovna hodnote argumentu "
      "sizeOfSide a konstruktor nevyhodil vyjimku" << endl;  
    exit(1);
  } catch (int error) {
  }
  
  try {
    Sudoku s_space_in_symbols(4, "A CD");
    cerr << "CHYBA: V retezci symbols se nachazi mezera a konstruktor "
      "nevyhodil vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  cout << "OK" << endl;
}

/*
 * Test kopirovaciho konstruktoru
 */
void testCopyConstructor() {
  cout << "Test kopirovaciho konstruktoru..." << endl;
  Sudoku s;
  s.setAt(1, 2, '9');
  try {
    Sudoku copy_of_s(s);
  
    assert(s.getSymbols() == copy_of_s.getSymbols());
    assert(s.getSizeOfSide() == copy_of_s.getSizeOfSide());
    assert(s.getSizeOfSide() == copy_of_s.getSizeOfSide());
    assert(s.getAt(1, 1) == copy_of_s.getAt(1, 1));
    assert(s.getAt(1, 2) == copy_of_s.getAt(1, 2));
  } catch (int error) {
    cerr << "CHYBA: Kopirovaci konstruktor vyhodil vyjimku.\n"
      << "Volani:\n\tSudoku s;\n\ts.setAt(1, 2, '9');\n\tSudoku copy_of_s(s);" 
      << endl; 
    exit(1);
  }
  cout << "OK" << endl;
}

/*
 * Test metody setType()
 */
void testSetType() {
  cout << "Test metody setType()..." << endl;
  
  Sudoku s_to_be_changed1(4, str4x4);
  try {
    s_to_be_changed1.setType(16, str16x16);
  } catch (int error) {
    cerr << "CHYBA: Metoda setType vyhodila vyjimku kdyz nemela.\n"
      << "Volani:\n\tSudoku s_to_be_changed1(4, str4x4);\n\t" 
      << "s_to_be_changed1.setType(16, str16x16);" << endl;
    exit(1);
  }
  assert(s_to_be_changed1.getSizeOfSide() == 16);
  assert(s_to_be_changed1.getSymbols() == set16x16);
  
  Sudoku s_to_be_changed2(9, str9x9);
  try {
    s_to_be_changed2.setType(4, str4x4);
  } catch (int error) {
    cerr << "CHYBA: Metoda setType vyhodil vyjimku kdyz nemela.\n"
      << "Volani:\n\tSudoku s_to_be_changed2(9, str9x9);\n\t" 
      << "s_to_be_changed2.setType(4, str4x4);" << endl;
    exit(1);
  }
  assert(s_to_be_changed2.getSizeOfSide() == 4);
  assert(s_to_be_changed2.getSymbols() == set4x4);
  
  try {
    Sudoku s;
    s.setType(-5, str4x4);
    cerr << "CHYBA: Argument sizeOfSide byl zaporny a setType() nevyhodil "
      "vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }

  try {
    Sudoku s;
    s.setType(0, str4x4);
    cerr << "CHYBA: Argument sizeOfSide byl rovny 0 a setType() nevyhodil "
      "vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    Sudoku s;
    s.setType(1, str4x4);
    cerr << "CHYBA: Argument sizeOfSide byl rovny 1 a setType() nevyhodil "
      "vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    Sudoku s;
    s.setType(3, "123");
    cerr << "CHYBA: Argument sizeOfSide neni mocninou zadneho prirozeneho "
      "cisla a setType() nevyhodil vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }

  try {
    Sudoku s;
    s.setType(16, "123");
    cerr << "CHYBA: Delka retezce symbols neni rovna hodnote argumentu "
      "sizeOfSide a setType() nevyhodil vyjimku" << endl;  
    exit(1);
  } catch (int error) {
  }
  try {
    Sudoku s;
    s.setType(4, "A CD");
    cerr << "CHYBA: V retezci symbols se nachazi mezera a setType() nevyhodil "
      "vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  cout << "OK" << endl;
}

/*
 * Test metod setAt() a getAt()
 */
void testGetSetAt() {
  cout << "Test metod setAt() a getAt()..." << endl;
  Sudoku s;
  
  for (int i = 0; i < s.getSizeOfSide(); i++) {
    for (int j = 0; j < s.getSizeOfSide(); j++) {
      try {
        assert(s.getAt(i, j) == ' ');
      } catch (int error) {
        cerr << "CHYBA: Metoda getAt() vyhodila vyjimku, kdyz nemela.\n"
          << "Volani:\n\tSudoku s;\n\ts.getAt(" << i << ", " << j << ");" << endl;
        exit(1);
      }
    }
  }
  
  try {
    s.setAt(0, 0, '1');
  } catch (int error) {
    cerr << "CHYBA: Metoda setAt() vyhodila vyjimku, kdyz nemela.\n"
      << "Volani:\n\tSudoku s;\n\ts.setAt(0, 0, '1');" << endl;
    exit(1);
  }
  assert(s.getAt(0, 0) == '1');

  try {
    s.setAt(8, 8, '2');
  } catch (int error) {
    cerr << "CHYBA: Metoda setAt() vyhodila vyjimku, kdyz nemela.\n"
      << "Volani:\n\tSudoku s;\n\ts.setAt(8, 8, '2');" << endl;
    exit(1);
  }
  assert(s.getAt(8, 8) == '2');
  
  try {
    s.getAt(-1, 3);
    cerr << "CHYBA: Argument x je zaporny a metoda getAt() nevyhodila vyjimku"
      << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    s.getAt(2, -9);
    cerr << "CHYBA: Argument y je zaporny a metoda getAt() nevyhodila vyjimku"
      << endl; 
    exit(1);
  } catch (int error) {
  }

  try {
    s.getAt(9, 0);
    cerr << "CHYBA: Argument x je vetsi nebo rovny velikosti hraci plochy a "
      << "metoda getAt() nevyhodila vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }

  try {
    s.getAt(2, 10);
    cerr << "CHYBA: Argument y je vetsi nebo rovny velikosti hraci plochy a "
      << "metoda getAt() nevyhodila vyjimku" << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    s.getAt(9, 0);
    cerr << "CHYBA: Argument x je vetsi nebo rovny velikosti hraci plochy a "
      << "metoda getAt() nevyhodila vyjimku" << endl;
    exit(1);
  } catch (int error) {
  }

  try {
    s.setAt(-1, 1, '1');
    cerr << "CHYBA: Argument x je zaporny a metoda getAt() nevyhodila vyjimku"
      << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    s.setAt(2, -5, '1');
    cerr << "CHYBA: Argument y je zaporny a metoda getAt() nevyhodila vyjimku"
      << endl; 
    exit(1);
  } catch (int error) {
  }
  
  try {
    s.setAt(50, 1, '1');
    cerr << "CHYBA: Argument x je vetsi nebo rovny velikosti hraci plochy a "
      << "metoda setAt() nevyhodila vyjimku" << endl;
    exit(1);
  } catch (int error) {
  }

  try {
    s.setAt(3, 9, '1');
    cerr << "CHYBA: Argument y je vetsi nebo rovny velikosti hraci plochy a "
      << "metoda setAt() nevyhodila vyjimku" << endl;
    exit(1);
  } catch (int error) {
  }  
  
  try {
    s.setAt(2, 1, 'x');
    cerr << "CHYBA: Argument symbol neobsahuje znak nachazejici se v mnozine "
      "pripustnych znaku a metoda setAt() nevyhodila vyjimku" << endl;
    exit(1);
  } catch (int error) {
  }
  cout << "OK" << endl;
}

/*
 * Testy operatoru vstupu a vystupu
 */
void testIO() {
  cout << "Test operatoru '<<' a '>>'..." << endl;

  Sudoku s1(16, "123456789ABCDEFG");
  s1.setAt(0, 0, 'A');
  s1.setAt(15,15,'1');	
  ostringstream ostrm;
  try {
    ostrm << s1;
	cout << s1;
  } catch (int error) {
    cerr << "CHYBA: Pretizeny operator '<<' vyhodil vyjimku.\n"
      << "Volani:\n\tSudoku s1(9, \"ABCDEFGHI\");\n\tostringstream ostrm;\n\t"
      << "ostrm << s1;\n";
    exit(1);
  }
  istringstream istrm(ostrm.str());
  Sudoku s2(16,"123456789ABCDEFG");  
  try {
    istrm >> s2;
  } catch (int error) {
    cerr << "CHYBA: Pretizeny operator '>>' vyhodil vyjimku.\n"
      << "Volani:\n\tSudoku s2;\n\tistringstream istrm;\n\tistrm >> str;\n";
    exit(1);
  }

  assert(s1.getSizeOfSide() == s2.getSizeOfSide());
  assert(s1.getSymbols() == s2.getSymbols());
  assert(s1.getAt(0, 0) == s2.getAt(0, 0));
  assert(s1.getAt(15,15) == s2.getAt(15,15));
  cout << "OK" << endl;
}