<commit_before>package de.davherrmann.efficiently.app;

import static com.google.common.collect.Maps.newHashMap;

import java.util.Map;

import de.davherrmann.efficiently.immutable.Immutable;
import de.davherrmann.efficiently.server.Action;
import de.davherrmann.efficiently.server.Reducer;
import de.davherrmann.efficiently.server.Reducers;
import de.davherrmann.efficiently.server.StandardAction;

public class MySpecialReducer implements Reducer<MySpecialState>
{
    private final Reducers<MySpecialState> reducers = new Reducers<>();

    public MySpecialReducer()
    {
        // TODO show asynchronous service call

        // TODO custom action types?
        // reducers.add("assistantAction/reallyPrint", MySpecialAction.class, (state, path, action) -> state);

        // TODO could we use method references in path?
        // careful: type erasure -> Supplier == Supplier -> no difference between List<T> and T
        // reducers.add("", state -> path -> action -> state.in(path.assistant()::title).set(""));

        // initialise state
        reducers.add("initState", (state, path, action) -> resetState(state));

        reducers.add("changeLanguage/German", (state, path, action) -> addCaptionsTo(state, "German"));
        reducers.add("changeLanguage/English", (state, path, action) -> addCaptionsTo(state, "English"));

        // possible states
        reducers.add("setState/.*", (state, path, action) -> {
            // TODO improve: casting necessary?
            switch (((StandardAction) action).meta().get("name"))
            {
                case "firstPageEmpty":
                    // TODO can't use path here! should we inject path into the Immutable? better UX?
                    return resetState(state).in(path.assistant()::currentPage).set(0);
                case "firstPageEmptyWaiting":
                    return resetState(state) //
                        .in(path.assistant()::currentPage).set(0) //
                        .in(path::waitingForAsync).set(true);
                case "secondPageEmpty":
                    return resetState(state).in(p -> p.assistant()::currentPage).set(1);
                case "thirdPageWithDialog":
                    return resetState(state) //
                        .in(path.assistant()::currentPage).set(2) //
                        .in(path::wantToClose).set(true);
                case "thirdPageWithDialogWaiting":
                    return resetState(state) //
                        .in(path.assistant()::currentPage).set(2) //
                        .in(path::wantToClose).set(true) //
                        .in(path::waitingForAsync).set(true);
                case "English":
                    return addCaptionsTo(state, "English");
                case "German":
                    return addCaptionsTo(state, "German");
            }
            return state;
        });

        // async start/stop
        // TODO should/can the framework do this?
        reducers.add("startWaitingForAsync", (state, path, action) -> state //
            .in(path::waitingForAsync).set(true));
        reducers.add("stopWaitingForAsync", (state, path, action) -> state //
            .in(path::waitingForAsync).set(false));

        // assistant actions
        reducers.add("assistantAction/next", (state, path, action) -> state //
            .in(path.assistant()::currentPage).update(page -> page + 1));
        reducers.add("assistantAction/previous", (state, path, action) -> state //
            .in(path.assistant()::currentPage).update(page -> page - 1));
        reducers.add("assistantAction/close", (state, path, action) -> state //
            .in(path::wantToClose).set(true));
        // TODO casting is not really safe here, could be any action
        reducers.add("assistantAction/reallyPrint", (state, path, action) -> state //
            .in(path.assistant()::title).set(((MySpecialAction) action).meta()));

        // dialog actions
        reducers.add("dialogAction/reallyClose", (state, path, action) -> state //
            .in(path::wantToClose).set(false) //
            .in(path.assistant()::title).update(title -> title + " closed..."));

        // table actions
        reducers.add("requestNewItems", (state, path, action) -> state //
            .inList(path::items).update(items -> items.size() > 100
                ? items
                : items.addAll(PersonService.persons())));

        // default action
        // TODO use this as default action?
        // TODO pass action as parameter as well!
        // TODO findFirst vs all?
        reducers.add(".*", (state, path, action) -> {
            System.out.println("we have no reducer for action: " + action.type());
            return state  //
                .in(path.assistant()::title).update(title -> title + "!");
        });

        // TODO allow state subset?
        // reducers.add("assistantAction/next", this::assistantNavigateNext, path -> path.assistant());

        // TODO maybe something like (receiving only a part of the state!)
        //actionReducers.add("assistantAction/next", this::navigateToNextPage, state -> state.assistant())

        // Allow function annotations?
        // @Action("assistantAction/next")
        //actionReducerMap.putAll(AnnotatedActionReducers.from(this));
    }

    private Immutable<MySpecialState> resetState(Immutable<MySpecialState> state)
    {
        final MySpecialState path = state.path();
        final Immutable<MySpecialState> initialState = state.clear() //
            .in(path.ewb()::actions).set(new String[]{"print", "close", "save"}) //
            .in(path.ewb()::title).set("MyEWB") //

            .in(path.assistant()::actions).set(new String[]{"print", "close", "save"}) //
            .in(path.assistant()::title).set("MyAssistant") //
            .in(path.assistant()::currentPage).set(2) //

            .in(path::wantToClose).set(false) //
            .inList(path::items).set(PersonService.persons()) //

            .in(path.form()::firstname).set("Foo") //
            .in(path.form()::lastname).set("Bar") //
            .in(path.form()::firstnameLabel).set("First Name FOo") //

            .inList(path::possibleStates).update(list -> list //
                .add(possibleState("firstPageEmpty", "Seite 1 leer")) //
                .add(possibleState("firstPageEmptyWaiting", "Seite 1 leer wartend")) //
                .add(possibleState("secondPageEmpty", "Seite 2 leer")) //
                .add(possibleState("thirdPageWithDialog", "Seite 3 mit Dialog")) //
                .add(possibleState("thirdPageWithDialogWaiting", "Seite 3 mit Dialog wartend")) //
                .add(possibleState("German", "Deutsch")) //
                .add(possibleState("English", "Englisch")) //
            );
        return addCaptionsTo(initialState, "German");
    }

    private Immutable<MySpecialState> addCaptionsTo(Immutable<MySpecialState> state, String language)
    {
        return state //
            .in(p -> p.assistant()::title).set(captionFor("Deutscher Titel", language)) //
            .in(p -> p.form()::firstnameLabel).set(captionFor("Vorname", language));
    }

    private String captionFor(String fromString, String language)
    {
        if ("German".equals(language))
        {
            return fromString;
        }

        Map<String, String> translation = newHashMap();
        translation.put("Deutscher Titel", "English Title");
        translation.put("Vorname", "<NAME>");

        return translation.getOrDefault(fromString, "NO TRANSLATION");
    }

    private MySpecialState.PossibleState possibleState(final String name, String title)
    {
        return new Immutable<>(MySpecialState.PossibleState.class) //
            .in(path -> path::name).set(name) //
            .in(path -> path::title).set(title) //
            .asObject();
    }

    @Override
    public Immutable<MySpecialState> reduce(Immutable<MySpecialState> state, MySpecialState path, Action<?> action)
    {
        // TODO is this a good pattern? explicit call vs. implicit call?
        // TODO advantage: middleware - logging & co
        return reducers.reduce(state, path, action);
    }
}
<commit_msg>simplify several states<commit_after>package de.davherrmann.efficiently.app;

import static com.google.common.collect.Maps.newHashMap;

import java.util.Map;

import de.davherrmann.efficiently.immutable.Immutable;
import de.davherrmann.efficiently.server.Action;
import de.davherrmann.efficiently.server.Reducer;
import de.davherrmann.efficiently.server.Reducers;

public class MySpecialReducer implements Reducer<MySpecialState>
{
    private final Reducers<MySpecialState> reducers = new Reducers<>();

    public MySpecialReducer()
    {
        // TODO show asynchronous service call

        // TODO custom action types?
        // reducers.add("assistantAction/reallyPrint", MySpecialAction.class, (state, path, action) -> state);

        // TODO could we use method references in path?
        // careful: type erasure -> Supplier == Supplier -> no difference between List<T> and T
        // reducers.add("", state -> path -> action -> state.in(path.assistant()::title).set(""));

        // initialise state
        reducers.add("initState", (state, path, action) -> resetState(state));

        reducers.add("changeLanguage/German", (state, path, action) -> addCaptionsTo(state, "German"));
        reducers.add("changeLanguage/English", (state, path, action) -> addCaptionsTo(state, "English"));

        // possible states
        reducers.add("setState/firstPageEmpty", (state, path, action) -> resetState(state) //
            .in(path.assistant()::currentPage).set(0));
        reducers.add("setState/firstPageEmptyWaiting", (state, path, action) -> resetState(state) //
            .in(path.assistant()::currentPage).set(0) //
            .in(path::waitingForAsync).set(true));
        reducers.add("setState/secondPageEmpty", (state, path, action) -> resetState(state) //
            .in(path.assistant()::currentPage).set(1));
        reducers.add("setState/thirdPageWithDialog", (state, path, action) -> resetState(state) //
            .in(path.assistant()::currentPage).set(0) //
            .in(path::wantToClose).set(true));
        reducers.add("setState/thirdPageWithDialogWaiting", (state, path, action) -> resetState(state) //
            .in(path.assistant()::currentPage).set(0) //
            .in(path::wantToClose).set(true) //
            .in(path::waitingForAsync).set(true));
        reducers.add("setState/English", (state, path, action) -> addCaptionsTo(state, "English"));
        reducers.add("setState/German", (state, path, action) -> addCaptionsTo(state, "German"));

        // async start/stop
        // TODO should/can the framework do this?
        reducers.add("startWaitingForAsync", (state, path, action) -> state //
            .in(path::waitingForAsync).set(true));
        reducers.add("stopWaitingForAsync", (state, path, action) -> state //
            .in(path::waitingForAsync).set(false));

        // assistant actions
        reducers.add("assistantAction/next", (state, path, action) -> state //
            .in(path.assistant()::currentPage).update(page -> page + 1));
        reducers.add("assistantAction/previous", (state, path, action) -> state //
            .in(path.assistant()::currentPage).update(page -> page - 1));
        reducers.add("assistantAction/close", (state, path, action) -> state //
            .in(path::wantToClose).set(true));
        // TODO casting is not really safe here, could be any action
        reducers.add("assistantAction/reallyPrint", (state, path, action) -> state //
            .in(path.assistant()::title).set(((MySpecialAction) action).meta()));

        // dialog actions
        reducers.add("dialogAction/reallyClose", (state, path, action) -> state //
            .in(path::wantToClose).set(false) //
            .in(path.assistant()::title).update(title -> title + " closed..."));

        // table actions
        reducers.add("requestNewItems", (state, path, action) -> state //
            .inList(path::items).update(items -> items.size() > 100
                ? items
                : items.addAll(PersonService.persons())));

        // default action
        // TODO use this as default action?
        // TODO pass action as parameter as well!
        // TODO findFirst vs all?
        reducers.add(".*", (state, path, action) -> {
            System.out.println("we have no reducer for action: " + action.type());
            return state  //
                .in(path.assistant()::title).update(title -> title + "!");
        });

        // TODO allow state subset?
        // reducers.add("assistantAction/next", this::assistantNavigateNext, path -> path.assistant());

        // TODO maybe something like (receiving only a part of the state!)
        //actionReducers.add("assistantAction/next", this::navigateToNextPage, state -> state.assistant())

        // Allow function annotations?
        // @Action("assistantAction/next")
        //actionReducerMap.putAll(AnnotatedActionReducers.from(this));
    }

    private Immutable<MySpecialState> resetState(Immutable<MySpecialState> state)
    {
        final MySpecialState path = state.path();
        final Immutable<MySpecialState> initialState = state.clear() //
            .in(path.ewb()::actions).set(new String[]{"print", "close", "save"}) //
            .in(path.ewb()::title).set("MyEWB") //

            .in(path.assistant()::actions).set(new String[]{"print", "close", "save"}) //
            .in(path.assistant()::title).set("MyAssistant") //
            .in(path.assistant()::currentPage).set(2) //

            .in(path::wantToClose).set(false) //
            .inList(path::items).set(PersonService.persons()) //

            .in(path.form()::firstname).set("Foo") //
            .in(path.form()::lastname).set("Bar") //
            .in(path.form()::firstnameLabel).set("First Name FOo") //

            .inList(path::possibleStates).update(list -> list //
                .add(possibleState("firstPageEmpty", "Seite 1 leer")) //
                .add(possibleState("firstPageEmptyWaiting", "Seite 1 leer wartend")) //
                .add(possibleState("secondPageEmpty", "Seite 2 leer")) //
                .add(possibleState("thirdPageWithDialog", "Seite 3 mit Dialog")) //
                .add(possibleState("thirdPageWithDialogWaiting", "Seite 3 mit Dialog wartend")) //
                .add(possibleState("German", "Deutsch")) //
                .add(possibleState("English", "Englisch")) //
            );
        return addCaptionsTo(initialState, "German");
    }

    private Immutable<MySpecialState> addCaptionsTo(Immutable<MySpecialState> state, String language)
    {
        return state //
            .in(p -> p.assistant()::title).set(captionFor("Deutscher Titel", language)) //
            .in(p -> p.form()::firstnameLabel).set(captionFor("Vorname", language));
    }

    private String captionFor(String fromString, String language)
    {
        if ("German".equals(language))
        {
            return fromString;
        }

        Map<String, String> translation = newHashMap();
        translation.put("Deutscher Titel", "English Title");
        translation.put("Vorname", "<NAME>");

        return translation.getOrDefault(fromString, "NO TRANSLATION");
    }

    private MySpecialState.PossibleState possibleState(final String name, String title)
    {
        return new Immutable<>(MySpecialState.PossibleState.class) //
            .in(path -> path::name).set(name) //
            .in(path -> path::title).set(title) //
            .asObject();
    }

    @Override
    public Immutable<MySpecialState> reduce(Immutable<MySpecialState> state, MySpecialState path, Action<?> action)
    {
        // TODO is this a good pattern? explicit call vs. implicit call?
        // TODO advantage: middleware - logging & co
        return reducers.reduce(state, path, action);
    }
}

{'fraction_non_alphanumeric': 0.15335583052118484, 'fraction_numerical': 0.001999750031246094, 'mean_word_length': 2.430439442658092, 'pattern_counts': {'":': 7, '<': 33, '<?xml version=': 0, '>': 104, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5831841', 'n_tokens_mistral': 4593, 'n_tokens_neox': 4437, 'n_words': 877}