local super = require "GraphLayer"

CandlestickGraphLayer = super:new()

local defaults = {
    width = 1,
}

local nilDefaults = {
    'x', 'min', 'max', 'open', 'close', 'paint',
}

local freeGetterNames = {'x'}
local constrainedGetterNames = {'#'}
local commonGetterNames = {'min', 'max', 'open', 'close', 'width', 'paint'}

local freeInspectorInfo = {
    {'KeyArtifact', {'x'}, 'Position'},
    {'KeyArtifact', {'min'}, 'Low'},
    {'KeyArtifact', {'max'}, 'High'},
    {'KeyArtifact', {'open'}, 'Open'},
    {'KeyArtifact', {'close'}, 'Close'},
    {'number', {'width'}, 'Width'},
}

local constrainedInspectorInfo = {
    {'KeyArtifact', {'min'}, 'Low'},
    {'KeyArtifact', {'max'}, 'High'},
    {'KeyArtifact', {'open'}, 'Open'},
    {'KeyArtifact', {'close'}, 'Close'},
}

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

function CandlestickGraphLayer: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

-- NOTE: Version 1.0.4 and earlier saved 'position', 'bodyMin', 'bodyMax', and 'fill' properties.
--   'position' has been renamed to facilitate better conversions to/from other layer types.
--   'bodyMin' and 'bodyMax' have been renamed.
--   'fill' has been removed and is determined automatically.
function CandlestickGraphLayer:unarchivePosition(archived)
    self:setProperty('x', unarchive(archived))
end
function CandlestickGraphLayer:unarchiveBodyMin(archived)
    self:setProperty('open', unarchive(archived))
end
function CandlestickGraphLayer:unarchiveBodyMax(archived)
    self:setProperty('close', unarchive(archived))
end
function CandlestickGraphLayer:unarchiveFill(archived)
end

function CandlestickGraphLayer:unarchived()
    local dataset = self:getDataset()
    if dataset then
        local xField
        if self:isPositionConstrained() then
            if self:getProperty('x') == nil then
                local xFields = self:peerPropertyKeyArtifactValues(GraphLayer, 'x')
                xField = xFields[#xFields] or dataset:pickField('number')
                if xField then
                    self:setProperty('x', KeyArtifact:new(xField))
                end
            end
        end
        if self:getProperty('min') == nil and self:getProperty('max') == nil and self:getProperty('open') == nil and self:getProperty('close') == nil then
            local avoidingFields = { xField }
            local minField = dataset:pickField(self:getParent():getVerticalAxis():getPreferredType(), avoidingFields)
            avoidingFields[#avoidingFields + 1] = minField
            local maxField = dataset:pickField(self:getParent():getVerticalAxis():getPreferredType(), avoidingFields)
            avoidingFields[#avoidingFields + 1] = maxField
            local openField = dataset:pickField(self:getParent():getVerticalAxis():getPreferredType(), avoidingFields)
            avoidingFields[#avoidingFields + 1] = openField
            local closeField = dataset:pickField(self:getParent():getVerticalAxis():getPreferredType(), avoidingFields)
            if minField and maxField and openField and closeField then
                self:setProperty('min', KeyArtifact:new(minField))
                self:setProperty('max', KeyArtifact:new(maxField))
                self:setProperty('open', KeyArtifact:new(openField))
                self:setProperty('close', KeyArtifact:new(closeField))
            end
        end
    end
    super.unarchived(self)
end

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

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

function CandlestickGraphLayer:iterateValues(orientation, mapFunction)
    local dataset = self:getDataset()
    local propertyNames
    if self:isPositionConstrained() then
        propertyNames = {'open', 'close', 'min', 'max'}
    else
        if orientation == Graph.horizontalOrientation then
            propertyNames = {'x'}
        else
            propertyNames = {'open', 'close', 'min', 'max'}
        end
    end
    for index = 1, #propertyNames do
        local sequence = self:getPropertySequence(propertyNames[index], dataset)
        for _, value in sequence:iter() do
            mapFunction(value)
        end
    end
end

function CandlestickGraphLayer:isGroupable()
    return true
end

function CandlestickGraphLayer:draw(canvas, rect, propertySequence, xScaler, yScaler)
    local parent = self:getParent()
    local baseSize = parent:getBaseSize()
    local defaultPaint = self:getPaint()
    local isVertical = self:getOrientation() == Graph.verticalOrientation
    canvas:clipRect(rect)
    
    propertySequence:each(function(position, min, max, open, close, width, paint)
        canvas:setPaint(paint or defaultPaint)
            :setThickness(baseSize)
        local left, bottom, right, top, fill
        local x1, y1, x2, y2
        if position then
            if isVertical then
                y1, y2 = yScaler(min), yScaler(max)
                if y1 and y2 then
                    y1, y2 = math.min(y1, y2), math.max(y1, y2)
                    x1 = xScaler(position)
                    x2 = x1
                end
                open, close = yScaler(open), yScaler(close)
                if open and close then
                    fill = (open > close)
                    left, right = xScaler(position, width)
                    if left and right then
                        left, right = left + baseSize / 2, right - baseSize / 2
                        bottom, top = math.min(open, close), math.max(open, close)
                    end
                end
            else
                x1, x2 = xScaler(min), xScaler(max)
                if x1 and x2 then
                    x1, x2 = math.min(x1, x2), math.max(x1, x2)
                    y1 = yScaler(position)
                    y2 = y1
                end
                open, close = xScaler(open), xScaler(close)
                if open and close then
                    fill = (open > close)
                    bottom, top = yScaler(position, width)
                    if bottom and top then
                        bottom, top = bottom + baseSize / 2, top - baseSize / 2
                        left, right = math.min(open, close), math.max(open, close)
                    end
                end
            end
        end
        if left and bottom and right and top then
            local bodyPath = Path.rect({ left = left, bottom = bottom, right = right, top = top })
            if fill then
                canvas:fill(bodyPath)
            else
                canvas:stroke(bodyPath)
            end
            if x1 and y1 and x2 and y2 then
                local minWickLine = { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }
                local maxWickLine = { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }
                if isVertical then
                    minWickLine.y2 = bottom
                    maxWickLine.y1 = top
                else
                    minWickLine.x2 = left
                    maxWickLine.x1 = right
                end
                canvas:stroke(Path.line(minWickLine))
                canvas:stroke(Path.line(maxWickLine))
            end
        elseif x1 and y1 and x2 and y2 then
            canvas:stroke(Path.line({ x1 = x1, y1 = y1, x2 = x2, y2 = y2 }))
        end
    end)
end

return CandlestickGraphLayer
