/*
 * Copyright 2000-2016 Vaadin Ltd.
 *
 * Licensed 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 com.vaadin.data;

import java.io.Serializable;
import java.util.Objects;
import java.util.Optional;

import com.vaadin.server.SerializableConsumer;
import com.vaadin.server.SerializableFunction;
import com.vaadin.server.SerializableSupplier;

/**
 * Represents the result of an operation that might fail, such as type
 * conversion. A result may contain either a value, signifying a successful
 * operation, or an error message in case of a failure.
 * <p>
 * Result instances are created using the factory methods {@link #ok(R)} and
 * {@link #error(String)}, denoting success and failure respectively.
 * <p>
 * Unless otherwise specified, {@code Result} method arguments cannot be null.
 *
 * @param <R>
 *            the result value type
 */
public interface Result<R> extends Serializable {

    /**
     * Returns a successful result wrapping the given value.
     *
     * @param <R>
     *            the result value type
     * @param value
     *            the result value, can be null
     * @return a successful result
     */
    public static <R> Result<R> ok(R value) {
        return new SimpleResult<>(value, null);
    }

    /**
     * Returns a failure result wrapping the given error message.
     *
     * @param <R>
     *            the result value type
     * @param message
     *            the error message
     * @return a failure result
     */
    public static <R> Result<R> error(String message) {
        Objects.requireNonNull(message, "message cannot be null");
        return new SimpleResult<>(null, message);
    }

    /**
     * Returns a Result representing the result of invoking the given supplier.
     * If the supplier returns a value, returns a {@code Result.ok} of the
     * value; if an exception is thrown, returns the message in a
     * {@code Result.error}.
     *
     * @param <R>
     *            the result value type
     * @param supplier
     *            the supplier to run
     * @param onError
     *            the function to provide the error message
     * @return the result of invoking the supplier
     */
    public static <R> Result<R> of(SerializableSupplier<R> supplier,
            SerializableFunction<Exception, String> onError) {
        Objects.requireNonNull(supplier, "supplier cannot be null");
        Objects.requireNonNull(onError, "onError cannot be null");

        try {
            return ok(supplier.get());
        } catch (Exception e) {
            return error(onError.apply(e));
        }
    }

    /**
     * If this Result has a value, returns a Result of applying the given
     * function to the value. Otherwise, returns a Result bearing the same error
     * as this one. Note that any exceptions thrown by the mapping function are
     * not wrapped but allowed to propagate.
     *
     * @param <S>
     *            the type of the mapped value
     * @param mapper
     *            the mapping function
     * @return the mapped result
     */
    public default <S> Result<S> map(SerializableFunction<R, S> mapper) {
        return flatMap(value -> ok(mapper.apply(value)));
    }

    /**
     * If this Result has a value, applies the given Result-returning function
     * to the value. Otherwise, returns a Result bearing the same error as this
     * one. Note that any exceptions thrown by the mapping function are not
     * wrapped but allowed to propagate.
     *
     * @param <S>
     *            the type of the mapped value
     * @param mapper
     *            the mapping function
     * @return the mapped result
     */
    public <S> Result<S> flatMap(SerializableFunction<R, Result<S>> mapper);

    /**
     * Invokes either the first callback or the second one, depending on whether
     * this Result denotes a success or a failure, respectively.
     *
     * @param ifOk
     *            the function to call if success
     * @param ifError
     *            the function to call if failure
     */
    public void handle(SerializableConsumer<R> ifOk,
            SerializableConsumer<String> ifError);

    /**
     * Applies the {@code consumer} if result is not an error.
     *
     * @param consumer
     *            consumer to apply in case it's not an error
     */
    public default void ifOk(SerializableConsumer<R> consumer) {
        handle(consumer, error -> {
        });
    }

    /**
     * Applies the {@code consumer} if result is an error.
     *
     * @param consumer
     *            consumer to apply in case it's an error
     */
    public default void ifError(SerializableConsumer<String> consumer) {
        handle(value -> {
        }, consumer);
    }

    /**
     * Checks if the result denotes an error.
     *
     * @return <code>true</code> if the result denotes an error,
     *         <code>false</code> otherwise
     */
    public boolean isError();

    /**
     * Returns an Optional of the result message, or an empty Optional if none.
     *
     * @return the optional message
     */
    public Optional<String> getMessage();

    /**
     * Return the value, if the result denotes success, otherwise throw an
     * exception to be created by the provided supplier.
     *
     * @param <X>
     *            Type of the exception to be thrown
     * @param exceptionProvider
     *            The provider which will return the exception to be thrown
     *            based on the given error message
     * @return the value
     * @throws X
     *             if this result denotes an error
     */
    public <X extends Throwable> R getOrThrow(
            SerializableFunction<String, ? extends X> exceptionProvider)
            throws X;
}
