package com.github.davidmoten.rx;

import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

import com.github.davidmoten.util.Preconditions;

import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.plugins.RxJavaHooks;

//TODO as suggested by George Campbell, may replace with his PR
public class ResourceManager<T> {

    private final Func0<T> resourceFactory;
    private final Action1<? super T> disposeAction;
    private final boolean disposeEagerly;

    protected ResourceManager(Func0<T> resourceFactory, Action1<? super T> disposeAction,
            boolean disposeEagerly) {
        Preconditions.checkNotNull(resourceFactory);
        Preconditions.checkNotNull(disposeAction);
        this.resourceFactory = resourceFactory;
        this.disposeAction = disposeAction;
        this.disposeEagerly = disposeEagerly;
    }

    public static <T> ResourceManagerBuilder<T> resourceFactory(Func0<T> resourceFactory) {
        return new ResourceManagerBuilder<T>(resourceFactory);
    }

    public static <T extends Closeable> CloseableResourceManagerBuilder<T> closeableResourceFactory(
            Func0<T> resourceFactory) {
        return new CloseableResourceManagerBuilder<T>(resourceFactory);
    }

    public final static class ResourceManagerBuilder<T> {

        private final Func0<T> resourceFactory;
        private boolean disposeEagerly = false;

        private ResourceManagerBuilder(Func0<T> resourceFactory) {
            this.resourceFactory = resourceFactory;
        }

        public ResourceManagerBuilder<T> disposeEagerly(boolean value) {
            this.disposeEagerly = value;
            return this;
        }

        public ResourceManager<T> disposeAction(Action1<? super T> disposeAction) {
            return new ResourceManager<T>(resourceFactory, disposeAction, disposeEagerly);
        }
    }

    public final static class CloseableResourceManagerBuilder<T extends Closeable> {

        private final Func0<T> resourceFactory;
        private boolean disposeEagerly = false;

        private CloseableResourceManagerBuilder(Func0<T> resourceFactory) {
            this.resourceFactory = resourceFactory;
        }

        public CloseableResourceManagerBuilder<T> disposeEagerly(boolean value) {
            this.disposeEagerly = value;
            return this;
        }

        public ResourceManager<T> create() {
            return new ResourceManager<T>(resourceFactory, CloserHolder.INSTANCE, disposeEagerly);
        }

    }

    public static <T> ResourceManager<T> create(Func0<T> resourceFactory,
            Action1<? super T> disposeAction) {
        return new ResourceManager<T>(resourceFactory, disposeAction, false);
    }

    public static <T> ResourceManager<T> create(Callable<T> resourceFactory,
            Action1<? super T> disposeAction) {
        return new ResourceManager<T>(Functions.toFunc0(resourceFactory), disposeAction, false);
    }

    public static <T> ResourceManager<T> create(Callable<T> resourceFactory,
            Checked.A1<? super T> disposeAction) {
        return new ResourceManager<T>(Functions.toFunc0(resourceFactory), Checked.a1(disposeAction),
                false);
    }

    public static <T extends Closeable> ResourceManager<T> create(Func0<T> resourceFactory) {
        return create(resourceFactory, CloserHolder.INSTANCE);
    }

    public static <T extends Closeable> ResourceManager<T> create(Callable<T> resourceFactory) {
        return create(Functions.toFunc0(resourceFactory), CloserHolder.INSTANCE);
    }

    public <R> Observable<R> observable(
            Func1<? super T, ? extends Observable<? extends R>> observableFactory) {
        return Observable.using(resourceFactory, observableFactory, disposeAction, disposeEagerly);
    }

    public <R> ResourceManager<R> map(final Checked.F1<? super T, ? extends R> resourceMapper,
            final Checked.A1<? super R> disposeAction) {
        return map(Checked.f1(resourceMapper), Checked.a1(disposeAction));
    }

    public <R> ResourceManager<R> map(final Func1<? super T, ? extends R> resourceMapper,
            final Action1<? super R> disposeAction) {
        final AtomicReference<T> ref = new AtomicReference<T>();
        Func0<R> rf = new Func0<R>() {
            @Override
            public R call() {
                T a = resourceFactory.call();
                try {
                    R b = resourceMapper.call(a);
                    ref.set(a);
                    return b;
                } catch (Throwable e) {
                    ResourceManager.this.disposeAction.call(a);
                    if (e instanceof RuntimeException) {
                        throw (RuntimeException) e;
                    } else {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        Action1<R> disposer = new Action1<R>() {
            @Override
            public void call(R r) {
                try {
                    disposeAction.call(r);
                } catch (Throwable e) {
                    RxJavaHooks.onError(e);
                }
                try {
                    ResourceManager.this.disposeAction.call(ref.get());
                } catch (Throwable e) {
                    RxJavaHooks.onError(e);
                }
            }
        };
        return create(rf, disposer);
    }

    public <R extends Closeable> ResourceManager<R> map(
            final Func1<? super T, ? extends R> resourceMapper) {
        return map(resourceMapper, CloserHolder.INSTANCE);
    }

    private static final class CloserHolder {

        static final Action1<Closeable> INSTANCE = new Action1<Closeable>() {

            @Override
            public void call(Closeable c) {
                try {
                    c.close();
                } catch (IOException e) {
                    RxJavaHooks.onError(e);
                }
            }
        };
    }

}
