package freenet.node.simulator;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import freenet.client.ClientMetadata;
import freenet.client.FetchException;
import freenet.client.FetchException.FetchExceptionMode;
import freenet.client.HighLevelSimpleClient;
import freenet.client.InsertBlock;
import freenet.client.InsertException;
import freenet.crypt.RandomSource;
import freenet.keys.FreenetURI;
import freenet.node.Node;
import freenet.node.NodeStarter;
import freenet.node.Version;
import freenet.support.Logger;
import freenet.support.Logger.LogLevel;
import freenet.support.PooledExecutor;
import freenet.support.api.RandomAccessBucket;
import freenet.support.io.FileUtil;

/**
 * Push / Pull test over long period of time
 * 
 * Unlike LongTermPushPullTest, this only inserts one key per day. That key
 * is then re-pulled at increasing intervals.
 */
public class LongTermPushRepullTest extends LongTermTest {
	private static final int TEST_SIZE = 64 * 1024;

	private static final int DARKNET_PORT1 = 5010;
	private static final int OPENNET_PORT1 = 5011;
	private static final int DARKNET_PORT2 = 5012;
	private static final int OPENNET_PORT2 = 5013;

	private static final int MAX_N = 8;

	public static void main(String[] args) {
		if (args.length != 1) {
			System.err.println("Usage: java freenet.node.simulator.LongTermPushPullTest <unique identifier>");
			System.exit(1);
		}
		String uid = args[0];

		List<String> csvLine = new ArrayList<String>(3 + 2 * MAX_N);
		System.out.println("DATE:" + dateFormat.format(today.getTime()));
		csvLine.add(dateFormat.format(today.getTime()));

		System.out.println("Version:" + Version.buildNumber());
		csvLine.add(String.valueOf(Version.buildNumber()));

		int exitCode = 0;
		Node node = null;
		Node node2 = null;
		try {
			final File dir = new File("longterm-push-pull-test-" + uid);
			FileUtil.removeAll(dir);
			RandomSource random = NodeStarter.globalTestInit(dir.getPath(), false, LogLevel.ERROR, "", false);
			File seednodes = new File("seednodes.fref");
			if (!seednodes.exists() || seednodes.length() == 0 || !seednodes.canRead()) {
				System.err.println("Unable to read seednodes.fref, it doesn't exist, or is empty");
				System.exit(EXIT_NO_SEEDNODES);
			}

			final File innerDir = new File(dir, Integer.toString(DARKNET_PORT1));
			innerDir.mkdir();
			FileInputStream fis = new FileInputStream(seednodes);
			FileUtil.writeTo(fis, new File(innerDir, "seednodes.fref"));
			fis.close();

			// Create one node
			node = NodeStarter.createTestNode(DARKNET_PORT1, OPENNET_PORT1, dir.getPath(), false, Node.DEFAULT_MAX_HTL,
			        0, random, new PooledExecutor(), 1000, 4 * 1024 * 1024, true, true, true, true, true, true, true,
			        12 * 1024, true, true, false, false, null);
			Logger.getChain().setThreshold(LogLevel.ERROR);

			// Start it
			node.start(true);
			long t1 = System.currentTimeMillis();
			if (!TestUtil.waitForNodes(node)) {
				exitCode = EXIT_FAILED_TARGET;
				return;
			}
				
			long t2 = System.currentTimeMillis();
			System.out.println("SEED-TIME:" + (t2 - t1));
			csvLine.add(String.valueOf(t2 - t1));

			// Push one block only.
			
			RandomAccessBucket data = randomData(node);
			HighLevelSimpleClient client = node.clientCore.makeClient((short) 0, false, false);
			FreenetURI uri = new FreenetURI("KSK@" + uid + "-" + dateFormat.format(today.getTime()));
			System.out.println("PUSHING " + uri);
			
			try {
				InsertBlock block = new InsertBlock(data, new ClientMetadata(), uri);
				t1 = System.currentTimeMillis();
				client.insert(block, false, null);
				t2 = System.currentTimeMillis();
				
				System.out.println("PUSH-TIME-" + ":" + (t2 - t1));
				csvLine.add(String.valueOf(t2 - t1));
			} catch (InsertException e) {
				e.printStackTrace();
				csvLine.add("N/A");
			}

			data.free();

			node.park();

			// Node 2
			File innerDir2 = new File(dir, Integer.toString(DARKNET_PORT2));
			innerDir2.mkdir();
			fis = new FileInputStream(seednodes);
			FileUtil.writeTo(fis, new File(innerDir2, "seednodes.fref"));
			fis.close();
			node2 = NodeStarter.createTestNode(DARKNET_PORT2, OPENNET_PORT2, dir.getPath(), false,
			        Node.DEFAULT_MAX_HTL, 0, random, new PooledExecutor(), 1000, 5 * 1024 * 1024, true, true, true,
			        true, true, true, true, 12 * 1024, false, true, false, false, null);
			node2.start(true);

			t1 = System.currentTimeMillis();
			if (!TestUtil.waitForNodes(node2)) {
				exitCode = EXIT_FAILED_TARGET;
				return;
			}
			t2 = System.currentTimeMillis();
			System.out.println("SEED-TIME:" + (t2 - t1));
			csvLine.add(String.valueOf(t2 - t1));

			// PULL N+1 BLOCKS
			for (int i = 0; i <= MAX_N; i++) {
				client = node2.clientCore.makeClient((short) 0, false, false);
				Calendar targetDate = (Calendar) today.clone();
				targetDate.add(Calendar.DAY_OF_MONTH, -((1 << i) - 1));

				uri = new FreenetURI("KSK@" + uid + "-" + dateFormat.format(targetDate.getTime()));
				System.out.println("PULLING " + uri);

				try {
					t1 = System.currentTimeMillis();
					client.fetch(uri);
					t2 = System.currentTimeMillis();

					System.out.println("PULL-TIME-" + i + ":" + (t2 - t1));
					csvLine.add(String.valueOf(t2 - t1));
				} catch (FetchException e) {
					if (e.getMode() != FetchExceptionMode.ALL_DATA_NOT_FOUND
					        && e.getMode() != FetchExceptionMode.DATA_NOT_FOUND)
						e.printStackTrace();
					csvLine.add(FetchException.getShortMessage(e.getMode()));
				}
			}
		} catch (Throwable t) {
			t.printStackTrace();
			exitCode = EXIT_THREW_SOMETHING;
		} finally {
			try {
				if (node != null)
					node.park();
			} catch (Throwable t1) {
			}
			try {
				if (node2 != null)
					node2.park();
			} catch (Throwable t1) {
			}

			File file = new File(uid + ".csv");
			writeToStatusLog(file, csvLine);
			
			System.exit(exitCode);
		}
	}

	private static RandomAccessBucket randomData(Node node) throws IOException {
	    RandomAccessBucket data = node.clientCore.tempBucketFactory.makeBucket(TEST_SIZE);
		OutputStream os = data.getOutputStream();
		try {
		byte[] buf = new byte[4096];
		for (long written = 0; written < TEST_SIZE;) {
			node.fastWeakRandom.nextBytes(buf);
			int toWrite = (int) Math.min(TEST_SIZE - written, buf.length);
			os.write(buf, 0, toWrite);
			written += toWrite;
		}
		} finally {
		os.close();
		}
		return data;
	}
}
