/**
 * Copyright © 2012-2023 by Data Virtuality GmbH
 */
package org.teiid.jboss.coordination;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;

/**
 * @author Allan Vital
 * @date 2023.06.05
 */
public class TestCoordinatorLatch {

    private final CoordinatorLatch coordinatorLatch = CoordinatorLatch.getInstance();
    private InternalListener listener;

    @BeforeEach
    public void setup() {
        coordinatorLatch.reset();
        listener = new InternalListener();
        coordinatorLatch.addListener(listener);
    }

    @Test
    public void testShouldBeCoordinatorWhenNoSpecificHandlingIsSet() {
        Assertions.assertTrue(coordinatorLatch.isCoordinatorNode());
    }

    @Test
    public void testShouldBeCoordinatorWhenInitAndSetCoordinatorAreCalled() {
        coordinatorLatch.init("node1", "node1");
        coordinatorLatch.setCoordinator("node1");
        Assertions.assertTrue(coordinatorLatch.isCoordinatorNode());
    }

    @Test
    public void testShouldNotBeCoordinatorWhenInitAndSetCoordinatorAreCalled() {
        coordinatorLatch.init("node1", "node1");
        Assertions.assertEquals("", coordinatorLatch.getPrevCoordinator());
        coordinatorLatch.setCoordinator("node2");
        Assertions.assertEquals("node1", coordinatorLatch.getPrevCoordinator());
        Assertions.assertFalse(coordinatorLatch.isCoordinatorNode());
    }

    @Test
    public void testShouldNotNotifyListenersWhenThereIsNoCoordinatorChange() {
        coordinatorLatch.init("node1", "node1");
        coordinatorLatch.setCoordinator("node1");
        coordinatorLatch.setCoordinator("node1");
        Assertions.assertEquals(0, listener.getHowManyNotifications());
    }

    @Test
    public void testShouldNotifyListenersWhenThereIsACoordinatorChange() {
        coordinatorLatch.init("node1", "node1");
        coordinatorLatch.setCoordinator("node2");
        Assertions.assertEquals(1, listener.getHowManyNotifications());
    }

    @Test
    public void testShouldNotifyAllTimesThereIsACoordinatorChange() {
        List<String> nodes = Arrays.asList("node1", "node2");
        coordinatorLatch.init(nodes.get(0), nodes.get(0));
        for (int i = 1; i <= 1000; i++) {
            String newCoordinator = nodes.get(i % 2);
            coordinatorLatch.setCoordinator(newCoordinator);
        }
        Assertions.assertEquals(1000, listener.getHowManyNotifications());
    }

    @Test
    public void testShouldNotifyCorrectNumberEvenInAsynchronousTries() throws Exception {
        coordinatorLatch.init("node1", "node1");
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                coordinatorLatch.setCoordinator(UUID.randomUUID().toString());
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100000; i++) {
                coordinatorLatch.setCoordinator(UUID.randomUUID().toString());
            }
        });
        t1.start();
        t2.start();
        int attemptsCount = 0;
        while(t1.isAlive() || t2.isAlive()) {
            if (attemptsCount > 60) {
                Assertions.fail("threads for coordination did not end on time, aborting");
            } else {
                Thread.sleep(1000);
            }
            attemptsCount++;
        }
        Assertions.assertEquals(200000, listener.getHowManyNotifications());
    }

    @Test
    public void testShouldNotNotifyRemovedListener() {
        coordinatorLatch.init("node1", "node1");
        coordinatorLatch.removeListener(listener);
        coordinatorLatch.setCoordinator("node2");
        Assertions.assertEquals(0, listener.getHowManyNotifications());
    }

    class InternalListener implements CoordinatorChangeEventListener {

        private int howManyNotifications = 0;

        public int getHowManyNotifications() {
            return this.howManyNotifications;
        }

        @Override
        public void onCoordinatorChange(String oldCoordinator, String newCoordinator, boolean isCoordinator) {
            howManyNotifications++;
        }
    }

}