package org.ourfilesystem.simulator.db;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import org.ourfilesystem.db.FileReference;
import org.ourfilesystem.db.LocalFileReference;
import org.ourfilesystem.db.LocalPost;
import org.ourfilesystem.db.Peer;
import org.ourfilesystem.db.StorageInterface;
import org.ourfilesystem.security.KeySet;
import org.ourfilesystem.utilities.FileUtils;

public class StorageImpl implements StorageInterface {
	
	public File BaseDir;
	
	public Peer MyPeerData;
	public KeySet KeySet;
	public ConcurrentHashMap<Long, Peer> Peers;
	public ConcurrentHashMap<Long, ConcurrentHashMap<Long, LocalPost>> Posts;
	public ConcurrentHashMap<Long, Long> MaxPostValue;
	public ConcurrentHashMap<Long, List<LocalPost>> File2Post;
	public ConcurrentHashMap<Long, LocalFileReference> Files;
	
	private long PostValue;
	
	public StorageImpl(String base) {
		BaseDir = new File(base);
		BaseDir.mkdirs();
		PostValue = 0;
		Peers = new ConcurrentHashMap<Long, Peer>();
		Posts = new ConcurrentHashMap<Long, ConcurrentHashMap<Long, LocalPost>>();
		MaxPostValue = new ConcurrentHashMap<Long, Long>();
		File2Post = new ConcurrentHashMap<Long, List<LocalPost>>();
		Files = new ConcurrentHashMap<Long, LocalFileReference>();
	}

	@Override
	public void saveMyPeerData(Peer peer) {
		MyPeerData = peer;
	}

	@Override
	public Peer getMyPeerData() {
		return MyPeerData;
	}

	@Override
	public void saveMyKeySet(KeySet keyset) {
		KeySet = keyset;
	}

	@Override
	public KeySet getMyKeySet() {
		return KeySet;
	}

	@Override
	public void savePeer(Peer peer) {
		Peers.put((Long)peer.getPeerKeysAndIdentity().getSignature().getDigest(), peer);
	}

	@Override
	public Peer getPeer(Object peerid) {
		return Peers.get((Long)peerid);
	}

	@Override
	public List<Peer> getPeerList() {
		List<Peer> pr = new LinkedList<Peer>();
		pr.addAll(Peers.values());
		return pr;
	}

	@Override
	public void savePost(LocalPost post) {
		ConcurrentHashMap<Long, LocalPost> pp = Posts.get(post.getPost().getSignedDigest().getPeerIdentifier());
		if (pp == null) {
			pp = new ConcurrentHashMap<Long, LocalPost>();
			Posts.put((Long)post.getPost().getSignedDigest().getPeerIdentifier(), pp);
		}
		/*
		 * Test if we already have the post or not, to see if we're wasting bandwidth.
		 */
		LocalPost tp = pp.get(post.getPost().getPostNumber());
		if (tp != null) {
			System.out.println("ERROR: The same post was received more than once! I am: " + MyPeerData.getPeerKeysAndIdentity().getSignature().getDigest() + " received the same post from peer: " + post.getPost().getSignedDigest().getPeerIdentifier() + " post number: " + post.getPost().getPostNumber());
		}
		pp.put(post.getPost().getPostNumber(), post);
		Long val = MaxPostValue.get(post.getPost().getSignedDigest().getPeerIdentifier());
		if (val == null) {
			val = post.getPost().getPostNumber();
			MaxPostValue.put((Long)post.getPost().getSignedDigest().getPeerIdentifier(), val);
		}
		if (val < post.getPost().getPostNumber()) {
			val = post.getPost().getPostNumber();
			MaxPostValue.put((Long)post.getPost().getSignedDigest().getPeerIdentifier(), val);
		}
		if (post.getPost().getFileReferenceDigest() != null) {
			List<LocalPost> pl = File2Post.get((Long)post.getPost().getFileReferenceDigest());
			if (pl == null) {
				pl = new LinkedList<LocalPost>();
				File2Post.put((Long)post.getPost().getFileReferenceDigest(), pl);
			}
			synchronized (pl) {
				pl.add(post);
			}
		}
	}

	@Override
	public long reserveNextPostNumber() {
		PostValue++;
		return PostValue;
	}

	@Override
	public List<LocalPost> getPeerPosts(Peer p, int page, int pagesize) {
		ConcurrentHashMap<Long, LocalPost> psts = Posts.get(p.getPeerKeysAndIdentity().getSignature().getDigest());
		List<LocalPost> rl = new LinkedList<LocalPost>();
		if (psts != null) {
			for (long cnt = (page*pagesize); cnt < ((page+1)*pagesize); cnt++) {
				LocalPost pst = psts.get(cnt);
				if (pst != null) {
					rl.add(pst);
				}
			}			
		}
		return rl;
	}

	@Override
	public List<LocalPost> getPeerPosts(Peer p, long start, long end) {
		ConcurrentHashMap<Long, LocalPost> psts = Posts.get(p.getPeerKeysAndIdentity().getSignature().getDigest());
		Long maxval = MaxPostValue.get(p.getPeerKeysAndIdentity().getSignature().getDigest());
		List<LocalPost> rl = new LinkedList<LocalPost>();
		if (psts != null) {
			for (long cnt = start; cnt <= end && cnt <= maxval; cnt++) {
				LocalPost pst = psts.get(cnt);
				if (pst != null) {
					rl.add(pst);
				}
			}			
		}
		return rl;
	}

	@Override
	public List<LocalPost> getFilePosts(Object dig, int page, int pagesize) {
		LinkedList<LocalPost> rl = new LinkedList<LocalPost>();
		List<LocalPost> pl = File2Post.get((Long)dig);
		if (pl != null) {
			synchronized (pl) {
				for (int idx = (page*pagesize); idx < ((page+1)*pagesize); idx++) {
					if (idx < pl.size()) {
						rl.add(pl.get(idx));
					}
				}
			}
		}
		return rl;
	}

	@Override
	public List<LocalPost> getPosts(Date fromdate) {
		List<LocalPost> rl = new LinkedList<LocalPost>();
		List<Long> peerlist = new LinkedList<Long>();
		peerlist.addAll(Posts.keySet());
		Iterator<Long> i = peerlist.iterator();
		while (i.hasNext()) {
			Long key = i.next();
			ConcurrentHashMap<Long, LocalPost> ppl = Posts.get(key);
			boolean done = false;
			for (long pn = MaxPostValue.get(key); pn >= 0 && !done; pn--) {
				LocalPost lp = ppl.get(pn);
				if (lp != null) {
					if (lp.getLocalDate().compareTo(fromdate) >= 0) {
						rl.add(lp);
					}
					else {
						done = true;
					}
				}
			}
		}
		return rl;
	}

	@Override
	public void saveFile(LocalFileReference ref) {
		try {
			File fcopy = File.createTempFile("reference", ".dat");
			FileUtils.copyFile(ref.getFileReference().getFile(), fcopy, false);
			FileReference fr = new FileReference();
			fr.setFile(fcopy);
			fr.setUnsignedDigest(ref.getFileReference().getUnsignedDigest());
			ref.setFileReference(fr);
			Files.put((Long)fr.getUnsignedDigest(), ref);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public LocalFileReference getFileReference(Object dig) {
		return Files.get(dig);
	}

	@Override
	public List<LocalFileReference> getFileReferences(int page, int pagesize) {
		List<LocalFileReference> tlist = new LinkedList<LocalFileReference>();
		tlist.addAll(Files.values());
		List<LocalFileReference> rl = new LinkedList<LocalFileReference>();
		Iterator<LocalFileReference> i = tlist.iterator();
		for (int cnt = 0; cnt < (page*pagesize) && i.hasNext(); cnt++) {
			i.next();
		}
		for (int cnt = 0; cnt < pagesize && i.hasNext(); cnt++) {
			rl.add(i.next());
		}
		return rl;
	}

	@Override
	public List<LocalFileReference> getFileReferences(Date fromdate) {
		List<LocalFileReference> tlist = new LinkedList<LocalFileReference>();
		tlist.addAll(Files.values());
		List<LocalFileReference> rl = new LinkedList<LocalFileReference>();
		Iterator<LocalFileReference> i = tlist.iterator();
		while (i.hasNext()) {
			LocalFileReference lf = i.next();
			if (lf.getLocalDate().compareTo(fromdate) >= 0) {
				rl.add(lf);
			}
		}
		return rl;
	}
	
	@Override
	public void close() {
		
	}

}
