package cache.common;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Arrays;
import java.util.Random;

import cache.randomAccessCache.RACache;
import cache.randomAccessCache.RACacheService;
import cache.randomAccessCache.RACacheSet;

import otherSupport.SettingsManager;


import junit.framework.TestCase;

public class CacheTests extends TestCase {
	
	@Override
	protected void setUp() throws Exception {
		super.setUp();
		
		new SettingsManager("minerva", true);
	}
	
	private CacheService getService(){
		//replace with whatever implementation you want to test
		return new RACacheService(); 
	}

	public void testBlankGet(){
		CacheService cs = getService();
		((RACache)cs.getCache("test")).emptyAllSets("test1");
		
		Object x = cs.get("test", "test1", "testKey1");
		assert(x == null);
	}
	
	public void testPutGetInMemory(){
		CacheService cacheService = getService();
		((RACache)cacheService.getCache("test")).emptyAllSets("test1");
		
		double d[] = new double[]{ 10, 20, 30 };
		cacheService.put("test", "test1", new int[]{ 1, 2, 3 }, d);
		Object x = cacheService.get("test", "test1", new int[]{ 1, 2, 3 });
		
		assertTrue(Arrays.equals((double[])x, d));
	}
	
	public void testPutGetOnDisk(){
		CacheService cacheService1 = getService();
		((RACache)cacheService1.getCache("test")).emptyAllSets("test1");
		
		double d[] = new double[]{ 10, 20, 30 };
		cacheService1.put("test", "test1", new int[]{ 1, 2, 3 }, d);
		
		CacheService cacheService2 = getService();
		
		Object x = cacheService2.get("test", "test1", new int[]{ 1, 2, 3 });
		
		assertTrue(Arrays.equals((double[])x, d));
	}
	
	public void testFileUUIDDetection(){
		CacheService cacheService1 = getService();
		((RACache)cacheService1.getCache("test")).emptyAllSets("test1");
		
		//write 2 things
		double d1[] = new double[]{ 10, 20, 30 };
		cacheService1.put("test", "test1", new int[]{ 1, 2, }, d1);
		double d2[] = new double[]{ 40, 50, 60 };
		cacheService1.put("test", "test1", new int[]{ 4, 5, 6 }, d2);
		
		//modify the first
		double d3[] = new double[]{ 70, 80, 90 };
		cacheService1.put("test", "test1", new int[]{ 1, 2 }, d3);
		
		//load in a new service
		CacheService cacheService2 = getService();
		
		//add something
		double d4[] = new double[]{ 11, 22, 33, 44 };
		cacheService2.put("test", "test1", new int[]{ 44, 55, 66, 77 }, d4);
		
		//make cs2 clean the file		
		cacheService2.getCache("test").organise("test1");
		
		//putting something into cs2 should cause a complete reload
		cacheService1.put("test", "test1", "raaar!", "turnip");
		
		//so we should now see the new entry
		Object x1 = cacheService1.get("test", "test1", new int[]{ 44, 55, 66, 77 });		
		assertTrue(Arrays.equals((double[])x1, d4));
		
	}
	
	public void testOverwritePutGetInMemory(){
		CacheService cacheService = getService();
		((RACache)cacheService.getCache("test")).emptyAllSets("test1");
		
		double d1[] = new double[]{ 10, 20, 30 };
		cacheService.put("test", "test1", new int[]{ 1, 2, 3 }, d1);
		
		double d2[] = new double[]{ 11, 22, 33 };
		cacheService.put("test", "test1", new int[]{ 1, 2, 3 }, d2);
		
		Object x = cacheService.get("test", "test1", new int[]{ 1, 2, 3 });
		
		assertTrue(Arrays.equals((double[])x, d2));
	}

	public void testPutDualAccessCollision() {
		String setKeyName = "testPutDualAccessCollision";
		try{
			CacheService cacheService1 = getService();
			((RACache)cacheService1.getCache("test")).emptyAllSets(setKeyName);
			
			CacheService cacheService2 = getService();
			
			
			//make cs1 write the entry
			double d1[] = new double[]{ 10, 20, 30 };
			cacheService1.put("test", setKeyName, new int[]{ 1, 2, 3 }, d1);
			
			//and normal recovery
			Object x1 = cacheService1.get("test", setKeyName, new int[]{ 1, 2, 3 });
			assertTrue(Arrays.equals((double[])x1, d1));

			//first read of cs2 will load the file and see that anyway
			Object x2 = cacheService2.get("test", setKeyName, new int[]{ 1, 2, 3 });
			assertTrue(Arrays.equals((double[])x2, d1));

			//now cs1 writes something new
			double d2[] = new double[]{ 11, 22, 33 };
			cacheService1.put("test", setKeyName, "rar", d2.clone());
			
			//and also overwrites the old thing
			double d3[] = new double[]{ 22, 44, 66 };
			cacheService1.put("test", setKeyName, new int[]{ 1, 2, 3 }, d3);
			
			//cs1 should see the overwritten one
			Object x3 = cacheService1.get("test", setKeyName, new int[]{ 1, 2, 3 });
			assertTrue(Arrays.equals((double[])x3, d3));

			//cs2 should not see the new thing turn up yet as get ops don't force a file nEntry check
			Object x4 = cacheService2.get("test", setKeyName, "rar");
			//assertTrue(x4 == null); // <-- this behaviour is kind of 'ok', if speed is too much affected by fastSync()
			// ... actually it does now
			assertTrue(Arrays.equals((double[])x4, d2));

			//and also shouldn't see the change of the original one
			Object x5 = cacheService2.get("test", setKeyName, new int[]{ 1, 2, 3 });
			assertTrue(Arrays.equals((double[])x5, d1));

			//making cs2 write something, again overwriting the original, will force it to load the new items
			double d4[] = new double[]{ 9, 8, 7 };
			cacheService2.put("test", setKeyName, new int[]{ 1, 2, 3 }, d4);
			
			//so now it should see the new one
			Object x6 = cacheService2.get("test", setKeyName, "rar");
			assertTrue(Arrays.equals((double[])x6, d2));
			
		}catch(Exception e){
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	/** Make sure the cache notices when the file is completely rewritten 
	 * This is similar to testFileUUIDDetection(), because I didn't know I'd written it
	 * but, critically, this requires the get() operation to notice that the data isn't
	 * there anymore
	 * */
	public void testCleanupAndGet(){
		try{
			String setKeyName = "testCleanupAndGet";
			
			CacheService cacheService1 = getService();
			((RACache)cacheService1.getCache("test")).emptyAllSets(setKeyName);
			
			CacheService cacheService2 = getService();
			
			
			//write 3 entries with cs1
			float d1[] = new float[]{ 10, 20, 30 };
			cacheService1.put("test", setKeyName, new int[]{ 1, 2 }, d1);
			
			double d2[] = new double[]{ 11, 22, 33, 44 };
			cacheService1.put("test", setKeyName, new int[]{ 4, 5, 6, 7 }, d2);
			
			int d3[] = new int[]{ 99, 88, 77 };
			cacheService1.put("test", setKeyName, new int[]{ 7, 8, 9, 10, 11 }, d3);
			
			//recover the 3rd entry with cs2, so it loads
			Object x3 = cacheService2.get("test", setKeyName, new int[]{ 7, 8, 9, 10, 11 });
			assertTrue(Arrays.equals((int[])x3, d3));
			
			//now overwrite entry 2 in cs1
			double d2B[] = new double[]{ 12, 34 };
			cacheService1.put("test", setKeyName, new int[]{ 4, 5, 6, 7 }, d2B);
			
			//cleanup file with cs1, which should remove the second entry, and move things about
			((RACache)cacheService1.getCache("test")).cleanAllSets(setKeyName, true);
			
			//now re-get entry2 with cs2, which should fail to get the correct part of the file
			Object x2B = cacheService2.get("test", setKeyName, new int[]{ 4, 5, 6, 7 });
			assertTrue(Arrays.equals((double[])x2B, d2B));
			
		}catch(Exception e){
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	
	/** Run this until the % keys in memory stabalises at < 100%, reducing the VM -Xmx###M setting can speed that up.
	 * Afterwards, set the index it gets to into (specialT/t)estBigReadSpeed and run that */
	 public void specialTestToSeeIfWeCanRunOutOfMemory() {
		int nSets = 100000000;
		int keySize = 4000;
		int objSize = 10000;
		
		System.out.println("Memory size estimate = " + (int)(nSets * (keySize*4 + objSize*8) /1024/1024) + "MB");
		
		CacheService cacheService = getService();
		((RACache)cacheService.getCache("test")).emptyAllSets("memTest");

		Random randGen = new Random();
		
		for(int i=0; i < nSets; i++){
			byte key[] = new byte[keySize];
			for(int j=0; j < keySize; j++){
				key[j] = (byte)(randGen.nextInt(256) - 128);
			}
			
			double obj[][][] = new double[3][2][objSize];
			for(int j1=0; j1 < 3; j1++) {
				for(int j2=0; j2 < 2; j2++) {
					for(int j3=0; j3 < objSize; j3++) {
						obj[j1][j2][j3] = randGen.nextDouble();
					}
				}
			}
			
			//one with a big key
			cacheService.put("test", "memTest", key, obj);
			
			//one with a simple int key but the same big data so we can read back
			cacheService.put("test", "memTest", i, obj);
						
			System.out.println(i + " / " + nSets);
			
			if((i % 10) == 0){
				if(cacheService instanceof RACacheService)
					((RACache)cacheService.getCache("test")).getAllSets("memTest")[0].dumpCacheMemoryStats();
			}
		}
	}
	
	/** see specialTestToSeeIfWeCanRunOutOfMemory first */
	public void specialTestBigReadSpeed(){
		CacheService cacheService = getService();
		
		
		long t0 = System.currentTimeMillis();
		int maxIdx = 2190; //whatever you let the testToSeeIfWeCanRunOutOfMemory() get up to
		
		
		
		Random randGen = new Random();
		
		long i=0;
		long i0 = 0;
		while(true){
			//for(int j=0; j < maxIdx; j++){
				int j = randGen.nextInt(maxIdx);
				
				Object x = cacheService.get("test", "memTest", j);
				assertTrue(x != null);
				
			//}
			
			long t = System.currentTimeMillis();
			if((t - t0) > 2000){
				System.out.println("i = " + i + ", n per sec = " + ((double)i - i0) / (t - t0) * 1000);
				if(cacheService instanceof RACacheService)
					((RACache)cacheService.getCache("test")).getAllSets("memTest")[0].dumpCacheMemoryStats();
				t0 = System.currentTimeMillis();
				i0 = i;
			}
			i++;
		}
	}
	
	public void testTagging(){
		(new File("/tmp/minerva/cache/test/minervaCache_test_test2.mrc")).delete();
		CacheService cacheService1 = getService();
		
		for(int i=0; i < 10; i++){
			double d[] = new double[]{ i, i*10, i*100 };
			cacheService1.put("test", "testSet2", i, d);
		}
		
		//set a tag
		cacheService1.setCacheTag("test", "newStuff", false);
		
		double d[] = new double[]{ 7, 77, 777 };
		cacheService1.put("test", "testSet2", 5, d);
		cacheService1.put("test", "testSet2", 15, d);
		
		//get should work with untagged and tagged stuff
		Object x1 = cacheService1.get("test", "testSet2", 3);
		assertTrue(Arrays.equals((double[])x1, new double[]{ 3, 30, 300 } ));
		
		Object x2 = cacheService1.get("test", "testSet2", 15);
		assertTrue(Arrays.equals((double[])x2, new double[]{ 7, 77, 777 } ));
		
		//conflicts should accept active tag
		Object x3 = cacheService1.get("test", "testSet2", 5);
		assertTrue(Arrays.equals((double[])x2, new double[]{ 7, 77, 777 } ));
		
		
		//set a different tag, copying things
		cacheService1.setCacheTag("test", "copiedOldStuff", true);
		
		//get a few old ones, which should move them to the new tag (we can't check that here)
		Object x4 = cacheService1.get("test", "testSet2", 2);
		assertTrue(Arrays.equals((double[])x4, new double[]{ 2, 20, 200 } ));
		
		//and now get something which is in both the other tags, but noy the active one
		//from this, we might get either
		Object x5 = cacheService1.get("test", "testSet2", 5);
		assertTrue(Arrays.equals((double[])x5, new double[]{ 5, 50, 500 } ) || 
				   Arrays.equals((double[])x5, new double[]{ 7, 77, 777 } ));
		
				
	}
	
}
