package net.lecousin.framework.core.test.io;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import org.junit.Assert;
import org.junit.Test;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.lecousin.framework.concurrent.Task;
import net.lecousin.framework.core.test.LCCoreAbstractTest;
import net.lecousin.framework.io.FileIO;
import net.lecousin.framework.io.IO;

public abstract class TestIO extends LCCoreAbstractTest {
	
	public static abstract class UsingTestData extends TestIO {

		@SuppressFBWarnings("EI_EXPOSE_REP2")
		public UsingTestData(byte[] testBuf, int nbBuf) {
			this.testBuf = testBuf;
			this.nbBuf = nbBuf;
		}
		
		protected byte[] testBuf;
		protected int nbBuf;
		
		private static final byte[] testBuf1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\nabcdefghijklmnopqrstuvwxyz\r\n0123456789012345678901234567890123456789\r\n\r\n".getBytes();;
		private static final int nbBuf1Large = 100000;
		private static final int nbBuf1Large_faster = 60000;
		private static final int nbBuf1Medium = 1000;
		private static final int nbBuf1Medium_faster = 800;
		private static final int nbBuf1Small = 100;
		private static final int nbBuf1Tiny = 1;
		private static final int nbBuf1Empty = 0;
		
		public static List<Object[]> generateTestCases(boolean faster) {
			LinkedList<Object[]> list = new LinkedList<>();
			list.add(new Object[] { testBuf1, Integer.valueOf(nbBuf1Empty) });
			list.add(new Object[] { testBuf1, Integer.valueOf(nbBuf1Tiny) });
			list.add(new Object[] { testBuf1, Integer.valueOf(nbBuf1Small) });
			list.add(new Object[] { testBuf1, Integer.valueOf(faster ? nbBuf1Medium_faster : nbBuf1Medium) });
			list.add(new Object[] { testBuf1, Integer.valueOf(faster ? nbBuf1Large_faster : nbBuf1Large) });
			return list;
		}
		
	}
	
	public static abstract class UsingGeneratedTestFiles extends UsingTestData {
		
		public UsingGeneratedTestFiles(File testFile, byte[] testBuf, int nbBuf) {
			super(testBuf, nbBuf);
			this.testFile = testFile;
		}
		
		private static File[] files_normal;
		private static File[] files_faster;

		protected File testFile;
		
		public static synchronized List<Object[]> generateTestCases(boolean faster) {
			List<Object[]> base = UsingTestData.generateTestCases(faster);
			File[] files = faster ? files_faster : files_normal;
			if (files == null) {
				files = new File[base.size()];
				for (int i = 0; i < files.length; ++i) {
					Object[] params = base.get(i);
					try { files[i] = generateFile((byte[])params[0], ((Integer)params[1]).intValue()); }
					catch (IOException e) {
						throw new RuntimeException("Error generating file", e);
					}
				}
				if (faster) files_faster = files; else files_normal = files;
			}
			LinkedList<Object[]> list = new LinkedList<>();
			for (int i = 0; i < files.length; ++i) {
				Object[] params = base.get(i);
				list.add(new Object[] { files[i], params[0], params[1] });
			}
			return list;
		}
		
		private static File generateFile(byte[] testBuf, int nbBuf) throws IOException {
			System.out.println("Generating file of "+nbBuf+" * "+testBuf.length+" = "+(nbBuf*testBuf.length));
			File file = File.createTempFile("test", "lcfw");
			file.deleteOnExit();
			FileOutputStream out = new FileOutputStream(file);
			for (int i = 0; i < nbBuf; ++i)
				out.write(testBuf);
			out.flush();
			out.close();
			return file;
		}
		
		protected FileIO.ReadOnly openFile() {
			return new FileIO.ReadOnly(testFile, Task.PRIORITY_IMPORTANT);
		}
		
		protected long getFileSize() {
			return ((long)testBuf.length) * nbBuf;
		}
	}

	protected static final Random rand = new Random();
	
	protected abstract IO getIOForCommonTests() throws Exception;
	
	@Test(timeout=120000)
	public void testBasicCommonFunctions() throws Exception {
		IO io = getIOForCommonTests();
		basicTests(io);
		io.close();
	}

	protected boolean canSetPriority() { return true; }
	
	@SuppressWarnings("unused")
	protected void basicTests(IO io) throws Exception {
		// priority
		if (canSetPriority()) {
			byte p = io.getPriority();
			io.setPriority(Task.PRIORITY_LOW);
			Assert.assertEquals(Task.PRIORITY_LOW, io.getPriority());
			io.setPriority(p);
			Assert.assertEquals(p, io.getPriority());
		}
		// basic functions, only for test coverage
		io.getTaskManager();
		io.getSourceDescription();
		io.getWrappedIO();
	}

}

@@ -368,16 +368,87 @@
 .Task;%0D%0A
+import net.lecousin.framework.concurrent.synch.ISynchronizationPoint;%0D%0A
 import n
@@ -579,16 +579,71 @@
 .io.IO;%0D
+%0Aimport net.lecousin.framework.mutable.MutableBoolean;%0D
 %0A%0D%0Apubli
@@ -4161,17 +4161,449 @@
 %0D%0A%09%09
-io.close(
+// closeable%0D%0A%09%09MutableBoolean closed = new MutableBoolean(false);%0D%0A%09%09io.addCloseListener(() -%3E %7B%0D%0A%09%09%09closed.set(true);%0D%0A%09%09%7D);%0D%0A%09%09io.lockClose();%0D%0A%09%09ISynchronizationPoint%3CException%3E close = io.closeAsync();%0D%0A%09%09Assert.assertFalse(io.isClosed());%0D%0A%09%09Assert.assertFalse(close.isUnblocked());%0D%0A%09%09Assert.assertFalse(closed.get());%0D%0A%09%09io.unlockClose();%0D%0A%09%09close.blockThrow(0);%0D%0A%09%09Assert.assertTrue(closed.get());%0D%0A%09%09Assert.assertTrue(io.isClosed()
 );%0D%0A

add tests for ConcurrentCloseable