package $packageName;

import java.util.concurrent.atomic.AtomicInteger;

@javax.annotation.Generated("org.cryptomator.generator.UseCaseProcessor")
@org.cryptomator.domain.di.PerView
public class $simpleName implements org.cryptomator.generator.Unsubscribable {

    private static final AtomicInteger EXECUTION_ID = new AtomicInteger((int)System.currentTimeMillis() & 0x7fffffff);

    private final org.cryptomator.domain.executor.PostExecutionThread postExecutionThread;
    private final org.cryptomator.domain.executor.ThreadExecutor threadExecutor;

#foreach( $anInjected in $injected )
    private final $anInjected.type $anInjected.lowerCaseName;
#end

    private $implClassName impl;
    private org.cryptomator.domain.executor.BackgroundTasks.Registration registration;
    private io.reactivex.disposables.Disposable disposable = io.reactivex.internal.disposables.EmptyDisposable.INSTANCE;

    @javax.inject.Inject
    public ${simpleName}(
            org.cryptomator.domain.executor.ThreadExecutor threadExecutor,
            org.cryptomator.domain.executor.PostExecutionThread postExecutionThread
#foreach( $anInjected in $injected )
            ,$anInjected.type $anInjected.lowerCaseName
#end        ) {
        this.threadExecutor = threadExecutor;
        this.postExecutionThread = postExecutionThread;
#foreach( $anInjected in $injected )
        this.$anInjected.lowerCaseName = $anInjected.lowerCaseName;
#end
    }

    @Override
    public void unsubscribe() {
        if (disposable != null && !disposable.isDisposed()) {
            registration.unregister();
            disposable.dispose();
            disposable = null;
#if ( $hasCancelHandler )
            cancel();
#end
            impl = null;
        }
    }

#if ( $hasCancelHandler )
    public void cancel() {
        $implClassName local = impl;
        if (local != null) {
            local.onCancel();
        }
    }
#end

#foreach( $parameter in $parameters )
    public Launcher with${parameter.upperCaseName}($parameter.type $parameter.lowerCaseName) {
        return new Launcher().and${parameter.upperCaseName}($parameter.lowerCaseName);
    }
#end


#if ( $hasParameters )
    public class Launcher {

        private Launcher() {}

#foreach( $parameter in $parameters )
        private $parameter.type $parameter.lowerCaseName;
#end

#foreach( $parameter in $parameters )
        public Launcher and${parameter.upperCaseName}($parameter.type $parameter.lowerCaseName) {
            this.$parameter.lowerCaseName = $parameter.lowerCaseName;
            return this;
        }
#end

#if ( $hasProgressAware )
        public void run(final org.cryptomator.domain.usecases.ProgressAwareResultHandler<$resultClassName,$progressStateName> resultHandler) {
#else
        public void run(final org.cryptomator.domain.usecases.ResultHandler<$resultClassName> resultHandler) {
#end
            if(registration != null) {
                registration.unregister();
            }
            registration = org.cryptomator.domain.executor.BackgroundTasks.register(${simpleName}.class);
            validate();
            impl = new ${implClassName}(
#foreach ( $injectedOrParam in $injectedAndParams )
                #if ( $injectedOrParam.parameter )
                    this.$injectedOrParam.lowerCaseName
                #elseif ( $injectedOrParam.progressAware)
                    null
                #else
                    ${simpleName}.this.$injectedOrParam.lowerCaseName
                #end
                #if( $foreach.hasNext ),#end
#end
            );
#if ( $hasProgressAware )
            final io.reactivex.subscribers.DisposableSubscriber<org.cryptomator.domain.usecases.ResultWithProgress<$resultClassName,$progressStateName>> subscriber = new io.reactivex.subscribers.DisposableSubscriber<org.cryptomator.domain.usecases.ResultWithProgress<$resultClassName,$progressStateName>>(){
                @Override
                public void onComplete() {
                    resultHandler.onFinished();
                }
                @Override
                public void onError(Throwable e) {
                    resultHandler.onError(e);
                    resultHandler.onFinished();
                }
                @Override
                public void onNext(org.cryptomator.domain.usecases.ResultWithProgress<$resultClassName,$progressStateName> result) {
                    resultHandler.onProgress(result.progress());
                    if (result.value() != null) {
                        resultHandler.onSuccess(result.value());
                    }
                }
            };
            ${simpleName}.this.disposable = subscriber;
            io.reactivex.Flowable.fromPublisher(new org.reactivestreams.Publisher<org.cryptomator.domain.usecases.ResultWithProgress<$resultClassName,$progressStateName>>(){
                public void subscribe(final org.reactivestreams.Subscriber<? super org.cryptomator.domain.usecases.ResultWithProgress<$resultClassName,$progressStateName>> subscriber) {
                    org.cryptomator.domain.usecases.ProgressAware<$progressStateName> progressAware = new org.cryptomator.domain.usecases.ThrottlingProgressAware<$progressStateName>(new org.cryptomator.domain.usecases.ProgressAware<$progressStateName>() {
                        @Override
                        public void onProgress(org.cryptomator.domain.usecases.cloud.Progress<$progressStateName> progress) {
                            subscriber.onNext(org.cryptomator.domain.usecases.ResultWithProgress.<$resultClassName,$progressStateName>progress(progress));
                        }
                    });

                    final int id = EXECUTION_ID.getAndIncrement();
                    try {
                        #if ($resultIsVoid)
                            timber.log.Timber.tag("${simpleName}").d("started %x", id);
                            impl.execute(progressAware);
                            subscriber.onNext(org.cryptomator.domain.usecases.ResultWithProgress.<$resultClassName,$progressStateName>finalResult(null));
                            timber.log.Timber.tag("${simpleName}").d("finished %x", id);
                            subscriber.onComplete();
                            registration.unregister();
                        #else
                            timber.log.Timber.tag("${simpleName}").d("started %x", id);
                            subscriber.onNext(org.cryptomator.domain.usecases.ResultWithProgress.<$resultClassName,$progressStateName>finalResult(impl.execute(progressAware)));
                            timber.log.Timber.tag("${simpleName}").d("finished %x", id);
                            subscriber.onComplete();
                            registration.unregister();
                        #end
                    } catch (Throwable e) {
                        timber.log.Timber.tag("${simpleName}").d("failed %x", id);
                        subscriber.onError(e);
                        registration.unregister();
                    }
                }
#else
            final io.reactivex.subscribers.DisposableSubscriber<$subscriberResultClassName> subscriber = new io.reactivex.subscribers.DisposableSubscriber<$subscriberResultClassName>(){
                @Override
                public void onComplete() {
                    resultHandler.onFinished();
                    registration.unregister();
                }
                @Override
                public void onError(Throwable e) {
                    resultHandler.onError(e);
                    resultHandler.onFinished();
                    registration.unregister();
                }
                @Override
                public void onNext($subscriberResultClassName result) {
    #if ($resultIsVoid)
                    resultHandler.onSuccess(null);
    #else
                    resultHandler.onSuccess(result);
    #end
                }
            };
            ${simpleName}.this.disposable = subscriber;
            io.reactivex.Flowable.fromCallable(new java.util.concurrent.Callable<$subscriberResultClassName>(){
                    public $subscriberResultClassName call() throws Exception {
                        final int id = EXECUTION_ID.getAndIncrement();
                        boolean failed = true;
                        try {
                        #if ($resultIsVoid)
                            timber.log.Timber.tag("${simpleName}").d("started %x", id);
                            impl.execute();
                            failed = false;
                            timber.log.Timber.tag("${simpleName}").d("finished %x", id);
                            return new Object();
                        #else
                            timber.log.Timber.tag("${simpleName}").d("started %x", id);
                            $resultClassName result = impl.execute();
                            failed = false;
                            timber.log.Timber.tag("${simpleName}").d("finished %x", id);
                            return result;
                        #end
                        } finally {
                            if (failed) {
                                timber.log.Timber.tag("${simpleName}").d("failed %x", id);
                            }
                        }
                    }
#end
                })
                .subscribeOn(io.reactivex.schedulers.Schedulers.from(threadExecutor))
                .onBackpressureLatest()
                .observeOn(postExecutionThread.getScheduler())
                .subscribe(subscriber);
        }

        private void validate() {
#foreach( $parameter in $parameters )
    #if (!$parameter.optional)
            if ($parameter.lowerCaseName == null) {
                throw new IllegalStateException("$parameter.lowerCaseName is required");
            }
    #end
#end
        }
    }
#else ## $hasParameters

public void run(final org.cryptomator.domain.usecases.ResultHandler<$resultClassName> resultHandler) {
        if(registration != null) {
                    registration.unregister();
        }
        registration = org.cryptomator.domain.executor.BackgroundTasks.register(${simpleName}.class);
        impl = new ${implClassName}(
#foreach ( $anInjected in $injected )
            this.$anInjected.lowerCaseName
            #if( $foreach.hasNext ),#end
#end
        );
        final io.reactivex.subscribers.DisposableSubscriber<$subscriberResultClassName> subscriber = new io.reactivex.subscribers.DisposableSubscriber<$subscriberResultClassName>(){
            @Override
            public void onComplete() {
                resultHandler.onFinished();
                registration.unregister();
            }
            @Override
            public void onError(Throwable e) {
                resultHandler.onError(e);
                resultHandler.onFinished();
                registration.unregister();
            }
            @Override
            public void onNext($subscriberResultClassName result) {
    #if ($resultIsVoid)
                resultHandler.onSuccess(null);
    #else
                resultHandler.onSuccess(result);
    #end
            }
        };
        this.disposable = subscriber;
        io.reactivex.Flowable.fromCallable(new java.util.concurrent.Callable<$subscriberResultClassName>(){
                public $subscriberResultClassName call() throws Exception {
                    final int id = EXECUTION_ID.getAndIncrement();
                    boolean failed = true;
#if ($resultIsVoid)
                    timber.log.Timber.tag("${simpleName}").d("started %x", id);
                    try {
                        impl.execute();
                        failed = false;
                    } finally {
                        timber.log.Timber.tag("${simpleName}").d("finished %x", id);
                    }
                    return new Object();
#else
                    timber.log.Timber.tag("${simpleName}").d("started %x", id);
                    try {
                        $resultClassName result = impl.execute();
                        failed = false;
                        return result;
                    } finally {
                        if (failed) {
                            timber.log.Timber.tag("${simpleName}").d("failed %x", id);
                        } else {
                            timber.log.Timber.tag("${simpleName}").d("finished %x", id);
                        }
                    }
#end
                }
            })
            .subscribeOn(io.reactivex.schedulers.Schedulers.from(threadExecutor))
            .observeOn(postExecutionThread.getScheduler())
            .subscribe(subscriber);
    }

#end ## $hasParameters

}
