"""
author: chaohui.yang
date: 2022-09-07
"""
import math
from abc import abstractmethod, ABCMeta
from datetime import datetime
import numpy as np
from matplotlib.ticker import MultipleLocator
from scipy import interpolate
import xlrd
import json
import jsbeautifier
import matplotlib.pyplot as plt

np.set_printoptions(precision=5)
np.set_printoptions(suppress=True)


def readDataFromExcel(filePath: str):
    """
    从EXCEL中读取目标x,y以及gamma值，以及测量的XYZ值
    :param filePath: ecxel路径
    :return: 转换关系矩阵RGB_2_XYZ, 包含11组测量值的矩阵XYZ_11, 包含11个测量值索引的数组XYZ11_index,target_x, target_y, target_gamma
    """
    workbook = xlrd.open_workbook(filePath)
    sheet = workbook.sheets()[0]
    # 使用表格中定义的target_x，target_y，以及target_gamma值
    target_x = sheet.cell_value(8, 1)
    target_y = sheet.cell_value(9, 1)
    target_gamma = sheet.cell_value(10, 1)
    print(f"从表格读取到:\ntarget_x = {target_x}\ntarget_y = {target_y}\ntarget_gamma = {target_gamma}\n")
    XYZ11_index = sheet.row_values(1, 1)[3:14]
    rows_X = sheet.row_values(2, 1)[0:14]  # 获取第X行内容
    rows_Y = sheet.row_values(3, 1)[0:14]  # 获取第Y行内容
    rows_Z = sheet.row_values(4, 1)[0:14]  # 获取第Z行内容
    XYZ_all = [[rows_X[i], rows_Y[i], rows_Z[i]] for i in range(len(rows_X))]
    XYZ_11 = np.array(XYZ_all[3:14]).T
    RGB_2_XYZ = np.array(XYZ_all[0:3]).T  # np.array([XYZ[0], XYZ[1], XYZ[2]])
    return RGB_2_XYZ, XYZ_11, XYZ11_index, target_x, target_y, target_gamma


def cal_target_XYZ11(XYZ_2_RGB, target_x, target_y, target_gamma):
    """
    根据“转换关系”矩阵XYZ_2_RGB，计算出11个目标XYZ值
    :param XYZ_2_RGB: 表示“转换关系”的矩阵，也就是测量而得的R/G/B对应的XYZ值所合成的矩阵(3X3)
    :param target_x:
    :param target_y:
    :param target_gamma:
    :return: targetXYZ11:包含11个目标XYZ值的矩阵（3X11）
    """
    print("以下求最大可行目标亮度值....")
    targetY_list = []
    for i in range(0, 3):
        targetY = 1 / (XYZ_2_RGB[i][0] * target_x / target_y + XYZ_2_RGB[i][1] + XYZ_2_RGB[i][2] * (
                1 - target_x - target_y) / target_y)
        targetY_list.append(targetY)
        print(i + 1, "式子的解：targetY = ", targetY)
    targetY_max = round(min(targetY_list), 5)
    print("最大可行目标亮度值： targetY_max = ", targetY_max)
    targetXYZ11 = np.zeros((3, 11))
    # print("targetXYZ11: \n", targetXYZ11)
    # 算出11个target_XYZ
    for i in range(0, 11):
        targetXYZ11[1][i] = targetY_max * ((1 - 0.1 * i) ** target_gamma)
        targetXYZ11[0][i] = (targetXYZ11[1][i] * target_x) / target_y
        targetXYZ11[2][i] = targetXYZ11[1][i] * (1 - target_x - target_y) / target_y
    # print("11个目标target_XYZ值: \n", targetXYZ11)
    return targetXYZ11


# 插值方法
def interpolate_to_256(ori_matrix, ori_matrix_index):
    """
    插值算法。根据输入数组和对应元素序列，插值使得最终为256个元素
    :param ori_matrix: 输入矩阵（n x m）,通常每行分别对应R/G/B值或X/Y/Z值
    :param ori_matrix_index: 对应每列矩阵元素的索引值序列（1 x m）
    :return: output_matrix: 输出插值后的矩阵（nx256）
    """
    # print(f"待扩充矩阵序列间隔为ori_index :\n{ori_matrix_index}")
    # print("待扩充矩阵为： \n", ori_matrix)
    new_index = np.arange(0, 256, 1)
    # 插值方式kind = linear|slinear|quadratic|cubic
    f = interpolate.interp1d(ori_matrix_index, ori_matrix, kind="linear")
    output_matrix = f(new_index)
    # print("扩充后的矩阵为  :\n", output_matrix)
    return output_matrix


def cal_RGB_by_XYZ(transMatrix, matrix_XYZ):
    """
    根据逆矩阵和XYZ计算RGB
    :param transMatrix: RGB TO XYZ的转换矩阵逆矩阵（3X3）
    :param matrix_XYZ: XYZ矩阵（3X3）
    :return: RGB矩阵（3X3）
    """
    matrix_RGB = np.dot(transMatrix, matrix_XYZ)
    return matrix_RGB


def calActualRGB11(targetRGB11, targetXYZ11, XYZ256):
    """
    根据11个目标RGB值、目标XYZ值以及测量再插值而得的256个XYZ，算出实际需要的11个RGB值
    :param targetRGB11: 包含11个目标RGB值的3x11矩阵
    :param targetXYZ11: 包含11个目标XYZ值的3x11矩阵
    :param XYZ256: 包含256个由测量的XYZ值的3x256矩阵
    :return: actualRGB11：包含11个实际需要的RGB值的3x11矩阵
    """
    print(f"以下开始计算实际需要的11组RGB值...\n")
    actualRGB11 = np.zeros([3, 11])
    targetRGB11 = targetRGB11.T[::-1].T
    print("翻转的targetRGB11 is: ", targetRGB11)
    targetXYZ11 = targetXYZ11.T[::-1].T
    print("翻转的targetXYZ11 is: ", targetXYZ11)
    for n in range(0, 3):
        for m in range(1, 11):
            distances = []
            for index in range(0, 256):
                distance = abs(XYZ256[n, index] - targetXYZ11[n, m])
                distances.append(distance)
            # print(f"targetXYZ11[{n}][{m}]对应的XYZ256的距离为:\n{distances}")
            min_index = distances.index(min(distances))
            print(f"目标点targetXYZ11[{n}][{m}] , 对应XYZ256中最短距离的元素值index为： {min_index}")
            # 当min_index刚好为首、尾index时无需参与gamma计算，直接取对应的target值
            if min_index == 0 or min_index == 255:
                actualRGB11[n, m] = targetRGB11[n, m]
                print(f"该点不计算panel gamma值\n")
            else:
                panelGamma = round(math.log((XYZ256[n, min_index]) / XYZ256[n, 255]) / math.log((min_index) / 255), 5)
                print(f"对应targetXYZ11[{n}][{m}]最短距离的XYZ256[{n}][{min_index}]的panel gamma值为：{panelGamma}\n")
                # 当panelGamma算出为0时，无法数学计算直接取对应的target值
                if panelGamma == 0:
                    actualRGB11[n, m] = targetRGB11[n, m]
                else:
                    actualRGB11[n, m] = (targetRGB11[n, m]) ** (1 / panelGamma)
        actualRGB11[n] = actualRGB11[n][::-1]
    # print("实际需要的11组RGB值为 : \n", actualRGB11)
    return actualRGB11


def showRGBcurve(RGB255):
    """
    根据计RGB255点绘出R/G/B三条曲线
    :param RGB255: 最终计算而得的RGB255数组（3X256），每行分别对应R/G/B的256个值
    """
    x = np.arange(0, 256)
    plt.title("RGB255")
    # x轴的刻度间隔
    x_major_locator = MultipleLocator(15)
    # y轴的刻度间隔
    y_major_locator = MultipleLocator(10)
    # ax为两条坐标轴的实例
    ax = plt.gca()
    ax.xaxis.set_major_locator(x_major_locator)
    ax.yaxis.set_major_locator(y_major_locator)
    plt.plot(x, RGB255[0], label="R", color='red')
    plt.plot(x, RGB255[1], label="G", color='green')
    plt.plot(x, RGB255[2], label="B", color='blue')
    plt.legend()
    plt.show()


class RGBcalculator(metaclass=ABCMeta):
    @abstractmethod
    def calRGB255(self, filePath: str):
        pass


class Firetv_RGBcalculator(RGBcalculator):
    target_x = 0
    target_y = 0
    target_gamma = 2.2
    RGB_2_XYZ = np.zeros((3, 3))
    XYZ_11 = np.zeros((3, 11))
    XYZ11_index = [255, 229, 204, 178, 153, 127, 102, 76, 51, 26, 0]

    def calRGB255(self, FilePath: str):
        # 从excel中读取“转换矩阵”，“11组测量的XYZ”,以及“11组XYZ对应的坐标索引”
        self.RGB_2_XYZ, self.XYZ_11, selfXYZ11_index, self.target_x, \
        self.target_y, self.target_gamma = readDataFromExcel(FilePath)
        print("转换矩阵RGB_to_XYZ为：\n", self.RGB_2_XYZ)
        XYZ_2_RGB = np.linalg.pinv(self.RGB_2_XYZ)
        print("转换逆矩阵XYZ_2_RGB为：\n", XYZ_2_RGB)
        targetXYZ11 = cal_target_XYZ11(XYZ_2_RGB, self.target_x, self.target_y, self.target_gamma)
        print("11个目标target_XYZ值: \n", targetXYZ11)
        # 算出11组目标RGB
        targetRGB11 = cal_RGB_by_XYZ(XYZ_2_RGB, targetXYZ11)
        print("11个目标targetRGB11值: \n", targetRGB11)
        # 对测量出来的XYZ11做插值运算
        XYZ256 = interpolate_to_256(self.XYZ_11, self.XYZ11_index)
        # XYZ256 = interpolate3d_to_256(XYZ_11, XYZ11_index)
        print(f"测量的11个XYZ11为：\n {self.XYZ_11}")
        print(f"对XYZ11插值扩展后为XYZ256：\n {XYZ256}")
        ActualRGB11 = calActualRGB11(targetRGB11, targetXYZ11, XYZ256)
        print("实际需要的11个RGB值: \n", ActualRGB11)
        # 对实际需要的RGB11做插值运算得到最终的RGB256
        ActualRGB256 = interpolate_to_256(ActualRGB11, self.XYZ11_index)
        ActualRGB256 = np.multiply(ActualRGB256, 255)  # 去归一化，以RGB-255显示
        ActualRGB256 = np.around(ActualRGB256, 4)  # 小数点保留4位
        print(f"最终需要的256组RGB值: \n{ActualRGB256}")
        return ActualRGB256

    def calRGB255_for_Debug(self, new_XYZ_of_RGB, new_XYZ_11, new_XYZ11_index, new_target_x,
                            new_target_y, new_target_gamma):
        """

        :param new_XYZ_of_RGB: 3X3矩阵
        :param new_XYZ_11:  3X11矩阵
        :param new_XYZ11_index: 1X11数组
        :param new_target_x:
        :param new_target_y:
        :param new_target_gamma:
        :return:
        """
        self.target_x = new_target_x
        self.target_y = new_target_y
        self.target_gamma = new_target_gamma
        # “转换矩阵”，“11组测量的XYZ”,以及“11组XYZ对应的`Z坐标索引”
        self.RGB_2_XYZ = new_XYZ_of_RGB
        self.XYZ_11 = new_XYZ_11
        self.XYZ11_index = new_XYZ11_index
        print(f"获取到:\ntarget_x = {self.target_x}\ntarget_y = {self.target_y}\ntarget_gamma = {self.target_gamma}\n")
        print("转换矩阵RGB_to_XYZ为：\n", self.RGB_2_XYZ)
        XYZ_2_RGB = np.linalg.pinv(self.RGB_2_XYZ)
        print("转换逆矩阵XYZ_2_RGB为：\n", XYZ_2_RGB)
        targetXYZ11 = cal_target_XYZ11(XYZ_2_RGB, self.target_x, self.target_y, self.target_gamma)
        print("11个目标target_XYZ值: \n", targetXYZ11)
        # 算出11组目标RGB
        targetRGB11 = cal_RGB_by_XYZ(XYZ_2_RGB, targetXYZ11)
        print("11个目标targetRGB11值: \n", targetRGB11)
        # 对测量出来的XYZ11做插值运算
        XYZ256 = interpolate_to_256(self.XYZ_11, self.XYZ11_index)
        # XYZ256 = interpolate3d_to_256(XYZ_11, XYZ11_index)
        print(f"测量的11个XYZ11为：\n {self.XYZ_11}")
        print(f"对XYZ11插值扩展后为XYZ256：\n {XYZ256}")
        ActualRGB11 = calActualRGB11(targetRGB11, targetXYZ11, XYZ256)
        print("实际需要的11个RGB值: \n", ActualRGB11)

        ActualXYZ11 = np.dot(XYZ_2_RGB, ActualRGB11)
        print(f"实际需要的11组XYZ值: \n{ActualXYZ11}")
        # 对实际需要的RGB11做插值运算得到最终的RGB256
        ActualRGB256 = interpolate_to_256(ActualRGB11, self.XYZ11_index)
        # showRGBcurve(np.multiply(ActualRGB256, 255))
        ActualRGB256 = np.multiply(ActualRGB256, 1023)  # 去归一化，以RGB-255显示
        ActualRGB256 = np.around(ActualRGB256, 0).astype(int)  # 小数点保留4位

        ActualXYZ256 = np.dot(XYZ_2_RGB, ActualRGB256)
        print(f"最终需要的256组XYZ值: \n{ActualXYZ256}")
        print(f"最终需要的256组RGB值: \n{ActualRGB256}")
        return ActualRGB256


def dumpRGB2ini(RGB255):
    """
    功能：将最终计算的RGB255值输出至文本，供机芯调用.该格式的ini暂时用不到
    :param RGB255:最终计算的RGB255数组（3X256），每行分别对应R/G/B的256个值
    """
    wb_mode = {}
    list = ["COLD", "STANDARD", "STANDARD2", "WARM", "WARM2", "SDR2HDR COLD", "SDR2HDR STANDARD", "SDR2HDR WARM"]
    for str in list:
        wb_mode[str] = [{"R_VALUE": []}, {"G_VALUE": []}, {"B_VALUE": []}]
        wb_mode[str][0]["R_VALUE"] = RGB255[0, :].tolist()
        wb_mode[str][1]["G_VALUE"] = RGB255[1, :].tolist()
        wb_mode[str][2]["B_VALUE"] = RGB255[2, :].tolist()
    # print("wb_mode: \n", wb_mode)
    options = jsbeautifier.default_options()
    options.indent_size = 2
    json_dict = jsbeautifier.beautify(json.dumps(wb_mode), options)
    print(json_dict)
    filename = 'gamma.ini'
    with open(filename, 'w', encoding='utf-8') as f:
        f.write(json_dict)
    print(f"gamma文件已保存至{filename}")


def dumpRGB2ini_commom(RGB255):
    """
    功能：将最终计算的RGB255值按一定格式输出至文本，供机芯调用.该格式比较通用
    :param RGB255: 最终计算的RGB255数组（3X256），每行分别对应R/G/B的256个值
    """
    RGB_list = []
    RGB_reg = "{0x000,0x010,0x020,0x030,0x040,0x050,0x060,0x070,0x080,0x090,0x0A0,0x0B0,0x0C0,0x0D0,0x0E0,0x0F0,\
0x100,0x110,0x120,0x130,0x140,0x150,0x160,0x170,0x180,0x190,0x1A0,0x1B0,0x1C0,0x1D0,0x1E0,0x1F0,\
0x200,0x210,0x220,0x230,0x240,0x250,0x260,0x270,0x280,0x290,0x2A0,0x2B0,0x2C0,0x2D0,0x2E0,0x2F0,\
0x300,0x310,0x320,0x330,0x340,0x350,0x360,0x370,0x380,0x390,0x3A0,0x3B0,0x3C0,0x3D0,0x3E0,0x3F0,\
0x400,0x410,0x420,0x430,0x440,0x450,0x460,0x470,0x480,0x490,0x4A0,0x4B0,0x4C0,0x4D0,0x4E0,0x4F0,\
0x500,0x510,0x520,0x530,0x540,0x550,0x560,0x570,0x580,0x590,0x5A0,0x5B0,0x5C0,0x5D0,0x5E0,0x5F0,\
0x600,0x610,0x620,0x630,0x640,0x650,0x660,0x670,0x680,0x690,0x6A0,0x6B0,0x6C0,0x6D0,0x6E0,0x6F0,\
0x700,0x710,0x720,0x730,0x740,0x750,0x760,0x770,0x780,0x790,0x7A0,0x7B0,0x7C0,0x7D0,0x7E0,0x7F0,\
0x800,0x810,0x820,0x830,0x840,0x850,0x860,0x870,0x880,0x890,0x8A0,0x8B0,0x8C0,0x8D0,0x8E0,0x8F0,\
0x900,0x910,0x920,0x930,0x940,0x950,0x960,0x970,0x980,0x990,0x9A0,0x9B0,0x9C0,0x9D0,0x9E0,0x9F0,\
0xA00,0xA10,0xA20,0xA30,0xA40,0xA50,0xA60,0xA70,0xA80,0xA90,0xAA0,0xAB0,0xAC0,0xAD0,0xAE0,0xAF0,\
0xB00,0xB10,0xB20,0xB30,0xB40,0xB50,0xB60,0xB70,0xB80,0xB90,0xBA0,0xBB0,0xBC0,0xBD0,0xBE0,0xBF0,\
0xC00,0xC10,0xC20,0xC30,0xC40,0xC50,0xC60,0xC70,0xC80,0xC90,0xCA0,0xCB0,0xCC0,0xCD0,0xCE0,0xCF0,\
0xD00,0xD10,0xD20,0xD30,0xD40,0xD50,0xD60,0xD70,0xD80,0xD90,0xDA0,0xDB0,0xDC0,0xDD0,0xDE0,0xDF0,\
0xE00,0xE10,0xE20,0xE30,0xE40,0xE50,0xE60,0xE70,0xE80,0xE90,0xEA0,0xEB0,0xEC0,0xED0,0xEE0,0xEF0,\
0xF00,0xF10,0xF20,0xF30,0xF40,0xF50,0xF60,0xF70,0xF80,0xF90,0xFA0,0xFB0,0xFC0,0xFD0,0xFE0,0xFF0}"
    # print("RGB255:\n", RGB255)
    list = ["R //Gamma - R", "G //Gamma - G", "B //Gamma - B"]
    for i in range(0, 3):
        RGB_list.append(list[i])
        for n in range(0, 256):
            RGB_list.append(RGB255[i][n])
        RGB_list.append("\n")
    # 按照已知gamma.txt样式生成内容
    RGB_list.append("/*")
    RGB_list.append("//Settint R")
    RGB_list.append(RGB_reg)
    RGB_list.append("\n")
    RGB_list.append("//Settint G")
    RGB_list.append(RGB_reg)
    RGB_list.append("\n")
    RGB_list.append("//Settint R")
    RGB_list.append(RGB_reg)
    RGB_list.append("\n")
    RGB_list.append("/*")
    # print("RGB_list: \n", RGB_list)
    time = datetime.strftime(datetime.now(), '%Y%m%d_%H%M%S')  #
    filename = 'gamma.ini'
    with open(filename, 'w') as fp:
        [fp.write(str(item) + '\n') for item in RGB_list]
        fp.close()
    print(f"gamma文件已保存至{filename}")


class Factory(metaclass=ABCMeta):
    @abstractmethod
    def create(self):
        pass


class FiretvFactory(Factory):
    def __init__(self):
        print("FiretvFactory...")

    def create(self):
        return Firetv_RGBcalculator()
