package net.northking.cctp.element.desktop.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import net.northking.cctp.element.core.DeviceDriver;
import net.northking.cctp.element.core.IExecuteContext;
import net.northking.cctp.element.core.IStepTarget;
import net.northking.cctp.element.core.exception.ComponentRuntimeException;
import net.northking.cctp.element.core.exception.ExecuteException;
import net.northking.cctp.element.desktop.constants.CmdConstant;
import net.northking.cctp.element.desktop.constants.DesktopConstant;
import net.northking.cctp.element.desktop.constants.ErrorMessageConstant;
import net.northking.cctp.element.desktop.constants.RobotParamConstant;
import net.northking.cctp.element.desktop.dto.EngineResult;
import net.northking.cctp.pc.driver.socket.PCDriver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * <p>Title: PublicService</p>
 * <p>Description: 公共服务</p>
 *
 * @Author chenjianfeng
 * @Version V1.0
 * @Date 2022/10/20
 * <p>Copyright: NorthKing@RPA（C）2020 版权</p>
 */

public class PublicService {

    private static final Logger logger = LoggerFactory.getLogger(PublicService.class);

    /**
     * 解析参数并发送
     * @param targets 目标集合
     * @param deviceDriver webSocket客户端
     * @param cmdSuffix 指令后缀
     * @param baseMap 基础信息
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return 发送结果
     */
    public static EngineResult parseParamAndSend(List<IStepTarget> targets, DeviceDriver deviceDriver, String cmdSuffix,
                                           Map<String, Object> baseMap,  long timeout,TimeUnit unit){
        return parseParamAndSend(targets, deviceDriver, cmdSuffix, baseMap, timeout, unit, "", null, false);
    }

    /**
     * 解析参数并发送
     * @param targets 目标集合
     * @param deviceDriver webSocket客户端
     * @param cmdSuffix 指令后缀
     * @param baseMap 基础信息
     * @param timeout 超时时间
     * @param unit 时间单位
     * @param key 返回参数key
     * @param executeContent 上下文参数
     * @return 发送结果
     */
    public static EngineResult parseParamAndSend(List<IStepTarget> targets, DeviceDriver deviceDriver, String cmdSuffix,
                                           Map<String, Object> baseMap, long timeout,TimeUnit unit, String key,
                                                 IExecuteContext executeContent, boolean isContain){
        EngineResult engineResult;
        PCDriver pcDriver = (PCDriver) deviceDriver;
        if (CollUtil.isEmpty(targets)){
            engineResult = processDefaultTarget(pcDriver, cmdSuffix, baseMap, timeout, unit, key, executeContent);
        }else {
            engineResult = processSelectedTarget(pcDriver, targets, cmdSuffix, baseMap, timeout, unit, key, executeContent);
        }
        if (engineResult == null){
            throw new ExecuteException(ErrorMessageConstant.COMPONENT_EXECUTE_ERROR);
        }else{
            if (cmdSuffix.equals(CmdConstant.CMD_SUFFIX_CLICK)){
                logger.debug("点击组件，设置步骤截图信息至上下文中");
                String takeScreenImg = engineResult.getTakeScreenImg();
                if (takeScreenImg != null) {
                    logger.debug("步骤执行时截图【{}】，结果放至上下文中", takeScreenImg);
                    executeContent.setVariable(DesktopConstant.PC_STEP_SNAPSHOT, takeScreenImg);
                }
            }
            if (DesktopConstant.SUCCESS_CODE == engineResult.getState()){
                return engineResult;
            }
            if (isContain){
                if (StrUtil.isBlank(engineResult.getValue())) {
                    engineResult.setValue("false");
                }
                executeContentSet(key, executeContent, engineResult);
                return engineResult;
            }else {
                throw new ExecuteException(engineResult.getMsg());
            }
        }
    }

    /**
     * 上下文参数设置
     * @param key 参数key
     * @param executeContent 上下文
     * @param engineResult 执行结果
     */
    private static void executeContentSet(String key, IExecuteContext executeContent, EngineResult engineResult){
        if (StrUtil.isNotBlank(key)) {
            try {
                if (key.contains("#{") && key.contains("}")) {
                    String keyValue = ReUtil.get("(?<=\\#\\{)(.+?)(?=\\})", key, 1);
                    // #{} 全局变量
                    executeContent.setProjectGlobalVariable(keyValue, engineResult.getValue());
                }
                if (key.contains("${") && key.contains("}")) {
                    String keyValue = ReUtil.get("(?<=\\$\\{)(.+?)(?=\\})", key, 1);
                    // #{} 全局变量
                    executeContent.setVariable(keyValue, engineResult.getValue());
                }
            } catch (Exception e) {
                throw new ExecuteException(ErrorMessageConstant.EXECUTE_CONTENT_SET_ERROR);
            }
        }
    }

    /**
     * 默认目标处理
     * @param pcDriver pc驱动
     * @param cmdSuffix 指令后缀
     * @param baseMap 基础信息
     * @param timeout 超时时间
     * @param unit 时间单位
     * @param key 返回参数key
     * @param executeContent 上下文参数
     * @return 发送结果
     */
    private static EngineResult processDefaultTarget(PCDriver pcDriver, String cmdSuffix,
                                                     Map<String, Object> baseMap, long timeout,
                                                     TimeUnit unit, String key, IExecuteContext executeContent){
        String cmd = CmdConstant.CMD_PREFIX_ELEMENT + cmdSuffix;
        baseMap.put(RobotParamConstant.CMD, cmd);
        JSONObject valueJson = new JSONObject();
        valueJson.putOnce(DesktopConstant.TARGETS_VALUE_SELECTOR, "");
        String serverResult = sendElement(baseMap, valueJson, pcDriver, timeout, unit);
        EngineResult engineResult = JSONUtil.toBean(serverResult, EngineResult.class);
        if (DesktopConstant.SUCCESS_CODE == engineResult.getState()){
            executeContentSet(key, executeContent, engineResult);
            return engineResult;
        }
        return new EngineResult();
    }

    /**
     * 处理选择的目标
     * @param pcDriver pc驱动
     * @param targets 目标集合
     * @param cmdSuffix 指令后缀
     * @param baseMap 基础信息
     * @param timeout  超时时间
     * @param unit 时间单位
     * @param key 返回参数key
     * @param executeContent 上下文参数
     * @return 发送结果
     */
    private static EngineResult processSelectedTarget(PCDriver pcDriver, List<IStepTarget> targets, String cmdSuffix,
                                                      Map<String, Object> baseMap, long timeout,
                                                      TimeUnit unit, String key, IExecuteContext executeContent){
        EngineResult engineResult = new EngineResult();
        Map<String, Object> oldBaseMap = new HashMap<>(baseMap.size());
        oldBaseMap.putAll(baseMap);
        String cmd = cmdSuffix;
        for (IStepTarget target : targets) {
            String using = target.getUsing();
            String value = target.getValue();
            JSONObject valueJson = JSONUtil.parseObj(value);
            String serverResult = "";

            if (ObjectUtil.equal(DesktopConstant.TARGETS_USING_SELECTOR, using)) {
                cmd = CmdConstant.CMD_PREFIX_ELEMENT + cmdSuffix;
                baseMap.put(RobotParamConstant.CMD, cmd);
                serverResult = sendElement(baseMap, valueJson, pcDriver, timeout, unit);
            } else if (ObjectUtil.equal(DesktopConstant.TARGETS_USING_IMAGE, using)) {
                cmd = CmdConstant.CMD_PREFIX_IMAGE + cmdSuffix;
                baseMap.put(RobotParamConstant.CMD, cmd);
                serverResult = sendImage(baseMap, valueJson, pcDriver, timeout, unit);
            } else if (ObjectUtil.equal(DesktopConstant.TARGETS_USING_OCR, using)) {
                cmd = CmdConstant.CMD_PREFIX_OCR + cmdSuffix;
                baseMap.put(RobotParamConstant.CMD, cmd);
                serverResult = sendOcr(baseMap, valueJson, pcDriver, timeout, unit, DesktopConstant.TARGETS_USING_OCR);
            } else if (ObjectUtil.equal(DesktopConstant.TARGETS_USING_HZOCR, using)) {
                cmd = CmdConstant.CMD_PREFIX_OCR + cmdSuffix;
                baseMap.put(RobotParamConstant.CMD, cmd);
                serverResult = sendOcr(baseMap, valueJson, pcDriver, timeout, unit, DesktopConstant.TARGETS_USING_HZOCR);
            } else if (ObjectUtil.equal(DesktopConstant.TARGETS_USING_POINT, using)) {
                cmd = CmdConstant.CMD_PREFIX_POINT + cmdSuffix;
                baseMap.put(RobotParamConstant.CMD, cmd);
                serverResult = sendPoint(baseMap, valueJson, pcDriver, timeout, unit);
            }
            String requestId = baseMap.get(RobotParamConstant.SEQ_ID).toString() + "-" + cmd;
            baseMap = new HashMap<>(oldBaseMap.size());
            baseMap.putAll(oldBaseMap);
            if (StrUtil.isNotBlank(serverResult)) {
                logger.debug("serverResult => {}", serverResult);
                // 判断结果是否成功 value="#{}"
                engineResult = JSONUtil.toBean(serverResult, EngineResult.class);
                String resultId = engineResult.getSeqId() + "-" + engineResult.getCmd();
                if (resultId.equals(requestId)) {
                    if (DesktopConstant.SUCCESS_CODE == engineResult.getState()) {
                        executeContentSet(key, executeContent, engineResult);
                        return engineResult;
                    }
                }
            }
        }
        return engineResult;
    }

    /**
     * 通用发送与返回结果处理
     * @param params 发送参数
     * @param timeout 超时时间（秒）
     * @param driver 驱动
     * @return 结果
     */
    public static String commonSendAndReturn(Map<String, Object> params, long timeout, DeviceDriver driver){
        try {
            PCDriver pcDriver = (PCDriver) driver;
            pcDriver.send(JSONUtil.toJsonStr(params));
            if (!pcDriver.semaphore.tryAcquire(timeout +
                    DesktopConstant.DEFAULT_TIMEOUT_OFFSET, DesktopConstant.DEFAULT_TIMEUNIT)) {
                throw new ComponentRuntimeException(ErrorMessageConstant.COMPONENT_EXECUTE_TIMEOUT);
            }
            if (StrUtil.isBlank(pcDriver.resResult)) {
                throw new ComponentRuntimeException(ErrorMessageConstant.COMPONENT_EXECUTE_RESULT_IS_NULL);
            }
            EngineResult engineResult = JSONUtil.toBean(pcDriver.resResult, EngineResult.class);
            if (!engineResult.getState().equals(DesktopConstant.SUCCESS_CODE)){
                throw new ComponentRuntimeException(engineResult.getMsg());
            }
            return JSONUtil.toJsonStr(engineResult);
        }catch (Exception e){
            throw new ComponentRuntimeException(e.getMessage());
        }
    }

    /**
     * 发送元素信息
     * @param map 基础信息
     * @param valueJson 值
     * @param pcDriver webSocket客户端
     * @return 执行结果
     */
    private static String sendElement(Map<String, Object> map, JSONObject valueJson, PCDriver pcDriver,
                                      long timeout, TimeUnit unit) {
        String selector = valueJson.getStr(DesktopConstant.TARGETS_VALUE_SELECTOR);
        map.put(RobotParamConstant.SELECTOR, selector);
        logger.info("PC element send => " + JSONUtil.toJsonStr(map));
        return sendToRobot(map, pcDriver, timeout, unit);
    }

    /**
     * 发送图片消息
     * @param map 基础信息
     * @param valueJson 值
     * @param pcDriver webSocket客户端
     * @return 执行结果
     */
    private static String sendImage(Map<String, Object> map, JSONObject valueJson, PCDriver pcDriver,
                                    long timeout, TimeUnit unit){
        map.put(RobotParamConstant.SELECTOR, valueJson.getStr(DesktopConstant.TARGETS_VALUE_SELECTOR));
        map.put(RobotParamConstant.IMG_URI, valueJson.getStr(DesktopConstant.TARGETS_VALUE_IMG_URI));
        map.put(RobotParamConstant.MATCHING_THRESHOLD,
                valueJson.getStr(DesktopConstant.TARGETS_VALUE_MATCHING_THRESHOLD));
        logger.info("PC image send => " + JSONUtil.toJsonStr(map));
        return sendToRobot(map, pcDriver, timeout, unit);
    }

    /**
     * 发送OCR消息
     * @param map 基础信息
     * @param valueJson 值
     * @param pcDriver webSocket客户端
     * @return 执行结果
     */
    private static String sendOcr(Map<String, Object> map, JSONObject valueJson, PCDriver pcDriver,
                                  long timeout, TimeUnit unit, String using){
        map.put(RobotParamConstant.USING_TYPE, using);
        String ocrText = valueJson.getStr(DesktopConstant.TARGETS_VALUE_OCR_TEXT);
        if (StrUtil.isNotBlank(ocrText)) {
            map.put(RobotParamConstant.OCR_TEXT, ocrText);
        }
        map.put(RobotParamConstant.CASE_SENSITIVE,
                ObjectUtil.equal("true", valueJson.getStr(DesktopConstant.TARGETS_VALUE_CASE_SENSITIVE)));
        map.put(RobotParamConstant.TOP_BOUND, valueJson.getStr(DesktopConstant.TARGETS_VALUE_TOP_BOUND));
        map.put(RobotParamConstant.BOTTOM_BOUND, valueJson.getStr(DesktopConstant.TARGETS_VALUE_BOTTOM_BOUND));
        map.put(RobotParamConstant.RIGHT_BOUND, valueJson.getStr(DesktopConstant.TARGETS_VALUE_RIGHT_BOUND));
        map.put(RobotParamConstant.LEFT_BOUND, valueJson.getStr(DesktopConstant.TARGETS_VALUE_LEFT_BOUND));
        String index = valueJson.getStr(DesktopConstant.TARGETS_VALUE_INDEX);
        if (StrUtil.isNotBlank(index)) {
            map.put(RobotParamConstant.INDEX, index);
        }
        logger.info("PC ocr send => " + JSONUtil.toJsonStr(map));
        return sendToRobot(map, pcDriver, timeout, unit);
    }

    /**
     * 发送坐标点击消息
     * @param map 基础信息
     * @param valueJson 值
     * @param pcDriver webSocket客户端
     * @return 执行结果
     */
    private static String sendPoint(Map<String, Object> map, JSONObject valueJson, PCDriver pcDriver,
                                    long timeout, TimeUnit unit){
        map.put(RobotParamConstant.SELECTOR, valueJson.getStr(DesktopConstant.TARGETS_VALUE_SELECTOR));
        map.put(RobotParamConstant.POINT_X, valueJson.getStr(DesktopConstant.TARGETS_VALUE_POINT_X));
        map.put(RobotParamConstant.POINT_Y, valueJson.getStr(DesktopConstant.TARGETS_VALUE_POINT_Y));
        logger.info("PC point send => " + JSONUtil.toJsonStr(map));
        return sendToRobot(map, pcDriver, timeout, unit);
    }

    private static String sendToRobot(Map<String, Object> map, PCDriver pcDriver, long timeout, TimeUnit unit){
        try {
            pcDriver.send(JSONUtil.toJsonStr(map));
            logger.info("等待机器人返回结果....");
            logger.info("当前信号量可用许可数： " + pcDriver.semaphore.availablePermits());
            // 初始化设置许可证为0
            pcDriver.semaphore = new Semaphore(0);
            if (pcDriver.semaphore.tryAcquire(timeout +
                    DesktopConstant.DEFAULT_TIMEOUT_OFFSET, unit)) {
                String result = pcDriver.resResult;
                logger.info("机器人结果为：" + result);
                return result;
            }
            logger.info("驱动信号为：" + pcDriver.semaphore);
            EngineResult engineResult = new EngineResult();
            engineResult.setCmd(map.get(RobotParamConstant.CMD).toString());
            engineResult.setSeqId(map.get(RobotParamConstant.SEQ_ID).toString());
            engineResult.setState(DesktopConstant.FAIL_CODE);
            engineResult.setMsg("组件执行超时");
            return JSONUtil.toJsonStr(engineResult);
        }catch (Exception e){
            throw new ExecuteException(ErrorMessageConstant.COMPONENT_EXECUTE_ERROR, e);
        }
    }
}
