package app.furmeet.events.brasilfurfest.schedule.managers

import android.content.Context
import android.util.Log
import app.furmeet.events.brasilfurfest.helper.lastPathPart
import app.furmeet.events.brasilfurfest.schedule.abstractDataTypes.dataholder.UploadableFavorite
import com.google.gson.GsonBuilder
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.io.BufferedOutputStream
import java.io.PrintWriter
import java.io.Serializable
import java.io.StringWriter
import java.net.HttpURLConnection
import java.net.HttpURLConnection.HTTP_NOT_MODIFIED
import java.net.HttpURLConnection.HTTP_OK
import java.net.URL
import java.util.concurrent.Future
import java.util.zip.GZIPInputStream

class UpdateChecker(context: Context) : Serializable {
    val cacheManager = CacheManager(context)

    fun deleteGarbage() = cacheManager.removeGarbageFiles()

    fun downloadBaseJsonWithWatcher(watcher: (DownloadState, ByteArray?, Throwable?) -> Any?) =
            downloadWithWatcher(RemoteAssets.json, watcher)

    fun sendUploadableFavorites(uploadables: List<UploadableFavorite>, tierLevel: Int, editionUuid: String, identifier: String, closure: () -> Unit): Future<Unit> {
        val postBody = GsonBuilder().create().toJson(uploadables)
        return doAsync {
            try {
                val url = URL(RemoteAssets.attending)
                val connection = url.openConnection() as HttpURLConnection
                connection.doOutput = true
                connection.doInput = true
                connection.connectTimeout = 3000
                connection.readTimeout = 10000
                connection.setRequestProperty("Content-Type", "application/json")
                val stringWriter = StringWriter()
                val plainIdentifier = identifier.replace("-", "")
                stringWriter.append("$plainIdentifier\n")
                stringWriter.append("$editionUuid\n")
                stringWriter.append("$tierLevel\n")
                stringWriter.append("$postBody\n")
                val dataBytes = stringWriter.toString().toByteArray(charset = Charsets.UTF_8)
                connection.setRequestProperty("Content-Length", dataBytes.size.toString())
                val os = connection.outputStream
                val out = BufferedOutputStream(os)
                out.write(dataBytes)
                out.flush()
                os.flush()
                os.close()
                connection.connect()
                connection.responseCode
                connection.disconnect()
            } catch (t: Throwable) {
                val writer = StringWriter()
                t.printStackTrace(PrintWriter(writer))
                val s = writer.toString()
                Log.e(UpdateChecker::class.java.simpleName, s)
            } finally {
                uiThread { closure() }
            }
        }
    }

    fun downloadWithWatcher(httpThingToFetch: String, watcher: (DownloadState, ByteArray?, Throwable?) -> Any?) =
            if (cacheManager.goesOnline)
                doAsync {
                    try {
                        val url = URL(httpThingToFetch)
                        val diskIdentifier = url.toString().lastPathPart()
                        val connection = url.openConnection() as HttpURLConnection
                        cacheManager.getStamp(diskIdentifier)?.let {
                            connection.setRequestProperty("If-Modified-Since", it)
                        }
                        connection.useCaches = false
                        connection.connectTimeout = 3000
                        connection.readTimeout = 5000
                        connection.allowUserInteraction = false
                        connection.doOutput = false
                        connection.requestMethod = "GET"
                        connection.setRequestProperty("Accept-Encoding", "gzip")
                        uiThread { watcher(DownloadState.CONNECTING, null, null) }
                        connection.connect()
                        try {
                            val resCode = connection.responseCode
                            when (resCode) {
                                HTTP_OK -> {
                                    uiThread { watcher(DownloadState.CACHE_MISS, null, null) }
                                    val lastModified = connection.headerFields["last-modified"]?.firstOrNull()
                                    val data = connection.inputStream.buffered().let {
                                        if (connection.headerFields["content-encoding"]?.firstOrNull() == "gzip")
                                            GZIPInputStream(it)
                                        else
                                            it
                                    }.readBytes()
                                    cacheManager[diskIdentifier] = Pair(data, lastModified ?: "")
                                    uiThread { watcher(DownloadState.SUCCESS, data, null) }
                                }
                                HTTP_NOT_MODIFIED -> {
                                    uiThread { watcher(DownloadState.CACHE_HIT, null, null) }
                                    val bytes = cacheManager[diskIdentifier]?.first?.readBytes()
                                    uiThread { watcher(DownloadState.SUCCESS, bytes, null) }
                                }
                                else -> {
                                    throw IllegalStateException("Server replied with code $resCode. Expected either $HTTP_OK or $HTTP_NOT_MODIFIED.")
                                }
                            }
                        } catch (t: Throwable) {
                            uiThread { watcher(DownloadState.ERROR, null, t) }
                        } finally {
                            connection.disconnect()
                        }
                    } catch (t: Throwable) {
                        uiThread { watcher(DownloadState.ERROR, null, t) }
                    }
                }
            else
                doAsync {
                    uiThread {
                        watcher(DownloadState.CONNECTING, null, null)
                        watcher(DownloadState.CACHE_HIT, null, null)
                        watcher(DownloadState.SUCCESS, cacheManager[httpThingToFetch.lastPathPart()]!!.first.readBytes(), null)
                    }
                }

    enum class DownloadState {
        CONNECTING,
        CACHE_MISS,
        CACHE_HIT,
        ERROR,
        SUCCESS
    }
}