-- This file is generated by the neblua bundler
-- https://github.com/Tsukina-7mochi/neblua

local alreadyInstalled = _ENV.__NEBLUA_INSTALLED
_ENV.__NEBLUA_INSTALLED = true

package.bundleLoader = {}

package.bundleLoader["./entry/smoothFilter.lua"] = {
    line = debug.getinfo(1).currentline,
    loader = function(...)

package.manifest = {
    name = "aseprite-scripts/smooth-filter",
    description = "Applies filter to smooth the image.",
    version = "v0.1.1",
    author = "Mooncake Sugar",
    license = "MIT",
    homepage = "https://github.com/Tsukina-7mochi/aseprite-scripts/blob/master/smooth-filter/"
}

if not app then return end

-- settings
local scale    = 9
local smooth   = 4
local vicinity = 4

local dlg      = Dialog()
dlg:number { id = "scale", label = "Scale", text = "9" }
dlg:newrow()
dlg:number { id = "smooth", label = "Smooth", text = "4" }
dlg:newrow()
dlg:number { id = "vicinity", label = "Background detection", text = "4" }
dlg:newrow()
dlg:button { id = "ok", text = "&OK" }
dlg:button { id = "cancel", text = "&Cancel" }
dlg:show()

if not dlg.data.ok then return end

scale    = dlg.data.scale
smooth   = dlg.data.smooth
vicinity = dlg.data.vicinity

if smooth < 0 then
    smooth = 0
elseif smooth > math.floor(scale / 2) then
    smooth = math.floor(scale / 2)
end

if vicinity < 1 then
    vicinity = 1
end

-- duplicate sprite
if not app.activeSprite then
    return app.alert("No active sprite.")
end

local sprite = Sprite(app.activeSprite)
sprite:flatten()

local cel     = sprite.cels[1]
local image   = cel.image
local sBounds = sprite.bounds
local iBounds = cel.bounds

if not cel then
    return app.alert("No valid cel.")
end
if not image then
    return app.alert("No valid image.")
end

-- create image data array
imgData = {}
for y = 0, iBounds.height - 1 do
    local arr = {}
    for x = 0, iBounds.width - 1 do
        local pVal = image:getPixel(x, y)
        arr[#arr + 1] = pVal
    end
    imgData[#imgData + 1] = arr
end

-- scale
sprite:resize(iBounds.width * scale, iBounds.height * scale)
image = cel.image -- update image

-- smoothing
function drawTopLeft(x, y, col)
    -- top-left corner of enlarged pixel
    local ox, oy = (iBounds.x + x) * scale, (iBounds.y + y) * scale
    for i = 1, smooth do
        local rx, ry = i - 1, 0
        for j = 1, i do
            image:drawPixel(ox + rx, oy + ry, col)
            rx = rx - 1
            ry = ry + 1
        end
    end
end

function drawTopRight(x, y, col)
    -- top-right corner of enlarged pixel
    local ox, oy = (iBounds.x + x + 1) * scale - 1, (iBounds.y + y) * scale
    for i = 1, smooth do
        local rx, ry = -(i - 1), 0
        for j = 1, i do
            image:drawPixel(ox + rx, oy + ry, col)
            rx = rx + 1
            ry = ry + 1
        end
    end
end

function drawBottomLeft(x, y, col)
    -- bottom-left corner of enlarged pixel
    local ox, oy = (iBounds.x + x) * scale, (iBounds.y + y + 1) * scale
    for i = 1, smooth do
        local rx, ry = i - 1, 0
        for j = 1, i do
            image:drawPixel(ox + rx, oy + ry, col)
            rx = rx - 1
            ry = ry - 1
        end
    end
end

function drawBottomRight(x, y, col)
    -- bottom-right corner of enlarged pixel
    local ox, oy = (iBounds.x + x + 1) * scale - 1, (iBounds.y + y + 1) * scale - 1
    for i = 1, smooth do
        local rx, ry = -(i - 1), 0
        for j = 1, i do
            image:drawPixel(ox + rx, oy + ry, col)
            rx = rx + 1
            ry = ry - 1
        end
    end
end

for y = 0, iBounds.height - 2 do
    for x = 0, iBounds.width - 2 do
        -- process by 2x2 square
        local pTopLeft, pTopRight, pBottomLeft, pBottomRight = -1, -1, -1, -1

        pTopLeft                                             = imgData[y + 1][x + 1]
        pTopRight                                            = imgData[y + 1][x + 2]
        pBottomLeft                                          = imgData[y + 2][x + 1]
        pBottomRight                                         = imgData[y + 2][x + 2]

        -- AB
        -- CA
        if pTopLeft == pBottomRight and pTopRight ~= pBottomLeft then
            drawBottomLeft(x + 1, y, pTopLeft) -- B
            drawTopRight(x, y + 1, pTopLeft)   -- C
        end

        -- BA
        -- AC
        if pTopRight == pBottomLeft and pTopLeft ~= pBottomRight then
            drawBottomRight(x, y, pTopRight)     -- B
            drawTopLeft(x + 1, y + 1, pTopRight) -- C
        end

        -- AB
        -- BA
        if pTopRight == pBottomLeft and pTopLeft == pBottomRight then
            -- count colors in (2 * vicinity) x (2 * vicinity) square
            local cc1, cc2 = 0, 0
            for j = y - vicinity + 1, y + vicinity do
                for i = x - vicinity + 1, x + vicinity do
                    if 0 <= j and j < iBounds.height - 1 and 0 <= i and i < iBounds.width - 1 then
                        local c = imgData[j + 1][i + 1]
                        if c == pTopLeft then
                            cc1 = cc1 + 1
                        elseif c == pTopRight then
                            cc2 = cc2 + 1
                        end
                    end
                end
            end

            if cc1 > cc2 then
                -- regard top-left and bottom-right as background
                drawBottomRight(x, y, pTopRight)
                drawTopLeft(x + 1, y + 1, pTopRight)
            elseif cc1 < cc2 then
                -- regard top-right and bottom-left as background
                drawBottomLeft(x + 1, y, pTopLeft)
                drawTopRight(x, y + 1, pTopLeft)
            end
        end
    end
end


    end
    }


-- sentinel value for error message replacement
package.bundleLoader[debug.getinfo(1).short_src] = {
    line = 0,
    loader = nil,
}

---Splits a string by a search string
---@param str string
---@param searchStr string
---@return string[]
local function split(str, searchStr)
    if #str == 0 then
        return { str }
    end

    local result = {}
    local pos = 1

    while true do
        local startPos, endPos = str:find(searchStr, pos)

        if startPos == nil then
            break
        end

        table.insert(result, str:sub(pos, startPos - 1))
        pos = endPos + 1
    end

    if pos <= #str then
        table.insert(result, str:sub(pos))
    else
        table.insert(result, "")
    end

    return result
end

local config = split(package.config, "\n")
local pathSeparator = config[1]
local templateSeparator = config[2]
local substitutionPoint = config[3]
local internalPathSeparator = "/"

--- Resolves a given path using the specified path separator
---@param path string The path to resolve
---@return string
local function resolvePath(path)
    local segments = split(path, internalPathSeparator)
    local resultSegments = {}

    if segments[1] == "." then
        table.insert(resultSegments, ".")
    end
    if segments[1] == "" then
        table.insert(resultSegments, "")
    end
    if segments[1] == ".." then
        error("Cannot go above root directory")
    end

    for _, seg in ipairs(segments) do
        if seg == "." or seg == "" then
            -- Do nothing
        elseif seg == ".." then
            table.remove(resultSegments)
        else
            table.insert(resultSegments, seg)
        end
    end

    if segments[#segments] == "" then
        table.insert(resultSegments, "")
    end

    return table.concat(resultSegments, internalPathSeparator)
end

--- Searches for a module in the `package.bundleLoader`
---@param moduleName string
---@return function
---@return string
---@overload fun(moduleName: string): nil
local function bundlerSearcher(moduleName)
    moduleName = moduleName:gsub("%.", internalPathSeparator)

    local templates = split(package.path, templateSeparator)
    for _, template in ipairs(templates) do
        local path = template:gsub(pathSeparator, internalPathSeparator):gsub(substitutionPoint, moduleName)
        local resolvedPath = resolvePath(path)
        local loader = package.bundleLoader[resolvedPath]
        if loader ~= nil and loader.loader ~= nil then
            return loader.loader, path
        end
    end

    return nil
end

if not alreadyInstalled then
    --- Register the bundler searcher at the highest priority
    table.insert(package.searchers, 1, bundlerSearcher)

    -- Replace dofile
    local originalDoFile = dofile
    ---@diagnostic disable-next-line: duplicate-set-field
    _ENV.dofile = function(path)
        local loader = package.bundleLoader[path]
        if loader ~= nil and loader.loader ~= nil then
            return loader.loader()
        else
            return originalDoFile(path)
        end
    end

    -- Replace loadfile
    local originalLoadFile = loadfile
    ---@diagnostic disable-next-line: duplicate-set-field
    _ENV.loadfile = function(path, ...)
        local loader = package.bundleLoader[path]
        if loader ~= nil and loader.loader ~= nil then
            return loader.loader
        else
            return originalLoadFile(path, ...)
        end
    end
end

local loaderLineOffset = -2
---Error handler for xpcall
---@param err any
---@return any
local errorHandler = function(err)
    local srcName = debug.getinfo(1).short_src:gsub("[^%w]", "%%%0")
    local pattern = srcName .. ":(%d+):"

    local message = debug.traceback(err, 2):gsub(pattern, function(line)
        local lineNumber = tonumber(line)

        local loaderLine = -1
        local loaderName = nil
        for name, loader in pairs(package.bundleLoader) do
            if loader.line ~= nil and loaderLine < loader.line and loader.line < lineNumber then
                loaderLine = loader.line
                loaderName = name
            end
        end

        return loaderName .. ":" .. (lineNumber - loaderLine + loaderLineOffset) .. ":"
    end)

    return message
end

local fallbackStderr = true

local loader = bundlerSearcher("entry.smoothFilter")
if loader == nil then
    error("Cannot find entry point: " .. "entry.smoothFilter")
end

local result = table.pack(xpcall(loader, errorHandler, "entry.smoothFilter", ...))
local success = result[1]
-- print error to stdout and re-throw
if not success then
    if fallbackStderr then
        print(result[2])
    else
        io.stderr:write(result[2])
    end
    error("Error occurred in bundled file.")
end

return table.unpack(result, 2)
