from __future__ import annotations

import io
from typing import Union, Optional, List

from elma.lgr import LGR, LGR_Image
from elma.constants import LGR_MANDATORY_FILES
from elma.constants import LGR_LIMITED_SIZE_FILES

__all__ = ["check_LGR_error"]

LGR_PCX_MIN = 10    # unused as practically never important
LGR_PCX_MAX = 3500    # unused as practically never important
LGR_PCX_FILESIZE_MIN = 1    # unused as practically never important
LGR_PCX_FILESIZE_MAX = 10000000
LGR_PIC_MAX = 999
LGR_PIC_WIDTH_MAX = 6000
LGR_PIC_SIZE_MAX = 600000
LGR_TEX_MIN = 2
LGR_TEX_MAX = 99
LGR_MASK_MAX = 199
LGR_GRASS_MAX = 99
LGR_OBJ_WIDTH = 40
LGR_OBJ_HEIGHT = 40
LGR_OBJ_FRAMES_MAX = 1000
LGR_RECOMMEND_BIKE_WIDTH = 354
LGR_RECOMMEND_BIKE_HEIGHT = 298
LGR_WARNING_GRASS_HEIGHT_MIN = 41
LGR_WARNING_QCOLORS_WIDTH = 66
LGR_WARNING_QCOLORS_HEIGHT = 109

ERR_LGR_INVALID_PALETTE = 1
ERR_LGR_MISSING_MANDATORY_FILE = 2
ERR_DUPLICATE_NAME = 3
ERR_TOO_MANY_GRASS = 4
ERR_TOO_MANY_TEXTURES = 5
ERR_NOT_ENOUGH_TEXTURES = 6
ERR_TOO_MANY_PICTURES = 7
ERR_TOO_MANY_MASKS = 8
WARN_UNUSED_QFOOD = 501

ERR_FILE_TOO_LARGE = 1001
ERR_NAME_TOO_LONG = 1002
ERR_NAME_MISSING = 1003
ERR_PADDING_INVALID = 1004
ERR_IMAGE_TYPE_INVALID = 1005
ERR_DISTANCE_INVALID = 1006
ERR_TRANSPARENCY_INVALID = 1007
ERR_PIC_TOO_WIDE = 1008
ERR_PIC_TOO_MANY_PIXELS = 1009
ERR_OBJ_WIDTH_INVALID = 1010
ERR_OBJ_TOO_WIDE = 1011
ERR_SMALL_IMAGE_TOO_LARGE = 1012
ERR_IMAGE_INVALID_PALETTE = 1013
ERR_IMG_MISSING = 1014
ERR_CLIPPING_INVALID = 1015

WARN_OBJ_HEIGHT_INVALID = 5001
WARN_GRASS_HEIGHT_TOO_SMALL = 5002
WARN_PALETTE_MISMATCH = 5003
WARN_QCOLORS_WRONG_SIZE = 5004
WARN_QBIKE_TOO_SMALL = 5005


def check_LGR_error(lgro: Union[LGR, LGR_Image], palette: Optional[List[int]] = None) -> List[List]:
    """
    Returns a list of errors or warnings for an LGR object or LGR_Image object.
    If you pass an LGR_Image object, you can also put a palette that the object
    should have
    """
    message: List[List] = []
    if isinstance(lgro, LGR):
        use_palette = None
        if(len(lgro.palette) == 768 and
                max(lgro.palette) <= 255 and
                min(lgro.palette) >= 0):
            use_palette = lgro.palette
        else:
            message.append([
                ERR_LGR_INVALID_PALETTE,
                None,
                "The LGR file has an invalid palette! Please set a palette "
                "using get_palette() on one of the images or using "
                "LGR_Image.default_palette()"])
        n_pic = 0
        n_tex = 0
        n_mask = 0
        n_grass = 0
        apples = [False] * 9
        for item in LGR_MANDATORY_FILES:
            n_count = 0
            for obj in lgro.images:
                if item == obj.name.lower():
                    n_count = 1
                    break
            if n_count == 0:
                message.append([
                    ERR_LGR_MISSING_MANDATORY_FILE,
                    item,
                    "The LGR file is missing a mandatory file: %s" % item])
        len_lgr = len(lgro.images)
        for i in range(len_lgr):
            namelower = lgro.images[i].name.lower()
            if lgro.images[i].is_in_pictures_lst():
                if lgro.images[i].is_qup_qdown():
                    n_grass += 1
                elif lgro.images[i].is_food():
                    apples[int(namelower[5:6])-1] = True
                elif lgro.images[i].image_type == LGR_Image.PICTURE:
                    n_pic += 1
                elif lgro.images[i].image_type == LGR_Image.TEXTURE:
                    n_tex += 1
                elif lgro.images[i].image_type == LGR_Image.MASK:
                    n_mask += 1
            else:
                if namelower == "qgrass":
                    n_tex += 1

            for j in range(i+1, len_lgr, 1):
                if namelower == lgro.images[j].name.lower():
                    message.append([
                        ERR_DUPLICATE_NAME,
                        lgro.images[j],
                        "The LGR file has a duplicate of the "
                        "following filename: %s" % lgro.images[j].name])
            message_temp = check_LGR_error(lgro.images[i], use_palette)
            if message_temp:
                message.extend(message_temp)
        if n_grass > LGR_GRASS_MAX:
            message.append([
                ERR_TOO_MANY_GRASS,
                n_grass,
                "The LGR file has %s grass images but "
                "can only support %s at most" % (n_grass, LGR_GRASS_MAX)])
        if n_pic > LGR_PIC_MAX:
            message.append([
                ERR_TOO_MANY_PICTURES,
                n_pic,
                "The LGR file has %s picture images but "
                "can only support %s at most" % (n_pic, LGR_PIC_MAX)])
        if n_tex < LGR_TEX_MIN:
            message.append([
                ERR_NOT_ENOUGH_TEXTURES,
                n_tex,
                "The LGR file has %s texture images but "
                "needs at least %s" % (n_tex, LGR_TEX_MIN)])
        elif n_tex > LGR_TEX_MAX:
            message.append([
                ERR_TOO_MANY_TEXTURES,
                n_tex,
                "The LGR file has %s texture image(s) but "
                "can only support %s at most" % (n_tex, LGR_TEX_MAX)])
        if n_mask > LGR_MASK_MAX:
            message.append([
                ERR_TOO_MANY_MASKS,
                n_mask,
                "The LGR file has %s mask images but "
                "can only support %s at most" % (n_mask, LGR_MASK_MAX)])
        appleFinished = False
        for i in range(9):
            if apples[i]:
                if appleFinished:
                    message.append([
                        WARN_UNUSED_QFOOD,
                        None,
                        "Warning: qfood%s will not appear in "
                        "the game as qfood%s is missing" % (i+1, i)])
            else:
                appleFinished = True

        return message
    elif isinstance(lgro, LGR_Image):
        is_mask = False
        len_name = len(lgro.name)
        if len_name > 8:
            message.append([
                ERR_NAME_TOO_LONG,
                lgro,
                ("The name of %s is too long "
                 "(maximum 8 characters)") % lgro.name])
        if len_name == 0:
            message.append([
                ERR_NAME_MISSING,
                lgro,
                "An LGR_Image has no name! %s" % lgro])
        try:
            if len(bytes(lgro.padding)) != 7:
                message.append([
                    ERR_PADDING_INVALID,
                    lgro,
                    "%s's padding is invalid (must be an "
                    "array of 7 ints in range (0-255))" % lgro.name])
        except(ValueError, TypeError):
            message.append([
                ERR_PADDING_INVALID,
                lgro,
                "%s's padding is invalid (must be an "
                "array of 7 ints in range (0-255))" % lgro.name])
        namelower = lgro.name.lower()
        if lgro.is_object():
            if lgro.img.height != LGR_OBJ_HEIGHT:
                message.append([
                    WARN_OBJ_HEIGHT_INVALID,
                    lgro,
                    "Warning: %s should have a height of %s, "
                    "but the height is %s. If the height is "
                    "smaller, the program might crash unexpectedly. "
                    "If the height is larger, the additional columns "
                    "will be ignored." % (
                        lgro.name,
                        LGR_OBJ_HEIGHT,
                        lgro.img.height)])
            if lgro.img.width % LGR_OBJ_WIDTH != 0:
                message.append([
                    ERR_OBJ_WIDTH_INVALID,
                    lgro,
                    "As %s is an object, the width (%s) must be a "
                    "multiple of %s" % (
                        lgro.name,
                        lgro.img.width,
                        LGR_OBJ_WIDTH)])
            if lgro.img.width > LGR_OBJ_WIDTH*LGR_OBJ_FRAMES_MAX:
                message.append([
                    ERR_OBJ_TOO_WIDE,
                    lgro,
                    "%s must have a width at most %s, "
                    "but the width is %s" % (
                        lgro.name,
                        lgro.img.width,
                        LGR_OBJ_WIDTH * LGR_OBJ_FRAMES_MAX)])
        elif lgro.is_in_pictures_lst():
            if lgro.is_qup_qdown():
                if lgro.img.height < LGR_WARNING_GRASS_HEIGHT_MIN:
                    message.append([
                        WARN_GRASS_HEIGHT_TOO_SMALL,
                        lgro,
                        "Warning: %s should have a minimum "
                        "height of %s, but the height is %s" % (
                            lgro.name,
                            LGR_WARNING_GRASS_HEIGHT_MIN,
                            lgro.img.height)])
            else:
                if 100 > lgro.image_type < 102:
                    message.append([
                        ERR_IMAGE_TYPE_INVALID,
                        lgro,
                        "%s's image_type is invalid" % lgro.name])
                else:
                    if lgro.image_type == LGR_Image.PICTURE:
                        if lgro.img.width > LGR_PIC_WIDTH_MAX:
                            message.append([
                                ERR_PIC_TOO_WIDE,
                                lgro,
                                "%s is too wide (%s) - the maximum is %s" %
                                (lgro.name,
                                 lgro.img.width,
                                 LGR_PIC_WIDTH_MAX)])
                        if(lgro.img.width * lgro.img.height >
                                LGR_PIC_SIZE_MAX):
                            message.append([
                                ERR_PIC_TOO_MANY_PIXELS,
                                lgro,
                                "%s has too many pixels (%s) - the "
                                "maximum is %s. Depending on the image, "
                                "the game might crash" % (
                                    lgro.name,
                                    lgro.img.width * lgro.img.height,
                                    LGR_PIC_SIZE_MAX)])
                    elif lgro.image_type == LGR_Image.MASK:
                        is_mask = True
                if not(1 <= lgro.default_distance <= 999) and not is_mask:
                    message.append([
                        ERR_DISTANCE_INVALID,
                        lgro,
                        "%s's distance (%s) is invalid "
                        "(must be integer between 1-999)" % (
                            lgro.name,
                            lgro.default_distance)])
                if not(0 <= lgro.default_clipping <= 2) and not is_mask:
                    message.append([
                        ERR_CLIPPING_INVALID,
                        lgro,
                        "%s's clipping (%s) is invalid" % (
                            lgro.name,
                            lgro.default_clipping)])
                if not(11 <= lgro.transparency <= 15):
                    message.append([
                        ERR_TRANSPARENCY_INVALID,
                        lgro,
                        "%s's transparency (%s) is invalid" % (
                            lgro.name,
                            lgro.transparency)])

        if lgro.img:
            with io.BytesIO() as f:
                lgro.save_PCX(f)
                size = f.tell()
                if size > LGR_PCX_FILESIZE_MAX:
                    message.append([
                        ERR_FILE_TOO_LARGE,
                        lgro,
                        "The file produced by %s is %s bytes in size, "
                        "over the limit of %s" % (
                            lgro.name,
                            size,
                            LGR_PCX_FILESIZE_MAX)])
            if namelower in LGR_LIMITED_SIZE_FILES and (lgro.img.width > 255 or lgro.img.height > 255):
                message.append([
                    ERR_SMALL_IMAGE_TOO_LARGE,
                    lgro,
                    "%s must have dimensions at most 255x255, "
                    "but the dimensions are %sx%s" % (
                        lgro.name,
                        lgro.img.width,
                        lgro.img.height)])
            if lgro.is_valid_palette_image():
                if palette and not is_mask and lgro.get_palette() != palette:
                    message.append([
                        WARN_PALETTE_MISMATCH,
                        lgro,
                        ("Warning: %s's palette does not "
                         "match the LGR's palette!") % lgro.name])
            else:
                message.append([
                    ERR_IMAGE_INVALID_PALETTE,
                    lgro,
                    "%s has an invalid palette" % lgro.name])
            if namelower == "qcolors":
                if lgro.img.width != LGR_WARNING_QCOLORS_WIDTH or lgro.img.height != LGR_WARNING_QCOLORS_HEIGHT:
                    message.append([
                        WARN_QCOLORS_WRONG_SIZE,
                        lgro,
                        "Warning: qcolors usually has dimensions of %sx%s, "
                        "but the dimensions are %sx%s" % (
                            LGR_WARNING_QCOLORS_WIDTH,
                            LGR_WARNING_QCOLORS_HEIGHT,
                            lgro.img.width,
                            lgro.img.height)])
            if namelower == "q1bike" or namelower == "q2bike":
                if(lgro.img.width < LGR_RECOMMEND_BIKE_WIDTH or
                        lgro.img.height < LGR_RECOMMEND_BIKE_HEIGHT):
                    message.append([
                        WARN_QBIKE_TOO_SMALL,
                        lgro,
                        "Warning: %s needs dimensions of at least %sx%s to be "
                        "rendered properly, but the dimensions are %sx%s" % (
                            lgro.name,
                            LGR_RECOMMEND_BIKE_WIDTH,
                            LGR_RECOMMEND_BIKE_HEIGHT,
                            lgro.img.width,
                            lgro.img.height)])
        else:
            message.append([
                ERR_IMG_MISSING,
                lgro,
                "%s has no image!" % lgro.name])
    else:
        raise ValueError("only LGR and LGR_Image objects can be "
                         "evaluated with this function")
    return message
