package org.ourfilesystem.test.db;

/*
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.io.PrintWriter;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import static org.junit.Assert.*;

import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.junit.Test;
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.db.StorageImpl2;
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;
import org.ourfilesystem.utilities.FileUtils;

public class StorageImplTest {

	public static 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 static 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 static 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 static 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 static 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 static Peer getPeer(long v) {
		Peer p = new Peer();
		p.setLocation("overhere" + v);
		p.setPeerKeysAndIdentity(getPublicKeySetSigned(v));	
		p.setLocationSignature(getSignedDigest(v));
		return p;
	}
	
	public static 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 static 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 static FileReference getFileRef(long id) {
		FileReference ref = new FileReference();
		ref.setFile(getFile(id));
		ref.setUnsignedDigest(getBBytes(id));
		return ref;
	}
	
	public static 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 static 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);
		FileUtils.deleteDir(f);
	}
	
	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; }
		Post p0 = lp0.getPost();
		Post p1 = lp1.getPost();
		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 test() {
		deleteDir("tmp");
		
		KeySet ks = new KeySet();
		ks.setPrivateEncryptionKey(getPrivateKey(10));
		ks.setPrivateSigningKey(getPrivateKey(100));
		ks.setPublicKeySet(getPublicKeySetSigned(5));
		
		StorageImpl2 s = new StorageImpl2("tmp");
		s.saveMyKeySet(ks);
		
		Peer p = getPeer(10);
		p.setLocationSignature(getSignedDigest(10));
		
		s.savePeer(p);
		s.saveMyPeerData(p);
		
		LocalPost lp = getLocalPost(12L, (BBytes)p.getPeerKeysAndIdentity().getSignature().getDigest(), null, false);
		s.savePost(lp);
		
		s.close();
		
		StorageImpl2 s2 = new StorageImpl2("tmp");
		Peer p2 = s2.getPeer(p.getPeerKeysAndIdentity().getSignature().getDigest());
		assertTrue(peerEquals(p, p2));
		
		List<LocalPost> lp2lst = s2.getPeerPosts(p2, 0, 20);
		assertEquals(1, lp2lst.size());
		
		LocalPost lp2 = lp2lst.get(0);
		
		assertTrue(postEquals(lp, lp2));
		
		LocalFileReference lf0 = getLocalFileReference(73L);
		s2.saveFile(lf0);
		
		LocalFileReference lf1 = getLocalFileReference(75L);
		Peer p13 = getPeer(13L);
		s2.savePeer(p13);
		
		LocalPost lp99 = getLocalPost(13L, (BBytes)p13.getPeerKeysAndIdentity().getSignature().getDigest(), 
				(BBytes)lf1.getFileReference().getUnsignedDigest(), false);
		s2.savePost(lp99);
		
		s2.saveFile(lf1);
		
		LocalPost lp0 = getLocalPost(14L, (BBytes)p.getPeerKeysAndIdentity().getSignature().getDigest(), 
				(BBytes)lf0.getFileReference().getUnsignedDigest(), true);
		
		s2.savePost(lp0);
		
		s2.close();
		
		StorageImpl2 s3 = new StorageImpl2("tmp");
		List<LocalPost> lp3lst = s3.getPosts(new Date(14L));
		
		assertEquals(lp3lst.size(), 1);
		
		LocalPost lp3 = lp3lst.get(0);
		
		assertTrue(postEquals(lp0, lp3));
		
		LocalFileReference lf3 = s3.getFileReference(lp3.getPost().getFileReferenceDigest());
		
		assertTrue(fileRefEquals(lf0, lf3));
		
		lp3lst = s3.getFilePosts(lf1.getFileReference().getUnsignedDigest(), 0, 10);
		
		assertEquals(lp3lst.size(), 1);
		
		lp3 = lp3lst.get(0);
		
		assertTrue(postEquals(lp3, lp99));
		
		List<LocalFileReference> lf3list = s3.getFileReferences(new Date(73L));
		
		assertEquals(lf3list.size(), 2);
		
		boolean onefound = false;
		boolean twofound = false;
		
		Iterator<LocalFileReference> i = lf3list.iterator();
		while (i.hasNext()) {
			LocalFileReference lf = i.next();
			if (fileRefEquals(lf, lf0)) {
				onefound = true;
			}
			if (fileRefEquals(lf, lf1)) {
				twofound = true;
			}
		}
		assertTrue(onefound);
		assertTrue(twofound);
		
		List<LocalPost> psts = s3.getPeerPosts(p13, 0L, 100L);
		s3.close();
		
		assertEquals(psts.size(), 1);
		
		LocalPost lp99_2 = psts.get(0);
		assertTrue(postEquals(lp99, lp99_2));
		
		StorageImpl2 s4 = new StorageImpl2("tmp");
		
		List<LocalFileReference> allrefs = s4.getFileReferences(0, 100);
		assertEquals(allrefs.size(), 2);
		
		onefound = false;
		twofound = false;
		
		i = allrefs.iterator();
		while (i.hasNext()) {
			LocalFileReference lf = i.next();
			if (fileRefEquals(lf, lf0)) {
				onefound = true;
			}
			if (fileRefEquals(lf, lf1)) {
				twofound = true;
			}
		}
		assertTrue(onefound);
		assertTrue(twofound);
		
		KeySet ks2 = s4.getMyKeySet();

		assertTrue(KeySetEquals(ks, ks2));
		
		Peer mypeer = s4.getMyPeerData();
		
		assertTrue(peerEquals(mypeer, p));
		
		List<Peer> pl = s4.getPeerList();
		assertEquals(pl.size(), 2);
		
		boolean p0eq = false;
		boolean p1eq = false;
		Iterator<Peer> pi = pl.iterator();
		while (pi.hasNext()) {
			Peer u = pi.next();
			if (peerEquals(u, p)) { p0eq = true ; }
			if (peerEquals(u, p13)) { p1eq = true; }
		}
		assertTrue(p0eq);
		assertTrue(p1eq);
	
	}
	
}
