#include "pilha_int.H"

#include <new>

/* O construtor por cópia tem de construir uma matriz dinâmica própria
   para guardar cópias dos itens da pilha original.  Por uma questão de
   simplicidade escolhe-se uma capacidade idêntica à da pilha original,
   embora qualquer capacidade não-inferior ao nóero de itens da pilha
   original bastasse. */
PilhaInt::PilhaInt(PilhaInt const& outra_pilha) 
    : capacidade(outra_pilha.capacidade),
      itens(new Item[capacidade]),
      numero_de_itens(outra_pilha.numero_de_itens) 
{ 
    /* Depois de inicializados quer a capacidade quer o número de
       itens e depois de construída a matriz dinâmica, é necessário
       copiar os itens um a um entre a pilha original e a pilha
       construída. */
    for(int i = 0; i != numero_de_itens; ++i) 
	itens[i] = outra_pilha.itens[i]; 

    assert(cumpreInvariante());
}

/* O operador de atribuição por cópia tem duas verificações.  A
   primeira verifica se por acaso não se trata de uma auto-atribuição,
   em que a instância implícita é idêntica (é a mesma variável) que a
   pilha modelo passada como argumento por referência.  

   Sem este teste o operador teria o trabalho de copiar os itens para
   eles próprios na matriz dinâmcia, o que é uma tarefa inútil.

   A segunda verificação serve para reciclar a matriz dinâmica quando
   tal for possível.  Tal como o cirurgião plástico reciclará (i.e.,
   deixará em paz) um nariz com as características desejadas.

   É um exercício interessante verificar o que aconteceria se se
   fizesse uma auto-atribuição e não existisse nenhuma das
   verificações... */
PilhaInt& PilhaInt::operator = (PilhaInt const& outra_pilha)
{
    assert(cumpreInvariante() and outra_pilha.cumpreInvariante());
    if(this != &outra_pilha) {
	if(capacidade != outra_pilha.capacidade) { 
	    capacidade = outra_pilha.capacidade; 
	    delete[] itens;
	    itens = new Item[capacidade]; 
	} 
	numero_de_itens = outra_pilha.numero_de_itens; 
	for(int i = 0; i != numero_de_itens; ++i) 
	    itens[i] = outra_pilha.itens[i]; 
    }
          return *this; 

    assert(cumpreInvariante());
}

/* O ideal seria criar um método privado para aumentar a matriz
   dinâmica e chamá-lo na instrução de selecção.  Nesse caso o
   procedimento poe() passaria a em-linha (inline). */ 
void PilhaInt::poe(Item const& novo_item) 
{ 
    assert(cumpreInvariante());

    if(numero_de_itens == capacidade) {
	try { 
	    Item* const novos_itens = new Item[capacidade * 2];
	    capacidade *= 2; 
	    for(int i = 0; i != altura(); ++i) 
		novos_itens[i] = itens[i]; 
	    delete[] itens; 
	    itens = novos_itens; 
	} catch(std::bad_alloc) {
	    throw MemoriaEsgotada(capacidade * 2);
	}
    } 
    itens[numero_de_itens++] = novo_item; 

    assert(cumpreInvariante());
}

#ifdef TESTE

#include <iostream>

using namespace std;

#define erro(msg) {cout << __FILE__ << ':' << __LINE__ << ": " << (msg) \
                       << endl; \
                   ocorreu_erro = true; }

int main() 
{
    bool ocorreu_erro = false;

    cout << "Iniciando testes da classe PilhaInt..." << endl;

    PilhaInt pilha;

    // Coloca 10000 valores na pilha:
    for(int i = 0; i != 10000; ++i)
        pilha.poe(i);

    // Retira 10000 valores na pilha:
    for(int i = 9999; i != -1; --i) {
	if(pilha.topo() != i)
	    erro("pilha.topo() devia ser i.");
        pilha.tira();
    }

    // Coloca 10000 valores na pilha:
    for(int i = 0; i != 10000; ++i)
        pilha.poe(i);

    if(pilha.altura() != 10000)
	erro("pilha.altura() devia ser 10000.");

    PilhaInt copia = pilha;

    if(copia.altura() != pilha.altura())
	erro("copia.altura() devia ser igual a pilha.altura().");

    PilhaInt nova;
    nova = pilha;

    if(nova.altura() != pilha.altura())
	erro("nova.altura() devia ser igual a pilha.altura().");

    PilhaInt outra = pilha;

    while(not pilha.estaVazia()) {
	if(pilha.topo() != outra.topo())
	    erro("pilha.topo() devia ser igual a outra.topo()");
	pilha.tira();
	outra.tira();
    }

    if(pilha.altura() != outra.altura())
	erro("pilha.altura() devia ser igual a outra.altura().");

    if(pilha.altura() != 0)
	erro("pilha.altura() devia ser 0.");

    while(not copia.estaVazia()) {
	if(copia.topo() != nova.topo())
	    erro("copia.topo() devia ser igual a nova.topo()");
	copia.tira();
	nova.tira();
    }

    if(copia.altura() != nova.altura())
	erro("copia.altura() devia ser igual a nova.altura().");

    if(copia.altura() != 0)
	erro("copia.altura() devia ser 0.");

    cout << "Teste finalizados..." << endl;

    return ocorreu_erro? 1 : 0;
}

#endif // TESTE