import os
from typing import Optional

from .dscl_types import *
from UniTAP.libs.lib_uicl.uicl_types import UICL_Image, UICL_ImageParameters
from UniTAP.libs.lib_helper import OS_Requirements, lib_method_wrapper

DSCL_CURRENT_VERSION = 1
DSCL = OS_Requirements("DSCL").get_lib()
DSCL_PATH = OS_Requirements("DSCL").get_path_to_lib()
DSC_TOOLS_FOLDER = os.path.dirname(DSCL_PATH)


def from_cstr(src_str):
    try:
        return src_str.value.decode('cp1252')
    except BaseException:
        return src_str.value.decode('utf-8')


class DSCLError(Exception):
    def __init__(self, message, errors=None):
        super().__init__(message)
        self.errors = errors


def DSCL_GetRequiredBufferSize(image_to_encode: DSCL_Image):
    _DSCL_GetRequiredBufferSize = lib_method_wrapper(DSCL.DSCL_GetRequiredBufferSize,
                                                     [POINTER(DSCL_Image)],
                                                     c_uint64)

    return _DSCL_GetRequiredBufferSize(byref(image_to_encode))


def DSCL_GetRequiredBufferSizeFromPPS(pps: DSCL_PPS):
    _DSCL_GetRequiredBufferSizeFromPPS = lib_method_wrapper(DSCL.DSCL_GetRequiredBufferSizeFromPPS,
                                                     [POINTER(DSCL_PPS)],
                                                     c_uint64)

    return _DSCL_GetRequiredBufferSizeFromPPS(byref(pps))


def DSCL_Encode(image: UICL_Image, pps: DSCL_PPS) -> DSCL_Image:
    _DSCL_Encode = lib_method_wrapper(DSCL.DSCL_Encode,
                                      [POINTER(UICL_Image), POINTER(DSCL_PPS), POINTER(DSCL_Image),
                                       POINTER(DSCL_EncoderConfig), c_char_p, c_int],
                                      DSCL_RESULT)

    max_enc_image_size = DSCL_GetRequiredBufferSizeFromPPS(pps)

    if max_enc_image_size <= 132:
        raise DSCLError("Failed to calculate buffer size for DSC encoding!")

    encoded_image = DSCL_Image()
    encoded_image.DataPtr = (c_uint8 * max_enc_image_size)()
    encoded_image.DataSize = max_enc_image_size

    _log_struct = DSCL_EncoderConfig()
    _log_string_size = 65536
    _log_string = create_string_buffer(_log_string_size)

    result = _DSCL_Encode(byref(image), byref(pps), byref(encoded_image), byref(_log_struct),
                          _log_string, _log_string_size)

    log = str(from_cstr(_log_string))

    if result < DSCL_SUCCESS:
        raise DSCLError(f"Encoding finished with error: {result}. Error log: {log}", result)

    return encoded_image


def DSCL_Decode(encoded_image: DSCL_Image, decode_simple422_to_444: Optional[bool] = False) -> UICL_Image:
    _DSCL_Decode = lib_method_wrapper(DSCL.DSCL_Decode,
                                      [POINTER(DSCL_Image), POINTER(UICL_Image),
                                       POINTER(DSCL_DecoderConfig)],
                                      DSCL_RESULT)

    decoded_image = UICL_Image()
    decoded_image.Parameters = UICL_ImageParameters()
    decoded_image.DataPtr = POINTER(c_uint8)()
    decoded_image.DataSize = 0

    decoder_config = DSCL_DecoderConfig()
    decoder_config.decodeSimple422to444 = False if decode_simple422_to_444 is None else decode_simple422_to_444

    result = _DSCL_Decode(byref(encoded_image), byref(decoded_image), byref(decoder_config))

    if result < DSCL_SUCCESS:
        raise DSCLError(f"Decoding failed with error code: {result}", result)

    return decoded_image


def DSCL_ResultToString(result):
    _DSCL_ResultToString = lib_method_wrapper(DSCL.DSCL_ResultToString,
                                              [DSCL_RESULT, c_char_p, c_uint64],
                                              c_uint64)

    _error_string = create_string_buffer(1024)
    _DSCL_ResultToString(result, _error_string, 1024)
    return from_cstr(_error_string)


def DSCL_CalculateDSCCRC(image: DSCL_Image):
    _DSCL_CalculateDSCCRC = lib_method_wrapper(DSCL.DSCL_CalculateDSCCRC,
                                               [POINTER(DSCL_Image), POINTER(DSCL_CRC16)],
                                               c_uint64)
    crc_value = DSCL_CRC16()
    result = _DSCL_CalculateDSCCRC(byref(image), byref(crc_value))

    if result < DSCL_SUCCESS:
        raise DSCLError(f"Calculate DSC CRC: Failed to calculate CRC", result)

    return crc_value


def DSCL_ExtractPPSFromData(data):
    _DSCL_ExtractPPSFromData = lib_method_wrapper(DSCL.DSCL_ExtractPPSFromData,
                                                  [POINTER(c_uint8), c_uint64, POINTER(DSCL_PPS)],
                                                  c_uint64)
    _pps = DSCL_PPS()
    _size = len(data)
    _data = (c_uint8 * _size)(*data)
    result = _DSCL_ExtractPPSFromData(_data, _size, byref(_pps))
    if result < DSCL_SUCCESS:
        raise DSCLError(f"Cannot extract PPS from data. Code: {result}")

    return _pps


def DSCL_ExtractPPS(image: DSCL_Image):
    _DSCL_ExtractPPS = lib_method_wrapper(DSCL.DSCL_ExtractPPS,
                                          [POINTER(DSCL_Image), POINTER(DSCL_PPS)],
                                          c_uint64)
    _pps = DSCL_PPS()
    result = _DSCL_ExtractPPS(byref(image), byref(_pps))
    if result < DSCL_SUCCESS:
        raise DSCLError(f"Cannot extract PPS from DSCL Image. Code: {result}")

    return _pps


def DSCL_VerifyPPS(image: DSCL_Image):
    _DSCL_VerifyPPS = lib_method_wrapper(DSCL.DSCL_VerifyPPS,
                                         [POINTER(DSCL_Image), c_char_p, c_uint64],
                                         c_bool)

    _error_string = create_string_buffer(1024)
    return _DSCL_VerifyPPS(byref(image), _error_string, 1024)


def DSCL_GeneratePPS(decoded_image_parameters: UICL_ImageParameters,
                     is_simple_422: bool, is_block_prediction_enabled: bool, bits_per_pixel: int,
                     horizontal_slice_count: int, vertical_slice_count: int, buffer_bit_depth: int,
                     dsc_version: DSCL_DscVersion) -> DSCL_PPS:
    _DSCL_GeneratePPS = lib_method_wrapper(DSCL.DSCL_GeneratePPS,
                                           [POINTER(DSCL_PPS), POINTER(UICL_ImageParameters), c_bool, c_bool,
                                            c_uint16, c_uint16, c_uint16, c_uint8, DSCL_DscVersion],
                                           c_bool)
    _pps = DSCL_PPS()
    result = _DSCL_GeneratePPS(byref(_pps), byref(decoded_image_parameters), is_simple_422,
                               is_block_prediction_enabled, bits_per_pixel, horizontal_slice_count,
                               vertical_slice_count, buffer_bit_depth, dsc_version)
    if result < DSCL_SUCCESS:
        raise DSCLError(f"Cannot generate PPS. Error code {result}")

    return _pps


def DSCL_LoadFromFile(file_name: str) -> DSCL_RESULT:
    _DSCL_LoadFromFile = lib_method_wrapper(DSCL.DSCL_LoadFromFile,
                                            [c_char_p, POINTER(DSCL_Image)],
                                            DSCL_Image)
    image = DSCL_Image()
    image.DataPtr = POINTER(c_uint8)()
    image.DataSize = 0

    result = _DSCL_LoadFromFile(c_char_p(file_name.encode('utf-8')), byref(image))

    if result < DSCL_SUCCESS:
        raise DSCLError(f"Cannot load image from file. Error code {result}.")

    return result


def DSCL_FreeImage(image: DSCL_Image) -> DSCL_RESULT:
    _DSCL_FreeImage = lib_method_wrapper(DSCL.DSCL_FreeImage,
                                         [POINTER(POINTER(DSCL_Image))],
                                         c_uint64)

    result = _DSCL_FreeImage(byref(byref(image)))

    return result
