local super = require "Chart"

PieChart = super:new()

local defaults = {
    startAngle = 0,
    outlineThickness = 1,
    thickness = 0,
    hole = 0,
}

local nilDefaults = {
    'value',
    'label',
}

function PieChart: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 PieChart:unarchiveLabelFont(archived)
    -- NOTE: Version 1.4.2 and earlier stored pie chart label fonts without a typography scheme.
    self._legacyLabelFont = unarchive(archived)
end

function PieChart:unarchived()
    local dataset = self:getDataset()
    if self._legacyLabelFont then
        self:setFont(TypographyScheme.labelFont, self._legacyLabelFont)
    end
    if dataset then
        if self:getProperty('value') == nil and self:getProperty('label') == nil then
            local valueField = dataset:pickField('number')
            if valueField then
                self:setProperty('value', KeyArtifact:new(valueField))
            end
            local labelField = dataset:pickField('string')
            if labelField then
                self:setProperty('label', KeyArtifact:new(labelField))
            end
        end
    end
    super.unarchived(self)
end

function PieChart:padding()
    local thickness = self:getBaseSize() * self:getProperty('outlineThickness')
    return {
        left = thickness/2,
        bottom = thickness/2,
        right = thickness/2,
        top = thickness/2,
    }
end

function PieChart:getInspectors()
    local list = super.getInspectors(self)
    list:add(self:createInspector('KeyArtifact', {'value'; dataSource = 'dataset'}, 'Value'))
    list:add(self:createInspector('KeyArtifact', {'label'; dataSource = 'dataset'}, 'Label'))
    return list
end

function PieChart:getColorInspectors()
    local list = super.getColorInspectors(self)
    list:add(self:createColorInspector(ColorScheme.backgroundPaint, 'Labels'))
    list:add(self:createColorInspector(ColorScheme.strokePaint, 'Stroke'))
    return list
end

function PieChart:getDataColorInspectors()
    local list = super.getDataColorInspectors(self)
    local colorScheme = self:getColorScheme()
    for index = 1, colorScheme:getDataPaintCount() do
        list:add(self:createDataColorInspector(index, 'Segment ' .. index))
    end
    return list
end

function PieChart:getFontInspectors()
    local list = super.getFontInspectors(self)
    list:add(self:createFontInspector(TypographyScheme.labelFont, 'Labels'))
    return list
end

local function drawThumbnail(canvas, rect, fonts, paints)
    if paints.background then
        canvas:setPaint(paints.background)
            :fill(Path.rect(canvas:metrics():rect(), 3))
    end
    rect = rect:offset(0, 1)
    local PADY = 4
    canvas:setPaint(paints.title)
        :setFont(fonts.title)
        :drawText('Title', rect:midx(), rect:maxy() - fonts.title:ascent(), 0.5)
    rect = rect:inset{
        left = 0,
        bottom = 0,
        right = 0,
        top = fonts.title:ascent() + fonts.title:descent() + PADY,
    }
    local midx, midy = rect:midx(), rect:midy()
    local radius = math.min(rect:width(), rect:height()) / 2
    local labelOffset = (fonts.label:descent() - fonts.label:ascent()) / 2
    canvas:setPaint(paints.fill1)
        :fill(Path.arc{ cx = midx, cy = midy, x = midx, y = midy + radius, angle = math.pi * -3 / 4 }:addLine{ x = midx, y = midy })
        :setPaint(paints.label)
        :setFont(fonts.label)
        :drawText('Label', midx + radius / 2 * math.cos(math.pi * 1 / 8), midy + radius / 2 * math.sin(math.pi * 1 / 8) + labelOffset, 0.5)
    if paints.fill2 and paints.fill3 then
        canvas:setPaint(paints.fill2)
            :fill(Path.arc{ cx = midx, cy = midy, x = midx - radius, y = midy, angle = math.pi * 3 / 4 }:addLine{ x = midx, y = midy })
            :setPaint(paints.fill3)
            :fill(Path.arc{ cx = midx, cy = midy, x = midx - radius, y = midy, angle = math.pi * -2 / 4 }:addLine{ x = midx, y = midy })
            :setPaint(paints.label)
            :drawText('Label', midx + radius / 2 * math.cos(math.pi * 11 / 8), midy + radius / 2 * math.sin(math.pi * 11 / 8) + labelOffset, 0.5)
            :drawText('Label', midx + radius * 0.6 * math.cos(math.pi * 6 / 8), midy + radius * 0.6 * math.sin(math.pi * 6 / 8) + labelOffset, 0.5)
    else
        canvas:setPaint(paints.fill2)
            :fill(Path.arc{ cx = midx, cy = midy, x = midx, y = midy + radius, angle = math.pi * 5 / 4 }:addLine{ x = midx, y = midy })
            :setPaint(paints.label)
            :drawText('Label', midx + radius / 2 * math.cos(math.pi * 9 / 8), midy + radius / 2 * math.sin(math.pi * 9 / 8) + labelOffset, 0.5)
    end
    canvas:setPaint(paints.stroke)
        :setThickness(0.75)
        :stroke(Path.oval{ left = midx - radius, bottom = midy - radius, right = midx + radius, top = midy + radius })
end

function PieChart:drawTypographySchemePreview(canvas, rect, typographyScheme)
    local SIZE = 12
    local fonts = {
        title = typographyScheme:getFont(TypographyScheme.titleFont, SIZE),
        label = typographyScheme:getFont(TypographyScheme.labelFont, SIZE),
    }
    local paints = {
        title = Color.gray(0, 1),
        label = Color.gray(0, 1),
        stroke = Color.gray(0, 0.4),
        fill1 = Color.gray(0, 0.4),
        fill2 = Color.invisible,
    }
    drawThumbnail(canvas, rect, fonts, paints)
end

function PieChart:drawColorSchemePreview(canvas, rect, colorScheme)
    local SIZE = 12
    local typographyScheme = self:getTypographyScheme()
    local fonts = {
        title = typographyScheme:getFont(TypographyScheme.titleFont, SIZE),
        label = typographyScheme:getFont(TypographyScheme.labelFont, SIZE),
    }
    local paints = {
        background = colorScheme:getPaint(ColorScheme.pageBackgroundPaint),
        title = colorScheme:getPaint(ColorScheme.titlePaint),
        label = colorScheme:getPaint(ColorScheme.backgroundPaint),
        stroke = colorScheme:getPaint(ColorScheme.strokePaint),
        fill1 = colorScheme:getDataSeriesPaint(1, 3),
        fill2 = colorScheme:getDataSeriesPaint(2, 3),
        fill3 = colorScheme:getDataSeriesPaint(3, 3),
    }
    drawThumbnail(canvas, rect, fonts, paints)
end

function PieChart:draw(canvas)
    super.draw(self, canvas)
    local dataset = self:getDataset()
    local values, labels
    if dataset then
        values = self:getProperty('value')
        values = values and values:evaluate(dataset)
        labels = self:getProperty('label')
        labels = labels and labels:evaluate(dataset)
    end
    
    local contentRect = self:contentRect()
    local cx = contentRect:midx()
    local cy = contentRect:midy()
    local radius = math.min(contentRect:width(), contentRect:height()) / 2
    local hole = self:getProperty('hole')
    
    local total = 0
    local count = 0
    if values then
        for _, value in values:iter() do
            value = tonumber(value)
            if value and value > 0 then
                total = total + value
                count = count + 1
            end
        end
    end
    
    local baseAngle = self:getProperty('startAngle') + math.pi / 2
    local clockwise = true
    local function sweepAngle(angle)
        if clockwise then
            return baseAngle - angle
        else
            return baseAngle + angle
        end
    end
    local baseSize = self:getBaseSize()
    local thickness = baseSize * self:getProperty('thickness')
    local outlineThickness = baseSize * self:getProperty('outlineThickness')
    local pixelSize = canvas:metrics():pixelSize()
    local colorScheme = self:getColorScheme()
    local strokePaint = self:getPaint(ColorScheme.strokePaint)
    local loopTotal = 0
    local strokePath
    if thickness > 0 then
        strokePath = Path.point{x = cx, y = cy}
    end
    if values then
        for index, value in values:iter() do
            value = tonumber(value)
            if value and value > 0 and (count < 100 or value > total / 200) then
                canvas:setPaint(colorScheme:getDataSeriesPaint(index, count))
                    :setThickness(pixelSize)
                local startAngle = sweepAngle(2 * math.pi * loopTotal / total)
                if loopTotal == 0 then
                    local path = Path.line{
                        x1 = cx + radius * math.cos(startAngle),
                        y1 = cy + radius * math.sin(startAngle),
                        x2 = cx + hole * radius * math.cos(startAngle),
                        y2 = cy + hole * radius * math.sin(startAngle),
                    }
                    canvas:stroke(path)
                end
                if strokePath and value ~= total then
                    strokePath:addSubpath{
                        x = cx + radius * math.cos(startAngle),
                        y = cy + radius * math.sin(startAngle),
                    }
                    strokePath:addLine{
                        x = cx + hole * radius * math.cos(startAngle),
                        y = cy + hole * radius * math.sin(startAngle),
                    }
                end
                loopTotal = loopTotal + value
                local endAngle = sweepAngle(2 * math.pi * loopTotal / total)
                local path = Path.line{
                    x1 = cx + radius * math.cos(endAngle),
                    y1 = cy + radius * math.sin(endAngle),
                    x2 = cx + hole * radius * math.cos(endAngle),
                    y2 = cy + hole * radius * math.sin(endAngle),
                }
                if loopTotal ~= total then
                    canvas:stroke(path)
                end
                if hole > 0 then
                    path:addArc{cx = cx, cy = cy, angle = startAngle - endAngle}
                end
                path:addLine{
                    x = cx + radius * math.cos(startAngle),
                    y = cy + radius * math.sin(startAngle),
                }
                path:addArc{cx = cx, cy = cy, angle = endAngle - startAngle}
                canvas:fill(path)
            end
        end
    end
    if loopTotal == 0 then
        local path
        if hole == 0 then
            path = Path.oval(Rect:new{left = cx - radius + 1, bottom = cy - radius + 1, right = cx + radius - 1, top = cy + radius - 1})
        else
            path = Path.arc{
                x = cx + radius,
                y = cy,
                cx = cx,
                cy = cy,
                angle = 2 * math.pi,
            }
            path:addSubpath{ x = cx + hole * radius, y = cy }
                :addArc{ cx = cx, cy = cy, angle = -2 * math.pi }
        end
        canvas:setPaint(Color.gray(0.9))
            :fill(path)
            :setPaint(Color.gray(0.6))
            :setThickness(baseSize)
            :stroke(path)
        return
    elseif loopTotal < total then
        local startAngle = sweepAngle(2 * math.pi * loopTotal / total)
        local endAngle = sweepAngle(2 * math.pi)
        if strokePath then
            strokePath:addSubpath{
                x = cx + radius * math.cos(startAngle),
                y = cy + radius * math.sin(startAngle),
            }
            strokePath:addLine{
                x = cx + hole * radius * math.cos(startAngle),
                y = cy + hole * radius * math.sin(startAngle),
            }
        end
        local path = Path.line{
            x1 = cx + radius * math.cos(endAngle),
            y1 = cy + radius * math.sin(endAngle),
            x2 = cx + hole * radius * math.cos(endAngle),
            y2 = cy + hole * radius * math.sin(endAngle),
        }
        if hole > 0 then
            path:addArc{cx = cx, cy = cy, angle = startAngle - endAngle}
        end
        path:addLine{
            x = cx + radius * math.cos(startAngle),
            y = cy + radius * math.sin(startAngle),
        }
        path:addArc{cx = cx, cy = cy, angle = endAngle - startAngle}
        canvas:setPaint(Color.gray(0.9))
            :fill(path)
    end
    if strokePath then
        canvas:setPaint(strokePaint)
            :setThickness(thickness)
            :stroke(strokePath)
    end
    if outlineThickness > 0 then
        strokePath = Path.oval{
            left = cx - radius,
            bottom = cy - radius,
            right = cx + radius,
            top = cy + radius,
        }
        if hole > 0 then
            strokePath:addOval{
                left = cx - hole * radius,
                bottom = cy - hole * radius,
                right = cx + hole * radius,
                top = cy + hole * radius,
            }
        end
        canvas:setPaint(strokePaint)
            :setThickness(outlineThickness)
            :stroke(strokePath)
    end
    
    if labels then
        local labelFont = self:getFont(TypographyScheme.labelFont)
        local labelPaint = self:getPaint(ColorScheme.backgroundPaint)
        loopTotal = 0
        for index, value in values:iter() do
            value = tonumber(value)
            if value and value > 0 and (count < 100 or value > total / 200) then
                local startAngle = sweepAngle(2 * math.pi * loopTotal / total)
                loopTotal = loopTotal + value
                local endAngle = sweepAngle(2 * math.pi * loopTotal / total)
                local label = labels:getValue(index)
                if label then
                    local labelRadius
                    if value == total then
                        labelRadius = 0
                    elseif value > total / 2 then
                        labelRadius = radius / 2
                    else
                        labelRadius = radius * ((5 + math.cos(endAngle - startAngle)) / 8)
                    end
                    if hole > 0 then
                        labelRadius = math.max(labelRadius, radius * (1 + hole) / 2)
                    end
                    local labelX = cx + labelRadius * math.cos((endAngle + startAngle) / 2)
                    local labelY = cy + labelRadius * math.sin((endAngle + startAngle) / 2)
                    TextStrokePointStamp(canvas, labelX, labelY, label, strokePaint, labelFont, 0.5, 0.5, nil, 0, 0, 2 * baseSize)
                    TextPointStamp(canvas, labelX, labelY, label, labelPaint, labelFont, 0.5, 0.5)
                end
            end
        end
    end
end

return PieChart
