import java.util.*;
import java.io.*;

/** 
 *  Esta classe permite representar o estado de um
 *  diretorio em termos dos seus elementos em um
 *  determinado momento.  
 *
 *  @author  ilmr
 */
public class HistDir implements Serializable {
    // Propriedades particulares (private) da classe aqui:
    private Date dataRegistro;
    private Collection membrosDiretorio;
    
    /**
     *  Recebe uma String que deve 
     *  representar um diretorio.  As informacoes sobre
     *  esse diretorio devem ser gravadas em um arquivo no 
     *  diretorio corrente.  Diferentes invocacoes a build 
     *  devem gerar diferentes arquivos de informacao do 
     *  estado do diretorio.
     *
     *  @param  dir  Nome do diretorio
     *  @throws IllegalArgumentException Argumento nao e um diretorio.
     */
    public HistDir(String dir) 
	throws IllegalArgumentException, FileNotFoundException, IOException {
	Calendar cal = new GregorianCalendar();
	// obtem info do diretorio
	File[] fList;
	File fd = new File(dir);
	if (fd.isDirectory()) {
	    fList = fd.listFiles();
	}
	else {
	    throw new IllegalArgumentException(dir);
	}
	// inicializa campos da classe
	dataRegistro = cal.getTime();
	membrosDiretorio = Arrays.asList(fList);
	// nome do arquivo de registro
	String outName = "reg" + cal.get(Calendar.YEAR)
	    + cal.get(Calendar.DAY_OF_YEAR) + "_"
	    + cal.get(Calendar.HOUR_OF_DAY) + cal.get(Calendar.MINUTE);
	// serializacao: escrita
	ObjectOutputStream oos = new ObjectOutputStream
	    (new FileOutputStream(outName));
	oos.writeObject(this);
	oos.close();
    }

    /**
     *  O construtor sem argumentos assume o diretorio corrente
     *  como diretorio a ser registrado.
     */
    public HistDir() throws FileNotFoundException, IOException {
	// diretorio corrente tem nome simbolico .
	this(".");
    }

    /**
     *  Recebe o nome de um arquivo que deve
     *  representar o estado de um diretorio previamente
     *  gravado e retornar um objeto HistDir com esse estado.
     *
     *  @param arqest  Arquivo com estado do diretorio.
     *  @return Objeto HistDir com dados sobre o diretorio.
     *  @throws IllegalArgumentException parametro nao corresponde
     *           a um arquivo previamente gravado.
     */
    public static HistDir getState(String arqest) 
	throws IllegalArgumentException {
	HistDir h = null;
	// serializacao: leitura
	try {
	    ObjectInputStream ois = new ObjectInputStream
		(new FileInputStream(arqest));
	    h = (HistDir) ois.readObject();
	    ois.close();
	}
	catch (Exception e) {
	    // em caso de erro (qualquer erro)
	    throw new IllegalArgumentException(arqest);
	}
	return h;
    }

    /** 
     *  Permite apresentar dados sobre o estado do diretorio 
     *  e seus membros na forma de uma String.
     *
     *  @return String que descreve estado do diretorio.
     */
    public String toString() {
	// usa a outra implementacao
	return toString(false);
    }

    /** 
     *  Como toString(), mas se argumento for true apresenta
     *  os membros em ordem alfabetica; se false, ordem de 
     *  apresentacao eh irrelevante.
     *  
     *  @param orden  Se true, lista de membros ordenada.
     *  @return String que descreve estado do diretorio.
     */
    public String toString(boolean orden) {
	Iterator i;
	File f;
	StringBuffer resposta = new StringBuffer();
	// se quiser ordem em Collection generica, TreeSet eh a escolha:
	if (orden) {
	    TreeSet ts = new TreeSet(membrosDiretorio);
	    i = ts.iterator();
	}
	else
	    i = membrosDiretorio.iterator();
	// usa um dos iteradores para varrer a colecao
	while (i.hasNext()) {
	    f = (File) i.next();
	    // se diretorio, coloca um "D" na primeira coluna
	    // depois complementa linha com nome e tamanho do arquivo
	    resposta.append(f.isDirectory() ? "D" : " ").
		append("   " + f.getName()).
		append("\t\t" + f.length() + "\n");
	}
	return resposta.toString();
    }

    /**
     *  Lista a diferenca entre este objeto e o objeto especificado
     *  no argumento.
     *
     *  @param estado Objeto HistDir que representa outro objeto
     *           de estado de diretorio.
     *  @return  String com a lista de diferencas entre os estados
     *           ou a frase "Estado do diretorio ... nao alterado desde ..."
     */
    public String listChanges(HistDir estado) {
	StringBuffer lista = new StringBuffer();
	// Data sempre sera diferente
	if (estado.membrosDiretorio.equals(membrosDiretorio)) {
	    lista.append("Estado do diretorio nao alterado desde" +
		estado.dataRegistro + "\n");
	}
	else {
	    lista.append("Houve modificacoes entre " + 
			 estado.dataRegistro + " e " +
			 dataRegistro + "\n");
	    // vamos aproveitar para ilustrar uso do HashMap...
	    String nome;
	    File arq;
	    HashMap tabelaVelha = new HashMap();
	    // preenche HashMap com estado antigo
	    Iterator varreVelho = estado.membrosDiretorio.iterator();
	    while (varreVelho.hasNext()) {
		arq = (File) varreVelho.next();
		nome = arq.getName();
		tabelaVelha.put(nome,arq);
	    }
	    // teste se arquivos atuais estao no HashMap
	    Iterator corrente = membrosDiretorio.iterator();
	    File arqVelho;
	    while (corrente.hasNext()) {
		arq = (File) corrente.next();
		nome = arq.getName();
		if (tabelaVelha.containsKey(nome)) {
		    arqVelho = (File) tabelaVelha.get(nome);
		    if (! arq.equals(arqVelho)) {
			// arquivo foi modificado
			lista.append("M  " + nome +
				     arqVelho.length() + " -> " +
				     arq.length() + "\n");
		    }
		}
		else {
		    // arquivo novo
		    lista.append("N  " + nome + "   " + 
				 arq.length() + "\n");
		}
	    }
	}
	return lista.toString();
    }

    // metodo para teste da classe HistDir
    public static void main(String[] args) {
	HistDir corrente = null, passado = null;

	try {
	    // verificacao e inicializacao dos argumentos
	    switch(args.length) {
	    case 0:  
		corrente = new HistDir();
		break;
	    case 2:
		passado = HistDir.getState(args[1]);
	    case 1:
		corrente = new HistDir(args[0]);
		break;
	    default:
		System.out.println("Forma de uso: java HistDir [diretorio [estado]]");
		System.exit(1);
	    }
	    
	    // apresenta estado do diretorio indicado
	    System.out.println(corrente.toString());
	    System.out.println("Em ordem:");
	    System.out.println(corrente.toString(true));
	    
	    // se especificado, compara com passado
	    if (passado != null )
		System.out.println(corrente.listChanges(passado));
	}
	catch (Exception e) {
	    e.printStackTrace();
	}
    }
}