package btspn.sr;

import btspn.sr.cmd.EventStore;
import btspn.sr.cmd.InMemoryStore;
import btspn.sr.event.Holder;
import javaslang.Tuple;
import javaslang.collection.List;
import org.testng.annotations.Test;

import java.util.Objects;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertSame;

public class StateReducerTest {
    public static class State {

    }

    public static class Person {
        public final String firstName;
        public final String lastName;

        public Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Person person = (Person) o;
            return Objects.equals(firstName, person.firstName) &&
                    Objects.equals(lastName, person.lastName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(firstName, lastName);
        }
    }

    public static class ChangeFirstName {
        public final String firstName;

        public ChangeFirstName(String firstName) {
            this.firstName = firstName;
        }
    }

    public static class FirstNameChanged {
        public final String firstName;

        public FirstNameChanged(String firstName) {
            this.firstName = firstName;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            FirstNameChanged that = (FirstNameChanged) o;
            return Objects.equals(firstName, that.firstName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(firstName);
        }
    }

    public static Person changeFirstName(ChangeFirstName cmd, Person personN) {
        return new Person(cmd.firstName, personN.lastName);
    }

    public static Person changeFirstNameE(FirstNameChanged cmd, Person personN) {
        return new Person(cmd.firstName, personN.lastName);
    }


    public static List changeFirstNameEvent(ChangeFirstName cmd, Person personN) {
        return List.of(new FirstNameChanged(cmd.firstName));
    }


    public static List createPerson(CreatePerson cmd, Person personN) {
        return List.of(new PersonCreated(cmd.firstName, cmd.lastName));
    }


    @Test
    public void testEvent() throws Exception {


        Holder<Person> holder = Holder.hold(new Person("Jane", "Doe"));
        StateReducer<Person, State> sr = StateReducer.of(
                Dispatch.<Person, State>event()
                        .on(ChangeFirstName.class,
                                EventFunction.p(StateReducerTest::changeFirstName)));

        Person newPerson = sr.apply(holder, State::new, new ChangeFirstName("John"));
        assertEquals(newPerson.firstName, "John");
        assertEquals(newPerson.lastName, "Doe");
        assertSame(newPerson, holder.get());
    }

    @Test
    public void testCmd() throws Exception {
        EventStore eventStore = InMemoryStore.empty();

        EsStateReducer<Person, State> sr = EsStateReducer.of(
                Dispatch.<Person, State>cmd()
                        .on(CreatePerson.class, CommandFunction.pc(StateReducerTest::createPerson))
                        .on(ChangeFirstName.class, CommandFunction.pc(StateReducerTest::changeFirstNameEvent)),
                Dispatch.<Person, State>event()
                        .on(PersonCreated.class, EventFunction.p(StateReducerTest::personCreated))
                        .on(FirstNameChanged.class, EventFunction.p(StateReducerTest::firstNameChanged)),
                1);

        sr.apply(eventStore, State::new, 1, new CreatePerson("Jane", "Doe"));
        Person newPerson = sr.apply(eventStore, State::new, 1, new ChangeFirstName("John"));
        assertEquals(newPerson.firstName, "John");
        assertEquals(newPerson.lastName, "Doe");
        assertEquals(List.of(new PersonCreated("Jane", "Doe"), new FirstNameChanged("John")), eventStore.events(1));
        assertEquals(eventStore.lastSnapshot(1), Tuple.of(2, new Person("John", "Doe")));
    }


    private static Person firstNameChanged(FirstNameChanged event, Person personN) {
        return new Person(event.firstName, personN.lastName);
    }

    private static Person personCreated(PersonCreated event, Person personN) {
        return new Person(event.firstName, event.lastName);
    }

    private static class PersonCreated {
        private final String firstName;
        private final String lastName;

        public PersonCreated(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            PersonCreated that = (PersonCreated) o;
            return Objects.equals(firstName, that.firstName) &&
                    Objects.equals(lastName, that.lastName);
        }

        @Override
        public int hashCode() {
            return Objects.hash(firstName, lastName);
        }
    }

    private class CreatePerson {
        public final String firstName;
        public final String lastName;

        public CreatePerson(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }
}
@@ -2298,38 +2298,197 @@
 ) %7B%0A        
-return List.of
+List events = List.empty();%0A        if (personN == null) %7B%0A            events = events.append(new PersonCreated(cmd.firstName, null));%0A        %7D%0A        return events.append
 (new FirstNa
@@ -4473,24 +4473,1134 @@
 )));%0A    %7D%0A%0A
+    @Test%0A    public void testCmd2() throws Exception %7B%0A        EventStore eventStore = InMemoryStore.empty();%0A%0A        EsStateReducer%3CPerson, State%3E sr = EsStateReducer.of(%0A                Dispatch.%3CPerson, State%3Ecmd()%0A                        .on(CreatePerson.class, CommandFunction.pc(StateReducerTest::createPerson))%0A                        .on(ChangeFirstName.class, CommandFunction.pc(StateReducerTest::changeFirstNameEvent)),%0A                Dispatch.%3CPerson, State%3Eevent()%0A                        .on(PersonCreated.class, EventFunction.p(StateReducerTest::personCreated))%0A                        .on(FirstNameChanged.class, EventFunction.p(StateReducerTest::firstNameChanged)),%0A                1);%0A%0A        Person newPerson = sr.apply(eventStore, State::new, 1, new ChangeFirstName(%22John%22));%0A        assertEquals(newPerson.firstName, %22John%22);%0A        assertEquals(newPerson.lastName, null);%0A        assertEquals(List.of(new PersonCreated(%22John%22, null), new FirstNameChanged(%22John%22)), eventStore.events(1));%0A        assertEquals(eventStore.lastSnapshot(1), Tuple.of(2, new Person(%22John%22, null)));%0A    %7D%0A%0A
 %0A    private

Test for compound events