/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * 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.android.quicksearchbox.util

import android.util.Log
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadFactory

/** Executor that uses a single thread and an unbounded work queue. */
class SingleThreadNamedTaskExecutor(threadFactory: ThreadFactory?) : NamedTaskExecutor {
  private val mQueue: LinkedBlockingQueue<NamedTask>
  private val mWorker: Thread

  @Volatile private var mClosed = false
  override fun cancelPendingTasks() {
    if (DBG) Log.d(TAG, "Cancelling " + mQueue.size.toString() + " tasks: " + mWorker.name)
    if (mClosed) {
      throw IllegalStateException("cancelPendingTasks() after close()")
    }
    mQueue.clear()
  }

  override fun close() {
    mClosed = true
    mWorker.interrupt()
    mQueue.clear()
  }

  override fun execute(task: NamedTask?) {
    if (mClosed) {
      throw IllegalStateException("execute() after close()")
    }
    mQueue.add(task)
  }

  private inner class Worker : Runnable {
    override fun run() {
      try {
        loop()
      } finally {
        if (!mClosed) Log.w(TAG, "Worker exited before close")
      }
    }

    private fun loop() {
      val currentThread: Thread = Thread.currentThread()
      val threadName: String = currentThread.getName()
      while (!mClosed) {
        val task: NamedTask =
          try {
            mQueue.take()
          } catch (ex: InterruptedException) {
            continue
          }
        currentThread.setName(threadName + " " + task.name)
        try {
          if (DBG) Log.d(TAG, "Running task " + task.name)
          task.run()
          if (DBG) Log.d(TAG, "Task " + task.name + " complete")
        } catch (ex: RuntimeException) {
          Log.e(TAG, "Task " + task.name + " failed", ex)
        }
      }
    }
  }

  companion object {
    private const val DBG = false
    private const val TAG = "QSB.SingleThreadNamedTaskExecutor"
    @JvmStatic
    fun factory(threadFactory: ThreadFactory?): Factory<NamedTaskExecutor> {
      return object : Factory<NamedTaskExecutor> {
        override fun create(): NamedTaskExecutor {
          return SingleThreadNamedTaskExecutor(threadFactory)
        }
      }
    }
  }

  init {
    mQueue = LinkedBlockingQueue<NamedTask>()
    mWorker = threadFactory!!.newThread(Worker())
    mWorker.start()
  }
}