/*
 * *********************************************************************************************************************
 *
 *          !!
 *        .F88X
 *        X8888Y
 *      .}888888N;
 *        i888888N;        .:!              .I$WI:
 *          R888888I      .'N88~            i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
 *          .R888888I    .;N8888~          .X8'  "8I.!,/8"  !%NY8`"8I8~~8>,88I
 *            +888888N;  .8888888Y                                  "&&8Y.}8,
 *            ./888888N;  .R888888Y        .'}~    .>}'.`+>  i}!    "i'  +/'  .'i~  !11,.:">,  .~]!  .i}i
 *              ~888888%:  .I888888l      .]88~`1/iY88Ii+1'.R$8$8]"888888888>  Y8$  W8E  X8E  W8888'188Il}Y88$*
 *              18888888    E8888881    .]W%8$`R8X'&8%++N8i,8N%N8+l8%`  .}8N:.R$RE%N88N%N$K$R  188,FE$8%~Y88I
 *            .E888888I  .i8888888'      .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
 *            8888888I  .,N888888~        ~88i"8W,!N8*.I88.}888%F,i$88"F88"  888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
 *          i888888N'      I888Y          ]88;/EX*IFKFK88X  K8R  .l8W  88Y  ~88}'88E&%8W.X8N``]88!.$8K  .:W8I
 *        .i888888N;        I8Y          .&8$  .X88!  i881.:%888>I88  ;88]  +88+.';;;;:.Y88X  18N.,88l  .+88/
 *      .:R888888I
 *      .&888888I                                          Copyright (c) 2009-2020.  博锐尚格科技股份有限公司
 *        ~8888'
 *        .!88~                                                                     All rights reserved.
 *
 * *********************************************************************************************************************
 */

package com.persagy.server.scheduler.server


import com.alibaba.fastjson.JSON
import com.persagy.server.scheduler.models.TaskEntity
import com.persagy.server.scheduler.models.TaskResultEntity
import com.persagy.server.scheduler.pojo.ChannelHandlerContextWrapper
import com.persagy.server.scheduler.pojo.Command
import com.persagy.server.scheduler.pojo.TaskStatus
import com.persagy.server.scheduler.service.TasksService
import com.persagy.server.scheduler.utils.CommonUtil
import org.apache.log4j.Logger
import java.util.*
import kotlin.random.Random

class DispatchTask @Throws(Exception::class)
constructor(internal var clientList: ArrayList<ChannelHandlerContextWrapper>?) : Runnable {
    private var clientIndex = 0
    /**
     * 获取一个在执行任务时超时的任务id
     * @return
     */
    private val oneExecuteTimeOutTaskId: String?
        get() {
            if (tasks == null || tasks!!.size == 0)
                return null
            val currentTime = CommonUtil.time
            for (id in tasks!!.keys) {
                val task = tasks!![id]
                val expectTime = task!!.expectedFinishTime!!
                val sendTime = task.sentTime
                if (sendTime == null || sendTime <= 0)
                    continue
                if (currentTime - sendTime > expectTime && task.status != 0) {
                    return id
                }
            }
            return null
        }

    private val oneAvailableClient: ChannelHandlerContextWrapper?
        get() {
            var clientCount: Int
            if (clientList == null || clientList!!.size == 0)
                return null
            else {
                clientCount = clientList!!.size
                if (clientCount == 0)
                    return null
                clientIndex %= clientCount
            }
            var wrapper: ChannelHandlerContextWrapper?
            do {
                try {
                    wrapper = clientList!![clientIndex]
                } catch (ex: Exception) {
                    log.error(ex.message)
                    wrapper = null
                    clientCount = clientList!!.size
                }

                if (wrapper == null || !wrapper.isLastRefuseTimeOutPassed) {
                    wrapper = null
                }
                if (wrapper == null || wrapper.clientInfo == null || wrapper.clientInfo!!.length == 0)
                    wrapper = null
                if (clientCount == 0)
                    return null
                clientIndex = (clientIndex + 1) % clientCount
            } while (wrapper == null && clientIndex != 0)
//            if (wrapper != null)
//                log.info("当前选中index :$clientIndex, Client Info : ${wrapper.clientInfo}")
            return wrapper
        }
    val random = Random(99L)
    private//检测到在waiting状态的任务, 返回
    val oneAvailableTask: TaskEntity?
        get() {
            if(tasks!!.isEmpty())
                return null
            var times = random.nextInt(0, tasks!!.size)
            for (taskId in tasks!!.keys) {
                if(times != 0) {
                    --times
                    continue
                }
                val task = tasks!![taskId] ?: continue
                if (task.status == 0 && task.downloadTaskList.size > 0) {
                    return task
                }
            }
            return null
        }

    init {
        tasks = service.getAllTaskMapByStatus(listOf(0, 1, 2))
    }

    override fun run() {
        // 考虑加锁
        var cnt = Long.MIN_VALUE
        while (true) {
            try {
                try {
                    Thread.sleep(500)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
                // 获取到一个在waiting状态的任务
                val toBeSent = oneAvailableTask
                if (toBeSent != null) {
                    // 获取到一个潜在可以接受任务的客户端
                    val client = oneAvailableClient
                    client?.sendTask(toBeSent)
                }
                // 检测sending, sent状态的任务, 是否有超时需要重新下载的任务
                checkSendingTimeOut()
                checkExecuteTimeOut()
                if (++cnt % 60 == 0L) {
                     syncTask()
                }
            } catch (ex: Exception) {
                ex.printStackTrace()
                log.error(ex.message)
                println("出错")
            }
        }

    }



    private fun checkExecuteTimeOut() {
        val id = oneExecuteTimeOutTaskId
        if (id != null) {
            // 是否加锁
            tasks!![id]!!.status = 0
            println("任务" + id + "执行超时, 已重置")
        }
    }

    private fun checkSendingTimeOut() {
        for (client in clientList!!) {
            val id = client.oneSendingTimeOutTaskId
            if (id != null) {
                // 是否加锁
                if(tasks!!.containsKey(id))
                    tasks!![id]!!.status = 0
                client.resetSendingTimeOutTask(id)
                println("任务" + id + "发送超时, 已重置")
            }
        }
    }

    companion object {
        private val log = Logger.getLogger(DispatchTask::class.java)
        private val service = TasksService()
        var tasks: Map<String, TaskEntity>? = null

        /**
         * 是否当前状态下可以执行该命令
         * @param taskId task id
         * @param cmd command
         * @return 写一个状态, 如果不可执行该命令, 返回null
         */
        fun isAllowCmd(taskId: String, cmd: Command): TaskStatus? {
            val task = tasks!![taskId] ?: return null
            val status = TaskStatus.getTaskStatusById(task.status) ?: return null
            val allowCmd = SchedulerHandler.statusTransitionMap[status] ?: return null
            return allowCmd[cmd]
        }

        // 加锁
        fun changeStatusByCmd(taskId: String, cmd: Command, clientInfo: String) {
            val nextStatus = isAllowCmd(taskId, cmd) ?: return
            val task = tasks!![taskId] ?: return
            val newTask = TaskEntity()
            newTask.id = task.id
            newTask.sentTime = CommonUtil.time
            newTask.lastClient = clientInfo
            newTask.status = TaskStatus.getIdByTaskStatus(nextStatus)
            updateTask(newTask)
        }

        fun changeStatusByCmdWithReturnJson(taskId: String, cmd: Command, clientInfo: String, returnJson: String) {
            val nextStatus = isAllowCmd(taskId, cmd) ?: return
            val task = tasks!![taskId] ?: return
//            task.lastClient = clientInfo
//            task.resultJson = returnJson
//            task.status = TaskStatus.getIdByTaskStatus(nextStatus)
            val newTask = TaskEntity()
            newTask.id = task.id
            newTask.lastClient = clientInfo
            processResult(newTask, returnJson)
//            newTask.resultJson = returnJson
            newTask.status = TaskStatus.getIdByTaskStatus(nextStatus)
            updateTask(newTask)
        }

        /**
         * 转存结果到task_result表中
         */
        fun processResult(newTask: TaskEntity, returnJson: String) {
            val json = JSON.parseObject(returnJson)
            if(json == null || json.isEmpty() || !(json.containsKey("Content") || json.containsKey("content"))){
                newTask.resultJson = returnJson
                return
            }
            json.remove("Content")
            json.remove("content")
            newTask.resultJson = json.toString()
            val taskResult = TaskResultEntity()
            taskResult.taskId = newTask.id
            taskResult.content = returnJson
            createTaskResult(taskResult)
        }

        private fun updateTask(task: TaskEntity): Boolean {
            try {
                return service.updateSingleTask(task)
            } catch (e: Exception) {
                e.printStackTrace()
                log.error("任务:" + task.id + "update失败")
                return false
            }
        }

        /**
         * 插入TaskResult对象到表中
         */
        private fun createTaskResult(taskResult: TaskResultEntity): Boolean {
            try {
                return service.createTaskResult(taskResult)
            } catch (e: Exception) {
                e.printStackTrace()
                log.error("任务Result:" + taskResult.taskId + " create失败")
                return false
            }
        }

        fun syncTask() {
            try {
                val tmpTasks = service.getAllTaskMapByStatus(Arrays.asList(0, 1, 2))
                tasks = tmpTasks
                log.info("同步数据库任务完成")
            } catch (e: Exception) {
                e.printStackTrace()
                log.error("同步数据库失败")
            }

        }
    }
}
