/* 
  Pràctica de PS Quadrimestre Tardor 2004-2005 <EDAMAZE>
  Autors:
          Daniel Clemente Laboreo <www.danielclemente.com>
          Francesc Xavier Morales Pascual <www.xavimorales.biz>
*/

//laberint.cpp: lectura, escriptura, i obrir i tancar portes a un laberint


#include "laberint.hpp"

// Definir membres static
const char laberint::nom_mod[];
const int laberint::FilsColsIncorrecte;
const int laberint::PosicioInexistent;
const int laberint::PortaExterior;
const char laberint::MsgFilsColsIncorrecte[];
const char laberint::MsgPosicioInexistent[];
const char laberint::MsgPortaExterior[];


laberint::laberint(nat num_fil, nat num_col) throw(error) :nfil(num_fil),ncol(num_col) {
  // crea un laberint[num_fil][num_col] sense excavar, amb totes les cambres tancades

  reserva_memoria(num_fil,num_col);  // aqui reservem l'espai per a la matriu
  omple_vector_models();

  // Ara fem que totes les cambres apuntin al model 'cambra tancada' (15)
  nat i, j;
  for (i=0; i<num_fil; ++i)
    for (j=0; j<num_col; ++j)
      c[i][j]= &models[15];

}

laberint::laberint(istream& is) throw(error) {

  /* Exemple d'entrada:  |   En realitat, moltes posicions no es miren:
  3 4                    |        'a'=arbitrari.
  *********              | aaaaaaaaa       
  * * * * *              | *a*a*a*a* 
  * *******              | a a*a*a*a 
  *   * * *              | *a a*a*a* 
  *** * ***              | a*a a a*a 
  * *   * *              | *a*a a*a* 
  *********              | a*a*a*a*a 
                         |
  */

  char *line1, *line2;
  nat i, j;
  nat limit; // no és necessari però serveix per facilitar la lectura
  posicio pos;

  is >> nfil >> ncol; // llegim files i columnes

  // Creem un laberint nou
  reserva_memoria(nfil,ncol);
  omple_vector_models();

  // Ara fem que totes les cambres apuntin al model 'cambra tancada' (15)
  for (i=0; i<nfil; ++i)
    for (j=0; j<ncol; ++j)
      c[i][j]= &models[15];

  // Comencem a llegir les linies
  // Farem només un bucle que llegeixi línies de dues en dues (amb dos buffers) i que obri les portes que facin falta.

  // ncol*2+1 és el nombre de caràcters que conté cada línia. Li sumem un de més per al salt de linia
  limit = ncol*2+2;

  try {
    line1= new char[limit]; //buffer per detectar les portes E/O de tota una fila
    line2= new char[limit]; //buffer per detectar les portes N/S de tota una fila
  } catch (error e) {
    delete[] line1; // I line2 segur que no s'ha pogut crear
    throw; // Passar l'excepció a qui ha cridat la funció; no es pot continuar
  }

  // Baixem a la primera línia d'asteriscs, i la ignorem sencera
  is.ignore(1+limit); // El 1 és pel salt de línea que ve després dels dos números

  // 'i' correspon a la fila actual, 'j' a la columna actual. Tenen com a últim valor nfil i ncol respectivament
  for(i=1;i<=nfil;++i) {
    is.getline(line1,limit); // E/O
    is.getline(line2,limit); // N/S

    for (j=1;j<=ncol;++j) {
      // com partim d'un laberint sense excavar quan trobem una paret tancada no fem res
      pos=make_pair(i,j);
      if(line1[j*2]==' ')
        obre_porta(paret("est"), pos);
      if(line2[j*2-1]==' ')
        obre_porta(paret("sud"), pos);
    }
  }

  // Alliberar memòria dels buffers
  delete[] line1;
  delete[] line2;

}


laberint::laberint(const laberint & l) throw(error) {
  // Constructor per còpia
  // Crear nova matriu i fer que els elements valguin el mateix que a l
  nat i, j;

  // Això es copia
  nfil= l.nfil;
  ncol= l.ncol;

  reserva_memoria(nfil,ncol);
  omple_vector_models();

  // Tenim dos vectors models diferents (un a l i altre aquí). No podem fer que apuntin al mateix lloc (si fos static sí que podríem i seria més fàcil!).

  nat m; // Model

  for(i=0;i<nfil;++i) {
    for(j=0;j<ncol;++j) {
      // Per a cada cambra, trobem l'índex a l i aquí apuntem al mateix model
      m=l.busca_model(l.c[i][j]);
      c[i][j] = &models[m];
    }
  }


}

laberint & laberint::operator=(const laberint & l) throw(error) {
  // Aquest procediment és el mateix que la  constructora per còpia, però si tenen diferents files o columnes hem de destruir la matriu i fer una de nova

  nat i,j;

  if (this == &l)
    return *this; // No copiar-se a si mateix

  if(nfil!=l.nfil||ncol!=l.ncol) {
    elimina_reserva();
    reserva_memoria(l.nfil,l.ncol);
  }
  // A més, el vector de models no s'ha d'omplir altre cop

  nfil= l.nfil;
  ncol= l.ncol;

  nat m; // Model

  for(i=0;i<nfil;++i) {
    for(j=0;j<ncol;++j) {
      // Per a cada cambra, trobem l'índex a l i aquí apuntem al mateix model
      m=l.busca_model(l.c[i][j]);
      c[i][j] = &models[m];
    }
  }

  return *this;

}

// (aquesta és virtual)
laberint::~laberint() throw(error) {
  // Destruir la matriu
  elimina_reserva();
  // El vector de models s'elimina sol

}


nat laberint::num_files() const throw() {
  return nfil;
}
nat laberint::num_columnes() const throw() {
  return ncol;
}

const cambra laberint::operator()(const posicio& pos) const throw(error) {
  nat fil=pos.first-1;
  nat col=pos.second-1;

  comprova_posicio(fil,col);

  // La matriu és de punters a cambra, només cal seguir el punter
  return *c[fil][col];

}


void laberint::obre_porta(paret p, const posicio& pos) throw(error) {
  obre_tanca(p,pos,true);
}

void laberint::tanca_porta(paret p, const posicio& pos) throw(error) {
  obre_tanca(p,pos,false);
}

void laberint::obre_tanca(paret p, const posicio& pos, bool obrir) throw(error) {
  nat fil=pos.first-1;
  nat col=pos.second-1;
  nat m; // Model
  paret contraria=paret( ((int)p+2) % 4 ); // Canvia entre NORD-SUD i EST-OEST

  if((int)p==paret::NO_DIR) { // Paret invàlida
    // Hem de generar una excepció. Voldríem que fos laberint qui la generés, però els vostres jocs de proves demanen que sigui cambra (heu suposat que tothom utilitzaria cambra aquí).
    // Per tant, us complaem fent que cambra generi una excepció:
    cambra(0,0,0,0).obre_porta(p);
  }

  // La posició ha d'existir dins del laberint
  comprova_posicio(fil,col);

  // No es pot obrir una paret que dóna a l'exterior. Sí que es vàlid "tancar".
  if( (fil==0 && (int)p==paret::NORD) ||
      (fil==nfil-1 && (int)p==paret::SUD) ||
      (col==0 && (int)p==paret::OEST) ||
      (col==ncol-1 && (int)p==paret::EST) ) {
    if (obrir) {
      throw error(nom_mod, PortaExterior, MsgPortaExterior);
    } else
      return; // Sense fer res, no cal "tancar" perquè ja està tancada
  }


  // Troba el tipus de cambra que hi en aquesta posició
  m=busca_model(c[fil][col]);

  //   Aplica la màscara corresponent per obrir/tancar la porta.
  // exemple (veure fitxer .rep per tota l'explicació)
  // cambra *** és models[ 1101b ]  (O-S-E-N respectivament)
  //        *
  //        ***
  // Demanen obrir porta OEST (3 a l'enumeració)
  // Construïm la màscara apropiada: 1 << (3) = 1000b
  // Apliquem la màscara: neguem 1000, queda 0111, i fem & amb 1101.
  // Queda: 0101   ***
  //
  //               ***
  //   Per tancar, es fa una or.

  if(obrir)
    m= m & ~( 1 << (int)p );
  else
    m= m | ( 1 << (int)p );

  c[fil][col]= &models[m]; // Ara apunta a un altre model de cambra


  // Situar-se en la cambra adjacent
  switch((int)p) {
  case paret::NORD:
    --fil;
    break;
  case paret::EST:
    ++col;
    break;
  case paret::SUD:
    ++fil;
    break;
  case paret::OEST:
    --col;
    break;
  }

  // Obrir o tancar la porta (aquesta vegada la inversa de la porta original)
  m=busca_model(c[fil][col]);

  if(obrir)
    m= m & ~( 1 << (int)contraria );
  else
    m= m | ( 1 << (int)contraria );

  c[fil][col]= &models[m];

}

void laberint::comprova_posicio(const nat fil, const nat col) const throw(error) {
  // Donar error si la posició no és vàlida
  if(fil<0 || fil>=nfil || col<0 || col>=ncol)
    throw error(nom_mod, PosicioInexistent, MsgPosicioInexistent);
}

nat laberint::busca_model(pcambra pc) const {
  // Retorna l'índex del vector models on es troba la cambra apuntada

  // Desplaçament respecte models[0]
  // C++ dóna aquesta distància no en bytes sinó en número d'objectes. Perfecte; aquest és l'índex que volem.
  return pc-&models[0];

  // I no fa falta fer una cerca!
  // (encara que es faria així):
  // nat i=15;
  // while (pc != &models[i]) --i;
  // return i;
}

// (aquesta és virtual)
void laberint::print(ostream& s) const throw() {

  /* Exemple de laberint (de 3x4).
     Les coordenades són només per entendre millor el codi.
   
  012345678 
  ********* 0
  * * * * * 1
  * ******* 2
  *   * * * 3
  *** * *** 4
  * *   * * 5
  ********* 6
   
  */

  // L'algorisme és semblant al de llegir
  char *line1, *line2;
  try { // en cas que la reserva de memòria produeixi un error
    // ncol*2+1 és el nombre de caràcters per línia, sense el NULL
    line1 = new char[ncol*2+1+1];
    line2 = new char[ncol*2+1+1];
  } catch (error e) {
    delete[] line1; // I line2 segur que no s'ha creat
    return; // No es pot escriure, sortir de la funció (no podem llançar excepcions)
  }

  nat i, j;
  cambra actual;

  s << nfil << " " << ncol << endl; // Files i columnes

  for(i=0;i<ncol*2+1;++i)  // generem una primera línia de *
    line1[i]='*';
  line1[i]='\0'; // No oblidar-se del NULL !
  s << line1 << endl;

  // 'i' fa referència a la fila actual, 'j' a la columna actual
  for(i=1;i<=nfil;++i) {
    line1[0]='*';  //buffer per representar les portes E/O de tota una fila
    line2[0]='*';  //buffer per representar les portes N/S de tota una fila
    for(j=1;j<=ncol;++j) {
      actual = *c[i-1][j-1]; // Cambra que analitzarem

      line1[j*2-1]=' '; // Escrivim l'espai que representa la cambra


      line1[j*2]= actual.hi_ha_porta(paret("est"))?' ':'*'; // les portes E/O van a parar a les posicions parells
      line2[j*2-1]= actual.hi_ha_porta(paret("sud"))?' ':'*'; // en canvi les N/S van a parar a les posicions senars


      line2[j*2]='*'; // Columna de * de la dreta.

      line1[j*2+1]='\0'; // Acabar la cadena!
      line2[j*2+1]='\0'; // Acabar la cadena!
    }
    s << line1 << endl;
    s << line2 << endl;
  }

  delete[] line1;
  delete[] line2;

}

void laberint::reserva_memoria(const nat fila, const nat columna) throw (error) {
  // "fila" i "columna" comencen per 1

  // Les mides han de ser correctes
  if (fila<1 || columna<1)
    throw error(nom_mod, FilsColsIncorrecte, MsgFilsColsIncorrecte);

  nat i; // Comptador
  try {
    c = new (pcambra*)[fila]; // reservar espai per a nfil files, cada fila tindrà ncol posicions
    for (i=0;i<fila;++i)
      c[i] = new (pcambra)[columna];  // omple la fila de ncol elements, cadascú és un punter a cambra
  } catch(error e) {
    elimina_reserva();
  }
}

void laberint::elimina_reserva() {
  nat i;
  // Potser no estan totes les files a memòria (perquè ha fallat una constructora a meitat) però un delete[] NULL no molesta
  for(i=0;i<nfil;++i)
    delete[] c[i];
  delete[] c;
}

void laberint::omple_vector_models() {
  // Veure fitxer .rep pels detalls sobre aquest vector de models de cambra
  nat i;
  for (i=0; i<16; ++i)
    models[i]=cambra( !(i&1), !(i&4), !(i&2), !(i&8) );
  // Atenció!! La constructora de cambra no és cambra(n,e,s,o) sinó cambra(n,s,e,o)

}

