import base64
import os
from enum import Enum
from io import BytesIO
from typing import Optional, Union, Tuple, List

from PIL import Image, ImageDraw, ImageFont

from ..utils import config


class Color(Enum):
    """
    常用颜色 RGB 枚举

    + BLACK: 黑色
    + WHITE: 白色
    + GRAY: 灰色
    + LIGHTGRAY: 淡灰色
    + RED: 红色
    + GREEN: 绿色
    + DEEPBLUE: 深蓝色
    + LIGHTBLUE: 浅蓝色
    + DEEPRED: 深红色
    + LIGHTRED: 浅红色
    + DEEPGREEN: 深绿色
    + LIGHTGREEN: 浅绿色
    + CRIMSON: 总督红
    + FUCHSIA: 提督紫
    + DEEPSKYBLUE: 舰长蓝
    + LINK: 超链接蓝
    + PINK: 大会员粉
    """
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    GRAY = (169, 169, 169)
    LIGHTGRAY = (244, 244, 244)
    RED = (150, 0, 0)
    GREEN = (0, 150, 0)
    DEEPBLUE = (55, 187, 248)
    LIGHTBLUE = (175, 238, 238)
    DEEPRED = (240, 128, 128)
    LIGHTRED = (255, 220, 220)
    DEEPGREEN = (0, 255, 0)
    LIGHTGREEN = (184, 255, 184)
    CRIMSON = (220, 20, 60)
    FUCHSIA = (255, 0, 255)
    DEEPSKYBLUE = (0, 191, 255)
    LINK = (23, 139, 207)
    PINK = (251, 114, 153)


class PicGenerator:
    """
    基于 Pillow 的绘图器，可使用链式调用方法
    """

    def __init__(self,
                 width: int,
                 height: int,
                 normal_font: str = config.get("PAINTER_NORMAL_FONT"),
                 bold_font: str = config.get("PAINTER_BOLD_FONT")):
        """
        初始化绘图器

        Args:
            width: 画布宽度
            height: 画布高度
            normal_font: 普通字体路径。默认：config.get("PAINTER_NORMAL_FONT") = "normal.ttf"
            bold_font: 粗体字体路径。默认：config.get("PAINTER_BOLD_FONT") = "bold.ttf"
        """
        self.__width = width
        self.__height = height
        self.__canvas = Image.new("RGBA", (self.width, self.height))
        self.__draw = ImageDraw.Draw(self.__canvas)

        resource_base_path = os.path.dirname(os.path.dirname(__file__))
        self.__chapter_font = ImageFont.truetype(f"{resource_base_path}/resource/{bold_font}", 50)
        self.__section_font = ImageFont.truetype(f"{resource_base_path}/resource/{bold_font}", 40)
        self.__tip_font = ImageFont.truetype(f"{resource_base_path}/resource/{normal_font}", 25)
        self.__text_font = ImageFont.truetype(f"{resource_base_path}/resource/{normal_font}", 30)

        self.__xy = 0, 0
        self.__ROW_SPACE = 25

        self.__bottom_pic = None

    @property
    def width(self):
        return self.__width

    @property
    def height(self):
        return self.__height

    @property
    def x(self):
        return self.__xy[0]

    @property
    def y(self):
        return self.__xy[1]

    @property
    def xy(self):
        return self.__xy

    @property
    def row_space(self):
        return self.__ROW_SPACE

    @property
    def img(self):
        return self.__canvas

    def set_row_space(self, row_space: int):
        """
        设置默认行距

        Args:
            row_space: 行距
        """
        self.__ROW_SPACE = row_space
        return self

    def set_pos(self, x: Optional[int] = None, y: Optional[int] = None):
        """
        设置绘图坐标

        Args:
            x: X 坐标
            y: Y 坐标
        """
        x = x if x is not None else self.x
        y = y if y is not None else self.y
        self.__xy = x, y
        return self

    def move_pos(self, x: int, y: int):
        """
        移动绘图坐标

        Args:
            x: X 偏移量
            y: Y 偏移量
        """
        self.__xy = self.x + x, self.y + y
        return self

    def copy_bottom(self, height: int):
        """
        拷贝指定高度的画布底部图片
        与 crop_and_paste_bottom() 函数配合
        在事先不能确定画布高度时，先创建一个高度极大的画布，内容绘制结束时剪裁底部多余区域并将底部图片粘贴回原位

        Args:
            height: 拷贝高度，从图片底部计算
        """
        self.__bottom_pic = self.__canvas.crop((0, self.height - height, self.width, self.height))
        return self

    def crop_and_paste_bottom(self):
        """
        内容绘图结束后，根据当前绘图坐标剪裁多余的底部区域，并将事先拷贝的底部图片粘贴回底部
        """
        if self.__bottom_pic is None:
            self.__canvas = self.__canvas.crop((0, 0, self.width, self.y))
            return self

        self.__canvas = self.__canvas.crop((0, 0, self.width, self.y + self.__bottom_pic.height))
        self.draw_img(self.__bottom_pic, (0, self.y))
        return self

    def draw_rectangle(self,
                       x: int,
                       y: int,
                       width: int,
                       height: int,
                       color: Union[Color, Tuple[int, int, int]]):
        """
        绘制一个矩形，此方法不会移动绘图坐标

        Args:
            x: 矩形左上角的 x 坐标
            y: 矩形左上角的 y 坐标
            width: 矩形的宽度
            height: 矩形的高度
            color: 矩形的背景颜色
        """
        if isinstance(color, Color):
            color = color.value

        self.__draw.rectangle(((x, y), (x + width, y + height)), color)
        return self

    def draw_rounded_rectangle(self,
                               x: int,
                               y: int,
                               width: int,
                               height: int,
                               radius: int,
                               color: Union[Color, Tuple[int, int, int]]):
        """
        绘制一个圆角矩形，此方法不会移动绘图坐标

        Args:
            x: 圆角矩形左上角的 x 坐标
            y: 圆角矩形左上角的 y 坐标
            width: 圆角矩形的宽度
            height: 圆角矩形的高度
            radius: 圆角矩形的圆角半径
            color: 圆角矩形的背景颜色
        """
        if isinstance(color, Color):
            color = color.value

        self.__draw.rounded_rectangle(((x, y), (x + width, y + height)), radius, color)
        return self

    def auto_size_img_by_limit(self,
                               img: Image.Image,
                               xy_limit: Tuple[int, int],
                               xy: Optional[Tuple[int, int]] = None) -> Image:
        """
        将指定的图片限制为不可覆盖指定的点，若其将要覆盖指定的点，会自适应缩小图片至不会覆盖指定的点

        Args:
            img: 要限制的图片
            xy_limit: 指定不可被覆盖的点
            xy: 图片将要被绘制到的坐标。默认：自适应的当前绘图坐标
        """
        if xy is None:
            xy = self.__xy

        return self.auto_size_img_by_limit_cls(img, xy_limit, xy)

    @classmethod
    def auto_size_img_by_limit_cls(cls, img: Image.Image, xy_limit: Tuple[int, int], xy: Tuple[int, int]) -> Image:
        """
        将指定的图片限制为不可覆盖指定的点，若其将要覆盖指定的点，会自适应缩小图片至不会覆盖指定的点

        Args:
            img: 要限制的图片
            xy_limit: 指定不可被覆盖的点
            xy: 图片将要被绘制到的坐标
        """
        cover_margin = config.get("PAINTER_AUTO_SIZE_BY_LIMIT_MARGIN")
        xy_limit = xy_limit[0] - cover_margin, xy_limit[1] + cover_margin

        x_cover = xy[0] + img.width - xy_limit[0]

        if xy[1] >= xy_limit[1] or x_cover <= 0:
            return img

        width = img.width - x_cover
        height = int(img.height * (width / img.width))
        return img.resize((width, height))

    def draw_img(self, img: Union[str, Image.Image], xy: Optional[Tuple[int, int]] = None):
        """
        在当前绘图坐标绘制一张图片，会自动移动绘图坐标至下次绘图适合位置
        也可手动传入绘图坐标，手动传入时不会移动绘图坐标

        Args:
            img: 图片路径或 Image 图片实例
            xy: 绘图坐标。默认：自适应绘图坐标
        """
        if isinstance(img, str):
            img = Image.open(img)

        if xy is None:
            self.__canvas.paste(img, self.__xy)
            self.move_pos(0, img.height + self.__ROW_SPACE)
        else:
            self.__canvas.paste(img, xy)
        return self

    def draw_img_alpha(self, img: Union[str, Image.Image], xy: Optional[Tuple[int, int]] = None):
        """
        在当前绘图坐标绘制一张透明背景图片，会自动移动绘图坐标至下次绘图适合位置
        也可手动传入绘图坐标，手动传入时不会移动绘图坐标

        Args:
            img: 透明背景图片路径或 Image 图片实例
            xy: 绘图坐标。默认：自适应绘图坐标
        """
        if isinstance(img, str):
            img = Image.open(img)

        if xy is None:
            self.__canvas.paste(img, self.__xy, img)
            self.move_pos(0, img.height + self.__ROW_SPACE)
        else:
            self.__canvas.paste(img, xy, img)
        return self

    def draw_img_with_border(self,
                             img: Union[str, Image.Image],
                             xy: Optional[Tuple[int, int]] = None,
                             color: Optional[Union[Color, Tuple[int, int, int]]] = Color.BLACK,
                             radius: int = 10,
                             width: int = 1):
        """
        在当前绘图坐标绘制一张图片并附带圆角矩形边框，会自动移动绘图坐标至下次绘图适合位置
        也可手动传入绘图坐标，手动传入时不会移动绘图坐标

        Args:
            img: 图片路径或 Image 图片实例
            xy: 绘图坐标。默认：自适应绘图坐标
            color: 边框颜色。默认：黑色 (0, 0, 0)
            radius: 边框圆角半径。默认：10
            width: 边框粗细。默认：1
        """
        if isinstance(img, str):
            img = Image.open(img)

        if isinstance(color, Color):
            color = color.value

        if xy is None:
            xy = self.__xy
            self.draw_img(img)
        else:
            self.draw_img(img, xy)

        border = Image.new("RGBA", (img.width + (width * 2), img.height + (width * 2)))
        ImageDraw.Draw(border).rounded_rectangle((0, 0, img.width, img.height), radius, (0, 0, 0, 0), color, width)
        self.draw_img_alpha(border, (xy[0] - width, xy[1] - width))
        return self

    def draw_chapter(self,
                     chapter: str,
                     color: Union[Color, Tuple[int, int, int]] = Color.BLACK,
                     xy: Optional[Tuple[int, int]] = None):
        """
        在当前绘图坐标绘制一个章节标题，会自动移动绘图坐标至下次绘图适合位置
        也可手动传入绘图坐标，手动传入时不会移动绘图坐标

        Args:
            chapter: 章节名称
            color: 字体颜色。默认：黑色 (0, 0, 0)
            xy: 绘图坐标。默认：自适应绘图坐标
        """
        if isinstance(color, Color):
            color = color.value

        if xy is None:
            self.__draw.text(self.__xy, chapter, color, self.__chapter_font)
            self.move_pos(0, self.__chapter_font.size + self.__ROW_SPACE)
        else:
            self.__draw.text(xy, chapter, color, self.__chapter_font)
        return self

    def get_chapter_length(self, s: str) -> int:
        """
        获取绘制指定字符串的章节标题所需长度（像素数）

        Args:
            s: 要绘制的字符串
        """
        return int(self.__draw.textlength(s, self.__chapter_font))

    def draw_section(self,
                     section: str,
                     color: Union[Color, Tuple[int, int, int]] = Color.BLACK,
                     xy: Optional[Tuple[int, int]] = None):
        """
        在当前绘图坐标绘制一个小节标题，会自动移动绘图坐标至下次绘图适合位置
        也可手动传入绘图坐标，手动传入时不会移动绘图坐标

        Args:
            section: 小节名称
            color: 字体颜色。默认：黑色 (0, 0, 0)
            xy: 绘图坐标。默认：自适应绘图坐标
        """
        if isinstance(color, Color):
            color = color.value

        if xy is None:
            self.__draw.text(self.__xy, section, color, self.__section_font)
            self.move_pos(0, self.__section_font.size + self.__ROW_SPACE)
        else:
            self.__draw.text(xy, section, color, self.__section_font)
        return self

    def get_section_length(self, s: str) -> int:
        """
        获取绘制指定字符串的小节标题所需长度（像素数）

        Args:
            s: 要绘制的字符串
        """
        return int(self.__draw.textlength(s, self.__section_font))

    def draw_tip(self,
                 tip: str,
                 color: Union[Color, Tuple[int, int, int]] = Color.GRAY,
                 xy: Optional[Tuple[int, int]] = None):
        """
        在当前绘图坐标绘制一个提示，会自动移动绘图坐标至下次绘图适合位置
        也可手动传入绘图坐标，手动传入时不会移动绘图坐标

        Args:
            tip: 提示内容
            color: 字体颜色。默认：灰色 (169, 169, 169)
            xy: 绘图坐标。默认：自适应绘图坐标
        """
        if isinstance(color, Color):
            color = color.value

        if xy is None:
            self.__draw.text(self.__xy, tip, color, self.__tip_font)
            self.move_pos(0, self.__tip_font.size + self.__ROW_SPACE)
        else:
            self.__draw.text(xy, tip, color, self.__tip_font)
        return self

    def get_tip_length(self, s: str) -> int:
        """
        获取绘制指定字符串的提示所需长度（像素数）

        Args:
            s: 要绘制的字符串
        """
        return int(self.__draw.textlength(s, self.__tip_font))

    def draw_text(self,
                  texts: Union[str, List[str]],
                  colors: Optional[Union[Color, Tuple[int, int, int], List[Union[Color, Tuple[int, int, int]]]]] = None,
                  xy: Optional[Tuple[int, int]] = None):
        """
        在当前绘图坐标绘制一行文本，会自动移动绘图坐标至下次绘图适合位置
        也可手动传入绘图坐标，手动传入时不会移动绘图坐标
        传入文本列表和颜色列表可将一行文本绘制为不同颜色，文本列表和颜色列表需一一对应
        颜色列表少于文本列表时将使用默认黑色 (0, 0, 0)，颜色列表多于文本列表时将舍弃多余颜色

        Args:
            texts: 文本内容
            colors: 字体颜色。默认：黑色 (0, 0, 0)
            xy: 绘图坐标。默认：自适应绘图坐标
        """
        if colors is None:
            colors = []

        if isinstance(texts, str):
            texts = [texts]

        if isinstance(colors, (Color, tuple)):
            colors = [colors]

        for i in range(len(texts) - len(colors)):
            colors.append(Color.BLACK)

        for i in range(len(colors)):
            if isinstance(colors[i], Color):
                colors[i] = colors[i].value

        if xy is None:
            x = self.x
            for i in range(len(texts)):
                self.__draw.text(self.__xy, texts[i], colors[i], self.__text_font)
                self.move_pos(int(self.__draw.textlength(texts[i], self.__text_font)), 0)
            self.move_pos(x - self.x, self.__text_font.size + self.__ROW_SPACE)
        else:
            for i in range(len(texts)):
                self.__draw.text(xy, texts[i], colors[i], self.__text_font)
                xy = xy[0] + self.__draw.textlength(texts[i], self.__text_font), xy[1]
        return self

    def draw_text_right(self,
                        margin_right: int,
                        texts: Union[str, List[str]],
                        colors: Optional[Union[Color, Tuple[int, int, int],
                                               List[Union[Color, Tuple[int, int, int]]]]] = None,
                        xy_limit: Optional[Tuple[int, int]] = (0, 0)):
        """
        在当前绘图坐标绘制一行右对齐文本，会自动移动绘图坐标保证不会覆盖指定的点，会自动移动绘图坐标至下次绘图适合位置
        传入文本列表和颜色列表可将一行文本绘制为不同颜色，文本列表和颜色列表需一一对应
        颜色列表少于文本列表时将使用默认黑色 (0, 0, 0)，颜色列表多于文本列表时将舍弃多余颜色

        Args:
            margin_right: 右边距
            texts: 文本内容
            colors: 字体颜色。默认：黑色 (0, 0, 0)
            xy_limit: 指定不可被覆盖的点。默认：(0, 0)
        """
        xy = self.__xy

        x = self.width - self.__draw.textlength("".join(texts), self.__text_font) - margin_right

        cover_margin = config.get("PAINTER_AUTO_SIZE_BY_LIMIT_MARGIN")
        xy_limit = xy_limit[0] - cover_margin, xy_limit[1] + cover_margin
        y = max(xy[1], xy_limit[1])

        self.draw_text(texts, colors, (x, y))
        self.set_pos(xy[0], y + self.__text_font.size + self.__ROW_SPACE)

        return self

    def get_text_length(self, s: str) -> int:
        """
        获取绘制指定字符串的文本所需长度（像素数）

        Args:
            s: 要绘制的字符串
        """
        return int(self.__draw.textlength(s, self.__text_font))

    def draw_text_multiline(self,
                            margin: int,
                            texts: Union[str, List[str]],
                            colors: Optional[
                                Union[Color, Tuple[int, int, int], List[Union[Color, Tuple[int, int, int]]]]
                            ] = None,
                            xy: Optional[Tuple[int, int]] = None):
        """
        在当前绘图坐标绘制多行文本，文本到达边界处会自动换行，绘制结束后会自动移动绘图坐标至下次绘图适合位置
        也可手动传入绘图坐标，手动传入时不会移动绘图坐标
        传入文本列表和颜色列表可将多行文本绘制为不同颜色，文本列表和颜色列表需一一对应
        颜色列表少于文本列表时将使用默认黑色 (0, 0, 0)，颜色列表多于文本列表时将舍弃多余颜色

        Args:
            margin: 外边距
            texts: 文本内容
            colors: 字体颜色。默认：黑色 (0, 0, 0)
            xy: 绘图坐标。默认：自适应绘图坐标
        """
        if colors is None:
            colors = []

        if isinstance(texts, str):
            texts = [texts]

        if isinstance(colors, (Color, tuple)):
            colors = [colors]

        for i in range(len(texts) - len(colors)):
            colors.append(Color.BLACK)

        for i in range(len(colors)):
            if isinstance(colors[i], Color):
                colors[i] = colors[i].value

        if xy is None:
            x = self.x
            for i in range(len(texts)):
                for c in texts[i]:
                    length = int(self.__draw.textlength(c, self.__text_font))
                    if self.x + length > self.width - margin:
                        self.move_pos(x - self.x, self.__text_font.size + self.__ROW_SPACE)
                    self.__draw.text(self.__xy, c, colors[i], self.__text_font)
                    self.move_pos(int(self.__draw.textlength(c, self.__text_font)), 0)
            self.move_pos(x - self.x, self.__text_font.size + self.__ROW_SPACE)
        else:
            x = xy[0]
            for i in range(len(texts)):
                for c in texts[i]:
                    length = int(self.__draw.textlength(c, self.__text_font))
                    if xy[0] + length > self.width - margin:
                        xy = x, xy[1] + self.__text_font.size + self.__ROW_SPACE
                    self.__draw.text(xy, c, colors[i], self.__text_font)
                    xy = xy[0] + self.__draw.textlength(c, self.__text_font), xy[1]
        return self

    def show(self):
        """
        显示图片
        """
        self.__canvas.show()
        return self

    def save(self, path: str):
        """
        保存图片

        Args:
            path: 保存路径
        """
        self.__canvas.save(path)
        return self

    def base64(self) -> str:
        """
        结束绘图，获取 Base64 字符串

        Returns:
            Base64 字符串
        """
        io = BytesIO()
        self.__canvas.save(io, format="PNG")

        return base64.b64encode(io.getvalue()).decode()
