/*
 * Copyright 2022 Google LLC
 *
 * 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.google.android.libraries.mobiledatadownload.downloader.offroad.dagger.downloader2;

import static com.google.common.util.concurrent.Futures.immediateFuture;

import android.content.Context;

import com.google.android.downloader.AndroidConnectivityHandler;
import com.google.android.downloader.Downloader;
import com.google.android.downloader.Downloader.StateChangeCallback;
import com.google.android.downloader.FloggerDownloaderLogger;
import com.google.android.downloader.PlatformUrlEngine;
import com.google.android.downloader.UrlEngine;
import com.google.android.libraries.mobiledatadownload.Flags;
import com.google.android.libraries.mobiledatadownload.annotations.MddControlExecutor;
import com.google.android.libraries.mobiledatadownload.annotations.MddDownloadExecutor;
import com.google.android.libraries.mobiledatadownload.annotations.SocketTrafficTag;
import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader;
import com.google.android.libraries.mobiledatadownload.downloader.OAuthTokenProvider;
import com.google.android.libraries.mobiledatadownload.downloader.offroad.ExceptionHandler;
import com.google.android.libraries.mobiledatadownload.downloader.offroad.Offroad2FileDownloader;
import com.google.android.libraries.mobiledatadownload.downloader.offroad.dagger.BaseOffroadFileDownloaderModule;
import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage;
import com.google.android.libraries.mobiledatadownload.file.integration.downloader.DownloadMetadataStore;
import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.util.concurrent.ListeningExecutorService;

import java.util.concurrent.ScheduledExecutorService;

import javax.annotation.Nullable;
import javax.inject.Singleton;

import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;

/**
 * Dagger module for providing FileDownloader that uses Android Downloader2.
 *
 * <p>This module should only be used when {@link MultiSchemeFileDownloader} is being provided by
 * {@link FileDownloaderModule}. That module includes a map of FileDownloader Suppliers, which this
 * module assumes is available to bind into.
 */
@Module(
        includes = {
                BaseOffroadFileDownloaderModule.class,
                BaseFileDownloaderDepsModule.class,
        })
public abstract class BaseFileDownloaderModule {
    @Provides
    @Singleton
    @IntoMap
    @StringKey("https")
    static Supplier<FileDownloader> provideFileDownloader(
            Context context,
            @MddDownloadExecutor ScheduledExecutorService downloadExecutor,
            @MddControlExecutor ListeningExecutorService controlExecutor,
            SynchronousFileStorage fileStorage,
            DownloadMetadataStore downloadMetadataStore,
            Optional<DownloadProgressMonitor> downloadProgressMonitor,
            Optional<Lazy<UrlEngine>> urlEngineOptional,
            Optional<Lazy<ExceptionHandler>> exceptionHandlerOptional,
            Optional<Lazy<OAuthTokenProvider>> authTokenProviderOptional,
//      Optional<Supplier<CookieJar>> cookieJarSupplierOptional,
            @SocketTrafficTag Optional<Integer> trafficTag,
            Flags flags) {
        return () ->
                createOffroad2FileDownloader(
                        context,
                        downloadExecutor,
                        controlExecutor,
                        fileStorage,
                        downloadMetadataStore,
                        downloadProgressMonitor,
                        urlEngineOptional,
                        exceptionHandlerOptional,
                        authTokenProviderOptional,
//            cookieJarSupplierOptional,
                        trafficTag,
                        flags);
    }

    /**
     * Manual provider of Offroad2FileDownloader.
     *
     * <p>NOTE: This method should only be used when manually wiring up dependencies, such as when
     * dagger/hilt are not available. If using dagger/hilt, this method is not needed. By
     * registering
     * this module in the dagger graph, the above @Provides method will automatically provide this
     * dependency.
     */
    public static Offroad2FileDownloader createOffroad2FileDownloader(
            Context context,
            ScheduledExecutorService downloadExecutor,
            ListeningExecutorService controlExecutor,
            SynchronousFileStorage fileStorage,
            DownloadMetadataStore downloadMetadataStore,
            Optional<DownloadProgressMonitor> downloadProgressMonitor,
            Optional<Lazy<UrlEngine>> urlEngineOptional,
            Optional<Lazy<ExceptionHandler>> exceptionHandlerOptional,
            Optional<Lazy<OAuthTokenProvider>> authTokenProviderOptional,
//      Optional<Supplier<CookieJar>> cookieJarSupplierOptional,
            Optional<Integer> trafficTag,
            Flags flags) {
        @Nullable
        com.google.android.downloader.OAuthTokenProvider authTokenProvider =
                authTokenProviderOptional.isPresent()
                        ? convertToDownloaderAuthTokenProvider(
                        authTokenProviderOptional.get().get())
                        : null;

        ExceptionHandler handler =
                exceptionHandlerOptional.transform(Lazy::get).or(
                        ExceptionHandler.withDefaultHandling());

        UrlEngine urlEngine;
        if (urlEngineOptional.isPresent()) {
            urlEngine = urlEngineOptional.get().get();
        } else {
            // Use {@link PlatformUrlEngine} if one was not provided.
            urlEngine =
                    new PlatformUrlEngine(
                            controlExecutor,
                            /* connectTimeoutMs = */ flags.timeToWaitForDownloader(),
                            /* readTimeoutMs = */ flags.timeToWaitForDownloader()
                    );
        }

        AndroidConnectivityHandler connectivityHandler =
                new AndroidConnectivityHandler(
                        context, downloadExecutor, /* timeoutMillis= */
                        flags.timeToWaitForDownloader());

        FloggerDownloaderLogger logger = new FloggerDownloaderLogger();

        Downloader downloader =
                new Downloader.Builder()
                        .withIOExecutor(controlExecutor)
                        .withConnectivityHandler(connectivityHandler)
                        .withMaxConcurrentDownloads(flags.downloaderMaxThreads())
                        .withLogger(logger)
                        .addUrlEngine("https", urlEngine)
                        .build();

        if (downloadProgressMonitor.isPresent()) {
            // Wire up downloader's state changes to DownloadProgressMonitor to handle connectivity
            // pauses.
            StateChangeCallback callback =
                    state -> {
                        if (state.getNumDownloadsPendingConnectivity() > 0
                                && state.getNumDownloadsInFlight() == 0) {
                            // Handle network connectivity pauses
                            downloadProgressMonitor.get().pausedForConnectivity();
                        }
                    };
            downloader.registerStateChangeCallback(callback, controlExecutor);
        }

        return new Offroad2FileDownloader(
                downloader,
                fileStorage,
                downloadExecutor,
                authTokenProvider,
                downloadMetadataStore,
                handler,
//        cookieJarSupplierOptional,
                trafficTag);
    }

    private static com.google.android.downloader.OAuthTokenProvider
    convertToDownloaderAuthTokenProvider(OAuthTokenProvider authTokenProvider) {
        return uri -> immediateFuture(authTokenProvider.provideOAuthToken(uri.toString()));
    }

    private BaseFileDownloaderModule() {
    }
}
