/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.node.simulator;

import freenet.client.HighLevelSimpleClient;
import freenet.crypt.DummyRandomSource;
import freenet.keys.CHKBlock;
import freenet.keys.ClientCHK;
import freenet.keys.ClientCHKBlock;
import freenet.node.Node;
import freenet.node.NodeStarter;
import freenet.node.RequestStarter;
import freenet.support.*;
import freenet.support.Logger.LogLevel;
import freenet.support.compress.Compressor.COMPRESSOR_TYPE;
import freenet.support.io.FileUtil;

import java.io.File;
import java.nio.charset.StandardCharsets;

import static java.util.concurrent.TimeUnit.DAYS;

/**
 * Test a busy, bandwidth limited network. Hopefully this should reveal any serious problems with
 * load limiting and block transfer.
 * @author toad
 */
public class RealNodeBusyNetworkTest extends RealNodeRoutingTest {

    static final int NUMBER_OF_NODES = 25;
    static final int DEGREE = 5;
    static final short MAX_HTL = (short)8;
    static final int INSERT_KEYS = 50;
    static final boolean START_WITH_IDEAL_LOCATIONS = true;
    static final boolean FORCE_NEIGHBOUR_CONNECTIONS = true;
    static final boolean ENABLE_SWAPPING = false;
    static final boolean ENABLE_ULPRS = false;
    static final boolean ENABLE_PER_NODE_FAILURE_TABLES = false;
    static final boolean ENABLE_SWAP_QUEUEING = false;
    static final boolean ENABLE_PACKET_COALESCING = true;
    static final boolean ENABLE_FOAF = true;
    static final boolean FORK_ON_CACHEABLE = false;
    static final boolean REAL_TIME_FLAG = false;

    //static final int NUMBER_OF_NODES = 50;
    //static final short MAX_HTL = 10;

    static final int DARKNET_PORT_BASE = 5008;
    static final int DARKNET_PORT_END = DARKNET_PORT_BASE + NUMBER_OF_NODES;

    public static void main(String[] args) throws Exception {
        String name = "realNodeRequestInsertTest";
        File wd = new File(name);
        if(!FileUtil.removeAll(wd)) {
        	System.err.println("Mass delete failed, test may not be accurate.");
        	System.exit(EXIT_CANNOT_DELETE_OLD_DATA);
        }
        wd.mkdir();
        //NOTE: globalTestInit returns in ignored random source
        //NodeStarter.globalTestInit(name, false, LogLevel.ERROR, "freenet.node.Location:normal,freenet.node.simulator.RealNode:minor,freenet.node.Insert:MINOR,freenet.node.Request:MINOR,freenet.node.Node:MINOR");
        //NodeStarter.globalTestInit(name, false, LogLevel.ERROR, "freenet.node.Location:MINOR,freenet.io.comm:MINOR,freenet.node.NodeDispatcher:MINOR,freenet.node.simulator:MINOR,freenet.node.PeerManager:MINOR,freenet.node.RequestSender:MINOR");
        //NodeStarter.globalTestInit(name, false, LogLevel.ERROR, "freenet.node.FNP:MINOR,freenet.node.Packet:MINOR,freenet.io.comm:MINOR,freenet.node.PeerNode:MINOR,freenet.node.DarknetPeerNode:MINOR");
        NodeStarter.globalTestInit(name, false, LogLevel.ERROR, "", true);
        System.out.println("Busy network test (inserts/retrieves in quantity/stress test)");
        System.out.println();
        DummyRandomSource random = new DummyRandomSource();
        //DiffieHellman.init(random);
        Node[] nodes = new Node[NUMBER_OF_NODES];
        Logger.normal(RealNodeRoutingTest.class, "Creating nodes...");
        Executor executor = new PooledExecutor();
        for(int i=0;i<NUMBER_OF_NODES;i++) {
            nodes[i] =
            	NodeStarter.createTestNode(DARKNET_PORT_BASE+i, 0, name, false, MAX_HTL, 20 /* 5% */, random, executor, 500*NUMBER_OF_NODES, (CHKBlock.DATA_LENGTH+CHKBlock.TOTAL_HEADERS_LENGTH)*100, true, ENABLE_SWAPPING, false, ENABLE_ULPRS, ENABLE_PER_NODE_FAILURE_TABLES, ENABLE_SWAP_QUEUEING, ENABLE_PACKET_COALESCING, 8000, ENABLE_FOAF, false, true, false, null);
            Logger.normal(RealNodeRoutingTest.class, "Created node "+i);
        }

        // Now link them up
        makeKleinbergNetwork(nodes, START_WITH_IDEAL_LOCATIONS, DEGREE, FORCE_NEIGHBOUR_CONNECTIONS, random);

        Logger.normal(RealNodeRoutingTest.class, "Added random links");

        for(int i=0;i<NUMBER_OF_NODES;i++) {
            nodes[i].start(false);
            System.err.println("Started node "+i+"/"+nodes.length);
        }

        waitForAllConnected(nodes);

        waitForPingAverage(0.95, nodes, random, MAX_PINGS, 1000);

        System.out.println();
        System.out.println("Ping average > 95%, lets do some inserts/requests");
        System.out.println();

        HighLevelSimpleClient[] clients = new HighLevelSimpleClient[nodes.length];
        for(int i=0;i<clients.length;i++) {
        	clients[i] = nodes[i].getClientCore().makeClient(RequestStarter.IMMEDIATE_SPLITFILE_PRIORITY_CLASS, false, false);
        }

        // Insert 100 keys into random nodes

        ClientCHK[] keys = new ClientCHK[INSERT_KEYS];

        String baseString = System.currentTimeMillis() + " ";
        for(int i=0;i<INSERT_KEYS;i++) {
        	System.err.println("Inserting "+i+" of "+INSERT_KEYS);
            int node1 = random.nextInt(NUMBER_OF_NODES);
            Node randomNode = nodes[node1];
            String dataString = baseString + i;
            byte[] data = dataString.getBytes(StandardCharsets.UTF_8);
            ClientCHKBlock b;
            b = ClientCHKBlock.encode(data, false, false, (short)-1, 0, COMPRESSOR_TYPE.DEFAULT_COMPRESSORDESCRIPTOR);
            CHKBlock block = b.getBlock();
            ClientCHK chk = b.getClientKey();
            byte[] encData = block.getData();
            byte[] encHeaders = block.getHeaders();
            ClientCHKBlock newBlock = new ClientCHKBlock(encData, encHeaders, chk, true);
            keys[i] = chk;
            Logger.minor(RealNodeRequestInsertTest.class, "Decoded: "+new String(newBlock.memoryDecode(), StandardCharsets.UTF_8));
            Logger.normal(RealNodeRequestInsertTest.class,"CHK: "+chk.getURI());
            Logger.minor(RealNodeRequestInsertTest.class,"Headers: "+HexUtil.bytesToHex(block.getHeaders()));
            // Insert it.
			try {
				randomNode.getClientCore().realPut(block, false, FORK_ON_CACHEABLE, false, false, REAL_TIME_FLAG);
				Logger.error(RealNodeRequestInsertTest.class, "Inserted to "+node1);
				Logger.minor(RealNodeRequestInsertTest.class, "Data: "+Fields.hashCode(encData)+", Headers: "+Fields.hashCode(encHeaders));
			} catch (freenet.node.LowLevelPutException putEx) {
				Logger.error(RealNodeRequestInsertTest.class, "Insert failed: "+ putEx);
				System.err.println("Insert failed: "+ putEx);
				System.exit(EXIT_INSERT_FAILED);
			}
        }

        // Now queue requests for each key on every node.
        for(int i=0;i<INSERT_KEYS;i++) {
        	ClientCHK key = keys[i];
        	System.err.println("Queueing requests for "+i+" of "+INSERT_KEYS);
        	for(int j=0;j<nodes.length;j++) {
        		clients[j].prefetch(key.getURI(), DAYS.toMillis(1), 32768, null);
        	}
        	long totalRunningRequests = 0;
        	for(int j=0;j<nodes.length;j++) {
        		totalRunningRequests += nodes[j].getClientCore().countQueuedRequests();
        	}
        	System.err.println("Running requests: "+totalRunningRequests);
        }

        // Now wait until finished. How???

        while(true) {
        	long totalRunningRequests = 0;
        	for(int i=0;i<nodes.length;i++) {
        		totalRunningRequests += nodes[i].getClientCore().countQueuedRequests();
        	}
        	System.err.println("Running requests: "+totalRunningRequests);
        	if(totalRunningRequests == 0) break;
        	Thread.sleep(1000);
        }
        System.exit(0);
    }
}
