local super = require "Object"

ColorScheme = super:new()

ColorScheme.pageBackgroundPaint = 'page'
ColorScheme.backgroundPaint = 'background'
ColorScheme.alternateBackgroundPaint = 'background-alt'
ColorScheme.strokePaint = 'stroke'
ColorScheme.gridPaint = 'grid'
ColorScheme.labelPaint = 'label'
ColorScheme.titlePaint = 'title'
ColorScheme.emptyFillPaint = 'empty'
ColorScheme.highlightPaint = 'highlight'

ColorScheme.DATA_CATEGORICAL = 'categorical'
ColorScheme.DATA_SEQUENTIAL = 'sequential'

ColorScheme.INHERIT = {}

local presetPaints = {
    basic = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.invisible,
            [ColorScheme.strokePaint] = Color.black,
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.black,
            [ColorScheme.titlePaint] = Color.black,
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_CATEGORICAL,
            { red = 0.90, green = 0.26, blue = 0.34, alpha = 1 },
            { red = 0.97, green = 0.82, blue = 0.20, alpha = 1 },
            { red = 0.45, green = 0.90, blue = 0.98, alpha = 1 },
            { red = 0.53, green = 0.83, blue = 0.30, alpha = 1 },
            { red = 0.81, green = 0.49, blue = 0.89, alpha = 1 },
            { red = 0.92, green = 0.58, blue = 0.36, alpha = 1 },
            { red = 0.30, green = 0.57, blue = 0.96, alpha = 1 },
            { red = 0.80, green = 0.85, blue = 0.85, alpha = 1 },
        },
    },
    basic2 = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.gray(0.97),
            [ColorScheme.alternateBackgroundPaint] = Color.invisible,
            [ColorScheme.strokePaint] = Color.gray(0.4),
            [ColorScheme.gridPaint] = Color.white,
            [ColorScheme.labelPaint] = Color.gray(0.4),
            [ColorScheme.titlePaint] = Color.gray(0.4),
            [ColorScheme.emptyFillPaint] = Color.gray(0.92),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_CATEGORICAL,
            { red = 0.90, green = 0.26, blue = 0.34, alpha = 1 },
            { red = 0.97, green = 0.79, blue = 0.20, alpha = 1 },
            { red = 0.35, green = 0.81, blue = 0.90, alpha = 1 },
            { red = 0.25, green = 0.71, blue = 0.45, alpha = 1 },
            { red = 0.76, green = 0.42, blue = 0.71, alpha = 1 },
            { red = 0.92, green = 0.58, blue = 0.41, alpha = 1 },
            { red = 0.15, green = 0.52, blue = 0.86, alpha = 1 },
            { red = 0.80, green = 0.85, blue = 0.85, alpha = 1 },
        },
    },
    neon = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.gray(0.3),
            [ColorScheme.backgroundPaint] = Color.gray(0.2),
            [ColorScheme.alternateBackgroundPaint] = Color.invisible,
            [ColorScheme.strokePaint] = Color.gray(0.8),
            [ColorScheme.gridPaint] = Color.gray(0.4),
            [ColorScheme.labelPaint] = Color.gray(0.9),
            [ColorScheme.titlePaint] = Color.gray(0.9),
            [ColorScheme.emptyFillPaint] = Color.gray(0.3),
            [ColorScheme.highlightPaint] = Color.gray(0.8, 0.15),
        },
        data = {
            type = ColorScheme.DATA_CATEGORICAL,
            { red = 0.90, green = 0.26, blue = 0.34, alpha = 1 },
            { red = 0.97, green = 0.82, blue = 0.20, alpha = 1 },
            { red = 0.45, green = 0.90, blue = 0.98, alpha = 1 },
            { red = 0.53, green = 0.83, blue = 0.30, alpha = 1 },
            { red = 0.81, green = 0.49, blue = 0.89, alpha = 1 },
            { red = 0.92, green = 0.58, blue = 0.36, alpha = 1 },
            { red = 0.30, green = 0.57, blue = 0.96, alpha = 1 },
            { red = 0.80, green = 0.85, blue = 0.85, alpha = 1 },
        },
    },
    manila = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.rgba(0.99, 0.98, 0.96, 1),
            [ColorScheme.backgroundPaint] = Color.rgba(0.98, 0.96, 0.92, 1),
            [ColorScheme.alternateBackgroundPaint] = Color.invisible,
            [ColorScheme.strokePaint] = Color.rgba(0.37, 0.21, 0.09, 1),
            [ColorScheme.gridPaint] = Color.rgba(0.88, 0.82, 0.70, 1),
            [ColorScheme.labelPaint] = Color.rgba(0.37, 0.21, 0.09, 1),
            [ColorScheme.titlePaint] = Color.rgba(0.37, 0.21, 0.09, 1),
            [ColorScheme.emptyFillPaint] = Color.rgba(0.88, 0.82, 0.70, 1),
            [ColorScheme.highlightPaint] = Color.rgba(0.37, 0.21, 0.09, 0.15),
        },
        data = {
            type = ColorScheme.DATA_CATEGORICAL,
            { red = 0.68, green = 0.17, blue = 0.26, alpha = 1 },
            { red = 0.80, green = 0.63, blue = 0.15, alpha = 1 },
            { red = 0.45, green = 0.68, blue = 0.72, alpha = 1 },
            { red = 0.36, green = 0.64, blue = 0.10, alpha = 1 },
            { red = 0.59, green = 0.36, blue = 0.61, alpha = 1 },
            { red = 0.74, green = 0.43, blue = 0.25, alpha = 1 },
            { red = 0.22, green = 0.40, blue = 0.70, alpha = 1 },
            { red = 0.53, green = 0.56, blue = 0.52, alpha = 1 },
        },
    },
    plastic = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.rgba(0.94, 0.96, 0.98, 1),
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.rgba(0.00, 0.25, 0.50, 1),
            [ColorScheme.gridPaint] = Color.rgba(0.78, 0.86, 0.94, 1),
            [ColorScheme.labelPaint] = Color.rgba(0.20, 0.40, 0.60, 1),
            [ColorScheme.titlePaint] = Color.rgba(0.00, 0.25, 0.50, 1),
            [ColorScheme.emptyFillPaint] = Color.rgba(0.78, 0.86, 0.94, 1),
            [ColorScheme.highlightPaint] = Color.rgba(0.00, 0.25, 0.50, 0.15),
        },
        data = {
            type = ColorScheme.DATA_CATEGORICAL,
            { red = 0.82, green = 0.21, blue = 0.37, alpha = 1 },
            { red = 0.95, green = 0.71, blue = 0.18, alpha = 1 },
            { red = 0.38, green = 0.80, blue = 0.89, alpha = 1 },
            { red = 0.36, green = 0.73, blue = 0.11, alpha = 1 },
            { red = 0.58, green = 0.40, blue = 0.72, alpha = 1 },
            { red = 0.84, green = 0.46, blue = 0.28, alpha = 1 },
            { red = 0.19, green = 0.44, blue = 0.84, alpha = 1 },
            { red = 0.57, green = 0.64, blue = 0.65, alpha = 1 },
        },
    },
    toner = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.black,
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.black,
            [ColorScheme.titlePaint] = Color.black,
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.25, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.27, green = 0.27, blue = 0.27, alpha = 1 },
            { red = 0.65, green = 0.65, blue = 0.65, alpha = 1 },
            { red = 0.86, green = 0.86, blue = 0.86, alpha = 1 },
        },
    },
    red = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.52, green = 0.00, blue = 0.15, alpha = 1 },
            { red = 1.00, green = 0.30, blue = 0.24, alpha = 1 },
            { red = 1.00, green = 0.77, blue = 0.65, alpha = 1 },
        },
    },
    orange = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.52, green = 0.00, blue = 0.15, alpha = 1 },
            { red = 1.00, green = 0.56, blue = 0.07, alpha = 1 },
            { red = 1.00, green = 0.95, blue = 0.69, alpha = 1 },
        },
    },
    yellow = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.54, green = 0.38, blue = 0.17, alpha = 1 },
            { red = 0.95, green = 0.79, blue = 0.00, alpha = 1 },
            { red = 1.00, green = 0.95, blue = 0.69, alpha = 1 },
        },
    },
    green = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.10, green = 0.44, blue = 0.21, alpha = 1 },
            { red = 0.50, green = 0.74, blue = 0.01, alpha = 1 },
            { red = 0.83, green = 0.94, blue = 0.56, alpha = 1 },
        },
    },
    cyan = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.21, green = 0.33, blue = 0.62, alpha = 1 },
            { red = 0.26, green = 0.76, blue = 0.86, alpha = 1 },
            { red = 0.75, green = 0.96, blue = 0.95, alpha = 1 },
        },
    },
    blue = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.14, green = 0.08, blue = 0.60, alpha = 1 },
            { red = 0.33, green = 0.52, blue = 1.00, alpha = 1 },
            { red = 0.74, green = 0.84, blue = 1.00, alpha = 1 },
        },
    },
    purple = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.white,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.9),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.36, green = 0.00, blue = 0.47, alpha = 1 },
            { red = 0.71, green = 0.28, blue = 0.91, alpha = 1 },
            { red = 0.92, green = 0.65, blue = 1.00, alpha = 1 },
        },
    },
    ['red-green'] = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.invisible,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.92),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 1.00, green = 0.40, blue = 0.33, alpha = 1 },
            { red = 0.96, green = 0.96, blue = 0.33, alpha = 1 },
            { red = 0.33, green = 0.80, blue = 0.33, alpha = 1 },
        },
    },
    ['red-blue'] = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.gray(0.97),
            [ColorScheme.alternateBackgroundPaint] = Color.invisible,
            [ColorScheme.strokePaint] = Color.gray(0.4),
            [ColorScheme.gridPaint] = Color.white,
            [ColorScheme.labelPaint] = Color.gray(0.4),
            [ColorScheme.titlePaint] = Color.gray(0.4),
            [ColorScheme.emptyFillPaint] = Color.gray(0.92),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 1.00, green = 0.25, blue = 0.30, alpha = 1 },
            { red = 0.85, green = 0.85, blue = 0.85, alpha = 1 },
            { red = 0.10, green = 0.60, blue = 1.00, alpha = 1 },
        },
    },
    ['red-purple'] = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.gray(0.97),
            [ColorScheme.alternateBackgroundPaint] = Color.invisible,
            [ColorScheme.strokePaint] = Color.gray(0.4),
            [ColorScheme.gridPaint] = Color.white,
            [ColorScheme.labelPaint] = Color.gray(0.4),
            [ColorScheme.titlePaint] = Color.gray(0.4),
            [ColorScheme.emptyFillPaint] = Color.gray(0.92),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.95, green = 0.20, blue = 0.20, alpha = 1 },
            { red = 0.85, green = 0.85, blue = 0.85, alpha = 1 },
            { red = 0.50, green = 0.30, blue = 1.00, alpha = 1 },
        },
    },
    ['orange-blue'] = {
        named = {
            [ColorScheme.pageBackgroundPaint] = Color.white,
            [ColorScheme.backgroundPaint] = Color.white,
            [ColorScheme.alternateBackgroundPaint] = Color.invisible,
            [ColorScheme.strokePaint] = Color.gray(0.2),
            [ColorScheme.gridPaint] = Color.gray(0.93),
            [ColorScheme.labelPaint] = Color.gray(0.2),
            [ColorScheme.titlePaint] = Color.gray(0.2),
            [ColorScheme.emptyFillPaint] = Color.gray(0.92),
            [ColorScheme.highlightPaint] = Color.gray(0.5, 0.15),
        },
        data = {
            type = ColorScheme.DATA_SEQUENTIAL,
            { red = 0.93, green = 0.67, blue = 0.00, alpha = 1 },
            { red = 0.90, green = 0.90, blue = 0.90, alpha = 1 },
            { red = 0.33, green = 0.73, blue = 1.00, alpha = 1 },
        },
    },
}

local function getPaintForParams(params)
    if params then
        return Color.rgba(params.red, params.green, params.blue, params.alpha)
    end
end

local DEFAULT_COLOR_SCHEME -- assigned below, at end of file

function ColorScheme:new()
    self = super.new(self)
    
    self:addProperty('name', nil)
    self:addProperty('reversed', nil)
    self.paints = {}
    self.dataPaints = {}

    self._parent = DEFAULT_COLOR_SCHEME
    self._loadingHook = PropertyHook:new(false)
    self._loadingObserver = function(sender)
        if self._loadingHook:getValue() == false then -- done loading
            self:invalidate(self)
        end
    end
    self._loadingHook:addObserver(self._loadingObserver)
    self._changeObserver = function(sender)
        if not self._loadingHook:getValue() then
            self:setArchiveName(nil)
            self:invalidate(sender)
        end
    end
    self._dataTypeHook = PropertyHook:new(nil)
    self._dataTypeHook:addObserver(self._changeObserver)
    
    return self
end

function ColorScheme:unarchiveName(archived)
    self:load(unarchive(archived))
end

function ColorScheme:unarchivePaints(archived)
    for archivedPaintName, archivedPaint in pairs(archived) do
        local paintName, paint = unarchive(archivedPaintName), unarchive(archivedPaint)
        local dataPaintIndex = tonumber(paintName)
        if dataPaintIndex then
            -- NOTE: Version 1.4.2 and earlier did not have data paints.
            self:setDataType(ColorScheme.DATA_CATEGORICAL)
            self:setDataPaint(dataPaintIndex, paint)
        else
            self:setPaint(paintName, paint)
        end
    end
    -- NOTE: Version 1.1.3 and earlier did not have 'empty' paints.
    if self:getPaint(ColorScheme.emptyFillPaint) == nil then
        self:setPaint(ColorScheme.emptyFillPaint, self:getPaint(ColorScheme.gridPaint))
    end
end

function ColorScheme:unarchiveDataType(archived)
    self:setDataType(unarchive(archived))
end

function ColorScheme:unarchiveDataPaints(archived)
    for index, archivedPaint in pairs(archived) do
        self:setDataPaint(index, unarchive(archivedPaint))
    end
end

function ColorScheme:archive()
    local typeName, properties = self:class(), {}
    local schemeName = self:getProperty('name')
    properties.reversed = self:getProperty('reversed')
    if schemeName then
        properties.name = schemeName
    else
        properties.paints = {}
        for paintName, paintHook in pairs(self.paints) do
            properties.paints[paintName] = paintHook:getValue()
        end
        properties.dataType = self:getExplicitDataType()
        properties.dataPaints = {}
        for index = 1, self:getDataPaintCount() do
            local paintHook = self.dataPaints[index]
            if paintHook then
                properties.dataPaints[index] = getPaintForParams(paintHook:getValue())
            end
        end
    end
    return typeName, properties
end

function ColorScheme:setParent(parent)
    if not self._parent or self._parent == DEFAULT_COLOR_SCHEME then
        self._parent = parent
        parent:addObserver(self)
    end
end

function ColorScheme:getPresets(view)
    local results = {
        ColorScheme:get('basic2'),
        ColorScheme:get('basic'),
        ColorScheme:get('neon'),
        ColorScheme:get('plastic'),
        ColorScheme:get('manila'),
        false,
        ColorScheme:get('red'),
        ColorScheme:get('orange'),
        ColorScheme:get('yellow'),
        ColorScheme:get('green'),
        ColorScheme:get('cyan'),
        ColorScheme:get('blue'),
        ColorScheme:get('purple'),
        ColorScheme:get('toner'),
        false,
        ColorScheme:get('red-green'),
        ColorScheme:get('red-blue'),
        ColorScheme:get('red-purple'),
        ColorScheme:get('orange-blue'),
    }
    if not view:getColorScheme():getName() then
        results[#results + 1] = false
        results[#results + 1] = view:getColorScheme()
    end
    return results
end

function ColorScheme:get(presetName)
    local self = self:new()
    
    self:notUndoably(function()
        self:load(presetName)
    end)
    
    return self
end

function ColorScheme:load(presetName)
    self._loadingHook:setValue(true)
    if presetName == ColorScheme.INHERIT then
        for name in pairs(self.paints) do
            self:setPaint(name, nil)
        end
        self:setDataType(nil)
        for index in pairs(self.dataPaints) do
            self:setDataPaint(index, nil)
        end
        presetName = nil
    else
        local preset = presetPaints[presetName]
        if preset then
            for name, paint in pairs(preset.named) do
                self:setPaint(name, paint)
            end
            self:setDataType(preset.data.type)
            for index = 1, #preset.data do
                self:setDataPaintParams(index, preset.data[index])
            end
        end
    end
    self:setArchiveName(presetName)
    self._loadingHook:setValue(false)
end

function ColorScheme:setArchiveName(name)
    self:setProperty('name', name)
end

function ColorScheme:getName()
    local name = self:getProperty('name')
    if name == nil then
        local isCustom = false
        for paintName, paintHook in pairs(self.paints) do
            if paintHook:getValue() ~= nil then
                isCustom = true
            end
        end
        for paintName, paintHook in pairs(self.dataPaints) do
            if paintHook:getValue() ~= nil then
                isCustom = true
            end
        end
        if self:getExplicitDataType() ~= nil then
            isCustom = true
        end
        if not isCustom then
            name = ColorScheme.INHERIT
        end
    end
    return name
end

function ColorScheme:setDataPaintParams(index, params)
    if self:isReversed() then
        index = 1 + self:getDataPaintCount() - index
    end
    local hook = self.dataPaints[index]
    if not hook then
        hook = PropertyHook:new()
        hook:addObserver(self._changeObserver)
        self.dataPaints[index] = hook
    end
    hook:setValue(params)
end

function ColorScheme:setDataPaint(index, paint)
    local _, params
    if paint then
        _, params = paint:archive()
    end
    self:setDataPaintParams(index, params)
end

function ColorScheme:setPaint(name, value)
    name = tostring(name)
    local hook = self.paints[name]
    if not hook then
        hook = PropertyHook:new()
        hook:addObserver(self._changeObserver)
        self.paints[name] = hook
    end
    hook:setValue(value)
end

local function gammaBlend(params1, params2, weight)
    if params1 and params2 then
        local gamma = 2.2
        return {
            red = ((1 - weight) * params1.red ^ gamma + weight * params2.red ^ gamma) ^ (1 / gamma),
            green = ((1 - weight) * params1.green ^ gamma + weight * params2.green ^ gamma) ^ (1 / gamma),
            blue = ((1 - weight) * params1.blue ^ gamma + weight * params2.blue ^ gamma) ^ (1 / gamma),
            alpha = (1 - weight) * params1.alpha + weight * params2.alpha,
        }
    end
end

local function luminance(params)
    return 0.299 * params.red + 0.587 * params.green + 0.114 * params.blue
end

function ColorScheme:getExplicitDataType()
    return self._dataTypeHook:getValue()
end

function ColorScheme:getDataType()
    return self:getExplicitDataType()
        or (self._parent and self._parent:getDataType())
end

function ColorScheme:getDataTypeHook()
    return self._dataTypeHook
end

function ColorScheme:setDataType(value)
    if value ~= ColorScheme.DATA_SEQUENTIAL then
        self:setProperty('reversed', nil)
    end
    self._dataTypeHook:setValue(value)
end

function ColorScheme:getDataTypeInspector()
    local inspector, hook
    inspector = Inspector:new{
        title = 'Data Type',
        undoTitle = 'Data Type',
        type = 'Class',
        constraint = function()
            return {
                { ColorScheme.DATA_CATEGORICAL, 'Categorical' },
                { ColorScheme.DATA_SEQUENTIAL, 'Sequential' },
            }
        end,
    }
    hook = Hook:new(
        function()
            return self:getDataType()
        end,
        function(value)
            self:setDataType(value)
        end)
    self:getDataTypeHook():addObserver(hook)
    inspector:addHook(hook)
    return inspector
end

function ColorScheme:getDataPaintCount()
    if self:getDataType() == ColorScheme.DATA_CATEGORICAL then
        return 8
    else
        return 3
    end
end

function ColorScheme:isDiverging()
    if self:getDataType() == ColorScheme.DATA_SEQUENTIAL then
        local l1 = luminance(self:getDataPaintParams(1))
        local l2 = luminance(self:getDataPaintParams(2))
        local l3 = luminance(self:getDataPaintParams(3))
        return (l1 < l2 and l3 < l2) or (l2 < l1 and l2 < l3)
    end
end

function ColorScheme:isReversed()
    local reversed = self:getProperty('reversed') or false
    local parentReversed = (self._parent and self._parent:isReversed()) or false
    return reversed ~= parentReversed
end

function ColorScheme:getDataColorExtraInspectors()
    local list = List:new()
    if self:getDataType() == ColorScheme.DATA_SEQUENTIAL then
        local inspector = Inspector:new{
            title = 'Reversed',
            type = 'Toggle',
            icon = 'reverse-toggle',
        }
        inspector:addHook(self:getPropertyHook('reversed'))
        list:add(inspector)
    end
    return list
end

function ColorScheme:getExplicitDataPaintParams(index, reverse)
    index = 1 + (index - 1) % self:getDataPaintCount()
    if reverse then
        index = 1 + self:getDataPaintCount() - index
    end
    local hook = self.dataPaints[index]
    return hook and hook:getValue()
end

function ColorScheme:getDataPaintParams(index, reverse)
    return self:getExplicitDataPaintParams(index, reverse)
        or (self._parent and self._parent:getDataPaintParams(index, reverse))
end

function ColorScheme:getDataPaint(index)
    return getPaintForParams(self:getDataPaintParams(index, self:isReversed()))
end

function ColorScheme:getDataSeriesPaint(index, count, preventBlending)
    if self:getDataType() == ColorScheme.DATA_CATEGORICAL then
        return self:getDataPaint(index)
    else
        local fraction
        if self:isDiverging() or preventBlending then
            fraction = (index - 1) / math.max(1, count - 1)
        else
            fraction = (index - 0.5) / (count)
        end
        local fractionIndex = 1 + (self:getDataPaintCount() - 1) * fraction
        local params1 = self:getDataPaintParams(math.floor(fractionIndex), self:isReversed())
        local params2 = self:getDataPaintParams(math.ceil(fractionIndex), self:isReversed())
        return getPaintForParams(gammaBlend(params1, params2, fractionIndex % 1))
    end
end

function ColorScheme:getAccentDataPaintIndex(index)
    if self:getDataType() ~= ColorScheme.DATA_CATEGORICAL then
        if index ~= 1 then
            return self:getDataPaintCount()
        end
    end
    return index
end

function ColorScheme:getExplicitPaint(name)
    local hook = self.paints[name]
    return hook and hook:getValue()
end

function ColorScheme:getPaint(name)
    return self:getExplicitPaint(name)
        or (self._parent and self._parent:getPaint(name))
end

function ColorScheme.drawPreview(canvas, paints)
    local rect = Rect:new(canvas:metrics():rect())
    
    canvas:setPaint(paints['stroke'])
        :fill(Path.rect(rect, 3))
    
    rect = rect:inset{left = 1, bottom = 1, right = 1, top = 1}
    
    canvas:clip(Path.rect(rect, 2))
        :setPaint(paints['background'])
        :fill(Path.rect(rect))
        :setPaint(paints[2])
        :fill(Path.oval(Rect:new{left = rect:midx() - 1, bottom = rect:miny() - 2, right = rect:maxx() + 2, top = rect:midy() + 1}))
        :setPaint(paints[1])
        :fill(Path.oval(Rect:new{left = rect:minx() - 1, bottom = rect:miny() - 2, right = rect:midx() + 2, top = rect:midy() + 1}))
end

DEFAULT_COLOR_SCHEME = ColorScheme:get('basic2') -- no parent

return ColorScheme
