import re
import os
import time
import requests
import io
from typing import Union
from PIL import Image
from unicodedata import east_asian_width

# ------------------------------字符类处理函数---------------------------------
TabWidth = 4
Tab = " " * TabWidth

def getCharWidth(c: str):
    """返回字符宽度
    F W A ：全宽，Na、H：半宽，N：0
    """
    w = east_asian_width(c)
    if w == "N":
        # \t 应该返回多少宽度？
        return 0
    return 2 if w in ("F", "W", "A") else 1


def getStringWidth(s: str):
    """返回字符串去除CSI转义序列、\n、\t后的显示长度"""
    raw = re.sub(r"\033\[[\d;\?]*[a-zA-Z]", "", s)  # 去除csi转义序列
    return sum(getCharWidth(c) for c in raw)

def getEscapeString(s: str):
    """将一些不可见的控制字符转为可见的转义字符，包括空格32之前的和127 Delete（Oct：177）"""
    res = ""
    for i in s:
        if ord(i) < ord(" ") or i == "\177":
            res += i.encode("unicode-escape").decode()
        else:
            res += i
    return res


def padString(s: str, width: int, mode=-1, fillchar=" "):
    """填充字符串s到宽度width （基于占位宽度）
    - mode: -1 左对齐右侧补充字符，0 居中对齐两边补充字符，1右对齐 左侧补充字符"""
    w = getStringWidth(s)
    if w >= width:
        return s
    width_fill_char = getStringWidth(fillchar)
    n = (width - w) // width_fill_char
    if mode == -1:
        s += fillchar * n
    elif mode == 0:
        h = n // 2
        s = fillchar * h + s + fillchar * (n - h)
    elif mode == 1:
        s = fillchar * n + s
    else:
        raise ValueError(f"Parameter mode must be in -1,0,1 (got {mode}).")
    return s


# textwrap.wrap()的简化版，但是该方法不会解析转义序列，因此不采用
def splitLinesByWidth(s: str, width: int) -> list[str]:
    """按照显示宽度分割字符串，\\n 也会被分割，请不要包含 \\t 等字符，CSI转义序列会被保存但不计入宽度"""
    res, csi = [], []  # 结果，转义序列位置
    line, lwidth, i = "", 0, 0
    for match in re.finditer(r"\033\[[\d;\?]*[a-zA-Z]", s):
        csi.append(match.span())
    while i < len(s):
        chr = s[i]
        if csi and csi[0][0] == i:
            i = csi[0][1]
            line += s[csi[0][0] : csi[0][1]]
            csi.pop(0)
            continue
        if chr != "\n":
            line += chr
            lwidth += getCharWidth(chr)
        if lwidth >= width or chr == "\n":
            # ? 如果只剩1宽度，加入一个双宽字符，会溢出1宽度
            res.append(line)
            line = ""
            lwidth = 0
        i += 1
    if line:
        res.append(line)
    return res


# ------------------------------颜色类处理函数---------------------------------

RGB = Union[list[int], tuple[int, int, int]]


def hex2RGB(hex: str):
    """hex color to RGB color"""
    if hex[0] == "#":
        hex = hex[1:]
    hexes = []
    if len(hex) == 6:
        hexes = [hex[:2], hex[2:4], hex[4:]]
    elif len(hex) == 3:
        hexes = [hex[:1] * 2, hex[1:2] * 2, hex[2:] * 2]
    else:
        raise ValueError("Hex color should be like #F0F or #00FFFF")
    return [int(i, 16) for i in hexes]


def genGradient(color_start, color_end, num):
    """生成两个RGB颜色之间的渐变色列表
    - color_start: 起始颜色，格式为 (r, g, b)
    - color_end: 结束颜色，格式为 (r, g, b)
    - num: 总共要生成的渐变色数量（包括起始和结束颜色）

    Returns:
        包含起始颜色、渐变色和结束颜色的列表"""
    r_start, g_start, b_start = color_start
    r_end, g_end, b_end = color_end
    num -= 1
    r_step = (r_end - r_start) / num
    g_step = (g_end - g_start) / num
    b_step = (b_end - b_start) / num
    gradient = []
    for i in range(num + 1):
        r = int(r_start + r_step * i)
        g = int(g_start + g_step * i)
        b = int(b_start + b_step * i)
        gradient.append((r, g, b))
    return gradient

def toImage(img_path):
    try:
        if isinstance(img_path,str):
            # 使用Path的请先str()转为字符串
            if os.path.exists(img_path):
                img = Image.open(img_path)
            elif img_path.startswith("http"):
                res = requests.get(img_path)
                img = Image.open(io.BytesIO(res.content))
            else:
                raise ValueError(f'Not a vaild path or url: {img_path}.')
        elif isinstance(img_path, Image.Image):
            img = img_path
        else:
            raise TypeError("Invalid type!")
    except Exception as e:
        raise ValueError(f"Parameter img_path({img_path}) is not "
                         "a valid image path or instance of Image.")
    return img

def getIMG(img_path: Union[str, Image.Image], height:int, width:int, resample=1):
    img = toImage(img_path)
    # 计算缩放比例
    img_width, img_height = img.size
    ratio_width = width / img_width
    ratio_height = height / img_height
    ratio = min(ratio_width, ratio_height)
    new_width = int(img_width * ratio)
    new_height = int(img_height * ratio)
    if new_height % 2:
        new_height += 1
    # 缩放图片
    img = img.resize((new_width, new_height), resample)
    img = img.convert("RGB")
    return img


# ------------------------------时间类处理函数---------------------------------

PRECISE = 1 / 64 # windows下的时间精度

def sleepPrecise(seconds):
    start = time.perf_counter()
    n = seconds // PRECISE - 1
    if n > 0:
        time.sleep(n * PRECISE)
    while time.perf_counter() - start < seconds:
        pass

# 通过耗时测试性能（本身也耗时，自测耗1μs左右）
class Timer:
    def __init__(self) -> None:
        self.t1 = 0
        self.t2 = 0

    def __enter__(self):
        self.t1 = time.perf_counter()

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.t2 = time.perf_counter()
        print(f"{self.t2 - self.t1:.9f}")

TIMER = Timer()


class FrameTimer:
    def __init__(self, num: int, spf: Union[int, float]=0, iterator=None) -> None:
        """ ## 帧计时器 (确保每一帧间隔相同)
        - spf: 每帧用时，等价于 1s / fps
        - num: 总帧数
        - iterator: 可选，迭代器 或 可索引对象，长度必须>=num
        
        for i, v in FrameTimer:
            i = 当前帧数，从0到num-1
            v = None，或iterator迭代到相同次数的值，或对应下标的值"""
        self.spf = spf
        self.num = num
        self.cur = 0
        self.t0 = 0
        self.total_spent = 0
        self.last_frame_start = 0
        self.iterator = iterator
        if self.iterator == None:
            pass
        elif hasattr(self.iterator, '__next__'):
            self.__getiter = lambda: self.iterator.__next__()
        elif hasattr(self.iterator, '__getitem__'):
            self.__getiter = lambda: self.iterator[self.cur - 1]

    def __iter__(self):
        return self
    
    def __next__(self):
        if self.cur == 0:
            self.t0 = time.perf_counter()
            self.last_frame_start = self.t0
            self.cur += 1
            return 0, self.__getiter()
        if self.cur >= self.num:
            raise StopIteration
        i = self.cur
        self.cur += 1
        now = time.perf_counter()
        if self.spf:
            self.total_spent = now - self.t0
            t = i * self.spf - self.total_spent
        else:
            frame_spent = now - self.last_frame_start
            t = self.__frametime - frame_spent
        if t > 0: sleepPrecise(t)
        self.last_frame_start = time.perf_counter()
        return i, self.__getiter()
    
    def __getiter(self):
        return None
    
    def frameTime(self, t):
        self.__frametime = t