package org.ourfilesystem.simulator;

/*
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.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import org.junit.Test;
import org.ourfilesystem.core.Core;
import org.ourfilesystem.db.DataBaseComImpl;
import org.ourfilesystem.db.LocalFileReference;
import org.ourfilesystem.db.LocalPost;
import org.ourfilesystem.db.Peer;
import org.ourfilesystem.simulator.com.ComConnectionImpl;
import org.ourfilesystem.simulator.com.ComPeerImpl;
import org.ourfilesystem.simulator.db.StorageImpl;
import org.ourfilesystem.simulator.db.TimeImpl;
import org.ourfilesystem.simulator.security.CryptoDataBaseImpl;
import org.ourfilesystem.utilities.Cloning;


public class Instance {
	
	private long CurFileNumber = 0;
	private long CurPostNumber = 0;
	
	private StorageImpl DB;
	private Core Core;
	private Random Random;
	
	private long ID;
	
	public Instance() {
		Random = new Random();
	}
	
	public void createNewFile() {
		try {
			File nf = File.createTempFile("datafile" + CurFileNumber, ".tmp");
			FileOutputStream fos = new FileOutputStream(nf);
			byte b[] = new byte[Long.SIZE/Byte.SIZE];
			ByteBuffer buf = ByteBuffer.wrap(b);
			buf.putLong((0x1000000 * ID) + CurFileNumber);
			fos.write(b);
			fos.close();
			Core.addLocalFile(nf, "User: " + ID + " adding file: " + CurFileNumber);
			CurFileNumber++;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void createPost() {
		Core.addLocalPost("User: " + ID + " added post: " + CurPostNumber, null);
		CurPostNumber++;
	}
	
	public void requestFile() {
		List<LocalPost> plist = DB.getPosts(new Date(0L));
		Iterator<LocalPost> i = plist.iterator();
		List<Object> files = new LinkedList<Object>();
		while (i.hasNext()) {
			LocalPost lp = i.next();
			if (lp.getPost().getFileReferenceDigest() != null && lp.getPost().isPosterHasFile() &&
					(!lp.getPost().getSignedDigest().getPeerIdentifier().equals(
							getMyPeer().getPeerKeysAndIdentity().getSignature().getDigest()
					))) {
				files.add(lp.getPost().getFileReferenceDigest());
			}
		}
		if (files.size() > 0) {
			int idx = (int)(Random.nextFloat() * files.size());
			Object dig = files.get(idx);
			Core.downloadFile(dig);
		}
	}
	
	public Peer getMyPeer() {
		return DB.getMyPeerData();
	}
	
	public void signPeer(Peer p) {
		Core.signPeer(Cloning.clonePeer(p));
	}
	
	public void update() {
		Core.updatePeers();
		Core.updatePosts();
	}
	
	public void setup(long id) {
		ID = id;
		Core = new Core();
		Core.setBusyMultiplier(1);
		Core.setMaxDownloadAttempts(30);
		Core.setMaxPostQuery(100);
		
		DataBaseComImpl dbcom = new DataBaseComImpl(Core);
		ComConnectionImpl com = new ComConnectionImpl(id, Core, dbcom);
		DB = new StorageImpl("dir" + id);
		CryptoDataBaseImpl crypt = new CryptoDataBaseImpl(id);
		TimeImpl time = new TimeImpl();
		UserImpl usr = new UserImpl();
		
		Core.setComDataBase(dbcom);
		Core.setUserDataBase(DB);
		Core.setComConnector(com);
		Core.addEventInterface(usr);
		
		dbcom.setSecurity(crypt);
		dbcom.setStore(DB);
		dbcom.setTime(time);
		dbcom.addEventInterface(usr);
		
		Core.setSecurity(crypt);
		Core.setUserDataBase(DB);
		Core.setTime(time);
		
		Core.setMyLocation(id);
		Core.generateNewKeys();
	}
	
	public static void run(Instance i[]) {
		for (int cnt = 0; cnt < 100; cnt++) {
			for (int cnt2 = 0; cnt2 < i.length; cnt2++) {
				i[cnt2].update();				
			}
			for (int cnt2 = 0; cnt2 < 100; cnt2++) {
				ComPeerImpl.StepProcess();			
			}		
		}
	}
	
	public void dump() {
		System.out.println("\n====================== (" + ID + ") =======================");
		List<Peer> pl = DB.getPeerList();
		Iterator<Peer> i = pl.iterator();
		while (i.hasNext()) {
			Peer p = i.next();
			System.out.println("PEER: " + p.getLocation() + " signed by: " + p.getPeerKeysAndIdentity().getSignature().getPeerIdentifier());
		}
		List<LocalPost> pstl = DB.getPosts(new Date(0L));
		Iterator<LocalPost> psti = pstl.iterator();
		while (psti.hasNext()) {
			LocalPost lp = psti.next();
			System.out.println("POST: " + lp.getLocalDate() + " " + lp.getPost().getPostNumber() +
					" " + lp.getPost().getMessage() + " " + Long.toHexString((Long)lp.getPost().getSignedDigest().getSignature()));
		}
		List<LocalFileReference> fl = DB.getFileReferences(new Date(0L));
		Iterator<LocalFileReference> fi = fl.iterator();
		while (fi.hasNext()) {
			LocalFileReference f = fi.next();
			System.out.println("FILE: " + f.getLocalDate() + " " + f.getFileReference().getFile() + " " + Long.toHexString((Long)f.getFileReference().getUnsignedDigest()));
		}
	}
	
	@Test
	public void main() {
		int NumInst = 4;
		Instance i[] = new Instance[NumInst];
		for (int cnt = 0; cnt < NumInst; cnt++) {
			i[cnt] = new Instance();
			i[cnt].setup(cnt);
		}
		
		Peer p0 = i[0].getMyPeer();
		Peer p1 = i[1].getMyPeer();
		Peer p2 = i[2].getMyPeer();
		Peer p3 = i[3].getMyPeer();
		
		i[0].signPeer(p1);  // p0 will now accept p1
		
		i[0].createPost();
		i[0].createNewFile();
		
		run(i);
		
		i[0].dump();
		i[1].dump();
		
		i[1].signPeer(p0);  // p1 will now accept p0.
		i[1].createNewFile();
		i[1].createPost();
		
		run(i);
		
		i[0].dump();
		i[1].dump();
		
		i[2].signPeer(p0);
		i[0].signPeer(p2);
		
		i[2].createPost();
		i[2].createNewFile();
		
		run(i);
		
		i[0].dump();
		i[1].dump();
		i[2].dump();
		i[3].dump();
		
		i[2].signPeer(p3);
		i[3].signPeer(p2);
		
		i[2].requestFile();
		
		run(i);
		
		i[0].dump();
		i[1].dump();
		i[2].dump();
		i[3].dump();
		
		run(i);
		
		i[0].requestFile();
		i[1].requestFile();
		i[2].requestFile();
		i[3].requestFile();
		
		run(i);
		
		i[0].dump();
		i[1].dump();
		i[2].dump();
		i[3].dump();
		
	}

}
