/**
 * Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package com.eventsourcing.queries;

import com.eventsourcing.*;
import com.eventsourcing.hlc.HybridTimestamp;
import com.eventsourcing.index.Index;
import com.eventsourcing.index.SimpleIndex;
import com.google.common.collect.Iterators;
import com.googlecode.concurrenttrees.common.Iterables;
import com.googlecode.cqengine.IndexedCollection;
import com.googlecode.cqengine.query.Query;
import com.googlecode.cqengine.resultset.ResultSet;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;
import lombok.Value;
import lombok.experimental.Accessors;
import org.testng.annotations.Test;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.eventsourcing.index.IndexEngine.IndexFeature.*;
import static com.eventsourcing.queries.QueryFactory.equal;
import static com.eventsourcing.queries.QueryFactory.isLatestEntity;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

public class IsLatestEntityTest extends RepositoryUsingTest {

    public IsLatestEntityTest() {
        super(IsLatestEntityTest.class.getPackage());
    }


    @Value
    @EqualsAndHashCode(callSuper = false)
    @Accessors(fluent = true)
    public static class TestEvent extends StandardEvent {
        private String test;
        private UUID reference;

        public final static SimpleIndex<TestEvent, UUID> REFERENCE_ID = SimpleIndex.as(TestEvent::reference);


        @Index({EQ, LT, GT})
        public final static SimpleIndex<TestEvent, HybridTimestamp> TIMESTAMP = SimpleIndex.as(StandardEntity::timestamp);
    }

    @Value
    @EqualsAndHashCode(callSuper = false)
    @Accessors(fluent = true)
    public static class TestCommand extends StandardCommand<Void, UUID> {
        private String test;
        private UUID reference;

        @Override public EventStream<Void> events() throws Exception {
            TestEvent event = new TestEvent(test, reference);
            return EventStream.of(event);
        }

        @Override public UUID result() {
            return reference;
        }
    }

    @Test @SneakyThrows
    public void test() {
        UUID uuid = UUID.randomUUID();
        repository.publish(new TestCommand("test1", uuid)).get();
        Query<EntityHandle<TestEvent>> query = isLatestEntity(equal(TestEvent.REFERENCE_ID, uuid),
                                                              TestEvent.TIMESTAMP);
        try (ResultSet<EntityHandle<TestEvent>> resultSet = repository.query(TestEvent.class, query)) {
            assertEquals(resultSet.size(), 1);
            assertEquals(resultSet.uniqueResult().get().test(), "test1");
        }
        repository.publish(new TestCommand("test2", uuid)).get();
        try (ResultSet<EntityHandle<TestEvent>> resultSet = repository.query(TestEvent.class, query)) {
            assertEquals(resultSet.size(), 1);
            assertEquals(resultSet.uniqueResult().get().test(), "test2");
        }
    }

    @Test @SneakyThrows
    public void testMassive() {
        UUID uuid = UUID.randomUUID();
        for (int i = 0; i < 10000; i++ ) {
            repository.publish(new TestCommand("test" + (i + 1), uuid)).get();
        }
        Query<EntityHandle<TestEvent>> query = isLatestEntity(equal(TestEvent.REFERENCE_ID, uuid),
                                                              TestEvent.TIMESTAMP);
        long t1 = System.nanoTime();
        try (ResultSet<EntityHandle<TestEvent>> resultSet = repository.query(TestEvent.class, query)) {
            assertEquals(resultSet.size(), 1);
            assertEquals(resultSet.uniqueResult().get().test(), "test10000");
            long t2 = System.nanoTime();
            long time = TimeUnit.SECONDS.convert(t2 - t1, TimeUnit.NANOSECONDS);
            if (time > 1) {
                System.err.println("Warning: [IsLatestEntityTest.testMassive] isLatestEntity is slow, took " + time +
                                           " seconds");
            }
        }
    }

    @Test @SneakyThrows
    public void testFunction() {
        UUID uuid = UUID.randomUUID();
        repository.publish(new TestCommand("test1", uuid)).get();
        repository.publish(new TestCommand("test2", uuid)).get();
        UUID uuidN = UUID.randomUUID();
        repository.publish(new TestCommand("testN1", uuidN)).get();
        repository.publish(new TestCommand("testN2", uuidN)).get();

        Query<EntityHandle<TestEvent>> query = isLatestEntity((h) -> equal(TestEvent.REFERENCE_ID, h.get().reference()),
                                                              TestEvent.TIMESTAMP);
        try (ResultSet<EntityHandle<TestEvent>> resultSet = repository.query(TestEvent.class, query)) {
            assertEquals(resultSet.size(), 2);
            List<EntityHandle<TestEvent>> result = Iterables.toList(resultSet);
            assertTrue(Iterators.any(result.iterator(), e -> e.get().test().contentEquals("test2")));
            assertTrue(Iterators.any(result.iterator(), e -> e.get().test().contentEquals("testN2")));
        }
    }
}