package com.github.davidmoten.rx;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observable.Transformer;
import rx.Observer;
import rx.Subscriber;
import rx.functions.Action1;
import rx.functions.Func1;

/**
 * Utility methods for RxJava.
 */
public final class RxUtil {

    /**
     * slf4j logger.
     */
    private static final Logger log = LoggerFactory.getLogger(RxUtil.class);

    private RxUtil() {
        // prevent instantiation
    }

    /**
     * Returns the concatenation of two {@link Observable}s but the first
     * sequence will be emitted in its entirety and ignored before o2 starts
     * emitting.
     * 
     * @param <T>
     *            the generic type of the second observable
     * @param o1
     *            the sequence to ignore
     * @param o2
     *            the sequence to emit after o1 ignored
     * @return observable result of concatenating two observables, ignoring the
     *         first
     */
    @SuppressWarnings("unchecked")
    public static <T> Observable<T> concatButIgnoreFirstSequence(Observable<?> o1,
            Observable<T> o2) {
        return Observable.concat((Observable<T>) o1.ignoreElements(), o2);
    }

    /**
     * Logs errors and onNext at info level using slf4j {@link Logger}.
     * 
     * @param <T>
     *            the return generic type
     * @return a logging {@link Observer}
     */
    public static <T> Observer<? super T> log() {
        return new Observer<T>() {

            @Override
            public void onCompleted() {
                // do nothing
            }

            @Override
            public void onError(Throwable e) {
                log.error(e.getMessage(), e);
            }

            @Override
            public void onNext(T t) {
                log.info(t + "");
            }
        };
    }

    /**
     * Returns an {@link Action1} that increments a counter when the call method
     * is called.
     * 
     * @param <T>
     *            generic type of item being counted
     * @return {@link Action1} to count calls.
     */
    public static <T> CountingAction<T> counter() {
        return new CountingAction<T>();
    }

    public static class CountingAction<T> implements Action1<T> {
        private final AtomicLong count = new AtomicLong(0);

        public Observable<Long> count() {
            return Observable.create(new OnSubscribe<Long>() {

                @Override
                public void call(Subscriber<? super Long> subscriber) {
                    subscriber.onNext(count.get());
                    subscriber.onCompleted();
                }
            });
        }

        @Override
        public void call(T t) {
            count.incrementAndGet();
        }
    }

    public static <T extends Number> Func1<T, Boolean> greaterThanZero() {
        return new Func1<T, Boolean>() {

            @Override
            public Boolean call(T t) {
                return t.doubleValue() > 0;
            }
        };
    }

    /**
     * Returns a {@link Func1} that returns an empty {@link Observable}.
     * 
     * @return
     */
    public static <T> Func1<T, Observable<Object>> toEmpty() {
        return Functions.constant(Observable.<Object> empty());
    }

    /**
     * Returns an {@link Transformer} that flattens a sequence of
     * {@link Observable} into a flat sequence of the items from the
     * Observables. This operator may interleave the items asynchronously.
     * 
     * @return Transformer
     */
    public static <T> Transformer<Observable<T>, T> flatten() {
        return new Transformer<Observable<T>, T>() {

            @Override
            public Observable<T> call(Observable<Observable<T>> source) {
                return source.flatMap(Functions.<Observable<T>> identity());
            }
        };
    }

    /**
     * Adds {@code n} to {@code requested} field and returns the value prior to
     * addition once the addition is successful (uses CAS semantics). If
     * overflows then sets {@code requested} field to {@code Long.MAX_VALUE}.
     * 
     * @param requested
     *            atomic field updater for a request count
     * @param object
     *            contains the field updated by the updater
     * @param n
     *            the number of requests to add to the requested count
     * @return requested value just prior to successful addition
     */
    public static <T> long getAndAddRequest(AtomicLongFieldUpdater<T> requested, T object, long n) {
        // add n to field but check for overflow
        while (true) {
            long current = requested.get(object);
            long next = current + n;
            // check for overflow
            if (next < 0) {
                next = Long.MAX_VALUE;
            }
            if (requested.compareAndSet(object, current, next)) {
                return current;
            }
        }
    }

    /**
     * Adds {@code n} to {@code requested} and returns the value prior to
     * addition once the addition is successful (uses CAS semantics). If
     * overflows then sets {@code requested} field to {@code Long.MAX_VALUE}.
     * 
     * @param requested
     *            atomic field updater for a request count
     * @param object
     *            contains the field updated by the updater
     * @param n
     *            the number of requests to add to the requested count
     * @return requested value just prior to successful addition
     */
    public static long getAndAddRequest(AtomicLong requested, long n) {
        // add n to field but check for overflow
        while (true) {
            long current = requested.get();
            long next = current + n;
            // check for overflow
            if (next < 0) {
                next = Long.MAX_VALUE;
            }
            if (requested.compareAndSet(current, next)) {
                return current;
            }
        }
    }

}