/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.cassandra.diag;

import java.io.Serializable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;

import com.google.common.collect.ImmutableList;

import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;

import org.apache.cassandra.config.DatabaseDescriptor;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class DiagnosticEventServiceTest
{

    @BeforeClass
    public static void setup()
    {
        DatabaseDescriptor.daemonInitialization();
    }

    @After
    public void cleanup()
    {
        DiagnosticEventService.instance().cleanup();
    }

    @Test
    public void testSubscribe()
    {
        DiagnosticEventService instance = DiagnosticEventService.instance();
        assertFalse(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
        Consumer<TestEvent1> consumer1 = (event) ->
        {
        };
        Consumer<TestEvent1> consumer2 = (event) ->
        {
        };
        Consumer<TestEvent1> consumer3 = (event) ->
        {
        };
        instance.subscribe(TestEvent1.class, consumer1);
        instance.subscribe(TestEvent1.class, consumer2);
        instance.subscribe(TestEvent1.class, consumer3);
        assertTrue(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
        instance.unsubscribe(consumer1);
        assertTrue(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
        instance.unsubscribe(consumer2);
        instance.unsubscribe(consumer3);
        assertFalse(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
    }

    @Test
    public void testSubscribeByType()
    {
        DiagnosticEventService instance = DiagnosticEventService.instance();
        assertFalse(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
        Consumer<TestEvent1> consumer1 = (event) ->
        {
        };
        Consumer<TestEvent1> consumer2 = (event) ->
        {
        };
        Consumer<TestEvent1> consumer3 = (event) ->
        {
        };

        assertFalse(instance.hasSubscribers(TestEvent1.class, TestEventType.TEST1));
        instance.subscribe(TestEvent1.class, TestEventType.TEST1, consumer1);
        assertTrue(instance.hasSubscribers(TestEvent1.class, TestEventType.TEST1));
        assertFalse(instance.hasSubscribers(TestEvent1.class, TestEventType.TEST2));

        instance.subscribe(TestEvent1.class, TestEventType.TEST2, consumer2);
        instance.subscribe(TestEvent1.class, TestEventType.TEST2, consumer2);
        instance.subscribe(TestEvent1.class, TestEventType.TEST2, consumer2);
        assertTrue(instance.hasSubscribers(TestEvent1.class, TestEventType.TEST2));

        assertFalse(instance.hasSubscribers(TestEvent2.class));

        instance.subscribe(TestEvent1.class, consumer3);
        assertTrue(instance.hasSubscribers(TestEvent1.class));
        assertTrue(instance.hasSubscribers(TestEvent1.class, TestEventType.TEST1));
        assertTrue(instance.hasSubscribers(TestEvent1.class, TestEventType.TEST2));
        assertTrue(instance.hasSubscribers(TestEvent1.class, TestEventType.TEST3));

        instance.unsubscribe(consumer1);
        assertTrue(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
        instance.unsubscribe(consumer2);
        instance.unsubscribe(consumer3);
        assertFalse(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
    }

    @Test
    public void testSubscribeAll()
    {
        DiagnosticEventService instance = DiagnosticEventService.instance();
        assertFalse(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
        Consumer<DiagnosticEvent> consumerAll1 = (event) ->
        {
        };
        Consumer<DiagnosticEvent> consumerAll2 = (event) ->
        {
        };
        Consumer<DiagnosticEvent> consumerAll3 = (event) ->
        {
        };
        instance.subscribeAll(consumerAll1);
        instance.subscribeAll(consumerAll2);
        instance.subscribeAll(consumerAll3);
        assertTrue(instance.hasSubscribers(TestEvent1.class));
        assertTrue(instance.hasSubscribers(TestEvent2.class));
        instance.unsubscribe(consumerAll1);
        assertTrue(instance.hasSubscribers(TestEvent1.class));
        assertTrue(instance.hasSubscribers(TestEvent2.class));
        instance.unsubscribe(consumerAll2);
        instance.unsubscribe(consumerAll3);
        assertFalse(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
    }

    @Test
    public void testCleanup()
    {
        DiagnosticEventService instance = DiagnosticEventService.instance();
        assertFalse(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
        Consumer<TestEvent1> consumer = (event) ->
        {
        };
        instance.subscribe(TestEvent1.class, consumer);
        Consumer<DiagnosticEvent> consumerAll = (event) ->
        {
        };
        instance.subscribeAll(consumerAll);
        assertTrue(instance.hasSubscribers(TestEvent1.class));
        assertTrue(instance.hasSubscribers(TestEvent2.class));
        instance.cleanup();
        assertFalse(instance.hasSubscribers(TestEvent1.class));
        assertFalse(instance.hasSubscribers(TestEvent2.class));
    }

    @Test
    public void testPublish()
    {
        DiagnosticEventService instance = DiagnosticEventService.instance();
        TestEvent1 a = new TestEvent1();
        TestEvent1 b = new TestEvent1();
        TestEvent1 c = new TestEvent1();
        List<TestEvent1> events = ImmutableList.of(a, b, c, c, c);

        List<DiagnosticEvent> consumed = new LinkedList<>();
        Consumer<TestEvent1> consumer = consumed::add;
        Consumer<DiagnosticEvent> consumerAll = consumed::add;

        DatabaseDescriptor.setDiagnosticEventsEnabled(true);
        instance.publish(c);
        instance.subscribe(TestEvent1.class, consumer);
        instance.publish(a);
        instance.unsubscribe(consumer);
        instance.publish(c);
        instance.subscribeAll(consumerAll);
        instance.publish(b);
        instance.subscribe(TestEvent1.class, TestEventType.TEST3, consumer);
        instance.publish(c);
        instance.subscribe(TestEvent1.class, TestEventType.TEST1, consumer);
        instance.publish(c);

        assertEquals(events, consumed);
    }

    @Test
    public void testEnabled()
    {
        DatabaseDescriptor.setDiagnosticEventsEnabled(false);
        DiagnosticEventService.instance().subscribe(TestEvent1.class, (event) -> fail());
        DiagnosticEventService.instance().publish(new TestEvent1());
        DatabaseDescriptor.setDiagnosticEventsEnabled(true);
    }

    public static class TestEvent1 extends DiagnosticEvent
    {
        public TestEventType getType()
        {
            return TestEventType.TEST1;
        }

        public HashMap<String, Serializable> toMap()
        {
            return null;
        }
    }

    public static class TestEvent2 extends DiagnosticEvent
    {
        public TestEventType getType()
        {
            return TestEventType.TEST2;
        }

        public HashMap<String, Serializable> toMap()
        {
            return null;
        }
    }

    public enum TestEventType { TEST1, TEST2, TEST3 }
}
