/*
 * $Id: HashStoreKeeper.java,v 1.8 2007/11/05 08:53:43 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: HashStoreKeeper.java,v $
 * Revision 1.8  2007/11/05 08:53:43  marbobo
 * + Bugfix (Statt List werden HashSet benutzt)
 *
 * Revision 1.7  2004/11/10 15:02:49  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.6  2004/01/16 14:27:28  koppy
 * Bugfix im IDIterator!!
 *
 * Revision 1.5  2003/11/21 14:47:36  koppy
 * Um die Möglichkeit, "Archivleichen" zu entfernen, erweitert
 *
 * Revision 1.4  2003/11/14 15:37:29  jonyp
 * *** empty log message ***
 *
 * Revision 1.3  2003/03/12 00:34:20  koppy
 * Möglichkeit den StoreKeeper dynamisch zu setzen
 *
 * Revision 1.2  2002/11/12 15:33:02  koppy
 * Behandlung von Streams hinzugefügt
 *
 * Revision 1.1  2002/10/21 14:36:14  bigmac
 * *** empty log message ***
 *
 * Revision 1.1  2002/04/05 15:35:21  koppy
 * initial revision
 *
 */

package vkc.server.archive;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;

import vkc.misc.VKCLogManager;
import vkc.util.FileUtils;



/**
 * Die Klasse implementiert einen (Daten)Lagerverwalter,
 * der in Abhängigkeit des Hashwertes der ID die Daten auf eine
 * vorher festgelegte (und anschliessend nicht änderbare) Anzahl von
 * Verzeichnissen verteilt
 * 
 * @author koppy
 */
public class HashStoreKeeper implements StoreKeeper {
    private static final boolean debug = true;
    private File storageDir = null;
    private int DIR_COUNT = 1000; // default
	
	private int modCount = 0;
	
    /**
     * Konstruktor
     */
    public HashStoreKeeper() {
    }

    /**
     * Konstruktor
     *
     * @param storageDir
     */
    public HashStoreKeeper(File storageDir) {
        this.setStorageDir(storageDir);
    }

    /**
     * Konstruktor
     *
     * @param dirCount
     */
    public HashStoreKeeper(int dirCount) {
        this.DIR_COUNT = dirCount;
    }

    /**
     * Setzt das Verzeichnis für diesen Datenverwalter
     *
     * @param storageDir
     */
    public void setStorageDir(File storageDir) {
        this.storageDir = storageDir;
    }

    /**
     * Alles Notwendige erstellen / initialisieren
     *
     */
    public void init() throws IOException {
        if (storageDir == null) {
            throw new IOException("StorageDirUrl not set!");
        }
		
		int count = storageDir.listFiles().length; 
        if ( count == DIR_COUNT)
            return;

		if( count != 0 )
			throw new IllegalStateException( "Die Anzahl der Verzeichnisse darf\n"
				+ "bei einem bestehenden Datenlager nicht modifiziert werden!\n"
				+ "Um dieses Datenlager wiederzuverwenden, \n"
				+ "muss die Anzahl der Verzeichnisse auf " +count+"gesetzt werden" );
			
        if (debug)
            System.out.println("Verzeichnisse werden angelegt...");

        // Verzeichnisse existieren noch nicht ==>> anlegen !
        for (int i = 0; i < DIR_COUNT; i++)
            new File(storageDir, String.valueOf(i)).mkdir();
    }

    /**
     * Bindet Byte-Array an Datei mit Namen id
     *
     * @param data - Byte-Array
     * @param id - Dateiname
     */
    public void insert(byte[] data, String id) throws IOException {
        FileUtils.saveByteArray(new File(getTargetDir(id), id), data, null, true);
        modCount++;
    }

    /**
     * Loescht die unter id gespeicherten Daten
     *
     * @param id - Dateiname
     */
    public void delete(String id) throws IOException {
        new File(getTargetDir(id), id).delete();
        modCount++;
    }

    /**
     * Liefert das Verzeichnis, in welches id gehoert
     *
     * @param id - Dateiname
     */
    public File getTargetDir(String id) {
        return new File(storageDir,
                        String.valueOf(Math.abs(id.hashCode()) % DIR_COUNT));
    }

    /**
     * Sucht die unter id gespeicherten Daten
     *
     * @param id - Dateiname
     *
     * @return falls GEFUNDEN, Datei mit Namen id sonst Verzeichnis, in welches
     *         Datei mit Namen id gehört
     */
    public File search(String id) throws IOException {
        File targetDir = getTargetDir(id);

        File dataFile = new File(targetDir, id);

        if (dataFile.exists())

            return dataFile;

        return targetDir;
    }
    
	/**
	 * @see vkc.server.archive.StoreKeeper#iterator()
	 */
	public Iterator iterator() {	
		return new IdIterator();
	}    
    
	/**
	 * Die Klasse implementiert einen Iterator für
	 * alle verfügbaren IDs
	 * 
	 * @author koppy
	 */
	private class IdIterator implements Iterator {
						 
			String current = null;
			int expectedModCount;
			int dirIndex = 0;
			int fileIndex = 0;			
			File [] files = null;
			File [] nextFiles = null;
			
			IdIterator() {					
							
				expectedModCount = modCount;
				if( storageDir != null && DIR_COUNT > 0 ){
					dirIndex = 0;
					fileIndex = 0;						
					files = new File(storageDir, ""+dirIndex).listFiles();					
				}					
				 			
			}			

			public boolean hasNext() {				
													
				if( hasNextInCurrentDir() )
					return true;					
					
				while( dirIndex < DIR_COUNT ){
					dirIndex++;
					files = new File(storageDir, String.valueOf(dirIndex)).listFiles();
					fileIndex = 0; 
					if( hasNextInCurrentDir() )
						return true;
				}
				
				return false;				
			}
			
			
			private boolean hasNextInCurrentDir(){
				return( files != null && fileIndex < files.length );
			}

			public Object next() { 
				if (modCount != expectedModCount)
					VKCLogManager.infoLog( this, "ConcurrentModificationException not thrown.");
						
				
				if ( !hasNextInCurrentDir() ) 
					throw new NoSuchElementException();
                				                
				current = files[fileIndex].getName();
				fileIndex++;
				
				while( !hasNextInCurrentDir() && dirIndex < DIR_COUNT ){
				
					dirIndex++;
					fileIndex = 0;
					if( dirIndex == DIR_COUNT -1 ){
						files = null;						
					}else{						
						files = new File(storageDir, ""+dirIndex).listFiles();						
					}
				}
				
				return current;
			}

			public void remove() {
				if (current == null)
					throw new IllegalStateException();
				if (modCount != expectedModCount)
					VKCLogManager.infoLog( this, "ConcurrentModificationException not thrown.");					
								
				try {
					HashStoreKeeper.this.delete( current );
					expectedModCount = modCount;
					current = null;
				} catch (IOException e) {					
					VKCLogManager.errorLog(this, "", e.getMessage(),e);
				}				
			}

		}
}
