local super = require "GraphLayer"

BarGraphLayer = super:new()

local defaults = {
}

local nilDefaults = {
    'x', 'y', 'width', 'value', 'paint',
}

local freeGetterNames = {'x', 'y'}
local constrainedGetterNames = {'#', 'value'}
local commonGetterNames = {'width', 'paint'}

local freeInspectorInfo = {
    {'KeyArtifact', {'x'}, 'X'},
    {'KeyArtifact', {'y'}, 'Y'},
    {'KeyArtifact', {'width'}, 'Width'},
}

local constrainedInspectorInfo = {
    {'KeyArtifact', {'value'}, 'Value'},
}

local commonInspectorInfo = {
    {'Color', {'getPaint:setPaint', custom = 'hasExplicitPaint:'}, 'Fill'},
}

function BarGraphLayer:new()
    self = super.new(self)
    
    for k, v in pairs(defaults) do
        self:addProperty(k, v)
    end
    for _, k in pairs(nilDefaults) do
        self:addProperty(k)
    end
    
    return self
end

function BarGraphLayer:unarchived()
    local dataset = self:getDataset()
    if dataset then
        if self:isPositionConstrained() then
            if self:getProperty('value') == nil then
                local valueFields = self:peerPropertyKeyArtifactValues(BarGraphLayer, 'value')
                local valueField = dataset:pickField('number', valueFields)
                if valueField then
                    self:setProperty('value', KeyArtifact:new(valueField))
                end
            end
            if self:getProperty('width') == nil then
                self:setProperty('width', 1)
            end
        else
            if self:getProperty('x') == nil and self:getProperty('y') == nil and self:getProperty('width') == nil then
                local widthFields = self:peerPropertyKeyArtifactValues(GraphLayer, 'width')
                local widthField = widthFields[#widthFields]
                local xFields = self:peerPropertyKeyArtifactValues(GraphLayer, 'x')
                local xField = xFields[#xFields] or dataset:pickField(self:getParent():getHorizontalAxis():getPreferredType(), { widthField })
                local yFields = self:peerPropertyKeyArtifactValues(BarGraphLayer, 'y')
                yFields[#yFields + 1] = widthField
                yFields[#yFields + 1] = xField
                local yField = dataset:pickField(self:getParent():getVerticalAxis():getPreferredType(), yFields)
                if xField and yField then
                    self:setProperty('x', KeyArtifact:new(xField))
                    self:setProperty('y', KeyArtifact:new(yField))
                end
                if widthField then
                    self:setProperty('width', KeyArtifact:new(widthField))
                else
                    self:setProperty('width', LogicArtifact:new('1'))
                end
            end
        end
    end
    super.unarchived(self)
end

function BarGraphLayer:getGetterPieceNames(constrained)
    local result = {}
    if constrained then
        appendtables(result, constrainedGetterNames)
    else
        appendtables(result, freeGetterNames)
    end
    appendtables(result, commonGetterNames)
    return result
end

function BarGraphLayer:getInspectorInfo(constrained)
    local result = {}
    if constrained then
        appendtables(result, constrainedInspectorInfo)
    else
        appendtables(result, freeInspectorInfo)
    end
    appendtables(result, commonInspectorInfo)
    return result
end

function BarGraphLayer:iterateValues(orientation, mapFunction, intralayerState)
    intralayerState = intralayerState or { min = 0, max = 0 }
    local dataset = self:getDataset()
    if self:isPositionConstrained() then
        local propertyName = 'value'
        local sequence = self:getPropertySequence(propertyName, dataset)
        local dataMin, dataMax = 0, 0
        for _, value in sequence:iter() do
            value = tonumber(value)
            if value and -math.huge < value and value < math.huge then
                if value >= 0 then
                    dataMax = math.max(dataMax, value)
                else
                    dataMin = math.min(dataMin, value)
                end
            end
        end
        local min = intralayerState.min + dataMin
        local max = intralayerState.max + dataMax
        mapFunction(min)
        mapFunction(max)
        if self:isStacked() then
            intralayerState.min = min
            intralayerState.max = max
        end
    else
        local propertyName
        if orientation == Graph.horizontalOrientation then
            propertyName = 'x'
        else
            propertyName = 'y'
        end
        local sequence = self:getPropertySequence(propertyName, dataset)
        if orientation == self:getOrientation() and sequence:length() > 0 then
            mapFunction(0)
        end
        for _, value in sequence:iter() do
            mapFunction(value)
        end
    end
    return intralayerState
end

function BarGraphLayer:cacheValueRange(orientation, min, max)
    if self:isPositionConstrained() then
        -- don't cache, because the values are affected by the intralayer state
    else
        super.cacheValueRange(self, orientation, min, max)
    end
end

function BarGraphLayer:isGroupable()
    return true
end

function BarGraphLayer:isStacked()
    local parent = self:getParent()
    return self:isPositionConstrained() and parent.isStacked and parent:isStacked()
end

function BarGraphLayer:draw(canvas, rect, propertySequence, xScaler, yScaler, intralayerState)
    local pbaseIterator = (intralayerState.pbase and intralayerState.pbase:iter()) or (function() return 0 end)
    local nbaseIterator = (intralayerState.nbase and intralayerState.nbase:iter()) or (function() return 0 end)
    local returnPbases, returnNbases = {}, {}
    local defaultPaint = self:getPaint()
    local isVertical = self:getOrientation() == Graph.verticalOrientation
    canvas:clipRect(rect)
    
    propertySequence:each(function(position, value, width, paint)
        local _, pbase = pbaseIterator()
        local _, nbase = nbaseIterator()
        pbase, nbase = pbase or 0, nbase or 0
        value = tonumber(value)
        local left, right, bottom, top
        if value then
            local base
            if value >= 0 then
                base = pbase
                pbase = pbase + value
                value = pbase
            else
                base = nbase
                nbase = nbase + value
                value = base
                base = nbase
            end
            if isVertical then
                left, right = xScaler(position, width)
                bottom, top = yScaler(base), yScaler(value)
            else
                bottom, top = yScaler(position, width)
                left, right = xScaler(base), xScaler(value)
            end
        end
        if left and right and bottom and top then
            local barRect = { left = left, bottom = bottom, right = right, top = top }
            canvas:setPaint(paint or defaultPaint)
                :fill(Path.rect(barRect))
        end
        returnPbases[#returnPbases + 1] = pbase
        returnNbases[#returnNbases + 1] = nbase
    end)
    
    if self:isStacked() then
        return {
            pbase = Sequence:newWithArray(returnPbases),
            nbase = Sequence:newWithArray(returnNbases),
        }
    end
end

return BarGraphLayer
