Sprouts.java

package sprouts.impl;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import sprouts.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.*;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;

/**
 *  Exposes an API for configuring the {@link SproutsFactory},
 *  which serves implementations of the various property types in the Sprouts library,
 *  like {@link Event}, {@link Val}, {@link Var}, {@link Vals} and {@link Vars}.
 *  The methods implemented here are used by the various factory methods of the sprouts API like
 *  {@link Var#of(Object)}, {@link Vals#of(Object, Object[])}, {@link Result#of(Object)}...<br>
 *  <b>So technically speaking, this is a configurable singleton, so be careful when using it
 *  as it effectively maintains global + mutable state!</b>
 */
public final class Sprouts implements SproutsFactory
{
    private static final Pattern DEFAULT_ID_PATTERN = Pattern.compile("[a-zA-Z0-9_]*");

    private static SproutsFactory FACTORY = new Sprouts();

    /**
     *  A {@link SproutsFactory} is used by the various factory methods of this API like
     *  {@link Var#of(Object)}, {@link Vals#of(Object, Object[])}, {@link Result#of(Object)}...
     *  to create instances of these properties. <br>
     *  You can plug in your own factory implementation through the {@link #setFactory(SproutsFactory)} method,
     *  where you can then serve your own implementations of the various property types in the Sprouts library.
     *
     *  @return The default factory for creating instances of the various property types in the Sprouts library.
     */
    public static SproutsFactory factory() { return FACTORY; }

    /**
     *  Sets the factory to be used by the various factory methods of this API like
     *  {@link Var#of(Object)}, {@link Vals#of(Object, Object[])}, {@link Result#of(Object)}...
     *  to create instances of these properties. <br>
     *  You can use a custom {@link SproutsFactory} to instantiate and serve your own
     *  implementations of the various property types in the Sprouts library. <br>
     *  <p><b>
     *      WARNING: This is a global + mutable state, so be careful when using it <br>
     *      as it will have global side effects on the various factory methods of this API.
     *  </b>
     *
     *  @param factory The factory to be used by the various factory methods of this API.
     *  @throws NullPointerException if the factory is null.
     */
    public static void setFactory( SproutsFactory factory ) {
        Objects.requireNonNull(factory);
        FACTORY = factory;
    }

    private Sprouts() {}


    @Override
    public <T> ValDelegate<T> delegateOf(
        Val<T> source,
        Channel channel,
        SingleChange change,
        @Nullable T newValue,
        @Nullable T oldValue
    ) {
        return new ValDelegateImpl<>(channel, change, source.id(), source.type(), newValue, oldValue);
    }

    @Override
    public <T> ValsDelegate<T> delegateOf(
        Vals<T> source,
        SequenceChange changeType,
        int index,
        Vals<T> newValues,
        Vals<T> oldValues
    ) {
        return new PropertyListDelegate<>(changeType, index, newValues, oldValues, source);
    }

    @Override
    public Event event() {
        return eventOf( Runnable::run );
    }

    @Override
    public Event eventOf( Event.Executor executor ) {
        return new EventImpl(executor);
    }

    @Override public <T> Val<@Nullable T> valOfNullable( Class<T> type, @Nullable T item ) {
        return Property.ofNullable( true, type, item );
    }

    @Override public <T> Val<@Nullable T> valOfNull( Class<T> type ) {
        return Property.ofNullable( true, type, null );
    }

    @Override public <T> Val<T> valOf( T item ) {
        return Property.of( true, item );
    }

    @Override public <T> Val<T> valOf( Val<T> toBeCopied ) {
        Objects.requireNonNull(toBeCopied);
        return Val.of( toBeCopied.get() ).withId( toBeCopied.id() );
    }

    @Override public <T> Val<@Nullable T> valOfNullable( Val<@Nullable T> toBeCopied ) {
        Objects.requireNonNull(toBeCopied);
        return Val.ofNullable( toBeCopied.type(), toBeCopied.orElseNull() ).withId( toBeCopied.id() );
    }

    @Override
    public <T> Viewable<T> viewOf(Val<T> source) {
        Objects.requireNonNull(source);
        return PropertyView.of( source );
    }

    @Override
    public <T extends @Nullable Object, U extends @Nullable Object> Viewable<@NonNull T> viewOf(Val<T> first, Val<U> second, BiFunction<T, U, @NonNull T> combiner ) {
        Objects.requireNonNull(first);
        Objects.requireNonNull(second);
        Objects.requireNonNull(combiner);
        return PropertyView.viewOf( first, second, combiner );
    }

    @Override
    public <T extends @Nullable Object, U extends @Nullable Object> Viewable<@Nullable T> viewOfNullable(Val<T> first, Val<U> second, BiFunction<T, U, @Nullable T> combiner ) {
        Objects.requireNonNull(first);
        Objects.requireNonNull(second);
        Objects.requireNonNull(combiner);
        return PropertyView.viewOfNullable( first, second, combiner );
    }

    @Override
    public <T extends @Nullable Object, U extends @Nullable Object, R> Viewable<R> viewOf(Class<R> type, Val<T> first, Val<U> second, BiFunction<T, U, R> combiner) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(first);
        Objects.requireNonNull(second);
        Objects.requireNonNull(combiner);
        return PropertyView.viewOf( type, first, second, combiner );
    }

    @Override
    public <T extends @Nullable Object, U extends @Nullable Object, R> Viewable<@Nullable R> viewOfNullable(Class<R> type, Val<T> first, Val<U> second, BiFunction<T, U, @Nullable R> combiner) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(first);
        Objects.requireNonNull(second);
        Objects.requireNonNull(combiner);
        return PropertyView.viewOfNullable( type, first, second, combiner );
    }

    @Override
    public <T> Viewables<T> viewOf(Vals<T> source) {
        Objects.requireNonNull(source);
        return Viewables.cast(source); // TODO: Implement
    }

    @Override
    public <T, U> Viewable<T> viewOf(Class<T> type, Val<U> source, Function<U, T> mapper) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(source);
        Objects.requireNonNull(mapper);
        return PropertyView.of(type, source, mapper);
    }

    @Override
    public <T, U> Viewables<U> viewOf(U nullObject, U errorObject, Vals<T> source, Function<T, @Nullable U> mapper) {
        Objects.requireNonNull(nullObject);
        Objects.requireNonNull(errorObject);
        Objects.requireNonNull(source);
        Objects.requireNonNull(mapper);
        return PropertyListView.of(nullObject, errorObject, source, mapper);
    }

    @Override
    public <T, U> Viewable<U> viewOf(U nullObject, U errorObject, Val<T> source, Function<T, @Nullable U> mapper) {
        Objects.requireNonNull(nullObject);
        Objects.requireNonNull(errorObject);
        Objects.requireNonNull(source);
        Objects.requireNonNull(mapper);
        return PropertyView.of(nullObject, errorObject, source, mapper);
    }

    @Override
    public <T, U> Viewable<@Nullable U> viewOfNullable(Class<U> type, Val<T> source, Function<T, @Nullable U> mapper) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(source);
        Objects.requireNonNull(mapper);
        return PropertyView.ofNullable(type, source, mapper);
    }

    @Override
    public <T, B> Var<B> lensOf(Var<T> source, Lens<T, B> lens) {
        B initialValue;
        try {
            initialValue = lens.getter(Util.fakeNonNull(source.orElseNull()));
        } catch (Exception e) {
            throw new IllegalArgumentException("Lens getter must not throw an exception", e);
        }
        Class<B> type = Util.expectedClassFromItem(initialValue);
        return new PropertyLens<>(
                type,
                Sprouts.factory().defaultId(),
                false,//does not allow null
                initialValue, //may NOT be null
                source,
                lens,
                null
            );
    }

    @Override
    public <T, B> Var<B> lensOf(Var<T> source, B nullObject, Lens<T, B> lens) {
        Objects.requireNonNull(nullObject, "Null object must not be null");
        Objects.requireNonNull(lens, "lens must not be null");
        return PropertyLens.of(source, nullObject, lens);
    }

    @Override
    public <T, B> Var<B> lensOfNullable(Class<B> type, Var<T> source, Lens<T, B> lens) {
        Objects.requireNonNull(type, "Type must not be null");
        Objects.requireNonNull(lens, "letter must not be null");
        return PropertyLens.ofNullable(type, source, lens);
    }

    @Override public <T> Var<T> varOfNullable(Class<T> type, @Nullable T item ) {
        return Property.ofNullable( false, type, item );
    }

    @Override public <T> Var<T> varOfNull(Class<T> type ) {
        return Property.ofNullable( false, type, null );
    }

    @Override public <T> Var<T> varOf(T item ) {
        return Property.of( false, item );
    }

    @Override public <T, V extends T> Var<T> varOf(Class<T> type, V item ) {
        return Property.of( false, type, item );
    }

    @Override public <T> Vals<T> valsOf(Class<T> type ) {
        return PropertyList.of( true, type );
    }

    @SuppressWarnings("unchecked")
    @Override public <T> Vals<T> valsOf(Class<T> type, Val<T>... vars ) {
        return PropertyList.of( true, type, (Var<T>[]) vars );
    }

    @SuppressWarnings("unchecked")
    @Override public <T> Vals<T> valsOf(Val<T> first, Val<T>... rest ) {
        Var<T>[] vars = new Var[rest.length];
        System.arraycopy(rest, 0, vars, 0, rest.length);
        return PropertyList.of( true, (Var<T>) first, vars );
    }

    @SuppressWarnings("unchecked")
    @Override public <T> Vals<T> valsOf( T first, T... rest ) { return PropertyList.of( true, first, rest); }

    @SuppressWarnings("unchecked")
    @Override public <T> Vals<T> valsOf( Class<T> type, T... items ) { return PropertyList.of( true, type, items ); }

    @Override public <T> Vals<T> valsOf( Class<T> type, Iterable<Val<T>> properties ) {
        return PropertyList.of( true, type, (Iterable) properties );
    }

    @Override public <T> Vals<T> valsOf( Class<T> type, Vals<T> vals ) {
        T[] values = (T[]) vals.stream().toArray(Object[]::new);
        return PropertyList.of(true, type, values);
    }

    @SuppressWarnings("unchecked")
    @Override public <T> Vals<@Nullable T> valsOfNullable( Class<T> type, Val<@Nullable T>... vals ) {
        Var<T>[] vars = new Var[vals.length];
        System.arraycopy(vals, 0, vars, 0, vals.length);
        return PropertyList.ofNullable( true, type, vars );
    }

    @Override public <T> Vals<@Nullable T> valsOfNullable( Class<T> type ) {
        return PropertyList.ofNullable( true, type );
    }

    @SuppressWarnings("unchecked")
    @Override public <T> Vals<@Nullable T> valsOfNullable( Class<T> type, @Nullable T... items ) {
        return PropertyList.ofNullable( true, type, items );
    }

    @SuppressWarnings("unchecked")
    @Override public <T> Vals<@Nullable T> valsOfNullable( Val<@Nullable T> first, Val<@Nullable T>... rest ) {
        Var<T>[] vars = new Var[rest.length];
        System.arraycopy(rest, 0, vars, 0, rest.length);
        return PropertyList.ofNullable( true, (Var<T>) first, vars );
    }

    @Override
    public <T> Vals<@Nullable T> valsOfNullable(Class<T> type, Vals<@Nullable T> vals) {
        T[] values = (T[]) vals.stream().toArray(Object[]::new);
        return valsOfNullable(type, values);
    }

    @Override
    public <T> Vars<T> varsOfNullable(Class<T> type, Iterable<Var<T>> vars) {
        Var<@Nullable T>[] varsArray = (Var<@Nullable T>[]) StreamSupport.stream(vars.spliterator(), false).toArray(Var[]::new);
        return varsOfNullable(type,  varsArray);
    }

    @Override
    public <T> Tuple<T> tupleOf(Class<T> type, Maybe<T>... maybes ) {
        T[] items = (T[]) new Object[maybes.length];
        for (int i = 0; i < maybes.length; i++) {
            items[i] = maybes[i].orElseNull();
        }
        return new TupleImpl<>(false, type, items, null);
    }

    @Override
    public <T> Tuple<T> tupleOf(Class<T> type ) {
        return new TupleImpl<>(false, type, null, null);
    }

    @Override
    public <T> Tuple<T> tupleOf(Maybe<T> first, Maybe<T>... rest ) {
        T[] items = (T[]) new Object[rest.length + 1];
        items[0] = first.orElseNull();
        for (int i = 0; i < rest.length; i++) {
            items[i + 1] = rest[i].orElseNull();
        }
        return new TupleImpl<>(false, Util.expectedClassFromItem(first.orElseThrowUnchecked()), items, null);
    }

    @Override
    public <T> Tuple<T> tupleOf(T first, T... rest ) {
        T[] items = (T[]) new Object[rest.length + 1];
        items[0] = first;
        System.arraycopy(rest, 0, items, 1, rest.length);
        return new TupleImpl<>(false, Util.expectedClassFromItem(first), items, null);
    }

    @Override
    public <T> Tuple<T> tupleOf(Class<T> type, T... items ) {
        return new TupleImpl<>(false, type, items, null);
    }

    @Override
    public <T> Tuple<T> tupleOf(Class<T> type, Iterable<T> iterable ) {
        List<T> items = new ArrayList<>();
        iterable.forEach(items::add);
        return new TupleImpl<>(false, type, items);
    }

    @Override
    public <T> Tuple<@Nullable T> tupleOfNullable(Class<T> type, Maybe<@Nullable T>... maybes ) {
        T[] items = (T[]) new Object[maybes.length];
        for (int i = 0; i < maybes.length; i++) {
            items[i] = maybes[i].orElseNull();
        }
        return new TupleImpl<>(true, type, items, null);
    }

    @Override
    public <T> Tuple<@Nullable T> tupleOfNullable(Class<T> type ) {
        return new TupleImpl<>(true, type, null, null);
    }

    @Override
    public <T> Tuple<@Nullable T> tupleOfNullable(Class<T> type, @Nullable T... values ) {
        return new TupleImpl<>(true, type, values, null);
    }

    @Override
    public <T> Tuple<@Nullable T> tupleOfNullable(Maybe<@Nullable T> first, Maybe<@Nullable T>... rest) {
        T[] items = (T[]) new Object[rest.length + 1];
        items[0] = first.orElseNull();
        for (int i = 0; i < rest.length; i++) {
            items[i + 1] = rest[i].orElseNull();
        }
        return new TupleImpl<>(true, first.type(), items, null);
    }

    @Override
    public <T> Tuple<@Nullable T> tupleOfNullable(Class<T> type, Iterable<@Nullable T> iterable) {
        List<T> items = new ArrayList<>();
        iterable.forEach(items::add);
        return new TupleImpl<>(true, type, items);
    }

    @Override
    public <K, V> Association<K, V> associationOf(Class<K> keyType, Class<V> valueType) {
        return new AssociationImpl<>(keyType, valueType);
    }

    @SuppressWarnings("unchecked")
    @Override public <T> Vars<T> varsOf( Class<T> type, Var<T>... vars ) { return PropertyList.of( false, type, vars ); }

    @Override public <T> Vars<T> varsOf( Class<T> type ) { return PropertyList.of( false, type ); }

    @SuppressWarnings("unchecked")
    @Override public <T> Vars<T> varsOf( Var<T> first, Var<T>... rest ) { return PropertyList.of( false, first, rest ); }

    @SuppressWarnings("unchecked")
    @Override public <T> Vars<T> varsOf( T first, T... rest ) { return PropertyList.of( false, first, rest ); }

    @SuppressWarnings("unchecked")
    @Override public <T> Vars<T> varsOf( Class<T> type, T... items ) { return PropertyList.of( false, type, items ); }

    @Override public <T> Vars<T> varsOf( Class<T> type, Iterable<Var<T>> vars ) { return PropertyList.of( false, type, vars ); }

    @SuppressWarnings("unchecked")
    @Override public <T> Vars<@Nullable T> varsOfNullable( Class<T> type, Var<@Nullable T>... vars ) {
        return PropertyList.ofNullable( false, type, vars );
    }

    @Override public <T> Vars<@Nullable T> varsOfNullable( Class<T> type ) { return PropertyList.ofNullable( false, type ); }

    @SuppressWarnings("unchecked")
    @Override public <T> Vars<@Nullable T> varsOfNullable( Class<T> type, @Nullable T... values ) {
        return PropertyList.ofNullable( false, type, values );
    }

    @SuppressWarnings("unchecked")
    @Override public <T> Vars<@Nullable T> varsOfNullable( Var<@Nullable T> first, Var<@Nullable T>... rest ) {
        return PropertyList.ofNullable( false, first, rest );
    }

    @Override public <V> Result<V> resultOf( Class<V> type ) {
        Objects.requireNonNull(type);
        return new ResultImpl<>(type, Collections.emptyList(), null);
    }

    @Override public <V> Result<V> resultOf( V value ) {
        Objects.requireNonNull(value);
        return resultOf(value, Collections.emptyList());
    }

    @Override public <V> Result<V> resultOf( Class<V> type, @Nullable V value ) {
        Objects.requireNonNull(type);
        return resultOf(type, value, Collections.emptyList());
    }

    @Override public <V> Result<V> resultOf( V value, Iterable<Problem> problems ) {
        Objects.requireNonNull(value);
        Objects.requireNonNull(problems);
        Class<V> itemType = Util.expectedClassFromItem(value);
        return new ResultImpl<>(itemType, problems, value);
    }

    @Override public <V> Result<V> resultOf( Class<V> type, Iterable<Problem> problems ) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(problems);
        return new ResultImpl<>(type, problems, null);
    }

    @Override public <V> Result<V> resultOf( Class<V> type, @Nullable V value, Iterable<Problem> problems ) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(problems);
        return new ResultImpl<>(type, problems, value);
    }

    @Override public <V> Result<V> resultOf( Class<V> type, @Nullable V value, Problem problem ) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(problem);
        return new ResultImpl<>(type, Collections.singletonList(problem), value);
    }

    @Override public <V> Result<V> resultOf( Class<V> type, Problem problem ) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(problem);
        return new ResultImpl<>(type, Collections.singletonList(problem), null);
    }

    @Override public <V> Result<List<V>> resultOfList( Class<V> type, Problem problem ) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(problem);
        return (Result<List<V>>) (Result) new ResultImpl<>(List.class, Collections.singletonList(problem), null);
    }

    @Override public <V> Result<List<V>> resultOfList( Class<V> type, List<V> list ) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(list);
        // We check the types of the list elements are of the correct type.
        boolean matches = list.stream().filter(Objects::nonNull).allMatch(e -> type.isAssignableFrom(e.getClass()));
        if ( !matches )
            throw new IllegalArgumentException("List elements must be of type " + type.getName());
        return (Result<List<V>>) (Result) new ResultImpl<>(List.class, Collections.emptyList(), list);
    }

    @Override public <V> Result<List<V>> resultOfList( Class<V> type, List<V> list, Iterable<Problem> problems ) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(list);
        Objects.requireNonNull(problems);
        boolean matches = list.stream().filter(Objects::nonNull).allMatch(e -> type.isAssignableFrom(e.getClass()));
        if ( !matches )
            throw new IllegalArgumentException("List elements must be of type " + type.getName());
        
        return (Result<List<V>>) (Result) new ResultImpl<>(List.class, problems, list);
    }

    @Override
    public <V> Result<V> resultOfTry( Class<V> type, Supplier<V> supplier ) {
        Objects.requireNonNull(type);
        Objects.requireNonNull(supplier);
        try {
            return resultOf(type, supplier.get());
        } catch (Exception e) {
            return resultOf(type, Problem.of(e));
        }
    }

    @Override
    public <O, D> WeakAction<O, D> actionOfWeak( O owner, BiConsumer<O, D> action ) {
        return new WeakActionImpl<>(owner, action);
    }

    @Override
    public <O> WeakObserver<O> observerOfWeak( O owner, Consumer<O> action ) {
        return new WeakObserverImpl<>(owner, action);
    }

    @Override
    public String defaultId() {
        return "";
    }

    @Override
    public Pattern idPattern() {
        return DEFAULT_ID_PATTERN;
    }

    @Override
    public Channel defaultChannel() {
        return From.VIEW_MODEL;
    }

    @Override
    public Channel defaultObservableChannel() {
        return From.ALL;
    }

}