from copy import deepcopy

import cv2
from scipy.signal import argrelmax
from skimage.metrics import structural_similarity

from mower.models import Digtal
from mower.solvers.navigation import NavigationSolver
from mower.utils import config
from mower.utils import typealias as tp
from mower.utils.character_recognize import match_avatar
from mower.utils.graph import SceneGraphSolver
from mower.utils.image import cropimg, loadres, thres2
from mower.utils.log import logger
from mower.utils.scene import Scene
from mower.utils.tile_pos import Calc, find_level
from mower.utils.vector import sa, va


class AutoFight(SceneGraphSolver):
    def run(
        self,
        level_name: str,
        opers: list,
        actions: list,
        groups: list = [],
    ):
        """
        Args:
            level_name: 关卡名
            opers: 干员列表
            actions: 行动列表
            groups: 干员组列表
        """
        logger.info("Start: 自动战斗")
        logger.info("地图坐标计算：https://github.com/yuanyan3060/Arknights-Tile-Pos")
        self.level_name = level_name
        level = find_level(level_name, None)
        self.calc = Calc(1920, 1080, level)
        self.actions = deepcopy(actions)  # 行动列表
        self.opers = opers  # 干员列表
        self.groups = deepcopy(groups)  # 干员组
        self.speed = 1  # 一倍/二倍速
        self.loading = True  # 等待加载
        self.playing = True  # 暂停/继续
        self.Bullet_Time = False  # 是否选中干员进入1/5速度
        self.Skill_Daemon = False  # 开始挂机使用技能
        self.condition_satified = False  # 前置条件是否满足
        self.operators = {}  # 可部署的干员
        self.location = {}  # 干员部署坐标
        self.post_delay = 0  # 行动后等待时间
        self.watching = {}  # 需要开技能的干员
        self.replacements = {}  # 将干员组的名字替换为对应干员
        self.success = True
        super().run()
        return self.success

    def change_group(self):
        for group in self.groups:
            for op in group["opers"]:
                self.replacements[op["name"]] = group["name"]
                op["name"] = group["name"]
                self.opers.append(op)

    def update_watching(self):
        for op in self.opers:
            name = op["name"]
            self.watching[name] = deepcopy(op)
            self.watching[name]["location"] = None

    def number(self, scope: tp.Scope, height: int, thres: int) -> int:
        "数字识别"
        img = cropimg(config.recog.gray, scope)
        default_height = 25
        if height != default_height:
            scale = 25 / height
            img = cv2.resize(img, None, None, scale, scale)
        img = thres2(img, thres)
        contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        rect = [cv2.boundingRect(c) for c in contours]
        rect.sort(key=lambda c: c[0])

        value = 0

        for x, y, w, h in rect:
            digit = cropimg(img, ((x, y), (x + w, y + h)))
            digit = cv2.copyMakeBorder(
                digit, 10, 10, 10, 10, cv2.BORDER_CONSTANT, None, (0,)
            )
            score = []
            for i in range(10):
                im = Digtal().secret_front[i]
                result = cv2.matchTemplate(digit, im, cv2.TM_SQDIFF_NORMED)
                min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
                score.append(min_val)
            value = value * 10 + score.index(min(score))

        return value

    def cost(self) -> int:
        "获取部署费用"
        cost = self.number(((1800, 745), (1920, 805)), 52, 200)
        logger.debug(cost)
        return cost

    def kills(self) -> int:
        "获取击杀数"
        img = cropimg(config.recog.gray, ((800, 30), (950, 60)))
        img = thres2(img, 127)
        sep = loadres("fight/kills_separator", True)
        result = cv2.matchTemplate(img, sep, cv2.TM_SQDIFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        x = min_loc[0] + 799
        kills = self.number(((800, 30), (x, 60)), 28, 127)
        logger.debug(kills)
        return kills

    def CD(self) -> int:
        "获取进入CD的干员数量"
        config.recog.update()
        y = 887
        img = cropimg(config.recog.gray, ((0, y), (1920, 905)))
        threshold = 0.7
        c = loadres("fight/c", True)
        mask = loadres("fight/c_mask", True)
        result = cv2.matchTemplate(img, c, cv2.TM_CCOEFF_NORMED, None, mask)[0]
        op = []
        for i in argrelmax(result, order=50)[0]:
            if result[i] > threshold:
                op.append(i)
        self.operators = {}
        number = 0
        for x in op:
            # 看最下方条的颜色判断是否正在转CD
            bar_scope = sa(((-20, 187), (10, 190)), (x, y))
            img = cropimg(config.recog.img, bar_scope)
            img = cv2.inRange(img, (1, 0, 0), (3, 255, 255))
            count = cv2.countNonZero(img)
            logger.debug(count)
            if count > 50:
                number += 1
        return number

    def sleep_post_delay(self):
        self.sleep(self.post_delay)
        self.post_delay = 0

    def check_condition(self) -> bool:
        if "kills" in self.action and self.action["kills"] > self.kills():
            return False
        if "costs" in self.action and self.action["costs"] > self.cost():
            return False
        if "cooling" in self.action and self.action["cooling"] > self.CD():
            return False
        if "cost_changes" in self.action:
            s = self.cost()
            while self.cost() - s < self.action["cost_changes"]:
                self.sleep(0.5)
        if "pre_delay" in self.action:
            self.sleep(self.action["pre_delay"] / 1000)
        if "post_delay" in self.action:
            self.pre_delay = self.action["post_delay"] / 1000
        if self.Bullet_Time:
            self.tap((400, 100))
            self.sleep(0.3)
            self.Bullet_Time = False
        return True

    def skill_ready(self, x: int, y: int) -> bool:
        """指定坐标的干员技能是否可以开启

        Args:
            x: 横坐标
            y: 纵坐标
        """
        skill_ready = loadres("fight/skill_ready")
        h, w, _ = skill_ready.shape
        pos = self.calc.get_character_screen_pos(x, y, False, False)
        pos = int(pos.x), int(pos.y)
        img = cropimg(config.recog.img, sa(((-15, -168), (20, -128)), pos))
        result = cv2.matchTemplate(img, skill_ready, cv2.TM_CCORR_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        logger.debug(f"{max_val=} {max_loc=}")
        return max_val >= 0.8

    def skill_stop(self, x: int, y: int) -> bool:
        """指定坐标的干员技能是否可以关闭

        Args:
            x: 横坐标
            y: 纵坐标
        """
        skill_stop = loadres("fight/skill_stop")
        h, w, _ = skill_stop.shape
        pos = self.calc.get_character_screen_pos(x, y, False, False)
        pos = int(pos.x), int(pos.y)
        img = cropimg(config.recog.img, sa(((-15, -168), (15, -138)), pos))
        result = cv2.matchTemplate(img, skill_stop, cv2.TM_SQDIFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        logger.debug(f"{min_val=} {min_loc=}")
        return min_val <= 0.2

    def update_operators(self):
        "识别可部署的干员"
        if config.conf.avatar_recog_pause:
            self.pause()
        config.recog.update()
        y = 887
        img = cropimg(config.recog.gray, ((0, y), (1920, 905)))
        threshold = 0.7
        c = loadres("fight/c", True)
        mask = loadres("fight/c_mask", True)
        result = cv2.matchTemplate(img, c, cv2.TM_CCOEFF_NORMED, None, mask)[0]
        op = []
        for i in argrelmax(result, order=50)[0]:
            if result[i] > threshold:
                op.append(i)
        segment = []
        cost = []
        self.operators = {}
        for x in op:
            # 看最下方条的颜色判断是否正在转CD
            bar_scope = sa(((-20, 187), (10, 190)), (x, y))
            img = cropimg(config.recog.img, bar_scope)
            img = cv2.inRange(img, (1, 0, 0), (3, 255, 255))
            count = cv2.countNonZero(img)
            logger.debug(count)
            if count > 50:
                continue

            scope = sa(((-58, 53), (42, 153)), (x, y))
            segment.append(scope)
            cost_scope = sa(((-13, 19), (30, 44)), (x, y))
            cost.append(self.number(cost_scope, 25, 80))
        operators = match_avatar(config.recog.img, segment)
        for idx, (name, scope) in enumerate(operators):
            name = self.replacements[name] if name in self.replacements else name
            self.operators[name] = {"scope": scope, "cost": cost[idx]}
        logger.debug(self.operators)
        if config.conf.avatar_recog_pause:
            self.play()

    @property
    def action(self):
        "下一步行动"
        if len(self.actions) > 0:
            return self.actions[0]
        return None

    def complete_action(self):
        "完成当前行动"
        self.actions.pop(0)
        self.condition_satified = False
        if self.post_delay > 0:
            self.sleep_post_delay()

    def toggle_play(self):
        "切换暂停与继续"
        config.device.tap((1800, 80))
        self.sleep(0.5)
        img = cropimg(config.recog.gray, ((740, 480), (1180, 665)))
        img = thres2(img, 250)
        res = loadres("fight/pause", True)
        ssim = structural_similarity(img, res)
        logger.debug(ssim)
        self.playing = ssim <= 0.9

    def play(self):
        logger.info("继续")
        while not self.playing:
            self.toggle_play()

    def pause(self):
        logger.info("暂停")
        while self.playing:
            self.toggle_play()

    def toggle_speed(self):
        target = 1 if self.speed == 2 else 2
        logger.info(f"切换至{target}倍速")
        config.device.tap((1650, 80))
        self.speed = target
        self.complete_action()

    def bullet_time(self):
        "选中干员进入1/5速度"
        if "name" in self.action:
            name = self.action["name"]
            x, y = self.location[name]
            self.location[name] = None
        else:
            x, y = self.action["location"]
        self.select(x, y)
        self.Bullet_Time = True
        self.complete_action()

    def drag_success(self):
        img = thres2(config.recog.gray, 200)
        contours, _ = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        rect = [cv2.boundingRect(c) for c in contours]
        left = min(x for x, y, w, h in rect if w > 20 and h > 20)
        right = max(x + w for x, y, w, h in rect if w > 20 and h > 20)
        top = min(y for x, y, w, h in rect if w > 20 and h > 20)
        bottom = max(y + h for x, y, w, h in rect if w > 20 and h > 20)
        width = right - left
        height = bottom - top
        logger.debug(f"{width=} {height=}")
        return width < 900 and height < 850

    def deploy(self):
        "部署干员"
        name = self.action["name"]
        if name not in self.operators:
            self.update_operators()
            return
        if self.cost() < self.operators[name]["cost"]:
            return
        start = self.get_pos(self.operators[name]["scope"])
        top = va(start, (0, -100))
        x, y = self.action["location"]
        pos = self.calc.get_character_screen_pos(x, y, True, False)
        pos = int(pos.x), int(pos.y)
        direction = self.action["direction"]
        logger.info(f"在({x}, {y})部署{name}，方向为{direction}")
        if direction in ["Left", "左"]:
            dir = (-200, 0)
        elif direction in ["Right", "右"]:
            dir = (200, 0)
        elif direction in ["Up", "上"]:
            dir = (0, -200)
        else:
            dir = (0, 200)
        dir = va(pos, dir)
        config.device.swipe_ext([start, top, pos], [100, 500], up_wait=400)
        if direction in ["None", "无"]:
            self.operators = {}
            self.complete_action()
            self.location[name] = x, y
            return

        for _ in range(5):
            if not (flag := self.drag_success()):
                self.sleep(0.1)
            else:
                break

        if flag:
            config.device.swipe_ext([pos, dir], [200])
            self.sleep(0.5)
            if self.drag_success():
                self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)
                return
            self.operators = {}
            self.complete_action()
            self.location[name] = x, y
        else:
            self.ctap(va(pos, (-300, -300)), 0.5, config.screenshot_avg / 1000)

    def select(self, x: int, y: int):
        "选中干员"
        pos = self.calc.get_character_screen_pos(x, y, False, False)
        pos = int(pos.x), int(pos.y)
        config.device.tap(pos)

    def withdraw(self):
        "撤下干员"
        if "name" in self.action:
            name = self.action["name"]
            x, y = self.location[name]
            self.location[name] = None
        else:
            x, y = self.action["location"]
        self.select(x, y)
        self.sleep(0.5)
        pos = self.calc.get_with_draw_screen_pos(x, y)
        pos = int(pos.x), int(pos.y)
        config.device.tap(pos)
        self.sleep(0.5)
        self.operators = {}
        self.complete_action()

    def use_skill(self, x, y):
        "开技能"
        self.select(x, y)
        self.sleep(0.5)
        pos = self.calc.get_skill_screen_pos(x, y)
        pos = int(pos.x), int(pos.y)
        self.tap(pos)
        self.sleep(0.8)
        if self.find("fight/attack"):
            self.tap((1900, 30))
            return False
        return True

    def action_skill(self):
        "使用action中指定位置的技能"
        if flag := ("name" in self.action):
            name = self.action["name"]
            x, y = self.location[name]

        else:
            x, y = self.action["location"]
        if self.skill_ready(x, y) or self.skill_stop(x, y):
            self.use_skill(x, y)
            if flag and self.watching[self.action["name"]]["skill_usage"] == 2:
                self.watching[self.action["name"]]["skill_times"] -= 1
                if self.watching[self.action["name"]]["skill_times"] <= 0:
                    del self.watching[self.action["name"]]

            self.complete_action()

    def transition(self):
        if (scene := self.scene()) == Scene.OPERATOR_FIGHT:
            if self.loading:
                self.pause()
                self.loading = False
                self.change_group()
                self.update_operators()
                self.update_watching()
                self.play()
                return

            if self.Skill_Daemon:
                for w, d in self.watching.items():
                    if w not in self.location or d["skill_usage"] == 0:
                        continue
                    x, y = self.location[w]
                    if self.skill_ready(x, y):
                        if self.use_skill(x, y):
                            if d["skill_usage"] == 2:
                                d["skill_times"] -= 1
                                if d["skill_times"] <= 0:
                                    del self.watching[w]
                            return
            if self.action is None:
                return
            if not self.condition_satified:
                self.condition_satified = self.check_condition()
                return
            logger.debug(self.action)
            if self.action["type"] in ["SpeedUp", "二倍速"]:
                self.toggle_speed()
            elif self.action["type"] in ["Deploy", "部署"]:
                self.deploy()
            elif self.action["type"] in ["Retreat", "撤退"]:
                self.withdraw()
            elif self.action["type"] in ["Skill", "技能"]:
                self.action_skill()
            elif self.action["type"] in ["SkillDaemon", "摆完挂机"]:
                self.Skill_Daemon = True
                self.complete_action()
            elif self.action["type"] in ["SkillUsage", "技能用法"]:
                self.watching[self.action["name"]]["skill_usage"] = self.action[
                    "skill_usage"
                ]
                self.complete_action()
            elif self.action["type"] in ["BulletTime", "子弹时间"]:
                self.bullet_time()

            elif self.action["type"] in ["Output", "打印"]:
                self.complete_action()

        elif scene == Scene.OPERATOR_FAILED:
            logger.warning("行动失败，请检查干员/练度")
            self.success = False
            return True

        elif scene == Scene.OPERATOR_FINISH:
            logger.info("行动结束")
            return True

        elif scene == Scene.CONFIRM:
            logger.warning("被顶号")
            self.success = False
            return True

        elif scene in self.waiting_scene:
            self.waiting_solver()
        elif scene == Scene.OPERATOR_SELECT:
            self.tap((1655, 781))
        elif scene == Scene.STORY:
            self.tap_element("story_skip")
        elif scene == Scene.STORY_SKIP:
            self.tap_element("story_skip_confirm_dialog", x_rate=0.94)
        else:
            NavigationSolver().run(self.level_name, mode="copy")
