/*
 * Copyright (c) 2016-present, RxJava Contributors.
 *
 * 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 io.reactivex.rxjava3.internal.util;

import java.io.Serializable;

import java.util.Objects;
import org.reactivestreams.*;

import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable;

/**
 * Lightweight notification handling utility class.
 */
public enum NotificationLite {
    COMPLETE
    ;

    /**
     * Wraps a Throwable.
     */
    static final class ErrorNotification implements Serializable {

        private static final long serialVersionUID = -8759979445933046293L;
        final Throwable e;
        ErrorNotification(Throwable e) {
            this.e = e;
        }

        @Override
        public String toString() {
            return "NotificationLite.Error[" + e + "]";
        }

        @Override
        public int hashCode() {
            return e.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof ErrorNotification) {
                ErrorNotification n = (ErrorNotification) obj;
                return Objects.equals(e, n.e);
            }
            return false;
        }
    }

    /**
     * Wraps a Subscription.
     */
    static final class SubscriptionNotification implements Serializable {

        private static final long serialVersionUID = -1322257508628817540L;
        final Subscription upstream;
        SubscriptionNotification(Subscription s) {
            this.upstream = s;
        }

        @Override
        public String toString() {
            return "NotificationLite.Subscription[" + upstream + "]";
        }
    }

    /**
     * Wraps a Disposable.
     */
    static final class DisposableNotification implements Serializable {

        private static final long serialVersionUID = -7482590109178395495L;
        final Disposable upstream;

        DisposableNotification(Disposable d) {
            this.upstream = d;
        }

        @Override
        public String toString() {
            return "NotificationLite.Disposable[" + upstream + "]";
        }
    }

    /**
     * Converts a value into a notification value.
     * @param <T> the actual value type
     * @param value the value to convert
     * @return the notification representing the value
     */
    public static <T> Object next(T value) {
        return value;
    }

    /**
     * Returns a complete notification.
     * @return a complete notification
     */
    public static Object complete() {
        return COMPLETE;
    }

    /**
     * Converts a Throwable into a notification value.
     * @param e the Throwable to convert
     * @return the notification representing the Throwable
     */
    public static Object error(Throwable e) {
        return new ErrorNotification(e);
    }

    /**
     * Converts a Subscription into a notification value.
     * @param s the Subscription to convert
     * @return the notification representing the Subscription
     */
    public static Object subscription(Subscription s) {
        return new SubscriptionNotification(s);
    }

    /**
     * Converts a Disposable into a notification value.
     * @param d the disposable to convert
     * @return the notification representing the Disposable
     */
    public static Object disposable(Disposable d) {
        return new DisposableNotification(d);
    }

    /**
     * Checks if the given object represents a complete notification.
     * @param o the object to check
     * @return true if the object represents a complete notification
     */
    public static boolean isComplete(Object o) {
        return o == COMPLETE;
    }

    /**
     * Checks if the given object represents a error notification.
     * @param o the object to check
     * @return true if the object represents a error notification
     */
    public static boolean isError(Object o) {
        return o instanceof ErrorNotification;
    }

    /**
     * Checks if the given object represents a subscription notification.
     * @param o the object to check
     * @return true if the object represents a subscription notification
     */
    public static boolean isSubscription(Object o) {
        return o instanceof SubscriptionNotification;
    }

    public static boolean isDisposable(Object o) {
        return o instanceof DisposableNotification;
    }

    /**
     * Extracts the value from the notification object.
     * @param <T> the expected value type when unwrapped
     * @param o the notification object
     * @return the extracted value
     */
    @SuppressWarnings("unchecked")
    public static <T> T getValue(Object o) {
        return (T)o;
    }

    /**
     * Extracts the Throwable from the notification object.
     * @param o the notification object
     * @return the extracted Throwable
     */
    public static Throwable getError(Object o) {
        return ((ErrorNotification)o).e;
    }

    /**
     * Extracts the Subscription from the notification object.
     * @param o the notification object
     * @return the extracted Subscription
     */
    public static Subscription getSubscription(Object o) {
        return ((SubscriptionNotification)o).upstream;
    }

    public static Disposable getDisposable(Object o) {
        return ((DisposableNotification)o).upstream;
    }

    /**
     * Calls the appropriate Subscriber method based on the type of the notification.
     * <p>Does not check for a subscription notification, see {@link #acceptFull(Object, Subscriber)}.
     * @param <T> the expected value type when unwrapped
     * @param o the notification object
     * @param s the subscriber to call methods on
     * @return true if the notification was a terminal event (i.e., complete or error)
     * @see #acceptFull(Object, Subscriber)
     */
    @SuppressWarnings("unchecked")
    public static <T> boolean accept(Object o, Subscriber<? super T> s) {
        if (o == COMPLETE) {
            s.onComplete();
            return true;
        } else
        if (o instanceof ErrorNotification) {
            s.onError(((ErrorNotification)o).e);
            return true;
        }
        s.onNext((T)o);
        return false;
    }

    /**
     * Calls the appropriate Observer method based on the type of the notification.
     * <p>Does not check for a subscription notification.
     * @param <T> the expected value type when unwrapped
     * @param o the notification object
     * @param observer the Observer to call methods on
     * @return true if the notification was a terminal event (i.e., complete or error)
     */
    @SuppressWarnings("unchecked")
    public static <T> boolean accept(Object o, Observer<? super T> observer) {
        if (o == COMPLETE) {
            observer.onComplete();
            return true;
        } else
        if (o instanceof ErrorNotification) {
            observer.onError(((ErrorNotification)o).e);
            return true;
        }
        observer.onNext((T)o);
        return false;
    }

    /**
     * Calls the appropriate Subscriber method based on the type of the notification.
     * @param <T> the expected value type when unwrapped
     * @param o the notification object
     * @param s the subscriber to call methods on
     * @return true if the notification was a terminal event (i.e., complete or error)
     * @see #accept(Object, Subscriber)
     */
    @SuppressWarnings("unchecked")
    public static <T> boolean acceptFull(Object o, Subscriber<? super T> s) {
        if (o == COMPLETE) {
            s.onComplete();
            return true;
        } else
        if (o instanceof ErrorNotification) {
            s.onError(((ErrorNotification)o).e);
            return true;
        } else
        if (o instanceof SubscriptionNotification) {
            s.onSubscribe(((SubscriptionNotification)o).upstream);
            return false;
        }
        s.onNext((T)o);
        return false;
    }

    /**
     * Calls the appropriate Observer method based on the type of the notification.
     * @param <T> the expected value type when unwrapped
     * @param o the notification object
     * @param observer the subscriber to call methods on
     * @return true if the notification was a terminal event (i.e., complete or error)
     * @see #accept(Object, Observer)
     */
    @SuppressWarnings("unchecked")
    public static <T> boolean acceptFull(Object o, Observer<? super T> observer) {
        if (o == COMPLETE) {
            observer.onComplete();
            return true;
        } else
        if (o instanceof ErrorNotification) {
            observer.onError(((ErrorNotification)o).e);
            return true;
        } else
        if (o instanceof DisposableNotification) {
            observer.onSubscribe(((DisposableNotification)o).upstream);
            return false;
        }
        observer.onNext((T)o);
        return false;
    }

    @Override
    public String toString() {
        return "NotificationLite.Complete";
    }
}
