#include <string>
#include <list>
#include <utility>
#include <iostream>
#include <fstream>

#include <eda/error>
#include <eda/gen_driver>
#include <eda/util>
#include <eda/mem_din>

#include "paret.hpp"
#include "cambra.hpp"
#include "laberint.hpp"
#include "teseus.hpp"
#include "dedalus.hpp"
#include "particio.hpp"

using util::nat;
using namespace std;

namespace check {
  bool chequea_camino_minimo(const posicio& ini, 
                             const posicio& final,
                             const laberint& L,
	                     const list<posicio>& camino_candidato);
  bool chequea_perfecto(const laberint& L);
};

void update_dirs(const string& dir, bool& n, bool& s, bool& e, bool& o) {
  if (dir == "N" || dir == "nord" || dir == "NORD") {
    n = true; return; 
  }
  if (dir == "S" || dir == "sud" || dir == "SUD") {
    s = true; return;
  }
  if (dir == "E" || dir == "est" || dir == "EST") {
    e = true; return;
  }
  if (dir == "W" || dir == "oest" || dir == "OEST") {
    o = true; return;
  }
  throw error(gen_driver::nom_mod, gen_driver::WrongTypeArgs,
	      gen_driver::WrongTypeArgsMsg);
}

void* user_init(gen_driver& dr) { 
  string id = dr.args(1);
  string tid = dr.args(2);
  
  if (tid == "cambra") {
  // init cambra
    if (dr.nargs() < 2 || dr.nargs() > 6)
      throw error(gen_driver::nom_mod, gen_driver::WrongNumArgs,
		  gen_driver::WrongNumArgsMsg);
    bool n, s, e, o; n = s = e = o = false;
    switch (dr.nargs()) {
    case 6 : { update_dirs(dr.args(6), n, s, e, o); }
    case 5 : { update_dirs(dr.args(5), n, s, e, o); }
    case 4 :  { update_dirs(dr.args(4), n, s, e, o); }
    case 3 : { update_dirs(dr.args(3), n, s, e, o); }
    default : { }

    }
    return static_cast<void*>(new cambra(n,s,e,o));
  } else if (tid == "laberint") {
	if (dr.nargs() < 2 || dr.nargs() > 4)
		throw error(gen_driver::nom_mod, gen_driver::WrongNumArgs,
		  	    gen_driver::WrongNumArgsMsg);
	if (dr.nargs() == 2) 
	  return static_cast<void*>(new laberint);
    	if (dr.nargs() == 3) {
		ifstream input_file(dr.args(3).c_str());
		if (!input_file)
		throw error(gen_driver::nom_mod, gen_driver::WrongTypeArgs,
			    gen_driver::WrongTypeArgsMsg);
		return static_cast<void*>(new laberint(input_file));
	}
	// dr.nargs() == 4
	int nf = util::toint(dr.args(3));
	int nc = util::toint(dr.args(4));
	if (nf < 0 || nc < 0) 
	  throw error(gen_driver::nom_mod, gen_driver::WrongTypeArgs,
		      gen_driver::WrongTypeArgsMsg);
	return static_cast<void*>(new laberint(nf, nc));
  } else if (tid == "laberint_teseus") {
  // init laberint_teseus
	if (dr.nargs() < 2 || dr.nargs() > 4)
		throw error(gen_driver::nom_mod, gen_driver::WrongNumArgs,
		  	    gen_driver::WrongNumArgsMsg);
	if (dr.nargs() == 2) 
	  return static_cast<void*>(new laberint_teseus);

    	if (dr.nargs() == 3) {
		ifstream input_file(dr.args(3).c_str());
		if (!input_file)
		throw error(gen_driver::nom_mod, gen_driver::WrongTypeArgs,
			    gen_driver::WrongTypeArgsMsg);
		return static_cast<void*>(new laberint_teseus(input_file));
	}
	// dr.nargs() == 4
	int nf = util::toint(dr.args(3));
	int nc = util::toint(dr.args(4));
	return static_cast<void*>(new laberint_teseus(nf, nc));
  } else if (tid == "particio<int>") {
    return static_cast<void*>(new particio<int>);
  }
  throw error(gen_driver::nom_mod, gen_driver::WrongTypeArgs,
	      gen_driver::WrongTypeArgsMsg);
}

// funciones de utilidad

// imprime un booleano
ostream& operator<=(ostream& os, bool b) {
  os << (b ? "true" : "false");
  return os;
}
 
// imprime (graficamente) una camara
ostream& operator<<(ostream& os, const cambra& c) {
  os << '*' << (c.hi_ha_porta(paret("nord")) ? ' ' : '*') << '*' << endl;
  os << (c.hi_ha_porta(paret("oest")) ? ' ' : '*') << ' ';
  os << (c.hi_ha_porta(paret("est")) ? ' ' : '*') << endl;
  os << '*' << (c.hi_ha_porta(paret("sud")) ? ' ' : '*') << '*';
  return os;
}

// imprime una posicion (fila, col)
ostream& operator<<(ostream& os, const posicio& pos) {
  os << "(" << pos.first << "," << pos.second << ")";
  return os;
}

#ifdef EXTENDED_VERSION
// imprime una particio<T>
template <typename T>
ostream& operator<<(ostream& os, const particio<T>& P) {
  P.print(os);
  return os;
}
#endif

// imprime una lista de T's
template <typename T>
ostream& operator<<(ostream& os, const list<T>& L) {
  list<T>::const_iterator it = L.begin();
  if (it == L.end()) { os << "[]"; return os; }
  T prev = *it;
  os << "[";
  ++it;
  while (it != L.end()) {
    os << prev << ",";
    prev = *it;
    ++it;
  }
  os << prev << "]"; 
  return os;
}

// cambra
void crea_cambra(cambra*& c) {
     c = new cambra;
}

void crea_otra_cambra(cambra*& c) {
    c = new cambra(true, false, false, false);	
}

bool test_ctor_cambra(gen_driver& d)
{ return d.generic_memtest<cambra> (crea_cambra, "ctor cambra"); }

bool test_copyctor_cambra(gen_driver& d)
{ return d.copyctor_memtest<cambra> (crea_cambra); }

bool test_asigna_cambra(gen_driver& d)
{ return d.assgn_memtest<cambra>(crea_cambra,crea_otra_cambra); }


void hi_ha_porta(gen_driver& dr) {
  dr.get_ostream() <= dr.object<cambra>()->hi_ha_porta(paret(dr.args(1)));
  dr.get_ostream() << endl;
}

void iguales(gen_driver& dr) {
  (dr.get_ostream() <= (*dr.object<cambra>() == *dr.object<cambra>(1))) << endl;
}

void diferentes(gen_driver& dr) {
  (dr.get_ostream() <= (*dr.object<cambra>() != *dr.object<cambra>(1))) << endl;
}

void obre_porta_c(gen_driver& dr) {
  dr.object<cambra>() -> obre_porta(paret(dr.args(1)));
}

void tanca_porta_c(gen_driver& dr) {
  dr.object<cambra>() -> tanca_porta(paret(dr.args(1)));
}
  
void print_c(gen_driver& dr) {
  dr.get_ostream() << (*dr.object<cambra>()) << endl;
}

// laberint

void crea_laberint(laberint*& l) {
     l = new laberint;	
}

void crea_otro_laberint(laberint*& l) {
     ifstream input_file("juegos/laberint_3x3.txt");
     l = new laberint(input_file);
}

bool test_ctor_laberint(gen_driver& d)
{ return d.generic_memtest<laberint> (crea_laberint, 
  "ctor laberint(nat,nat)"); }

bool test_ctor_laberint_fitxer(gen_driver& d)
{ return d.generic_memtest<laberint> (crea_otro_laberint, 
  "ctor laberint(istream&)"); }

bool test_copyctor_laberint(gen_driver& d)
{ return d.copyctor_memtest<laberint> (crea_laberint); }

bool test_asigna_laberint(gen_driver& d)
{ return d.assgn_memtest<laberint>(crea_laberint,crea_otro_laberint); }

void num_files(gen_driver& dr) {
  if (dr.object_type() != "laberint" && dr.object_type() != "laberint_teseus")
	throw error(gen_driver::nom_mod,gen_driver::WrongTypeArgs,
	            gen_driver::WrongTypeArgsMsg);
  dr.get_ostream() << dr.object<laberint>() -> num_files() << endl;
}

void num_columnes(gen_driver& dr) {
  if (dr.object_type() != "laberint" && dr.object_type() != "laberint_teseus")
	throw error(gen_driver::nom_mod,gen_driver::WrongTypeArgs,
	            gen_driver::WrongTypeArgsMsg);
  dr.get_ostream() << dr.object<laberint>() -> num_columnes() << endl;
}

void obt_cambra(gen_driver& dr) {
  if (dr.object_type() != "laberint" && dr.object_type() != "laberint_teseus")
	throw error(gen_driver::nom_mod,gen_driver::WrongTypeArgs,
	            gen_driver::WrongTypeArgsMsg);
  pair<nat,nat> pos = make_pair(util::toint(dr.args(1)),
                                util::toint(dr.args(2)));
  dr.get_ostream() << dr.object<laberint>() -> operator()(pos) << endl;
}

void obre_porta_l(gen_driver& dr) {
  if (dr.object_type() != "laberint" && dr.object_type() != "laberint_teseus")
	throw error(gen_driver::nom_mod,gen_driver::WrongTypeArgs,
	            gen_driver::WrongTypeArgsMsg);
  pair<nat,nat> pos = make_pair(util::toint(dr.args(2)),
                                util::toint(dr.args(3)));
  dr.object<laberint>() -> obre_porta(paret(dr.args(1)), pos);
}

void tanca_porta_l(gen_driver& dr) {
  if (dr.object_type() != "laberint" && dr.object_type() != "laberint_teseus")
	throw error(gen_driver::nom_mod,gen_driver::WrongTypeArgs,
	            gen_driver::WrongTypeArgsMsg);
  pair<nat,nat> pos = make_pair(util::toint(dr.args(2)),
                                util::toint(dr.args(3)));
  dr.object<laberint>() -> tanca_porta(paret(dr.args(1)), pos);
}

void print(gen_driver& dr) {
  if (dr.object_type() != "laberint" && dr.object_type() != "laberint_teseus")
	throw error(gen_driver::nom_mod,gen_driver::WrongTypeArgs,
	            gen_driver::WrongTypeArgsMsg);
  dr.object<laberint>() -> print(dr.get_ostream()); 
}

void check_perfecto(gen_driver& dr) {
  if (dr.object_type() != "laberint" && dr.object_type() != "laberint_teseus")
	throw error(gen_driver::nom_mod,gen_driver::WrongTypeArgs,
	            gen_driver::WrongTypeArgsMsg);
  laberint* pLab = dr.object<laberint>();
  (dr.get_ostream() <= check::chequea_perfecto(*pLab)) << endl;
}

// teseus
void crea_teseus(laberint_teseus*& l) {
	l = new laberint_teseus(10,10);
}

void crea_otro_teseus(laberint_teseus*& l) {
        ifstream input_file("juegos/laberint_3x3.txt");
	l = new laberint_teseus(input_file);
}

bool test_ctor_teseus(gen_driver& d)
{ return d.generic_memtest<laberint_teseus> (crea_teseus, 
  "ctor laberint_teseus(nat,nat)"); }

bool test_ctor_teseus_fitxer(gen_driver& d)
{ return d.generic_memtest<laberint_teseus> (crea_otro_teseus, 
  "ctor laberint_teseus(istream&)"); }

bool test_copyctor_teseus(gen_driver& d)
{ return d.copyctor_memtest<laberint_teseus> (crea_teseus); }

bool test_asigna_teseus(gen_driver& d)
{ return d.assgn_memtest<laberint_teseus>(crea_teseus,crea_otro_teseus); }

void resoldre(gen_driver& dr) {
  pair<nat,nat> ini = make_pair(util::toint(dr.args(1)),
                                util::toint(dr.args(2)));
  pair<nat,nat> final = make_pair(util::toint(dr.args(3)),
                                  util::toint(dr.args(4)));
  list<posicio> L;

  dr.object<laberint_teseus>() -> resoldre(ini, final, L);
  
  dr.get_ostream() << L << endl;
}

#ifdef EXTENDED_VERSION
void resoldrep(gen_driver& dr) {
  pair<nat,nat> ini = make_pair(util::toint(dr.args(1)),
                                util::toint(dr.args(2)));
  pair<nat,nat> final = make_pair(util::toint(dr.args(3)),
                                  util::toint(dr.args(4)));

  dr.object<laberint_teseus>() -> resoldre(ini, final, dr.get_ostream());
  
}
#endif

void check_camino_minimo(gen_driver& dr) {
  pair<nat,nat> ini = make_pair(util::toint(dr.args(1)),
                                util::toint(dr.args(2)));
  pair<nat,nat> final = make_pair(util::toint(dr.args(3)),
                                  util::toint(dr.args(4)));
  list<posicio> L;
  laberint_teseus* pLab = dr.object<laberint_teseus>();

  pLab -> resoldre(ini, final, L);
  
  (dr.get_ostream() <= check::chequea_camino_minimo(ini, final, *pLab, L)) << endl;
}

// dedalus

void construir(gen_driver& dr) {
	dedalus::construir(*dr.object<laberint>());
	dr.get_ostream() << "dedalus::construir OK" << endl;
}

#ifdef EXTENDED_VERSION
void construir_adv(gen_driver& dr) {
	dedalus::construir(*dr.object<laberint>(), util::toint(dr.args(1)), 
                                                   util::toint(dr.args(2)));
	dr.get_ostream() << "dedalus::construir_adv OK" << endl;
}
#endif

// particio<int>

void afegir(gen_driver& dr) {
  dr.object<particio<int> >() -> afegir(util::toint(dr.args(1)));
}
void unir(gen_driver& dr) {
  dr.object<particio<int> >() -> unir(util::toint(dr.args(1)), 
                                      util::toint(dr.args(2)));
}

void representant(gen_driver& dr) {
  dr.get_ostream() << dr.object<particio<int> >() -> representant(util::toint(dr.args(1))) << endl;
}

void size(gen_driver& dr) {
  dr.get_ostream() << dr.object<particio<int> >() -> size() << endl;
}

void same_block(gen_driver& dr) {
int r1 = dr.object<particio<int> >() -> representant(util::toint(dr.args(1)));
int r2 = dr.object<particio<int> >() -> representant(util::toint(dr.args(2)));
  (dr.get_ostream() <= (r1 == r2)) << endl;
}

#ifdef EXTENDED_VERSION
void print_part(gen_driver& dr) {
  dr.get_ostream() << *dr.object<particio<int> >() << endl;
}
#endif

void crea_particio_int(particio<int>*& l) {
  l = new particio<int>;
}

void crea_otra_particio_int(particio<int>*& l) {
  l = new particio<int>;
  l -> afegir(1);
  l -> afegir(2);
  l -> afegir(3);
  l -> unir(1,2);
}

bool test_ctor_particio_int(gen_driver& d)
{ return d.generic_memtest<particio<int> >(crea_particio_int,
                                           "ctor particio<int>", 
					   "particio<int>"); 
}

bool test_copyctor_particio_int(gen_driver& d)
{ return d.copyctor_memtest<particio<int> >(crea_particio_int,
					    "particio<int>"); 
}

bool test_asigna_particio_int(gen_driver& d)
{ return d.assgn_memtest<particio<int> >(crea_particio_int,
                                         crea_otra_particio_int,
					 "particio<int>"); 
}


/* --------------------------------< MAIN >--------------------------------- */

int main()
{
  gen_driver d;

  d.add_call("==", iguales, "cambra", "cambra");
  d.add_call("!=", diferentes, "cambra", "cambra");
  d.add_call("hi_ha_porta", hi_ha_porta, "cambra", "string");

  d.add_call("obre_porta_c", obre_porta_c, "cambra", "string");
  d.add_call("tanca_porta_c", tanca_porta_c, "cambra", "string");
  d.add_call("print_c", print_c, "cambra");

  d.add_call("num_files", num_files, "any");
  d.add_call("num_columnes", num_columnes, "any");
  d.add_call("obt_cambra", obt_cambra, "any", "nat nat");
  d.add_call("obre_porta_l", obre_porta_l, "any", "string nat nat");
  d.add_call("tanca_porta_l", tanca_porta_l, "any", "string nat nat");
  d.add_call("print", print, "any");
  d.add_call("chequea_perfecto", check_perfecto, "any");
  
  d.add_call("resoldre", resoldre, "laberint_teseus", "nat nat nat nat");
  d.add_call("chequea_camino_minimo", check_camino_minimo, 
             "laberint_teseus", "nat nat nat nat");

  d.add_call("construir", construir, "*");

  d.add_call("afegir", afegir, "particio<int>", "int");
  d.add_call("unir", unir, "particio<int>", "int int");
  d.add_call("repr", representant, "particio<int>", "int");
  d.add_call("size", size, "particio<int>");
  d.add_call("equiv", same_block, "particio<int>", "int int");

#ifdef EXTENDED_VERSION
  d.add_call("resoldrep", resoldrep, "laberint_teseus", "nat nat nat nat");
  d.add_call("construir_adv", construir_adv, "*", "nat nat");
  d.add_call("printp", print_part, "particio<int>");
#endif

  d.add_memory_test<cambra>(test_ctor_cambra);
  d.add_memory_test<cambra>(test_copyctor_cambra);
  d.add_memory_test<cambra>(test_asigna_cambra);

  d.add_memory_test<laberint>(test_ctor_laberint);
  d.add_memory_test<laberint>(test_ctor_laberint_fitxer);
  d.add_memory_test<laberint>(test_copyctor_laberint);
  d.add_memory_test<laberint>(test_asigna_laberint);

  d.add_memory_test<laberint_teseus>(test_ctor_teseus);
  d.add_memory_test<laberint_teseus>(test_ctor_teseus_fitxer);
  d.add_memory_test<laberint_teseus>(test_copyctor_teseus);
  d.add_memory_test<laberint_teseus>(test_asigna_teseus);

  d.add_memory_test<particio<int> >(test_ctor_particio_int, "particio<int>");
  d.add_memory_test<particio<int> >(test_copyctor_particio_int, "particio<int>");
  d.add_memory_test<particio<int> >(test_asigna_particio_int, "particio<int>");

  // instalacion de tipos
  d.install_type<cambra>();
  d.install_type<laberint>();
  d.install_type<laberint_teseus>();

  d.install_std_type<particio<int> >("particio<int>");

  d.install_std_type<paret>("paret");
  d.install_std_type<posicio>("posicio");
  d.install_std_type<list<posicio> >("list<posicio>");


  d.go();
}

