package javacodebook.io.randomaccess;

import java.io.*;

/**
 * Die Klasse FileManager ist die Schnittstelle zum RandomAccessFile.
 * Sie ermöglicht das hinzufügen, auffinden und löschen einzelner
 * Einträge und kann ein Array mit allen vorhandenen Datensätzen
 * erstellen.
 */
public class FileManager {

    private RandomAccessFile raf = null;
    private int entrySize;
    DataEntry entryType;

    /*
     * Erzeugt einen neuen FileManager, der die angegebene Datei als Datendatei
     * verwendet.
     * Als weiterer Parameter wird eine Instanz des verwendeten DataEntry-Typs
     * (also in unserem Fall: Ein Person-Objekt) übergeben. Dieses Objekt dient
     * als Vorlage für alle in dieser Klasse benötigten DataEntry-Objekte.
     */
    public FileManager(String fileName, DataEntry entryType)
    throws IOException{
        raf = new RandomAccessFile(fileName, "rw");
        this.entrySize = entryType.getMaxSize();
        this.entryType = (DataEntry)entryType.clone();
        this.entryType.clear();
    }

    public void addEntry(DataEntry entry) throws IOException {
        if(findEntry(entry.getSearchKey()) != null)
            throw new IOException("Eintrag ist bereits vorhanden!");
        else {
            raf.seek(raf.length());
            raf.setLength(raf.length() + entrySize);
            entry.writeData(raf);
        }
    }

    public DataEntry findEntry(String searchStr)
    throws IOException {
        //einen Dummy zum Lesen der Daten erzeugen
        DataEntry bufferEntry = (DataEntry)entryType.clone();
        //Suche am Anfang der Datei starten
        raf.seek(0);
        int entryNr = 0;
        boolean found = false;
        while(entryNr * entrySize < raf.length() -1) {
            bufferEntry.clear();
            bufferEntry.readData(raf);
            String value = bufferEntry.getSearchKey();
            //Wenn der gefundene Eintrag mit dem Suchstring übereinstimmt,
            //wird die Schleife beendet
            if(value.equals(searchStr)) {
                found = true;
                break;
            }
            entryNr++;
            //An die Anfangsposition des nächsten Eintrags springen
            raf.seek(entryNr * entrySize);
        }
        if(!found)
            return null;
        else
            return bufferEntry;
    }

    public void deleteEntry(DataEntry entry) throws IOException {
        if(!(findEntry(entry.getSearchKey()) != null))
            throw new IOException("Eintrag ist nicht vorhanden!");
        else {
            //Nach dem Finden und lesen des Eintrags wieder an seine
            //Anfangsposition springen. Dazu Ganzahl-Division ausnutzen, sie
            //liefert die Nummer des Eintrags und wird wiederum multipliziert
            //mit der max. Länge eines Eintrags.
            int entryNr = (int)raf.getFilePointer()/entrySize;
            raf.seek(entryNr * entrySize);

            //Eintrag löschen, indem er mit Nullbytes überschrieben wird.
            byte[] emptyBytes = new byte[entrySize];
            raf.write(emptyBytes);

            //wenn der gelöschte Eintrag nicht der letzte war, wird der letzte
            //Eintrag in die entstandene Lücke geschoben und die Datei verkürzt
            if(!(raf.getFilePointer() >= raf.length() - 1)) {
                int lastEntryNr = (int)raf.length()/entrySize - 1;
                moveEntry(lastEntryNr, entryNr);
            }
            //jetzt wird die Datei um einen Eintrag verkleinert
           raf.setLength(raf.length() - entrySize);
        }
    }

    public DataEntry[] getAllEntries() throws IOException {
        //Ein Array mit der benötigten Länge erzeugen
        DataEntry[] allEntries = new DataEntry[(int)(raf.length() + 1) / entrySize];

        //einen Dummy zum Lesen der Daten erzeugen
        DataEntry bufferEntry = (DataEntry)entryType.clone();
        //Suche am Anfang der Datei starten
        raf.seek(0);
        int entryNr = 0;
        while(entryNr * entrySize < raf.length() -1) {
            bufferEntry.clear();
            bufferEntry.readData(raf);
            //Einen Klon des bufferEntry-Objekts im Array ablegen
            allEntries[entryNr] = (DataEntry)bufferEntry.clone();
            entryNr++;
            //An die Anfangsposition des nächsten Eintrags springen
            raf.seek(entryNr * entrySize);
        }
        return allEntries;
    }

    public long getSize() throws IOException {
        return raf.length() / entrySize;
    }

    protected void finalize() throws IOException {
        raf.close();
    }

    /**
     * Wird ein Eintrag gelöscht und er war nicht der letzte am Ende,
     * so wird der letzte Eintrag in die entstandene Lücke verschoben und
     * die Datei wird verkürzt.
     */
    private void moveEntry(int sourceIndex, int targetIndex)
    throws IOException {
        //zu verschiebenden Eintrag lesen
        raf.seek(entrySize * sourceIndex);
        byte[] buffer = new byte[entrySize];
        raf.readFully(buffer);
        //an die Zielstelle schreiben
        raf.seek(entrySize * targetIndex);
        raf.write(buffer);
        //alte stelle löschen
        raf.seek(entrySize * sourceIndex);
        byte[] emptyBytes = new byte[entrySize];
        raf.write(emptyBytes);
    }
}