@file:Suppress("unused")

package tech.shadowfox.shadow.core.networking


import tech.shadowfox.shadow.core.utils.ResetManager
import tech.shadowfox.shadow.core.utils.ResettableLazy
import tech.shadowfox.shadow.core.utils.ResettableLazyManager
import okhttp3.*
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.util.HashMap
import java.util.HashSet


/**
 * Copyright 2017 Camaron Crowe
 *
 * 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.
 **/
@Suppress("MemberVisibilityCanPrivate")
sealed class NetworkProvider {
    protected fun addApiResetManager(key: String, manager: ResetManager) {
        resetManagers.getOrPut(key, { hashSetOf() }).add(manager)
    }

    companion object {

        private val httpClientInjectors: MutableList<(OkHttpClient.Builder) -> Unit> = arrayListOf()
        private val resetManagers: HashMap<String, HashSet<ResetManager>> = hashMapOf()

        fun resetAll() {
            resetManagers.keys.forEach { key ->
                resetManagers[key]!!.forEach { it.reset() }
            }
        }

        fun reset(key: String) {
            resetManagers[key]?.forEach(ResetManager::reset)
        }

        fun addBaseClientInjector(injector: (OkHttpClient.Builder) -> Unit) {
            httpClientInjectors.add(injector)
            resetAll()
        }

        fun baseClient() = OkHttpClient.Builder().apply {
            httpClientInjectors.forEach { it(this) }
        }

        fun initCache(cacheDir: File,
                      cacheSize: Long = (10 * 1024 * 1024),
                      networkAvailable: () -> Boolean = { true }) {

            addBaseClientInjector {
                it.cache(Cache(cacheDir, cacheSize))
                it.addInterceptor { chain ->
                    var request = chain.request()
                    request = if (networkAvailable()) {
                        request.newBuilder().header("Cache-Control", "public, max-age=" + 60).build()
                    } else {
                        request.newBuilder().header("Cache-Control", "public, only-if-cached, max-stale=" + 60 * 60 * 24 * 7).build()
                    }
                    chain.proceed(request)
                }
            }
        }

        fun <S> createNoAuthService(clazz: Class<S>, baseURL: String, client: OkHttpClient.Builder = baseClient()): S {
            return createService(clazz,null, baseURL = baseURL, client = client)
        }

        fun <S> createBasicService(clazz: Class<S>, basic: String, baseURL: String, client: OkHttpClient.Builder = baseClient()): S {
            return createService(clazz, "Basic $basic", baseURL = baseURL, client = client)
        }

        fun <S> createService(clazz: Class<S>, token: String?, baseURL: String, client: OkHttpClient.Builder = baseClient()): S {
            client.addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })

            if (token != null) {
                client.addInterceptor { chain ->
                    val original: Request = chain.request()
                    val request: Request = original.newBuilder()
                            .header("Accept", "application/json")
                            .header("Authorization", token)
                            .method(original.method(), original.body())
                            .build()

                    val response = chain.proceed(request)

                    response
                }
            }


            return Retrofit.Builder()
                    .client(client.build())
                    .baseUrl(baseURL)
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build().create(clazz)
        }
    }
}

@Suppress("MemberVisibilityCanPrivate")
abstract class NetworkInterface<out T> : NetworkProvider() {
    protected val resetManager by lazy { ResettableLazyManager() }
    protected val resetKey: String = ""

    init {
        addApiResetManager(resetKey, resetManager)
    }

    /**
     * Creates lazy api instance using selectApiInterface
     */
    val api: T by ResettableLazy(resetManager) { selectApiInterface() }

    /**
     * Resets the api manager
     */
    fun reset() {
        resetManager.reset()
    }

    /**
     * Creates a standard api instance.
     */
    protected abstract fun createApi(): T

    /**
     * Creates an api interface.  This generally doesn't need to change, but abstracted for
     * convenience.
     */
    protected open fun selectApiInterface(): T {
        return createApi()
    }
}