package org.ourfilesystem.utilities;

/*
OurFileSystem is a peer2peer file sharing program.
Copyright (C) 2012  Robert Gass

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

*/

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;

public abstract class FileIndex {
	
	private HashMap<Object, String> Index;
	private String IndexFile;
	private String DataDir;
	private FileOutputStream FOS;
	
	public FileIndex(String datadir, String indexfile) {
		DataDir = datadir;
		IndexFile = indexfile;
		loadIndex();
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				if (FOS != null) {
					try {
						FOS.close();
					}
					catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		});
	}
	
	private synchronized void loadIndex() {
		Index = new HashMap<Object, String>();
		File f = new File(IndexFile);
		boolean overwrite = true;
		if (f.exists()) {
			long position = 0;
			try {
				FileInputStream fis = new FileInputStream(f);
				while (position < f.length()) {
					Object o = readObject(fis);
					String str = FileUtils.readString(fis);
					if (o != null && str != null) {
						Index.put(o, str);
					}
					position = fis.getChannel().position();
				}
				fis.close();
				overwrite = false;
			}
			catch (Exception e) {
				e.printStackTrace();
			}
			if (overwrite) {
				overWriteIndex();
			}
		}
		else {
			File p = f.getParentFile();
			if (p != null) {
				if (!p.exists()) {
					p.mkdirs();
				}
			}
			try {
				f.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		openFile();
	}
	
	private synchronized void overWriteIndex() {
		try {
			File f = new File(IndexFile);
			FileOutputStream fos = new FileOutputStream(f);
			Iterator<Entry<Object, String>> i = Index.entrySet().iterator();
			while (i.hasNext()) {
				Entry<Object, String> e = i.next();
				writeObject(fos, e.getKey());
				FileUtils.writeString(e.getValue(), fos);
			}
			fos.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private synchronized void openFile() {
		if (FOS != null) {
			try {
				FOS.close();
			}
			catch (Exception e) {
			}
		}
		File f = new File(IndexFile);
		try {
			FOS = new FileOutputStream(f, true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private synchronized void put(Object b, String s) {
		if (FOS == null) {
			openFile();
		}
		try {
			writeObject(FOS, b);
			FileUtils.writeString(s, FOS);
			FOS.flush();
			Index.put(b, s);
		}
		catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("Sorry, we failed to append to the index.");
		}
	}
	
	private synchronized String getStr(Object b) {
		String rs = Index.get(b);
		return rs;
	}
	
	public void put(Object b, File f) {
		if (f.exists()) {
			String ps = f.getPath();
			if (ps.startsWith(DataDir)) {
				ps = ps.substring(DataDir.length());
				put(b, ps);
			}
			else {
				throw new RuntimeException("File is not under the data dir! datadir: " + DataDir + " File: " + f.getPath());
			}
		}
	}
	
	public File get(Object b) {
		File f = null;
		String str = getStr(b);
		if (str != null) {
			f = new File(DataDir + File.separator + str);
			if (!f.exists()) {
				f = null;
				System.out.println("File does not exist! " + DataDir + " :: " + str);
			}
		}
		return f;
	}

	public synchronized Set<Object> list() {
		HashSet<Object> s = new HashSet<Object>();
		s.addAll(Index.keySet());
		return s;
	}
	
	public synchronized void close() {
		if (FOS != null) {
			try {
				FOS.close();
			}
			catch (Exception e) {
				e.printStackTrace();
			}
			FOS = null;
		}
	}
	
	public synchronized int getSize() {
		return Index.size();
	}
	
	public abstract Object readObject(InputStream is) throws IOException;
	public abstract void writeObject(OutputStream os, Object o) throws IOException;
	
}
