package cn.i2edu.dubbing_lib

import android.annotation.SuppressLint
import android.app.Activity
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.os.Build
import android.os.Handler
import android.os.Message
import android.text.TextUtils
import android.view.View
import cn.i2edu.dubbing_lib.audioUtils.AudioDecoder
import cn.i2edu.dubbing_lib.audioUtils.AudioEncoder
import cn.i2edu.dubbing_lib.audioUtils.VideoAudioMixer
import cn.i2edu.dubbing_lib.audioUtils.compose.AudioComposer
import cn.i2edu.dubbing_lib.bean.VideoData
import cn.i2edu.dubbing_lib.callback.MixinHandlerCallback
import cn.i2edu.dubbing_lib.util.PausableThreadPool
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import java.io.File
import java.io.IOException
import java.lang.ref.WeakReference
import java.util.*

class DubbingLibPlugin: MethodCallHandler, MixinHandlerCallback {

  private val activity: Activity
  private var mediaRecorder: MediaRecorder? = null
  private var mediaPlayer: MediaPlayer? = null
  private var isRecording: Boolean = false
  private var timer: Timer? = null
  private val pausableThreadPool: PausableThreadPool = PausableThreadPool(1)
  private val mixinHandler: MixinHandler = MixinHandler(this)
  private var mixinResult: Result? = null
  private var videoData: VideoData? = null
  private var videoAudioMixer: VideoAudioMixer? = null
  private val Tag: String = "DubbingPlugin"
  // mix in 时用到的目录
  private var pathBgmDecodeDir: String? = null
  private var pathBgmRecordSyncDir: String? = null
  private var pathBgmRecordDecodeSyncDir: String? = null
  private var pathVideoMixinDir: String? = null

  companion object {
    
    private lateinit var channel: MethodChannel
    val CHANNEL_NAME = "cn.hwwwwh.flutter.plugins/DubbingPlugin"
    private val BGM_DOWNLOAD_FINISHED = 0X3999
    private val AUDIO_SYN_FINISHED = 0X4001
    private val AUDIO_MIX_VIDIO_FINISHED = 0X4002
    private val AUDIO_DECODE_FINISHED = 0x4003
    private val AUDIO_ENCODE_FINISHED = 0x4004
    
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      if (registrar.activity() == null) return
      channel = MethodChannel(registrar.messenger(), "dubbing_lib")
      channel.setMethodCallHandler(DubbingLibPlugin(registrar.activity()))
    }

    private class MixinHandler(plugin: DubbingLibPlugin): Handler() {
      private val plugin: WeakReference<DubbingLibPlugin> = WeakReference(plugin)

      override fun handleMessage(msg: Message) {
        if (plugin.get() != null) {
          plugin.get()?.onHandleMessage(msg)
        }
      }
    }
  }

  @SuppressLint("CheckResult")
  constructor(activity: Activity) {
    this.activity = activity
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    when (call.method) {
      "getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}")
      "setExtraFullScreen" -> {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
          activity.window.decorView.systemUiVisibility = activity.window.decorView.systemUiVisibility.or(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
        }
      }
      "startRecord" -> {
        val duration = call.argument<Int>("duration")!!
        val fileName = call.argument<String>("fileName")!!
        val index = call.argument<Int>("index")!!
        val pathAudio = call.argument<String>("pathAudio")!!
        val pathAudioDecode = call.argument<String>("pathAudioDecode")!!
        // 开启录音
        initMediaRecorder(pathAudio, pathAudioDecode, index, duration, fileName, result)
      }
      "playRecordAudio" -> {
        val fileName = call.argument<String>("fileName")!!
        playRecord(fileName, result)
      }
      "pauseRecordAudio" -> {
        mediaPlayer?.pause()
        result.success(true)
      }
      "getIsMediaPlayPause" -> {
        result.success(mediaPlayer != null && !mediaPlayer!!.isPlaying)
      }
      "cleanAudioData" -> {
        try {
          // 取得 video id, 根据前缀进行删除操作
          val videoId = call.argument<String>("videoId")!!
          val pathAudio = call.argument<String>("pathAudio")!!
          val pathAudioDecode = call.argument<String>("pathAudioDecode")!!
          deleteFileWithPrefix(videoId, pathAudio)
          deleteFileWithPrefix(videoId, pathAudioDecode)
          result.success(true)
        } catch (e: Exception){
          e.printStackTrace()
          result.error("1003", "clean cache file failed", null)
        }
      }
      "startMixinAudio" -> {
        try {
          videoData = VideoData(
                  videoId = call.argument<String>("videoId")!!,
                  bgmUrl = call.argument<String>("bgmUrl")!!,
                  durationList = call.argument<List<Long>>("durationList")!!,
                  endTimeList = call.argument<List<Long>>("endTimeList")!!,
                  audioDecodePaths =  call.argument<List<String>>("audioDecodePaths")!!,
                  videoPath = call.argument<String>("videoPath")!!
          )
          videoData!!.bgmPath = call.argument<String>("bgmPath")!!
          pathBgmDecodeDir = call.argument<String>("pathBgmDecode")!!
          pathBgmRecordSyncDir = call.argument<String>("pathBgmRecordSync")!!
          pathBgmRecordDecodeSyncDir = call.argument<String>("pathBgmRecordDecodeSync")!!
          pathVideoMixinDir = call.argument<String>("pathVideoMixin")!!

          mixinResult = result
          startMixinAudio()
        } catch (e: Exception){
          e.printStackTrace()
          result.error("1005", "mixin audio failed", null)
        }
      }
      else -> result.notImplemented()
    }
  }

  private fun initMediaRecorder(pathAudio: String, pathAudioDecode: String,
                                index: Int, duration: Int, fileName: String, result: Result) {
    // create file
    stopRecord()
    // config media recorder
    if (mediaRecorder == null) {
      mediaRecorder = MediaRecorder()
      mediaRecorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
      mediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)// 设置所录制的音视频文件的格式
      mediaRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)// 设置所录制的声音的编码格式
      mediaRecorder?.setAudioEncodingBitRate(96000)// 比特率
      mediaRecorder?.setAudioChannels(2)// 通道
      mediaRecorder?.setAudioSamplingRate(44100)// 采样率
    }
    // start record
    startRecord(pathAudio, pathAudioDecode, index, duration, fileName, result)
  }

  private fun startRecord(pathAudio: String, pathAudioDecode: String, index: Int, duration: Int, fileName: String, result: Result) {
    if (mediaRecorder == null) return
    val filePath = pathAudio + fileName
    try {
      mediaRecorder?.setOutputFile(filePath)
      mediaRecorder?.prepare()
      isRecording = true
      mediaRecorder?.start()
    } catch (e: IOException) {
      e.printStackTrace()
      isRecording = false
    }
    if (isRecording) {
      var piecePosition = 0
      if (timer != null) {
        timer?.cancel()
      }
      timer = Timer()
      // 这里参照少儿英语秀之前的逻辑
      timer?.schedule(object : TimerTask() {
        override fun run() {
          activity.runOnUiThread {
            isRecording = true
            piecePosition++
            // 传递进度
            channel.invokeMethod("recordProgress", mapOf("progress" to piecePosition * 5))
            while (piecePosition * 5 >= duration) {
              // 录制完毕
              try {
                result.success(filePath)
              } catch (e: Exception) {
                e.printStackTrace()
              } finally {
                timer?.cancel()
                stopRecord()
                // 解码音频
                Thread {
                  decodeRecord(pathAudioDecode, index, fileName, filePath)
                }.start()
              }
              break
            }
          }
        }
      }, 0, 5)
    } else {
      result.error("1002", "start record failed", null)
    }
  }

  private fun decodeRecord(pathAudioDecode: String, index: Int, fileName: String, recordPath: String) {
    val decodePath = doDecode(fileName, recordPath, pathAudioDecode)
    if (decodePath != null && File(decodePath).exists()) {
      // 解码成功
      activity.runOnUiThread {
        channel.invokeMethod("decodeResult", mapOf("index" to index, "path" to decodePath))
      }
    } else {
      // 解码失败
      activity.runOnUiThread {
        channel.invokeMethod("decodeResult", mapOf("index" to index, "path" to ""))
      }
    }
  }

  private fun stopRecord() {
    try {
      mediaRecorder?.stop()
    } catch (e: Exception) {
      // TODO from i2 dubit
      // 这里有可能会抛出两种异常，一种是IllegalStateException,抛出这种异常是由于在没有start的情况下使用了stop
      // 另外一种是RuntimeException， 这种异常的产生原因是在执行了start()过后马上执行stop(),具体可看mRecorder.stop()的源码注释
      e.printStackTrace()
    } finally {
      isRecording = false
      try {
        mediaRecorder?.reset()
        mediaRecorder?.release()
        mediaRecorder = null
      } catch (e: Exception) {
        e.printStackTrace()
      }
    }
  }

  private fun playRecord(fileName: String, result: Result) {
    resetMediaPlayer()
    mediaPlayer = MediaPlayer()
    mediaPlayer?.setDataSource(fileName)
    mediaPlayer?.prepare()
    mediaPlayer?.start()

    mediaPlayer?.setOnCompletionListener {
      resetMediaPlayer()
      // 播放完成
      result.success(true)
    }
  }

  private fun resetMediaPlayer() {
    mediaPlayer?.stop()
    mediaPlayer?.release()
    mediaPlayer = null
  }

  @Throws(Exception::class)
  private fun deleteFileWithPrefix(video: String, path: String): Boolean {
    val file = File(path)
    if (!file.exists()) return false
    // 取得所有文件
    val files = file.listFiles()
    if (files == null || files.isEmpty()) return false
    for (fileExist in files) {
      if (fileExist.isFile) {
        // 验证文件名
        if (fileExist.name.startsWith("${video}_")) {
          fileExist.delete()
        }
      }
    }
    return true
  }

/*  @Throws(Exception::class)
  private fun getExistCache(video: String, pathAudio: String, pathAudioDecode: String): Map<Int, String> {
    val map = hashMapOf<Int, String>()

    // 取得所有文件
    val file = File(pathAudio)
    if (!file.exists()) return map
    val files = file.listFiles()
    if (files == null || files.isEmpty()) return map

    // 取得所有文件（这里取decode的文件）
    val decodeFile = File(pathAudioDecode)
    if (!decodeFile.exists()) return map
    val decodeFiles = decodeFile.listFiles()
    if (decodeFiles == null || decodeFiles.isEmpty()) return map

    for (i in 0 until files.size) {
      val fileExist = files[i]
      if (fileExist.isFile) {
        // 验证文件名
        if (fileExist.name.startsWith("${video}_")) {
          // 填充map
          try {
            val videoIndex = fileExist.name.substring(fileExist.name.indexOf("_") + 1, fileExist.name.length).toInt()
            map[videoIndex]= fileExist.absolutePath.toString()
            // 取decode的文件
            var decodePath = ""
            for (decodeFile in decodeFiles) {
              val decodeVideoIndex = decodeFile.name.substring(decodeFile.name.indexOf("_") + 1, decodeFile.name.length).toInt()
              if (decodeVideoIndex == videoIndex) {
                decodePath = decodeFile.absolutePath
              }
            }
            if (decodePath.isEmpty()) {
              map.remove(videoIndex)
            } else {
              map[videoIndex] = "${map[videoIndex]}|$decodePath"
            }
          } catch (e: Exception) {
            e.printStackTrace()
          }
        }
      }
    }
    return map
  }*/

  // -------------- 合成录音相关

  override fun onHandleMessage(msg: Message) {
    when (msg.what) {
      BGM_DOWNLOAD_FINISHED -> {
        // step2 解码背景音乐
//        Toast.makeText(activity.applicationContext, "BGM_DOWNLOAD_FINISHED", Toast.LENGTH_SHORT).show()
        val i = videoData?.bgmUrl?.lastIndexOf('/')
        val name = videoData?.bgmUrl?.substring(i!!)
        decodeBgmAudio(name!!, videoData?.bgmPath!!)
      }
      AUDIO_DECODE_FINISHED -> {
        // step3  背景音乐与录音合成
//        Toast.makeText(activity.applicationContext, "AUDIO_DECODE_FINISHED", Toast.LENGTH_SHORT).show()
        syncAudios()
      }
      AUDIO_SYN_FINISHED -> {
        // step4 编码音频
//        Toast.makeText(activity.applicationContext, "AUDIO_SYN_FINISHED", Toast.LENGTH_SHORT).show()
        encodeAsynAudio()
      }
      AUDIO_ENCODE_FINISHED -> {
//        Toast.makeText(activity.applicationContext, "AUDIO_ENCODE_FINISHED", Toast.LENGTH_SHORT).show()
        mixinAudioAndVideo()
      }
      AUDIO_MIX_VIDIO_FINISHED -> {
//        Toast.makeText(activity.applicationContext, "AUDIO_MIX_VIDIO_FINISHED", Toast.LENGTH_SHORT).show()
        audioMixVideoFinish()
      }
    }
  }

  private fun startMixinAudio() {
    activity.runOnUiThread {
      // step2 解码背景音乐
      val message = Message.obtain()
      message.what = BGM_DOWNLOAD_FINISHED
      mixinHandler.sendMessage(message)
    }
  }

  private fun decodeBgmAudio(fileName:String, localPath: String) {
    if (TextUtils.isEmpty(localPath)) return
    pausableThreadPool.execute {
      val decodedPath = doDecode(fileName, localPath, pathBgmDecodeDir!!)
      videoData?.decodeBgmPath = decodedPath
      // step3  背景音乐与录音合成
      val message = Message.obtain()
      message.what = AUDIO_DECODE_FINISHED
      mixinHandler.sendMessage(message)
    }
  }

  private fun syncAudios() {
    // 合成背景音乐和录音 （从尾部开始）
    val fileDIR = File(pathBgmRecordSyncDir)
    if (!fileDIR.exists()) {
      fileDIR.mkdirs()
    }
    val mixFilePath = File(fileDIR.absolutePath + "/" + "mixin.mp3")
//        mixFilePath.createNewFile()
    // 合成操作
    val tempPath = arrayOf<String>(videoData?.decodeBgmPath!!)
    pausableThreadPool.execute {
      AudioComposer.composeAudio(tempPath[0],
              videoData?.audioDecodePaths,
              mixFilePath.absolutePath,
              false,
              videoData?.endTimeList,
              videoData?.durationList,
              object : AudioComposer.ComposeAudioInterface {
                override fun composeSuccess(result: String?) {
                  videoData?.decodeAsyncBgmPath = result!!
                  val message = Message.obtain()
                  message.what = AUDIO_SYN_FINISHED
                  mixinHandler.sendMessage(message)
                }

                override fun composeFail() {
                  activity.runOnUiThread {
                    mixinResult?.error("1005", "async bgm and record failed", "")
                  }
                }
              })
    }
  }

  private fun encodeAsynAudio() {
    pausableThreadPool.execute {
      val accEncoder = AudioEncoder
              .createAccEncoder(videoData?.decodeAsyncBgmPath)
//                String finalMixPath = FileBiz.get_temp_recorders_dir() + "/" + UUID.randomUUID() + ".mp4";
      val file = File(pathBgmRecordDecodeSyncDir)
      if (!file.exists()) file.mkdirs()
      val finalMixPath = pathBgmRecordDecodeSyncDir + "mixinDecode.aac"
      val isEncodeFinished = !TextUtils.isEmpty(accEncoder.encodeToFile(finalMixPath))
      if (!isEncodeFinished) {
        activity.runOnUiThread{
          mixinResult?.error("1005", "encode async audio failed", "")
        }
        return@execute
      }
      videoData?.encodeAudioWithBgmPath = finalMixPath
      val message = Message.obtain()
      message.what = AUDIO_ENCODE_FINISHED
      mixinHandler.sendMessage(message)
    }
  }

  private fun mixinAudioAndVideo() {
    pausableThreadPool.execute {
      if (videoAudioMixer == null) {
        videoAudioMixer = VideoAudioMixer(activity.applicationContext)
      }
      videoAudioMixer?.setListener(object: VideoAudioMixer.VideoAudioMixListener {
        override fun mixSuccess() {
          videoData?.mixVideoPath = pathVideoMixinDir + "${videoData?.videoId}_mix.mp4"
          val msg = Message()
          msg.what = AUDIO_MIX_VIDIO_FINISHED
          mixinHandler.sendMessage(msg)
        }

        override fun mixFail(reason: String?) {
          activity.runOnUiThread{
            mixinResult?.error("1005", "mix video and audio failed", "")
          }
        }
      })
      videoAudioMixer?.mux(videoData?.encodeAudioWithBgmPath, videoData?.videoPath,
              "${videoData?.videoId}_mix.mp4", pathVideoMixinDir)
    }
  }

  private fun audioMixVideoFinish() {
    mixinResult?.success(videoData?.mixVideoPath)
  }

  private fun doDecode(fileName: String, path: String, saveDirPath: String): String? {
    try {
      // 解码后的路径
      val decodeFile = File(saveDirPath)
      if (!decodeFile.exists()) {
        decodeFile.mkdirs()
      }
      val finalFile = File(decodeFile.absolutePath + "/" + fileName)
      if (!finalFile.exists()) {
        finalFile.createNewFile()
      }
      val audioDec = AudioDecoder
              .createDefualtDecoder(path)
      audioDec.decodeToFile(finalFile.absolutePath)
      return finalFile.absolutePath
    } catch (e: Exception) {
      e.printStackTrace()
    }
    return null
  }

}
