package org.thoughtcrime.securesms.gcm

import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.content.ContextCompat
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob
import org.thoughtcrime.securesms.messages.RestStrategy
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor

/**
 * Our goals with FCM processing are as follows:
 * (1) Ensure some service is active for the duration of the fetch and processing stages.
 * (2) Do not make unnecessary network requests.
 *
 * To fulfill goal 1, this class will not stop the services until there is no more running
 * requests.
 *
 * To fulfill goal 2, this class  will not enqueue a fetch if there are already 2 active fetches
 * (or rather, 1 active and 1 waiting, since we use a single thread executor).
 *
 * Unfortunately we can't do this all in [FcmReceiveService] because it won't let us process
 * the next FCM message until [FcmReceiveService.onMessageReceived] returns,
 * but as soon as that method returns, it could also destroy the service. By not letting us control
 * when the service is destroyed, we can't accomplish both goals within that service.
 */
object FcmFetchManager {

  private val TAG = Log.tag(FcmFetchManager::class.java)

  private val EXECUTOR = SerialMonoLifoExecutor(SignalExecutors.UNBOUNDED)

  @Volatile
  private var activeCount = 0

  @Volatile
  private var startedForeground = false

  /**
   * @return True if a service was successfully started, otherwise false.
   */
  @JvmStatic
  fun enqueue(context: Context, foreground: Boolean): Boolean {
    synchronized(this) {
      try {
        if (foreground) {
          Log.i(TAG, "Starting in the foreground.")
          ContextCompat.startForegroundService(context, Intent(context, FcmFetchForegroundService::class.java))
          startedForeground = true
        } else {
          Log.i(TAG, "Starting in the background.")
          context.startService(Intent(context, FcmFetchBackgroundService::class.java))
        }

        val performedReplace = EXECUTOR.enqueue { fetch(context) }

        if (performedReplace) {
          Log.i(TAG, "Already have one running and one enqueued. Ignoring.")
        } else {
          activeCount++
          Log.i(TAG, "Incrementing active count to $activeCount")
        }
      } catch (e: Exception) {
        Log.w(TAG, "Failed to start service!", e)
        return false
      }
    }

    return true
  }

  private fun fetch(context: Context) {
    retrieveMessages(context)

    synchronized(this) {
      activeCount--

      if (activeCount <= 0) {
        Log.i(TAG, "No more active. Stopping.")
        context.stopService(Intent(context, FcmFetchBackgroundService::class.java))

        if (startedForeground) {
          try {
            context.startService(FcmFetchForegroundService.buildStopIntent(context))
          } catch (e: IllegalStateException) {
            Log.w(TAG, "Failed to stop the foreground notification!", e)
          }

          startedForeground = false
        }
      }
    }
  }

  @JvmStatic
  fun retrieveMessages(context: Context) {
    val success = ApplicationDependencies.getBackgroundMessageRetriever().retrieveMessages(context, RestStrategy(), RestStrategy())

    if (success) {
      Log.i(TAG, "Successfully retrieved messages.")
    } else {
      if (Build.VERSION.SDK_INT >= 26) {
        Log.w(TAG, "[API ${Build.VERSION.SDK_INT}] Failed to retrieve messages. Scheduling on the system JobScheduler (API " + Build.VERSION.SDK_INT + ").")
        FcmJobService.schedule(context)
      } else {
        Log.w(TAG, "[API ${Build.VERSION.SDK_INT}] Failed to retrieve messages. Scheduling on JobManager (API " + Build.VERSION.SDK_INT + ").")
        ApplicationDependencies.getJobManager().add(PushNotificationReceiveJob())
      }
    }
  }
}
