package org.torproject.onionmasq.circuit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.torproject.onionmasq.events.ClosedConnectionEvent;
import org.torproject.onionmasq.events.FailedConnectionEvent;
import org.torproject.onionmasq.events.NewConnectionEvent;
import org.torproject.onionmasq.events.OnionmasqEvent;
import org.torproject.onionmasq.testhelper.TestHelper;

import java.io.IOException;
import java.util.ArrayList;

public class CircuitStoreTest {

    @Test
    public void testGetCircuitsForAppUid_deduplicate_circuits() throws IOException {
        NewConnectionEvent event1 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection1.json");
        NewConnectionEvent event2 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection2.json");

        CircuitStore store = new CircuitStore();
        store.handleEvent(event1);
        store.handleEvent(event1);
        assertEquals("Multiple socket connections can be routed over the same circuit. The corresponding circuit should be de-duplicated if the tor destination is the same", 1, store.getCircuitsForAppUid(event1.appId).size());

        store.handleEvent(event2);
        assertEquals("destination www.torproject.org and torproject.org are not the same, thus 2 circuits are expected", 2, store.getCircuitsForAppUid(event1.appId).size());
    }


    @Test
    public void testGetCircuitsForAppUid_removeCircuitsAfterLastOpenSocketWasClosed() throws IOException {
        NewConnectionEvent event1 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection1.json");
        NewConnectionEvent event2 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection2.json");
        ClosedConnectionEvent closedEvent1 = (ClosedConnectionEvent) createNewConnectionEvent("events/closedConnection1.json");
        ClosedConnectionEvent closedEvent2 = (ClosedConnectionEvent) createNewConnectionEvent("events/closedConnection2.json");

        CircuitStore store = new CircuitStore();
        store.handleEvent(event1);
        store.handleEvent(event1);
        store.handleEvent(event2);
        store.handleEvent(closedEvent1);
        assertEquals("A circuit should be removed from the store only if the last open socket was closed.", 1, store.getCircuitsForAppUid(event1.appId).size());

        store.handleEvent(closedEvent2);
        assertEquals("An empty Array is returned if no open socket connection for an appId is available.", 0, store.getCircuitsForAppUid(event1.appId).size());
    }

    @Test
    public void testGetCircuitsForAppUid_return_different_circuits() throws IOException {
        NewConnectionEvent event1 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection1.json");
        NewConnectionEvent event2 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection3.json");

        CircuitStore store = new CircuitStore();
        store.handleEvent(event1);
        store.handleEvent(event2);
        ArrayList<Circuit> circuits = store.getCircuitsForAppUid(event1.appId);
        assertEquals("2 circuits should be returned", 2, circuits.size());
        assertNotEquals("circuits should have different addresses", circuits.get(0).getRelayDetails().get(0).addresses.get(0),  circuits.get(1).getRelayDetails().get(0).addresses.get(0));
    }

    @Test
    public void testGetCircuitsForAppUid_removeCircuitOnFailure() throws IOException {
        NewConnectionEvent event1 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection1.json");
        NewConnectionEvent event2 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection2.json");
        FailedConnectionEvent failureEvent = (FailedConnectionEvent) createNewConnectionEvent("events/failedConnection1.json");

        CircuitStore store = new CircuitStore();
        store.handleEvent(event1);
        store.handleEvent(failureEvent);
        assertNotNull(store.getCircuitsForAppUid(event1.appId));
        assertEquals(0, store.getCircuitsForAppUid(event1.appId).size());

        store.handleEvent(event1);
        store.handleEvent(event2);
        store.handleEvent(failureEvent);
        assertEquals(1, store.getCircuitsForAppUid(event1.appId).size());
    }


    @Test
    public void testReset_removedCircuits() throws IOException {
        NewConnectionEvent event1 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection1.json");
        NewConnectionEvent event2 = (NewConnectionEvent) createNewConnectionEvent("events/newConnection3.json");

        CircuitStore store = new CircuitStore();
        store.handleEvent(event1);
        store.handleEvent(event2);
        store.reset();

        assertEquals(0, store.getCircuitsForAppUid(event1.appId).size());
    }

    private OnionmasqEvent createNewConnectionEvent(String path) throws IOException, NullPointerException {
        String json = TestHelper.getStringFromResources(path);
        return OnionmasqEvent.fromJson(json);
    }
}