from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import zbl
    from typing import Self, Sequence, Literal, Callable, TypeVar, Generator
    from ._typing import _Area, _Areas
    from aclib.cv._typing import HexColorRange
    T = TypeVar('T')

import sys

from aclib.cv import Image, FontLib, DotsetLib, TargetList
from aclib.winlib import winapi

from ._typing import WinTarget
from .window import Window


class CvWindow(Window):

    def __init__(self):
        super().__init__()
        self.__cvflib = FontLib()
        self.__cvdlib = DotsetLib()
        self.__zblcapture: zbl.Capture|None = None

    def __del__(self):
        self.cv_stop_wgc()

    FontLib = FontLib
    DotsetLib = DotsetLib

    def cvset(self, dlib: DotsetLib|None=..., flib: FontLib|None=...) -> Self:
        """传入None删除已设置的库，不传(或传入...)则不修改"""
        if isinstance(flib, FontLib):
            self.__cvflib = flib
        if isinstance(dlib, DotsetLib):
            self.__cvdlib = dlib
        if flib is None:
            self.__cvflib = FontLib()
        if dlib is None:
            self.__cvdlib = DotsetLib()
        return self

    def cv_start_wgc(self):
        """
        启用wgc截图，需要 ***使用64位python下载zbl依赖库：`pip install zbl`***  
        - 调用此方法可对使用gdi方式截图无效的窗口提供截图支持，但仅支持对`overlapped window`顶层窗口进行截图  
        - 不支持的窗口(`child window / popup window`)调用此方法会报参数错误  
        - 在`cv_stop_wgc`被调用前用户会看到窗口被黄色边框包裹提示用户该窗口正在被录制  
        """
        if sys.maxsize <= 2**32:
            raise RuntimeError(
                '要启用wgc截图，请使用64位Python')
        if not self.__zblcapture:
            import zbl
            self.__zblcapture = zbl.Capture(window_handle=self.handle)
            self.__zblcapture.__enter__()

    def cv_stop_wgc(self):
        if self.__zblcapture:
            self.__zblcapture.__exit__()
            self.__zblcapture = None

    def cvcapture(self, area: _Area=None, savepath='') -> Image | None:
        if area and not isinstance(area[0], int):
            area = tuple(map(int, area))
        if self.__zblcapture:
            screenshot = Image.fromarray(self.__zblcapture.grab())
            if area: screenshot = screenshot.crop(area[:2], area[2:])
        else:
            size, buffer = winapi.CaptureWindow(self.handle, area)
            screenshot = Image.frombuffer(buffer, size)
        if savepath:
            screenshot.tofile(savepath)
        return screenshot


    def __cviter_target(self, capareas: _Area|_Areas|None, capscale: float, func: Callable[[Image], T]) -> Generator[tuple[tuple[int,int], T], None, None]:
        if capareas is None:
            capareas = [(0, 0, *self.clientsize)]
        if hasattr(capareas[0], '__index__'):
            capareas = [capareas]
        for caparea in capareas:
            capstart = caparea[:2]
            screenshot = self.cvcapture(caparea)
            if not screenshot:
                continue
            if capscale != 1:
                screenshot = screenshot.scale(capscale)
            yield capstart, func(screenshot)

    def __cvfind_target(self, capareas: _Area|_Areas|None, capscale: float, func: Callable[[Image], WinTarget]) -> WinTarget:
        for start, target in self.__cviter_target(capareas, capscale, func):
            if target:
                return WinTarget.fromtarget(target, self).scale(1/capscale, (0, 0)).offset(*start)
        return WinTarget()

    def __cvfind_targets(self, capareas: _Area|_Areas|None, capscale: float, func: Callable[[Image], TargetList]) -> list[WinTarget]:
        found = []
        for start, targets in self.__cviter_target(capareas, capscale, func):
            found.extend(WinTarget.fromtarget(target, self).scale(1/capscale, (0, 0)).offset(*start) for target in targets)
        return found


    def cvfindcolor(self, rgbranges: HexColorRange, areas: _Area|_Areas=None) -> WinTarget:
        return self.__cvfind_target(areas, 1, lambda cap: cap.findcolor(rgbranges))

    def cvfindcolors(self, rgbranges: HexColorRange, areas: _Area|_Areas=None) -> list[WinTarget]:
        return self.__cvfind_targets(areas, 1, lambda cap: cap.findcolors(rgbranges))

    def cvfinddotset(self,
        dotsets: str|Sequence[str],
        areas: _Area|_Areas=None,
        matchcolor: Literal[0,1]|HexColorRange=None,
        similarity = 0.9,
        capscale = 1.0,
        usedlib: DotsetLib|None=None,
    ) -> WinTarget:
        if len(_dlib := usedlib or self.__cvdlib) == 0:
            return WinTarget()
        return self.__cvfind_target(
            areas, capscale, lambda cap: cap.finddotset(_dlib, dotsets, matchcolor, similarity))

    def cvfinddotsets(self,
        dotsets: str|Sequence[str],
        areas: _Area|_Areas=None,
        matchcolor: Literal[0,1]|HexColorRange=None,
        similarity = 0.9,
        capscale = 1.0,
        ignore_overlaps = False,
        usedlib: DotsetLib|None=None,
    ) -> list[WinTarget]:
        if len(_dlib := usedlib or self.__cvdlib) == 0:
            return []
        return self.__cvfind_targets(
            areas, capscale, lambda cap: cap.finddotsets(_dlib, dotsets, matchcolor, similarity, 1, ignore_overlaps))

    def cvfindtext(self,
        texts: str|Sequence[str],
        areas: _Area|_Areas=None,
        matchcolor: Literal[0,1]|HexColorRange=None,
        similarity = 0.9,
        txtdir: Literal[0,1]=0,
        txtwrap = True,
        capscale = 1.0,
        charset: str|Literal['']=None,
        useflib: FontLib|None=None,
    ) -> WinTarget:
        if len(_flib := useflib or self.__cvflib) == 0:
            return WinTarget()
        return self.__cvfind_target(
            areas, capscale, lambda cap: cap.findtext(_flib, texts, matchcolor, similarity, txtdir, txtwrap, 1, charset))

    def cvfindtexts(self,
        texts: str|Sequence[str],
        areas: _Area|_Areas=None,
        matchcolor: Literal[0,1]|HexColorRange=None,
        similarity = 0.9,
        txtdir: Literal[0,1]=0,
        txtwrap = True,
        capscale = 1.0,
        charset: str|Literal['']=None,
        ignore_overlaps = False,
        useflib: FontLib|None=None,
    ) -> list[WinTarget]:
        if len(_flib := useflib or self.__cvflib) == 0:
            return []
        return self.__cvfind_targets(
            areas, capscale, lambda cap: cap.findtexts(_flib, texts, matchcolor, similarity, txtdir, txtwrap, 1, charset, ignore_overlaps))

    def cvocr(self,
        areas: _Area|_Areas=None,
        matchcolor: Literal[0,1]|HexColorRange=None,
        similarity = 0.9,
        txtdir: Literal[0,1]=0,
        txtwrap = True,
        capscale = 1.0,
        charset: str|Literal['']=None,
        useflib: FontLib|None=None,
    ) -> list[WinTarget]:
        if len(_flib := useflib or self.__cvflib) == 0:
            return []
        texts = []
        for start, ocrgroups in self.__cviter_target(
            areas, capscale, lambda cap: cap.ocr(_flib, matchcolor, similarity, txtdir, txtwrap, 1, charset)
        ):
            texts.extend(ocrgroup.join().scale(1/capscale, (0, 0)).offset(*start) for ocrgroup in ocrgroups)
        return texts
