local images = require("images")
local bimg = require("bimg")
local unpack,sub,max,min = table.unpack,string.sub,math.max,math.min
return function(name, basalt)
    -- Image
    local base = basalt.getObject("VisualObject")(name, basalt)
    local objectType = "Image"

    local bimgLibrary = bimg()
    local bimgFrame = bimgLibrary.getFrameObject(1)
    local originalImage
    local image
    local activeFrame = 1

    local infinitePlay = false
    local animTimer
    local usePalette = false
    local autoSize = true

    local xOffset, yOffset = 0, 0

    base:setSize(24, 8)
    base:setZIndex(2)

    local function getPalette(id)
        local p = {}
        for k,v in pairs(colors)do
            if(type(v)=="number")then
                p[k] = {term.nativePaletteColor(v)}
            end
        end
        local globalPalette = bimgLibrary.getMetadata("palette")
        if(globalPalette~=nil)then
            for k,v in pairs(globalPalette)do
                p[k] = tonumber(v)
            end
        end
        local localPalette = bimgLibrary.getFrameData("palette")
        basalt.log(localPalette)
        if(localPalette~=nil)then
            for k,v in pairs(localPalette)do
                p[k] = tonumber(v)
            end
        end
        return p
    end

    local function checkAutoSize()
        if(autoSize)then
            if(bimgLibrary~=nil)then
                base:setSize(bimgLibrary.getSize())
            end
        end
    end

    local object = {
        getType = function(self)
            return objectType
        end,
        isType = function(self, t)
            return objectType==t or base.isType~=nil and base.isType(t) or false
        end,

        setOffset = function(self, _x, _y, rel)
            if(rel)then
                xOffset = xOffset + _x or 0
                yOffset = yOffset + _y or 0
            else
                xOffset = _x or xOffset
                yOffset = _y or yOffset
            end
            self:updateDraw()
            return self
        end,

        setXOffset = function(self, _x)
            return self:setOffset(self, _x, nil)
        end,

        setYOffset = function(self, _y)
            return self:setOffset(self, nil, _y)
        end,

        setSize = function(self, _x, _y)
            base:setSize(_x, _y)
            autoSize = false
            return self
        end,

        getOffset = function(self)
            return xOffset, yOffset
        end,

        getXOffset = function(self)
            return xOffset
        end,

        getYOffset = function(self)
            return yOffset
        end,

        selectFrame = function(self, id)
            if(bimgLibrary.getFrameObject(id)==nil)then
                bimgLibrary.addFrame(id)
            end
            bimgFrame = bimgLibrary.getFrameObject(id)
            image = bimgFrame.getImage(id)
            activeFrame = id
            self:updateDraw()
        end,

        addFrame = function(self, id)
            bimgLibrary.addFrame(id)
            return self
        end,

        getFrame = function(self, id)
            return bimgLibrary.getFrame(id)
        end,

        getFrameObject = function(self, id)
            return bimgLibrary.getFrameObject(id)
        end,

        removeFrame = function(self, id)
            bimgLibrary.removeFrame(id)
            return self
        end,

        moveFrame = function(self, id, dir)
            bimgLibrary.moveFrame(id, dir)
            return self
        end,

        getFrames = function(self)
            return bimgLibrary.getFrames()
        end,

        getFrameCount = function(self)
            return #bimgLibrary.getFrames()
        end,

        getActiveFrame = function(self)
            return activeFrame
        end,

        loadImage = function(self, path)
            if(fs.exists(path))then
                local newBimg = images.loadBIMG(path)
                bimgLibrary = bimg(newBimg)
                activeFrame = 1
                bimgFrame = bimgLibrary.getFrameObject(1)
                originalImage = bimgLibrary.createBimg()
                image = bimgFrame.getImage()
                checkAutoSize()
                self:updateDraw()
            end     
            return self
        end,

        setPath = function(self, path)
            return self:loadImage(path)
        end,

        setImage = function(self, t)
            if(type(t)=="table")then
                bimgLibrary = bimg(t)
                activeFrame = 1
                bimgFrame = bimgLibrary.getFrameObject(1)
                originalImage = bimgLibrary.createBimg()
                image = bimgFrame.getImage()
                checkAutoSize()
                self:updateDraw()
            end
            return self
        end,

        clear = function(self)
            bimgLibrary = bimg()
            bimgFrame = bimgLibrary.getFrameObject(1)
            image = nil
            self:updateDraw()
            return self
        end,

        getImage = function(self)
            return bimgLibrary.createBimg()
        end,

        getImageFrame = function(self, id)
            return bimgFrame.getImage(id)
        end,

        usePalette = function(self, use)
            usePalette = use~=nil and use or true
            return self
        end,

        getUsePalette = function(self)
            return usePalette
        end,

        setUsePalette = function(self, use)
            return self:usePalette(use)
        end,

        play = function(self, inf)
            if(bimgLibrary.getMetadata("animated"))then
                local t = bimgLibrary.getMetadata("duration") or bimgLibrary.getMetadata("secondsPerFrame") or 0.2
                self:listenEvent("other_event")
                animTimer = os.startTimer(t)
                infinitePlay = inf or false
            end
            return self
        end,

        setPlay = function(self, inf)
            return self:play(inf)
        end,

        stop = function(self)
            os.cancelTimer(animTimer)
            animTimer = nil
            infinitePlay = false
            return self
        end,

        eventHandler = function(self, event, timerId, ...)
            base.eventHandler(self, event, timerId, ...)
            if(event=="timer")then
                if(timerId==animTimer)then
                    if(bimgLibrary.getFrame(activeFrame+1)~=nil)then
                        activeFrame = activeFrame + 1
                        self:selectFrame(activeFrame)
                        local t = bimgLibrary.getFrameData(activeFrame, "duration") or bimgLibrary.getMetadata("secondsPerFrame") or 0.2
                        animTimer = os.startTimer(t)
                    else
                        if(infinitePlay)then
                            activeFrame = 1
                            self:selectFrame(activeFrame)
                            local t = bimgLibrary.getFrameData(activeFrame, "duration") or bimgLibrary.getMetadata("secondsPerFrame") or 0.2
                            animTimer = os.startTimer(t)
                        end
                    end
                    self:updateDraw()
                end
            end
        end,

        setMetadata = function(self, key, value)
            bimgLibrary.setMetadata(key, value)
            return self
        end,

        getMetadata = function(self, key)
            return bimgLibrary.getMetadata(key)
        end,

        getFrameMetadata = function(self, id, key)
            return bimgLibrary.getFrameData(id, key)
        end,

        setFrameMetadata = function(self, id, key, value)
            bimgLibrary.setFrameData(id, key, value)
            return self
        end,

        blit = function(self, text, fg, bg, _x, _y)
            x = _x or x
            y = _y or y
            bimgFrame.blit(text, fg, bg, x, y)
            image = bimgFrame.getImage()
            self:updateDraw()
            return self
        end,

        setText = function(self, text, _x, _y)
            x = _x or x
            y = _y or y
            bimgFrame.text(text, x, y)
            image = bimgFrame.getImage()
            self:updateDraw()
            return self
        end,

        setBg = function(self, bg, _x, _y)
            x = _x or x
            y = _y or y
            bimgFrame.bg(bg, x, y)
            image = bimgFrame.getImage()
            self:updateDraw()
            return self
        end,

        setFg = function(self, fg, _x, _y)
            x = _x or x
            y = _y or y
            bimgFrame.fg(fg, x, y)
            image = bimgFrame.getImage()
            self:updateDraw()
            return self
        end,

        getImageSize = function(self)
            return bimgLibrary.getSize()
        end,

        setImageSize = function(self, w, h)
            bimgLibrary.setSize(w, h)
            image = bimgFrame.getImage()
            self:updateDraw()
            return self
        end,

        resizeImage = function(self, w, h)
            local newBimg = images.resizeBIMG(originalImage, w, h)
            bimgLibrary = bimg(newBimg)
            activeFrame = 1
            bimgFrame = bimgLibrary.getFrameObject(1)
            image = bimgFrame.getImage()
            self:updateDraw()
            return self
        end,

        draw = function(self)
            base.draw(self)
            self:addDraw("image", function()
                local w,h = self:getSize()
                local x, y = self:getPosition()
                local wParent, hParent = self:getParent():getSize()
                local parentXOffset, parentYOffset = self:getParent():getOffset()
                
                if(x - parentXOffset > wParent)or(y - parentYOffset > hParent)or(x - parentXOffset + w < 1)or(y - parentYOffset + h < 1)then
                    return
                end

                if(usePalette)then
                    self:getParent():setPalette(getPalette(activeFrame))
                end

                if(image~=nil)then
                    for k,v in pairs(image)do
                        if(k+yOffset<=h)and(k+yOffset>=1)then
                            local t,f,b = v[1],v[2],v[3]

                            local startIdx = max(1 - xOffset, 1)
                            local endIdx = min(w - xOffset, #t)
                            
                            t = sub(t, startIdx, endIdx)
                            f = sub(f, startIdx, endIdx)
                            b = sub(b, startIdx, endIdx)

                            self:addBlit(max(1 + xOffset, 1), k + yOffset, t, f, b)
                        end
                    end
                end
            end)
        end,
    }

    object.__index = object
    return setmetatable(object, base)
end