package org.ourfilesystem.test.com;

/*
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 static org.junit.Assert.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.LinkedList;

import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.junit.Test;
import org.ourfilesystem.com.ComConnectionImpl;
import org.ourfilesystem.com.ComPeerInterface;
import org.ourfilesystem.com.SimpleConnector;
import org.ourfilesystem.com.SimpleServerSocket;
import org.ourfilesystem.db.FileReference;
import org.ourfilesystem.db.LocalFileReference;
import org.ourfilesystem.db.LocalPost;
import org.ourfilesystem.db.Peer;
import org.ourfilesystem.db.Post;
import org.ourfilesystem.security.KeySet;
import org.ourfilesystem.security.PublicKeySet;
import org.ourfilesystem.security.PublicKeySetSigned;
import org.ourfilesystem.security.SecurityTools;
import org.ourfilesystem.security.SignedDigest;
import org.ourfilesystem.utilities.BBytes;

public class ComTest {
	
	public RSAKeyParameters getPublicKey(long val) {
		RSAKeyParameters pub = new RSAKeyParameters(false,                 //boolean isPrivate,            
				BigInteger.valueOf(val),   //java.math.BigInteger modulus, 
				BigInteger.valueOf(val+1));  //java.math.BigInteger exponent 
		return pub;
	}
	
	public RSAPrivateCrtKeyParameters getPrivateKey(long val) {
		RSAPrivateCrtKeyParameters priv = new RSAPrivateCrtKeyParameters(BigInteger.valueOf(val),   //java.math.BigInteger modulus,          
																	 BigInteger.valueOf(val+1),    //java.math.BigInteger publicExponent,   
																	 BigInteger.valueOf(val+2),   //java.math.BigInteger privateExponent,  
																	 BigInteger.valueOf(val+3),     //java.math.BigInteger p,                
																	 BigInteger.valueOf(val+4),     //java.math.BigInteger q,                
																	 BigInteger.valueOf(val+5),    //java.math.BigInteger dP,               
																	 BigInteger.valueOf(val+6),    //java.math.BigInteger dQ,               
																	 BigInteger.valueOf(val+7));   //java.math.BigInteger qInv              
		return priv;
	}
	
	public BBytes getBBytes(long val) {
		byte b[] = new byte[Long.SIZE/Byte.SIZE];
		ByteBuffer buf = ByteBuffer.wrap(b);
		buf.putLong(val);
		return new BBytes(b);
	}
	
	public SignedDigest getSignedDigest(long v) {
		SignedDigest sd = new SignedDigest();
		sd.setDigest(getBBytes(400+v));
		sd.setPeerIdentifier(getBBytes(500+v));
		sd.setSignature(getBBytes(600+v));
		return sd;
	}
	
	public PublicKeySetSigned getPublicKeySetSigned(long v) {
		PublicKeySetSigned pub = new PublicKeySetSigned();
		pub.setPublicEncryptionKey(getPublicKey(v+200));
		pub.setPublicSigningKey(getPublicKey(v+300));
		pub.setSignature(getSignedDigest(v));
		return pub;
	}
	
	public Peer getPeer(long v) {
		Peer p = new Peer();
		p.setLocation("overhere" + v);
		p.setPeerKeysAndIdentity(getPublicKeySetSigned(v));	
		p.setLocationSignature(getSignedDigest(v));
		return p;
	}
	
	public Post getPost(long v, BBytes peerid, BBytes fileref, boolean has) {
		Post p = new Post();
		p.setMessage(getFile(v));
		p.setPostNumber(v);
		SignedDigest dig = new SignedDigest();
		dig.setDigest(getBBytes(750+v));
		dig.setPeerIdentifier(peerid);
		dig.setSignature(getBBytes(650+v));
		if (fileref != null) {
			p.setFileReferenceDigest(fileref);
			p.setPosterHasFile(has);
		}
		p.setSignedDigest(dig);
		return p;
	}
	
	public File getFile(long id) {
		File tf = null;
		try {
			tf = File.createTempFile("blahblah", ".dat");
			FileOutputStream fos = new FileOutputStream(tf);
			PrintWriter pw = new PrintWriter(fos);
			pw.println(id);
			pw.close();
		} catch (Exception e) {
			e.printStackTrace();
			fail("couldn't write test file.");
		}
		return tf;
	}
	
	public FileReference getFileRef(long id) {
		FileReference ref = new FileReference();
		ref.setFile(getFile(id));
		ref.setUnsignedDigest(getBBytes(id));
		return ref;
	}
	
	public LocalPost getLocalPost(long id, BBytes peerid, BBytes fileref, boolean has) {
		LocalPost lp = new LocalPost();
		lp.setPost(getPost(id, peerid, fileref, has));
		lp.setLocalDate(new Date(id));
		return lp;
	}
	
	public LocalFileReference getLocalFileReference(long id) {
		LocalFileReference lf = new LocalFileReference();
		lf.setFileReference(getFileRef(id));
		lf.setLocalDate(new Date(id));
		return lf;
	}
	
	public void deleteDir(String str) {
		File f = new File(str);
		deleteDir(f);
	}
	
	public void deleteDir(File f) {
		if (f.exists()) {
			if (f.isDirectory()) {
				File fl[] = f.listFiles();
				for (int c = 0; c < fl.length; c++) {
					deleteDir(fl[c]);
				}
			}
			f.delete();
		}
	}
	
	public static boolean RSAPrivateCrtKeyParametersEquals(Object o0, Object o1) {
		RSAPrivateCrtKeyParameters p0 = (RSAPrivateCrtKeyParameters)o0;
		RSAPrivateCrtKeyParameters p1 = (RSAPrivateCrtKeyParameters)o1;
		if (!p0.getDP().equals(p1.getDP())) {
			return false;
		}
		if (!p0.getDQ().equals(p1.getDQ())) {
			return false;
		}
		if (!p0.getExponent().equals(p1.getExponent())) {
			return false;
		}
		if (!p0.getModulus().equals(p1.getModulus())) {
			return false;
		}
		if (!p0.getP().equals(p1.getP())) {
			return false;
		}
		if (!p0.getPublicExponent().equals(p1.getPublicExponent())) {
			return false;
		}
		if (!p0.getQ().equals(p1.getQ())) {
			return false;
		}
		if (!p0.getQInv().equals(p1.getQInv())) {
			return false;
		}
		return true;
	}
	
	public static boolean KeySetEquals(KeySet k0, KeySet k1) {
		if (!RSAPrivateCrtKeyParametersEquals(k0.getPrivateEncryptionKey(), k1.getPrivateEncryptionKey())) {
			return false;
		}
		if (!RSAPrivateCrtKeyParametersEquals(k0.getPrivateSigningKey(), k1.getPrivateSigningKey())) {
			return false;
		}
		PublicKeySet p0 = k0.getPublicKeySet();
		PublicKeySet p1 = k1.getPublicKeySet();
		if (!RSAKeyParametersEquals(p0.getPublicEncryptionKey(), p1.getPublicEncryptionKey())) {
			return false;
		}
		if (!RSAKeyParametersEquals(p0.getPublicSigningKey(), p1.getPublicSigningKey())) {
			return false;
		}
		return true;
	}
	
	public static boolean RSAKeyParametersEquals(Object o0, Object o1) {
		RSAKeyParameters p0 = (RSAKeyParameters)o0;
		RSAKeyParameters p1 = (RSAKeyParameters)o1;
		if (!p0.getExponent().equals(p1.getExponent())) { return false; }
		if (!p0.getModulus().equals(p1.getModulus())) { return false; }
		return true;
	}
	
	public static boolean signedDigEquals(SignedDigest p0sig, SignedDigest p1sig) {
		if (!p0sig.getPeerIdentifier().equals(p1sig.getPeerIdentifier())) { System.out.println(">6"); return false; }
		if (!p0sig.getDigest().equals(p1sig.getDigest())) { System.out.println(">7"); return false; }
		if (!p0sig.getSignature().equals(p1sig.getSignature())) { System.out.println(">8"); return false; }
		return true;
	}
	
	public static boolean peerEquals(Peer p0, Peer p1) {
		if (p0.getLocation() != null) {
			if (p1.getLocation() != null) {
				if (!p0.getLocation().equals(p1.getLocation())) { System.out.println(">1"); return false; }
			}
			else {
				System.out.println(">2"); 
				return false;
			}
		}
		else if (p1.getLocation() != null) { System.out.println(">3");  return false; }
		if (p0.getNickname() != null) {
			if (p1.getNickname() != null) {
				if (!p0.getNickname().equals(p1.getNickname())) { System.out.println(">1.5"); return false; }
			} else { return false; }
		} else if (p1.getNickname() != null) { return false; }
		if (!RSAKeyParametersEquals(p0.getPeerKeysAndIdentity().getPublicEncryptionKey(), 
				p1.getPeerKeysAndIdentity().getPublicEncryptionKey())) {
			System.out.println(">4");
			return false;
		}
		if (!RSAKeyParametersEquals(p0.getPeerKeysAndIdentity().getPublicSigningKey(),
				p1.getPeerKeysAndIdentity().getPublicSigningKey())) {
			System.out.println(">5"); 
			return false;
		}
		SignedDigest p0sig = p0.getPeerKeysAndIdentity().getSignature();
		SignedDigest p1sig = p1.getPeerKeysAndIdentity().getSignature();
		if (!signedDigEquals(p0sig, p1sig)) { return false; }
		if (p0.getUpdateCount() != p1.getUpdateCount()) { System.out.println(">9"); return false; }
		return true;
	}
	
	public static boolean fileRefEquals(LocalFileReference lf0, LocalFileReference lf1) {
		FileReference f0 = lf0.getFileReference();
		FileReference f1 = lf1.getFileReference();
		try {
			BBytes d0 = SecurityTools.digestFile(f0.getFile());
			BBytes d1 = SecurityTools.digestFile(f1.getFile());
			if (!d0.equals(d1)) {
				return false;
			}
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		if (!f0.getUnsignedDigest().equals(f1.getUnsignedDigest())) {
			return false;
		}
		if (!lf0.getLocalDate().equals(lf1.getLocalDate())) {
			return false;
		}
		return true;
	}
	
	public static boolean postEquals(LocalPost lp0, LocalPost lp1) {
		if (!lp0.getLocalDate().equals(lp1.getLocalDate())) { return false; }
		return postEquals(lp0.getPost(), lp1.getPost());
	}
		
	public static boolean postEquals(Post p0, Post p1) {
		if (p0.getFileReferenceDigest() != null) {
			if (p1.getFileReferenceDigest() != null) {
				if (!p0.getFileReferenceDigest().equals(p1.getFileReferenceDigest())) {
					return false;
				}
			}
			else {
				return false;
			}
		}
		else if (p1.getFileReferenceDigest() != null) {
			return false;
		}
		if (p0.getPostNumber() != p1.getPostNumber()) {
			return false;
		}
		File f0 = (File) p0.getMessage();
		File f1 = (File) p1.getMessage();
		try {
			BBytes d0 = SecurityTools.digestFile(f0);
			BBytes d1 = SecurityTools.digestFile(f1);
			if (!d0.equals(d1)) { return false; }
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		if (!signedDigEquals(p0.getSignedDigest(), p1.getSignedDigest())) {
			return false;
		}
		return true;
	}
	

	@Test
	public void Test00() throws IOException {
		KeySet ks = new KeySet();
		ks.setPrivateEncryptionKey(getPrivateKey(10));
		ks.setPrivateSigningKey(getPrivateKey(100));
		ks.setPublicKeySet(getPublicKeySetSigned(5));

		ComTestFixture f0 = new ComTestFixture();
		ComTestFixture f1 = new ComTestFixture();
		
		
		Peer p1 = getPeer(11);
		Peer p2 = getPeer(12);
		Peer p3 = getPeer(13);
		Peer p4 = getPeer(14);
		Peer p5 = getPeer(15);

		Peer p0 = getPeer(10);
		FileReference fr0 = getFileRef(101);
		
		Post pt0 = getPost(0x200, (BBytes) p0.getPeerKeysAndIdentity().getSignature().getDigest(), null, false);
		Post pt1 = getPost(0x201, (BBytes) p0.getPeerKeysAndIdentity().getSignature().getDigest(), null, false);
		Post pt2 = getPost(0x202, (BBytes) p0.getPeerKeysAndIdentity().getSignature().getDigest(), null, false);
		Post pt3 = getPost(0x203, (BBytes) p0.getPeerKeysAndIdentity().getSignature().getDigest(), null, false);

		f0.MyPeer = p0;
		f0.MyKeySet = ks;
		f0.Peers.put(p0.getPeerKeysAndIdentity().getSignature().getDigest(), p0);
		//f0.Peers.put(p1.getPeerKeysAndIdentity().getSignature().getDigest(), p1);
		f0.Peers.put(p2.getPeerKeysAndIdentity().getSignature().getDigest(), p2);
		f0.Peers.put(p3.getPeerKeysAndIdentity().getSignature().getDigest(), p3);
		f0.Peers.put(p4.getPeerKeysAndIdentity().getSignature().getDigest(), p4);
		f0.Peers.put(p5.getPeerKeysAndIdentity().getSignature().getDigest(), p5);
		f0.ProcessFileReturn = fr0.getUnsignedDigest();
		f0.ProcessPostReturn = true;
		f0.PostReturn = new LinkedList<Post>();
		f0.PostReturn.add(pt0);
		f0.PostReturn.add(pt1);
		f0.PostReturn.add(pt2);
		f0.PostReturn.add(pt3);
		f0.PeerReturn = new LinkedList<Peer>();
		f0.PeerReturn.add(p1);
		f0.PeerReturn.add(p2);
		f0.PeerReturn.add(p3);
		f0.PeerReturn.add(p4);
		f0.ReturnFile = fr0.getFile();
		f0.Challenge = this.getBBytes(0x919);
		f0.Verified = true;
		SignedDigest sg0 = new SignedDigest();
		sg0.setDigest(f0.Challenge);
		sg0.setPeerIdentifier(null);
		sg0.setSignature(f0.Challenge);
		f0.SignedChallenge = sg0;

		f1.Challenge = this.getBBytes(0x919);
		f1.Verified = true;
		SignedDigest sg1 = new SignedDigest();
		sg1.setDigest(f1.Challenge);
		sg1.setPeerIdentifier(null);
		sg1.setSignature(f1.Challenge);
		f1.SignedChallenge = sg1;
		f1.MyPeer = p1;
		f1.MyKeySet = ks;
		f1.Peers.put(p0.getPeerKeysAndIdentity().getSignature().getDigest(), p0);

		SimpleServerSocket server0 = new SimpleServerSocket(new ServerSocket(9899));
		SimpleConnector con0 = new SimpleConnector(false, "", 0);
		new ComConnectionImpl(f0, f0, f0, f0, "tmp0", server0, con0, false);
		p0.setLocation("localhost:9899");
		
		SimpleServerSocket server1 = new SimpleServerSocket(new ServerSocket(9900));
		SimpleConnector con1 = new SimpleConnector(false, "", 0);
		ComConnectionImpl comcon1 = new ComConnectionImpl(f1, f1, f1, f1, "tmp1", server1, con1, false);
		p1.setLocation("localhost:9900");

		/*
		 * First try to connect when p0 does not know p1.
		 * Make sure the signature is requested and the connection is closed.
		 */
		ComPeerInterface c0 = comcon1.getComPeerInterface(p0);		
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		if (c0 != null) {
			assertEquals(c0.isOk(), false);
		}
		
		assertTrue(peerEquals(f0.SignatureRequest, p1));

		/*
		 * Now add p1 to p0's database and make sure we connect ok.
		 * Then request peers. 
		 */
		f0.Peers.put(p1.getPeerKeysAndIdentity().getSignature().getDigest(), p1);
		
		c0 = comcon1.getComPeerInterface(p0);		

		try {
			Thread.sleep(1000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		assertEquals(c0.getPendingRequests(), 0);
		
		assertTrue(c0.isOk());
		Peer rp0 = c0.getPeer();
		
		System.out.println("BLAH: " + rp0.getIntroduction());
		System.out.println("BLAH: " + rp0.getUpdateCount());
		System.out.println("BLAH: " + rp0.getLocation());
		System.out.println("BLAH: " + rp0.getNickname());
		System.out.println("BLAH: " + rp0.getPeerKeysAndIdentity().getSignature().getDigest());
		
		assertTrue(peerEquals(p0, rp0));
		
		c0.requestPeers();

		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		assertEquals(f1.ReceivedPeers.size(), 4);
		
		c0.requtestPosts(p0.getPeerKeysAndIdentity().getSignature().getDigest(), 0x201, 0x202);
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		assertEquals(f1.ReceivedPosts.size(), 4);
		
		c0.requestsFile(fr0.getUnsignedDigest());
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		try {
			BBytes d0 = SecurityTools.digestFile(f1.ReceivedFile);
			BBytes d1 = SecurityTools.digestFile(fr0.getFile());
		
			assertEquals(d0, d1);
		}
		catch (Exception e) {
			e.printStackTrace();
			fail();
		}
		
		c0.sendPost(pt2);
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		assertEquals(f0.ReceivedPosts.size(), 1);
		assertTrue(postEquals(f0.ReceivedPosts.get(0), pt2));
		
		f0.ReturnFile = null;
		
		c0.requestsFile(fr0.getUnsignedDigest());
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		assertEquals(f1.FileFailed.ConPeer, p0.getPeerKeysAndIdentity().getSignature().getDigest());
		assertEquals(f1.FileFailed.FileDig, fr0.getUnsignedDigest());
	}

}
