local tHex = require("tHex")

local rep,find,gmatch,sub,len = string.rep,string.find,string.gmatch,string.sub,string.len

return function(name, basalt)
    local base = basalt.getObject("ChangeableObject")(name, basalt)
    local objectType = "Textfield"
    local hIndex, wIndex, textX, textY = 1, 1, 1, 1

    local lines = { "" }
    local bgLines = { "" }
    local fgLines = { "" }
    local keyWords = { }
    local rules = { }

    local startSelX,endSelX,startSelY,endSelY

    local selectionBG,selectionFG = colors.lightBlue,colors.black

    base:setSize(30, 12)
    base:setZIndex(5)

    local function isSelected()
        if(startSelX~=nil)and(endSelX~=nil)and(startSelY~=nil)and(endSelY~=nil)then
            return true
        end
        return false
    end

    local function getSelectionCoordinates()
        local sx, ex, sy, ey = startSelX, endSelX, startSelY, endSelY
        if isSelected() then
            if startSelX < endSelX and startSelY <= endSelY then
                sx = startSelX
                ex = endSelX
                if startSelY < endSelY then
                    sy = startSelY
                    ey = endSelY
                else
                    sy = endSelY
                    ey = startSelY
                end
            elseif startSelX > endSelX and startSelY >= endSelY then
                sx = endSelX
                ex = startSelX
                if startSelY > endSelY then
                    sy = endSelY
                    ey = startSelY
                else
                    sy = startSelY
                    ey = endSelY
                end
            elseif startSelY > endSelY then
                sx = endSelX
                ex = startSelX
                sy = endSelY
                ey = startSelY
            end
            return sx, ex, sy, ey
        end
    end

    local function removeSelection(self)
        local sx, ex, sy, ey = getSelectionCoordinates()
        local startLine = lines[sy]
        local endLine = lines[ey]
        lines[sy] = startLine:sub(1, sx - 1) .. endLine:sub(ex + 1, endLine:len())
        bgLines[sy] = bgLines[sy]:sub(1, sx - 1) .. bgLines[ey]:sub(ex + 1, bgLines[ey]:len())
        fgLines[sy] = fgLines[sy]:sub(1, sx - 1) .. fgLines[ey]:sub(ex + 1, fgLines[ey]:len())
    
        for i = ey, sy + 1, -1 do
            if i ~= sy then
                table.remove(lines, i)
                table.remove(bgLines, i)
                table.remove(fgLines, i)
            end
        end
    
        textX, textY = sx, sy
        startSelX, endSelX, startSelY, endSelY = nil, nil, nil, nil
        return self
    end

    local function stringGetPositions(str, word)
        local pos = {}
        if(str:len()>0)then
            for w in gmatch(str, word)do
                local s, e = find(str, w)
                if(s~=nil)and(e~=nil)then
                    table.insert(pos,s)
                    table.insert(pos,e)
                    local startL = sub(str, 1, (s-1))
                    local endL = sub(str, e+1, str:len())
                    str = startL..(":"):rep(w:len())..endL
                end
            end
        end
        return pos
    end

    local function updateColors(self, l)
        l = l or textY
        local fgLine = tHex[self:getForeground()]:rep(fgLines[l]:len())
        local bgLine = tHex[self:getBackground()]:rep(bgLines[l]:len())
        for k,v in pairs(rules)do
            local pos = stringGetPositions(lines[l], v[1])
            if(#pos>0)then
                for x=1,#pos/2 do
                    local xP = x*2 - 1
                    if(v[2]~=nil)then
                        fgLine = fgLine:sub(1, pos[xP]-1)..tHex[v[2]]:rep(pos[xP+1]-(pos[xP]-1))..fgLine:sub(pos[xP+1]+1, fgLine:len())
                    end
                    if(v[3]~=nil)then
                        bgLine = bgLine:sub(1, pos[xP]-1)..tHex[v[3]]:rep(pos[xP+1]-(pos[xP]-1))..bgLine:sub(pos[xP+1]+1, bgLine:len())
                    end
                end
            end
        end
        for k,v in pairs(keyWords)do
            for _,b in pairs(v)do
                local pos = stringGetPositions(lines[l], b)
                if(#pos>0)then
                    for x=1,#pos/2 do
                        local xP = x*2 - 1
                        fgLine = fgLine:sub(1, pos[xP]-1)..tHex[k]:rep(pos[xP+1]-(pos[xP]-1))..fgLine:sub(pos[xP+1]+1, fgLine:len())
                    end
                end
            end
        end
        fgLines[l] = fgLine
        bgLines[l] = bgLine
        self:updateDraw()
    end

    local function updateAllColors(self)
        for n=1,#lines do
            updateColors(self, n)
        end
    end

    local object = {
        getType = function(self)
            return objectType
        end;

        setBackground = function(self, bg)
            base.setBackground(self, bg)
            updateAllColors(self)
            return self
        end,

        setForeground = function(self, fg)
            base.setForeground(self, fg)
            updateAllColors(self)
            return self
        end,

        setSelection = function(self, fg, bg)
            selectionFG = fg or selectionFG
            selectionBG = bg or selectionBG
            return self
        end,

        setSelectionFG = function(self, fg)
            return self:setSelection(fg, nil)
        end,

        setSelectionBG = function(self, bg)
            return self:setSelection(nil, bg)
        end,

        getSelection = function(self)
            return selectionFG, selectionBG
        end,

        getSelectionFG = function(self)
            return selectionFG
        end,

        getSelectionBG = function(self)
            return selectionBG
        end,

        getLines = function(self)
            return lines
        end,

        getLine = function(self, index)
            return lines[index]
        end,

        editLine = function(self, index, text)
            lines[index] = text or lines[index]
            updateColors(self, index)
            self:updateDraw()
            return self
        end,

        clear = function(self)
            lines = {""}
            bgLines = {""}
            fgLines = {""}
            startSelX,endSelX,startSelY,endSelY = nil,nil,nil,nil
            hIndex, wIndex, textX, textY = 1, 1, 1, 1
            self:updateDraw()
            return self
        end,

        addLine = function(self, text, index)
            if(text~=nil)then
                local bgColor = self:getBackground()
                local fgColor = self:getForeground()
                if(#lines==1)and(lines[1]=="")then
                    lines[1] = text
                    bgLines[1] = tHex[bgColor]:rep(text:len())
                    fgLines[1] = tHex[fgColor]:rep(text:len())
                    updateColors(self, 1)
                    return self
                end
                if (index ~= nil) then
                    table.insert(lines, index, text)
                    table.insert(bgLines, index, tHex[bgColor]:rep(text:len()))
                    table.insert(fgLines, index, tHex[fgColor]:rep(text:len()))
                else
                    table.insert(lines, text)
                    table.insert(bgLines, tHex[bgColor]:rep(text:len()))
                    table.insert(fgLines, tHex[fgColor]:rep(text:len()))
                end
            end
            updateColors(self, index or #lines)
            self:updateDraw()
            return self
        end;

        addKeywords = function(self, color, tab)
            if(keyWords[color]==nil)then
                keyWords[color] = {}
            end
            for k,v in pairs(tab)do
                table.insert(keyWords[color], v)
            end
            self:updateDraw()
            return self
        end;

        addRule = function(self, rule, fg, bg)
            table.insert(rules, {rule, fg, bg})
            self:updateDraw()
            return self
        end;

        editRule = function(self, rule, fg, bg)
            for k,v in pairs(rules)do
                if(v[1]==rule)then
                    rules[k][2] = fg
                    rules[k][3] = bg
                end
            end
            self:updateDraw()
            return self
        end;

        removeRule = function(self, rule)
            for k,v in pairs(rules)do
                if(v[1]==rule)then
                    table.remove(rules, k)
                end
            end
            self:updateDraw()
            return self
        end,

        setKeywords = function(self, color, tab)
            keyWords[color] = tab
            self:updateDraw()
            return self
        end,

        removeLine = function(self, index)
            if(#lines>1)then
                table.remove(lines, index or #lines)
                table.remove(bgLines, index or #bgLines)
                table.remove(fgLines, index or #fgLines)
            else
                lines = {""}
                bgLines = {""}
                fgLines = {""}
            end
            self:updateDraw()
            return self
        end,

        getTextCursor = function(self)
            return textX, textY
        end,

        getOffset = function(self)
            return wIndex, hIndex
        end,

        setOffset = function(self, xOff, yOff)
            wIndex = xOff or wIndex
            hIndex = yOff or hIndex
            self:updateDraw()
            return self
        end,

        getXOffset = function(self)
            return wIndex
        end,

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

        getYOffset = function(self)
            return hIndex
        end,

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

        getFocusHandler = function(self)
            base.getFocusHandler(self)
            local obx, oby = self:getPosition()
            self:getParent():setCursor(true, obx + textX - wIndex, oby + textY - hIndex, self:getForeground())
        end,

        loseFocusHandler = function(self)
            base.loseFocusHandler(self)
            self:getParent():setCursor(false)
        end,

        keyHandler = function(self, key)
            if (base.keyHandler(self, key)) then
                local parent = self:getParent()
                local obx, oby = self:getPosition()
                local w,h = self:getSize()
                    if (key == keys.backspace) then
                        -- on backspace
                        if(isSelected())then
                            removeSelection(self)
                        else
                            if (lines[textY] == "") then
                                if (textY > 1) then
                                    table.remove(lines, textY)
                                    table.remove(fgLines, textY)
                                    table.remove(bgLines, textY)
                                    textX = lines[textY - 1]:len() + 1
                                    wIndex = textX - w + 1
                                    if (wIndex < 1) then
                                        wIndex = 1
                                    end
                                    textY = textY - 1
                                end
                            elseif (textX <= 1) then
                                if (textY > 1) then
                                    textX = lines[textY - 1]:len() + 1
                                    wIndex = textX - w + 1
                                    if (wIndex < 1) then
                                        wIndex = 1
                                    end
                                    lines[textY - 1] = lines[textY - 1] .. lines[textY]
                                    fgLines[textY - 1] = fgLines[textY - 1] .. fgLines[textY]
                                    bgLines[textY - 1] = bgLines[textY - 1] .. bgLines[textY]
                                    table.remove(lines, textY)
                                    table.remove(fgLines, textY)
                                    table.remove(bgLines, textY)
                                    textY = textY - 1
                                end
                            else
                                lines[textY] = lines[textY]:sub(1, textX - 2) .. lines[textY]:sub(textX, lines[textY]:len())
                                fgLines[textY] = fgLines[textY]:sub(1, textX - 2) .. fgLines[textY]:sub(textX, fgLines[textY]:len())
                                bgLines[textY] = bgLines[textY]:sub(1, textX - 2) .. bgLines[textY]:sub(textX, bgLines[textY]:len())
                                if (textX > 1) then
                                    textX = textX - 1
                                end
                                if (wIndex > 1) then
                                    if (textX < wIndex) then
                                        wIndex = wIndex - 1
                                    end
                                end
                            end
                            if (textY < hIndex) then
                                hIndex = hIndex - 1
                            end
                        end
                        updateColors(self)
                        self:setValue("")
                    elseif (key == keys.delete) then
                        -- on delete
                        if(isSelected())then
                            removeSelection(self)
                        else
                            if (textX > lines[textY]:len()) then
                                if (lines[textY + 1] ~= nil) then
                                    lines[textY] = lines[textY] .. lines[textY + 1]
                                    table.remove(lines, textY + 1)
                                    table.remove(bgLines, textY + 1)
                                    table.remove(fgLines, textY + 1)
                                end
                            else
                                lines[textY] = lines[textY]:sub(1, textX - 1) .. lines[textY]:sub(textX + 1, lines[textY]:len())
                                fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. fgLines[textY]:sub(textX + 1, fgLines[textY]:len())
                                bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. bgLines[textY]:sub(textX + 1, bgLines[textY]:len())
                            end
                        end
                        updateColors(self)
                    elseif (key == keys.enter) then
                        if(isSelected())then
                            removeSelection(self)
                        end
                        -- on enter
                        table.insert(lines, textY + 1, lines[textY]:sub(textX, lines[textY]:len()))
                        table.insert(fgLines, textY + 1, fgLines[textY]:sub(textX, fgLines[textY]:len()))
                        table.insert(bgLines, textY + 1, bgLines[textY]:sub(textX, bgLines[textY]:len()))
                        lines[textY] = lines[textY]:sub(1, textX - 1)
                        fgLines[textY] = fgLines[textY]:sub(1, textX - 1)
                        bgLines[textY] = bgLines[textY]:sub(1, textX - 1)
                        textY = textY + 1
                        textX = 1
                        wIndex = 1
                        if (textY - hIndex >= h) then
                            hIndex = hIndex + 1
                        end
                        self:setValue("")
                    elseif (key == keys.up) then
                        startSelX, startSelY, endSelX, endSelY = nil, nil, nil, nil
                        -- arrow up
                        if (textY > 1) then
                            textY = textY - 1
                            if (textX > lines[textY]:len() + 1) then
                                textX = lines[textY]:len() + 1
                            end
                            if (wIndex > 1) then
                                if (textX < wIndex) then
                                    wIndex = textX - w + 1
                                    if (wIndex < 1) then
                                        wIndex = 1
                                    end
                                end
                            end
                            if (hIndex > 1) then
                                if (textY < hIndex) then
                                    hIndex = hIndex - 1
                                end
                            end
                        end
                    elseif (key == keys.down) then
                        startSelX, startSelY, endSelX, endSelY = nil, nil, nil, nil
                        -- arrow down
                        if (textY < #lines) then
                            textY = textY + 1
                            if (textX > lines[textY]:len() + 1) then
                                textX = lines[textY]:len() + 1
                            end
                            if (wIndex > 1) then
                                if (textX < wIndex) then
                                    wIndex = textX - w + 1
                                    if (wIndex < 1) then
                                        wIndex = 1
                                    end
                                end
                            end
                            if (textY >= hIndex + h) then
                                hIndex = hIndex + 1
                            end
                        end
                    elseif (key == keys.right) then
                        startSelX, startSelY, endSelX, endSelY = nil, nil, nil, nil
                        -- arrow right
                        textX = textX + 1
                        if (textY < #lines) then
                            if (textX > lines[textY]:len() + 1) then
                                textX = 1
                                textY = textY + 1
                            end
                        elseif (textX > lines[textY]:len()) then
                            textX = lines[textY]:len() + 1
                        end
                        if (textX < 1) then
                            textX = 1
                        end
                        if (textX < wIndex) or (textX >= w + wIndex) then
                            wIndex = textX - w + 1
                        end
                        if (wIndex < 1) then
                            wIndex = 1
                        end

                    elseif (key == keys.left) then
                        startSelX, startSelY, endSelX, endSelY = nil, nil, nil, nil
                        -- arrow left
                        textX = textX - 1
                        if (textX >= 1) then
                            if (textX < wIndex) or (textX >= w + wIndex) then
                                wIndex = textX
                            end
                        end
                        if (textY > 1) then
                            if (textX < 1) then
                                textY = textY - 1
                                textX = lines[textY]:len() + 1
                                wIndex = textX - w + 1
                            end
                        end
                        if (textX < 1) then
                            textX = 1
                        end
                        if (wIndex < 1) then
                            wIndex = 1
                        end
                    elseif(key == keys.tab)then
                        if(textX % 3 == 0 )then
                            lines[textY] = lines[textY]:sub(1, textX - 1) .. " " .. lines[textY]:sub(textX, lines[textY]:len())
                            fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[self:getForeground()] .. fgLines[textY]:sub(textX, fgLines[textY]:len())
                            bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[self:getBackground()] .. bgLines[textY]:sub(textX, bgLines[textY]:len())
                            textX = textX + 1
                        end
                        while textX % 3 ~= 0 do
                            lines[textY] = lines[textY]:sub(1, textX - 1) .. " " .. lines[textY]:sub(textX, lines[textY]:len())
                            fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[self:getForeground()] .. fgLines[textY]:sub(textX, fgLines[textY]:len())
                            bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[self:getBackground()] .. bgLines[textY]:sub(textX, bgLines[textY]:len())
                            textX = textX + 1
                        end
                    end

                if not((obx + textX - wIndex >= obx and obx + textX - wIndex < obx + w) and (oby + textY - hIndex >= oby and oby + textY - hIndex < oby + h)) then
                    wIndex = math.max(1, lines[textY]:len()-w+1)
                    hIndex = math.max(1, textY - h + 1)
                end

                local cursorX = (textX <= lines[textY]:len() and textX - 1 or lines[textY]:len()) - (wIndex - 1)
                if (cursorX > self:getX() + w - 1) then
                    cursorX = self:getX() + w - 1
                end
                local cursorY = (textY - hIndex < h and textY - hIndex or textY - hIndex - 1)
                if (cursorX < 1) then
                    cursorX = 0
                end
                parent:setCursor(true, obx + cursorX, oby + cursorY, self:getForeground())
                self:updateDraw()
                return true
            end
        end,

        charHandler = function(self, char)
            if(base.charHandler(self, char))then
                local parent = self:getParent()
                local obx, oby = self:getPosition()
                local w,h = self:getSize()
                if(isSelected())then
                    removeSelection(self)
                end
                lines[textY] = lines[textY]:sub(1, textX - 1) .. char .. lines[textY]:sub(textX, lines[textY]:len())
                fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[self:getForeground()] .. fgLines[textY]:sub(textX, fgLines[textY]:len())
                bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[self:getBackground()] .. bgLines[textY]:sub(textX, bgLines[textY]:len())
                textX = textX + 1
                if (textX >= w + wIndex) then
                    wIndex = wIndex + 1
                end
                updateColors(self)
                self:setValue("")

                if not((obx + textX - wIndex >= obx and obx + textX - wIndex < obx + w) and (oby + textY - hIndex >= oby and oby + textY - hIndex < oby + h)) then
                    wIndex = math.max(1, lines[textY]:len()-w+1)
                    hIndex = math.max(1, textY - h + 1)
                end

                local cursorX = (textX <= lines[textY]:len() and textX - 1 or lines[textY]:len()) - (wIndex - 1)
                if (cursorX > self:getX() + w - 1) then
                    cursorX = self:getX() + w - 1
                end
                local cursorY = (textY - hIndex < h and textY - hIndex or textY - hIndex - 1)
                if (cursorX < 1) then
                    cursorX = 0
                end
                parent:setCursor(true, obx + cursorX, oby + cursorY, self:getForeground())
                self:updateDraw()
                return true
            end
        end,

        dragHandler = function(self, button, x, y)
            if (base.dragHandler(self, button, x, y)) then
                local parent = self:getParent()
                local obx, oby = self:getAbsolutePosition()
                local ox, oy = self:getPosition()
                local w,h = self:getSize()
                if (lines[y - oby + hIndex] ~= nil) then
                    if(x - obx + wIndex > 0)and(x - obx + wIndex <= w)then
                        textX = x - obx + wIndex
                        textY = y - oby + hIndex

                        if textX > lines[textY]:len() then
                            textX = lines[textY]:len() + 1
                        end
                        endSelX = textX
                        endSelY = textY

                        if textX < wIndex then
                            wIndex = textX - 1
                            if wIndex < 1 then
                                wIndex = 1
                            end
                        end
                        parent:setCursor(not isSelected(), ox + textX - wIndex, oy + textY - hIndex, self:getForeground())
                        self:updateDraw()
                    end
                end
                return true
            end
        end,

        scrollHandler = function(self, dir, x, y)
            if (base.scrollHandler(self, dir, x, y)) then
                local parent = self:getParent()
                local obx, oby = self:getAbsolutePosition()
                local anchx, anchy = self:getPosition()
                local w,h = self:getSize()
                hIndex = hIndex + dir
                if (hIndex > #lines - (h - 1)) then
                    hIndex = #lines - (h - 1)
                end

                if (hIndex < 1) then
                    hIndex = 1
                end

                if (obx + textX - wIndex >= obx and obx + textX - wIndex < obx + w) and (anchy + textY - hIndex >= anchy and anchy + textY - hIndex < anchy + h) then
                    parent:setCursor(not isSelected(), anchx + textX - wIndex, anchy + textY - hIndex, self:getForeground())
                else
                    parent:setCursor(false)
                end
                self:updateDraw()
                return true
            end
        end,

        mouseHandler = function(self, button, x, y)
            if (base.mouseHandler(self, button, x, y)) then
                local parent = self:getParent()
                local obx, oby = self:getAbsolutePosition()
                local anchx, anchy = self:getPosition()
                    if (lines[y - oby + hIndex] ~= nil) then
                        textX = x - obx + wIndex
                        textY = y - oby + hIndex
                        endSelX = nil
                        endSelY = nil
                        startSelX = textX
                        startSelY = textY
                        if (textX > lines[textY]:len()) then
                            textX = lines[textY]:len() + 1
                            startSelX = textX
                        end
                        if (textX < wIndex) then
                            wIndex = textX - 1
                            if (wIndex < 1) then
                                wIndex = 1
                            end
                        end
                        self:updateDraw()
                    end
                parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, self:getForeground())
                return true
            end
        end,

        mouseUpHandler = function(self, button, x, y)
            if (base.mouseUpHandler(self, button, x, y)) then
                local obx, oby = self:getAbsolutePosition()
                    if (lines[y - oby + hIndex] ~= nil) then
                        endSelX = x - obx + wIndex
                        endSelY = y - oby + hIndex
                        if (endSelX > lines[endSelY]:len()) then
                            endSelX = lines[endSelY]:len() + 1
                        end
                        if(startSelX==endSelX)and(startSelY==endSelY)then
                            startSelX, endSelX, startSelY, endSelY = nil, nil, nil, nil
                        end
                        self:updateDraw()
                    end
                return true
            end
        end,

        eventHandler = function(self, event, paste, ...)
            base.eventHandler(self, event, paste, ...)
            if(event=="paste")then
                if(self:isFocused())then
                    local parent = self:getParent()
                    local fgColor, bgColor = self:getForeground(), self:getBackground()
                    local w, h = self:getSize()
                    lines[textY] = lines[textY]:sub(1, textX - 1) .. paste .. lines[textY]:sub(textX, lines[textY]:len())
                    fgLines[textY] = fgLines[textY]:sub(1, textX - 1) .. tHex[fgColor]:rep(paste:len()) .. fgLines[textY]:sub(textX, fgLines[textY]:len())
                    bgLines[textY] = bgLines[textY]:sub(1, textX - 1) .. tHex[bgColor]:rep(paste:len()) .. bgLines[textY]:sub(textX, bgLines[textY]:len())
                    textX = textX + paste:len()
                    if (textX >= w + wIndex) then
                        wIndex = (textX+1)-w
                    end
                    local anchx, anchy = self:getPosition()
                    parent:setCursor(true, anchx + textX - wIndex, anchy + textY - hIndex, fgColor)
                    updateColors(self)
                    self:updateDraw()
                end
            end
        end,

        draw = function(self)
            base.draw(self)
            self:addDraw("textfield", function()
                local w, h = self:getSize()
                local bgColor = tHex[self:getBackground()]
                local fgColor = tHex[self:getForeground()]
        
                for n = 1, h do
                    local text = ""
                    local bg = ""
                    local fg = ""
                    if lines[n + hIndex - 1] then
                        text = lines[n + hIndex - 1]
                        fg = fgLines[n + hIndex - 1]
                        bg = bgLines[n + hIndex - 1]
                    end
        
                    text = sub(text, wIndex, w + wIndex - 1)
                    bg = rep(bgColor, w)
                    fg = rep(fgColor, w)
        
                    self:addText(1, n, text)
                    self:addBG(1, n, bg)
                    self:addFG(1, n, fg)
                    self:addBlit(1, n, text, fg, bg)
                end
        
                if startSelX and endSelX and startSelY and endSelY then
                    local sx, ex, sy, ey = getSelectionCoordinates()
                    for n = sy, ey do
                        local line = #lines[n]
                        local xOffset = 0
                        if n == sy and n == ey then
                            xOffset = sx - 1 - (wIndex - 1)
                            line = line - (sx - 1 - (wIndex - 1)) - (line - ex + (wIndex - 1))
                        elseif n == ey then
                            line = line - (line - ex + (wIndex - 1))
                        elseif n == sy then
                            line = line - (sx - 1)
                            xOffset = sx - 1 - (wIndex - 1)
                        end
                        
                        local visible_line_length = math.min(line, w - xOffset)
                
                        self:addBG(1 + xOffset, n, rep(tHex[selectionBG], visible_line_length))
                        self:addFG(1 + xOffset, n, rep(tHex[selectionFG], visible_line_length))
                    end
                end
            end)
        end,

        load = function(self)
            self:listenEvent("mouse_click")
            self:listenEvent("mouse_up")
            self:listenEvent("mouse_scroll")
            self:listenEvent("mouse_drag")
            self:listenEvent("key")
            self:listenEvent("char")
            self:listenEvent("other_event")
        end,
    }

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