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.Environment
import android.os.Handler
import android.os.Message
import android.text.TextUtils
import android.view.View
import android.widget.Toast
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.audioEvaluator.resultParse.ReadSentenceResult
import cn.i2edu.dubbing_lib.audioUtils.audioEvaluator.resultParse.XmlResultParser
import cn.i2edu.dubbing_lib.audioUtils.compose.AudioComposer
import cn.i2edu.dubbing_lib.bean.SimpleWord
import cn.i2edu.dubbing_lib.bean.VideoData
import cn.i2edu.dubbing_lib.callback.MixinHandlerCallback
import cn.i2edu.dubbing_lib.util.PausableThreadPool
import com.alibaba.fastjson.JSON
import com.i2edu.dubbing_lib.listener.DownloadListener
import com.i2edu.dubbing_lib.util.AudioEvaluatorUtil
import com.i2edu.dubbing_lib.util.DownloadUtil
import com.i2edu.dubbing_lib.util.EvaluatorCallBack
import com.iflytek.cloud.EvaluatorResult
import com.iflytek.cloud.SpeechConstant
import com.iflytek.cloud.SpeechError
import com.iflytek.cloud.SpeechUtility
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: MethodChannel.Result? = null
  private var videoData: VideoData? = null
  private var videoAudioMixer: VideoAudioMixer? = null
  private val Tag: String = "DubbingPlugin"

  companion object {
    
    private lateinit var channel: MethodChannel
    val CHANNEL_NAME = "cn.hwwwwh.flutter.plugins/DubbingPlugin"
    val PATH_AUDIO = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_record/"
    val PATH_AUDIO_DECODE = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_record_decode/"
    val PATH_AUDIO_EVALUATOR_DECODE = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_record_evaluator_decode/"
    val PATH_BGM_DECODE = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_bgm_decode/"
    val PATH_BGM_RECORD_SYN = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_bgm_record_syn/"
    val PATH_BGM_RECORD_SYN_DECODE = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_bgm_record_syn_decode/"
    val PATH_VIDEO_MIXIN = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_video_mixin/"
    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) {
      val 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) {
      "initSpeechSdk" -> {
        SpeechUtility.createUtility(activity, SpeechConstant.APPID +"=589c15f3")
        result.success(true)
      }
      "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)
        }
      }
      "downLoadVideo" -> {
        downloadVideo(call.argument<String>("url")!!, result)
      }
      "startRecord" -> {
        val duration = call.argument<Int>("duration")!!
        val fileName = call.argument<String>("fileName")!!
        val index = call.argument<Int>("index")!!
        val en = call.argument<String>("content")!!
        // 开启录音
        initMediaRecorder(index, duration, fileName, en, 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")!!
          deleteFileWithPrefix(videoId, PATH_AUDIO)
          deleteFileWithPrefix(videoId, PATH_AUDIO_DECODE)
          result.success(true)
        } catch (e: Exception){
          e.printStackTrace()
          result.error("1003", "clean cache file failed", null)
        }
      }
      "findIsExistCacheVideo" -> {
        try {
          // 取得 video id, 根据前缀进行查找操作
          val videoId = call.argument<String>("videoId")!!
          val map = getExistCache(videoId)
          result.success(map)
        } catch (e: Exception){
          e.printStackTrace()
          result.error("1004", "find 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")!!
          )
          mixinResult = result
          startMixinAudio()
        } catch (e: Exception){
          e.printStackTrace()
          result.error("1005", "mixin audio failed", null)
        }
      }
      else -> result.notImplemented()
    }
  }

  private fun downloadVideo(url: String, result: MethodChannel.Result) {
    val downloadUtil = DownloadUtil()
    downloadUtil.downloadFile(url, object : DownloadListener {
      override fun onStart() {

      }

      override fun onProgress(progress: Int) {
        activity.runOnUiThread {
          channel.invokeMethod("downloadUpdate", mapOf("progress" to progress))
        }
      }

      override fun onFinish(localPath: String) {
        activity.runOnUiThread {
          result.success(localPath)
        }
      }

      override fun onFailure() {
        activity.runOnUiThread {
          result.error("10002", "下载存储错误", null)
        }
      }

    })
  }

  private fun initMediaRecorder(index: Int, duration: Int, fileName: String, en: String, result: MethodChannel.Result) {
    // create file
    val file = File(PATH_AUDIO)
    if (!file.exists()) {
      file.mkdirs()
    }
    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(index, duration, fileName, en, result)
  }

  private fun startRecord(index: Int, duration: Int, fileName: String, en: String, result: MethodChannel.Result) {
    if (mediaRecorder == null) return
    val filePath = PATH_AUDIO + 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(index, fileName, filePath)
                }.start()
                // 评测音频
                Thread {
                  evaluatorRecord(index, filePath, en)
                }.start()
              }
              break
            }
          }
        }
      }, 0, 5)
    } else {
      result.error("1002", "start record failed", null)
    }
  }

  private fun evaluatorRecord(index: Int, recordPath: String, en: String) {
    val dir = File(PATH_AUDIO_EVALUATOR_DECODE)
    if (!dir.exists()) dir.mkdirs()
    val decodePath = PATH_AUDIO_EVALUATOR_DECODE + "/" + UUID.randomUUID().toString() + ".pcm"
    AudioEvaluatorUtil.getInstance(context = activity.applicationContext)
            .setEvaluatorCallBack(object : EvaluatorCallBack {
              override fun onResult(result: EvaluatorResult) {
                activity.runOnUiThread {
                  val builder = StringBuilder()
                  builder.append(result.resultString)
                  if (!TextUtils.isEmpty(builder)) {
                    val resultParser = XmlResultParser()
                    val parseResult = resultParser.parse(builder.toString()) as ReadSentenceResult
                    Toast.makeText(activity.applicationContext,
                            "识别成功,结果：${if (parseResult.is_rejected) "您在乱读哦" else "您没乱读结果为:${parseResult.total_score}"}", Toast.LENGTH_SHORT).show()
                    val formatWords = arrayListOf<SimpleWord>()
                    for (sentence in parseResult.sentences) {
                      val words = sentence.words
                      for (word in words) {
                        // 过滤掉sil、 silv 表示静音， fil 表示噪音
                        if (word.content == "sil" || word.content == "silv" || word.content == "fil")
                          continue
                        formatWords.add(SimpleWord(word.content, word.total_score))
                      }
                    }

                    channel.invokeMethod("evaluatorResult",
                            mapOf("index" to index,"score" to (if(parseResult.is_rejected) null else parseResult.total_score), "words" to JSON.toJSONString(formatWords)))
                  } else {
                    Toast.makeText(activity.applicationContext, "识别失败,结果为空", Toast.LENGTH_SHORT).show()
                    channel.invokeMethod("evaluatorResult", mapOf("index" to index,"score" to null))
                  }
                }
              }

              override fun onError(error: SpeechError) {
                activity.runOnUiThread {
                  Toast.makeText(activity.applicationContext, "识别失败,错误描述：${error.errorDescription},错误码:${error.errorCode}",
                          Toast.LENGTH_SHORT).show()
                  channel.invokeMethod("evaluatorResult", mapOf("index" to index,"score" to null))
                }
              }
            })
            ?.startEvaluator(en, recordPath, decodePath)
  }

  private fun decodeRecord(index: Int, fileName: String, recordPath: String) {
    val decodePath = doDecode(fileName, recordPath, PATH_AUDIO_DECODE)
    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: MethodChannel.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): Map<Int, String> {
    val map = hashMapOf<Int, String>()

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

    // 取得所有文件（这里取decode的文件）
    val decodeFile = File(PATH_AUDIO_DECODE)
    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() {
    pausableThreadPool.execute {
      // 下载背景音乐
      val downloadUtil = DownloadUtil()
      downloadUtil.downloadBGMFile(videoData!!.bgmUrl, object: DownloadListener {
        override fun onStart() {
        }

        override fun onProgress(progress: Int) {
        }

        override fun onFinish(localPath: String) {
          // 下载成功传回bgm地址
          activity.runOnUiThread {
            channel.invokeMethod("bgmAudioPath", mapOf("bgmPath" to localPath))
            videoData!!.bgmPath = localPath
            // step2 解码背景音乐
            val message = Message.obtain()
            message.what = BGM_DOWNLOAD_FINISHED
            mixinHandler.sendMessage(message)
          }
        }

        override fun onFailure() {
          activity.runOnUiThread {
            mixinResult?.error("1005", "down load bgm failed", "")
          }
        }
      })
    }
  }

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

  private fun syncAudios() {
    // 合成背景音乐和录音 （从尾部开始）
    val fileDIR = File(PATH_BGM_RECORD_SYN)
    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(PATH_BGM_RECORD_SYN_DECODE)
      if (!file.exists()) file.mkdirs()
      val finalMixPath = PATH_BGM_RECORD_SYN_DECODE + "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 = PATH_VIDEO_MIXIN + "${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", PATH_VIDEO_MIXIN)
    }
  }

  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
  }

}
