from PIL import Image


def getErr(r, g, b, stdCol):
    r -= stdCol[0]
    g -= stdCol[1]
    b -= stdCol[2]
    return r * r + g * g + b * b


def getNear(r, g, b, curPal):
    ind = 0
    err = getErr(r, g, b, curPal[0])
    for i in range(1, len(curPal)):
        cur = getErr(r, g, b, curPal[i])
        if cur < err:
            err = cur
            ind = i
    return ind


def addVal(c, r, g, b, k):
    return [c[0] + (r * k) / 32, c[1] + (g * k) / 32, c[2] + (b * k) / 32]


def procImg(img, dW, dH, dX, dY, palInd, isLvl, isRed, palArr):
    if isRed and ((palInd & 1) == 0):
        raise ValueError("This white-black display")

    if not isRed:
        palInd = palInd & 0xFE

    curPal = palArr[palInd]

    sW = img.width
    sH = img.height

    pDst = Image.new("RGB", (dW, dH))
    pixels = pDst.load()

    if isLvl:
        # Level-based processing
        for j in range(dH):
            y = dY + j
            for i in range(dW):
                x = dX + i
                if 0 <= x < sW and 0 <= y < sH:
                    r, g, b = img.getpixel((x, y))
                    color_index = getNear(r, g, b, curPal)
                    pixels[i, j] = tuple(curPal[color_index])
                else:
                    pixels[i, j] = tuple(
                        curPal[(i + j) % 2]
                    )  # checkerboard for out of bounds
    else:
        # Dithering
        errArr = [[[0, 0, 0] for _ in range(dW)] for _ in range(2)]
        aInd = 0
        bInd = 1

        for j in range(dH):
            y = dY + j
            aInd, bInd = bInd, aInd  # swap buffers
            for i in range(dW):
                errArr[bInd][i] = [0, 0, 0]

            for i in range(dW):
                x = dX + i
                if 0 <= x < sW and 0 <= y < sH:
                    old = errArr[aInd][i]
                    r_orig, g_orig, b_orig = img.getpixel((x, y))
                    r = r_orig + old[0]
                    g = g_orig + old[1]
                    b = b_orig + old[2]

                    color_index = getNear(r, g, b, curPal)
                    colVal = curPal[color_index]
                    pixels[i, j] = tuple(colVal)

                    r_err = r - colVal[0]
                    g_err = g - colVal[1]
                    b_err = b - colVal[2]

                    # Propagate error (Floyd-Steinberg-like)
                    if i == 0:
                        errArr[bInd][i] = addVal(
                            errArr[bInd][i], r_err, g_err, b_err, 7.0
                        )
                        if dW > 1:
                            errArr[bInd][i + 1] = addVal(
                                errArr[bInd][i + 1], r_err, g_err, b_err, 2.0
                            )
                            errArr[aInd][i + 1] = addVal(
                                errArr[aInd][i + 1], r_err, g_err, b_err, 7.0
                            )
                    elif i == dW - 1:
                        errArr[bInd][i - 1] = addVal(
                            errArr[bInd][i - 1], r_err, g_err, b_err, 7.0
                        )
                        errArr[bInd][i] = addVal(
                            errArr[bInd][i], r_err, g_err, b_err, 9.0
                        )
                    else:
                        errArr[bInd][i - 1] = addVal(
                            errArr[bInd][i - 1], r_err, g_err, b_err, 3.0
                        )
                        errArr[bInd][i] = addVal(
                            errArr[bInd][i], r_err, g_err, b_err, 5.0
                        )
                        errArr[bInd][i + 1] = addVal(
                            errArr[bInd][i + 1], r_err, g_err, b_err, 1.0
                        )
                        errArr[aInd][i + 1] = addVal(
                            errArr[aInd][i + 1], r_err, g_err, b_err, 7.0
                        )

                else:
                    pixels[i, j] = tuple(curPal[(i + j) % 2])  # checkerboard

    return pDst
