package org.ggp.base.apps.tiltyard;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

import org.ggp.base.util.http.HttpReader;
import org.ggp.base.util.http.HttpWriter;
import org.ggp.base.util.loader.RemoteResourceLoader;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import external.JSON.JSONArray;
import external.JSON.JSONObject;

public class TiltyardRequestFarmTest extends Assert {
    @Before
    public void setUp() {
        new RequestFarmLoopThread().start();
    }

    @Test
    public void testThroughput() {
        new ResponderLoopThread(2000).start();
        new ReceiverLoopThread("OK").start();
        runTestingLoop();
    }

    /* TODO(schreib): Get all of these working at the same time.

    @Test
    public void testConnectionError() {
        new ReceiverLoopThread("CE").start();
        runTestingLoop();
    }

    @Test
    public void testTimeout() {
        new ResponderLoopThread(4000).start();
        new ReceiverLoopThread("TO").start();
        runTestingLoop();
    }

     */

    static long doMath(long a) {
        return a/2+3;
    }

    // Connections are run asynchronously in their own threads.
    class ResponderThread extends Thread {
        private Socket conn;
        private int sleepTime;

        public ResponderThread(Socket connection, int sleepTime) {
            conn = connection;
            this.sleepTime = sleepTime;
        }

        @Override
        public void run() {
            try {
                String line = HttpReader.readAsServer(conn);
                Thread.sleep(sleepTime);
                HttpWriter.writeAsServer(conn, "" + doMath(Long.parseLong(line)));
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    AtomicInteger nSuccesses = new AtomicInteger(0);

    // Connections are run asynchronously in their own threads.
    class ReceiverThread extends Thread {
        private Socket conn;
        private String response;

        public ReceiverThread(Socket connection, String expectedResponse) {
            conn = connection;
            response = expectedResponse;
        }

        @Override
        public void run() {
            try {
                String line = HttpReader.readAsServer(conn);
                HttpWriter.writeAsServer(conn, "cool");
                conn.close();
                JSONObject batchResponseJSON = new JSONObject(line);
                assertTrue(batchResponseJSON.has("responses"));
                assertEquals(2, batchResponseJSON.getJSONArray("responses").length());
                JSONObject responseJSON = batchResponseJSON.getJSONArray("responses").getJSONObject(0);
                assertEquals(response, responseJSON.getString("responseType"));
                if (responseJSON.getString("responseType").equals("OK")) {
                    long original = Long.parseLong(new JSONObject(responseJSON.getString("originalRequest")).getString("requestContent"));
                    long response = Long.parseLong(responseJSON.getString("response"));
                    assertEquals(response, doMath(original));
                }
                nSuccesses.incrementAndGet();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    class ResponderLoopThread extends Thread {
        private int sleepTime;
        public ResponderLoopThread(int sleepTime) {
            this.sleepTime = sleepTime;
        }

        @Override
        public void run() {
            try {
                @SuppressWarnings("resource")
                ServerSocket listener = new ServerSocket(12345);
                while (true) {
                    try {
                        Socket connection = listener.accept();
                        new ResponderThread(connection, sleepTime).start();
                    } catch (Exception e) {
                        System.err.println(e);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    class ReceiverLoopThread extends Thread {
        private String response;
        public ReceiverLoopThread(String expectResponse) {
            response = expectResponse;
        }

        @Override
        public void run() {
            try {
                @SuppressWarnings("resource")
                ServerSocket listener = new ServerSocket(12346);
                while (true) {
                    try {
                        Socket connection = listener.accept();
                        new ReceiverThread(connection, response).start();
                    } catch (Exception e) {
                        System.err.println(e);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    class RequestFarmLoopThread extends Thread {
        @Override
        public void run() {
            try {
                TiltyardRequestFarm.testMode = true;
                TiltyardRequestFarm.main(new String[]{});
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void runTestingLoop() {
        try {
            Random r = new Random();
            JSONObject theBatchRequest = new JSONObject();
            JSONArray theRequests = new JSONArray();
            JSONObject theRequest = new JSONObject();
            theRequest.put("targetPort", 12345);
            theRequest.put("targetHost", "127.0.0.1");
            theRequest.put("timeoutClock", 3000);
            theRequest.put("forPlayerName", "");
            theRequests.put(theRequest);
            theRequests.put(theRequest);
            theBatchRequest.put("requests", theRequests);
            theBatchRequest.put("callbackURL", "http://127.0.0.1:12346");

            int nRequests = 0;
            while (true) {
                theRequest.put("requestContent", "" + r.nextLong());
                theRequest.put("timestamp", System.currentTimeMillis());
                assertEquals("okay", RemoteResourceLoader.postRawWithTimeout("http://127.0.0.1:" + TiltyardRequestFarm.SERVER_PORT, theBatchRequest.toString(), 5000));
                nRequests++;
                Thread.sleep(10);
                int curNumSuccesses = nSuccesses.get();
                System.out.println("Successes so far: " + curNumSuccesses + " vs Requests: " + nRequests);
                if (curNumSuccesses > 3000) {
                    assertTrue(nRequests > curNumSuccesses);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}