local super = require "Object"

TypographyScheme = super:new()

TypographyScheme.titleFont = 'title'
TypographyScheme.subtitleFont = 'subtitle'
TypographyScheme.blockFont = 'block'
TypographyScheme.labelFont = 'label'
TypographyScheme.categoryFont = 'category'
TypographyScheme.quantityFont = 'quantity'

TypographyScheme.INHERIT = {}

local BASE_SIZE = 16

local PRESETS = {
    basic = {
        [TypographyScheme.titleFont] = { name = 'GillSans-SemiBold', size = 25 },
        [TypographyScheme.subtitleFont] = { name = 'GillSans-SemiBold', size = 17 },
        [TypographyScheme.blockFont] = { name = 'AvenirNext-Regular', size = 16 },
        [TypographyScheme.labelFont] = { name = 'AvenirNext-Medium', size = 16 },
        [TypographyScheme.categoryFont] = { name = 'AvenirNext-Medium', size = 16 },
        [TypographyScheme.quantityFont] = { name = 'AvenirNext-Medium', size = 14.5 },
    },
    dense = {
        [TypographyScheme.titleFont] = { name = 'GillSans-Bold', size = 19 },
        [TypographyScheme.subtitleFont] = { name = 'GillSans-Bold', size = 13.5 },
        [TypographyScheme.blockFont] = { name = 'AvenirNextCondensed-Regular', size = 16 },
        [TypographyScheme.labelFont] = { name = 'AvenirNextCondensed-Medium', size = 16 },
        [TypographyScheme.categoryFont] = { name = 'AvenirNextCondensed-Medium', size = 16 },
        [TypographyScheme.quantityFont] = { name = 'AvenirNextCondensed-Medium', size = 14.5 },
    },
    light = {
        [TypographyScheme.titleFont] = { name = 'GillSans-Light', size = 32 },
        [TypographyScheme.subtitleFont] = { name = 'GillSans-Italic', size = 17 },
        [TypographyScheme.blockFont] = { name = 'AvenirNext-Regular', size = 16 },
        [TypographyScheme.labelFont] = { name = 'AvenirNext-Regular', size = 16 },
        [TypographyScheme.categoryFont] = { name = 'AvenirNext-Regular', size = 16 },
        [TypographyScheme.quantityFont] = { name = 'AvenirNext-Regular', size = 14.5 },
    },
    serif = {
        [TypographyScheme.titleFont] = { name = 'Baskerville-SemiBold', size = 23 },
        [TypographyScheme.subtitleFont] = { name = 'Baskerville-SemiBold', size = 18.5 },
        [TypographyScheme.blockFont] = { name = 'Palatino', size = 16 },
        [TypographyScheme.labelFont] = { name = 'Palatino', size = 16 },
        [TypographyScheme.categoryFont] = { name = 'Palatino', size = 16 },
        [TypographyScheme.quantityFont] = { name = 'Baskerville-Bold', size = 13, numberCase = 'uppercase' },
    },
    heavy = {
        [TypographyScheme.titleFont] = { name = 'Futura-Bold', size = 23 },
        [TypographyScheme.subtitleFont] = { name = 'Futura-Medium', size = 16.5 },
        [TypographyScheme.blockFont] = { name = 'Palatino', size = 16 },
        [TypographyScheme.labelFont] = { name = 'Palatino', size = 16 },
        [TypographyScheme.categoryFont] = { name = 'Palatino', size = 16 },
        [TypographyScheme.quantityFont] = { name = 'Palatino', size = 15 },
    },
    fancy = {
        -- Hoefler Text mis-files its monospaced numbers under "proportional"
        [TypographyScheme.titleFont] = { name = 'Optima-Bold', size = 25 },
        [TypographyScheme.subtitleFont] = { name = 'Optima-Bold', size = 15.5 },
        [TypographyScheme.blockFont] = { name = 'HoeflerText-Regular', size = 16 },
        [TypographyScheme.labelFont] = { name = 'HoeflerText-Regular', size = 16, numberSpacing = 'proportional' },
        [TypographyScheme.categoryFont] = { name = 'HoeflerText-Regular', size = 16, numberSpacing = 'proportional' },
        [TypographyScheme.quantityFont] = { name = 'HoeflerText-Italic', size = 15, numberSpacing = 'proportional' },
    },
    fancy2 = {
        [TypographyScheme.titleFont] = { name = 'Didot-Italic', size = 30 },
        [TypographyScheme.subtitleFont] = { name = 'Didot', size = 18.5 },
        [TypographyScheme.blockFont] = { name = 'Cochin', size = 16 },
        [TypographyScheme.labelFont] = { name = 'Cochin', size = 16 },
        [TypographyScheme.categoryFont] = { name = 'Cochin', size = 16 },
        [TypographyScheme.quantityFont] = { name = 'Cochin-Bold', size = 13.5 },
    },
    casual = {
        [TypographyScheme.titleFont] = { name = 'Noteworthy-Bold', size = 23 },
        [TypographyScheme.subtitleFont] = { name = 'Noteworthy-Bold', size = 16 },
        [TypographyScheme.blockFont] = { name = 'Noteworthy-Light', size = 16 },
        [TypographyScheme.labelFont] = { name = 'Skia-Regular', size = 16, numberSpacing = 'monospaced' },
        [TypographyScheme.categoryFont] = { name = 'Skia-Regular', size = 16, numberSpacing = 'monospaced' },
        [TypographyScheme.quantityFont] = { name = 'Skia-Regular', size = 14.5, numberSpacing = 'monospaced' },
    },
}

function TypographyScheme:new()
    self = super.new(self)
    
    self:addProperty('name', nil)
    self:addProperty('size', nil)
    self.fonts = {}

    self._parent = nil
    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._fontChangeObserver = function(sender)
        if not self._loadingHook:getValue() then
            self:setArchiveName(nil)
            self:invalidate(sender)
        end
    end
    
    return self
end

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

function TypographyScheme:unarchiveFonts(archived)
    for archivedFontName, archivedFont in pairs(archived) do
        self:setFont(unarchive(archivedFontName), unarchive(archivedFont), 1)
    end
end

function TypographyScheme:archive()
    local typeName, properties = super.archive(self)
    local schemeName = self:getProperty('name')
    if not schemeName then
        properties.fonts = {}
        for fontName, fontHook in pairs(self.fonts) do
            local fontParams = fontHook:getValue()
            if fontParams then
                properties.fonts[fontName] = Font.get(fontParams)
            end
        end
    end
    return typeName, properties
end

function TypographyScheme:setParent(parent)
    if not self._parent then
        self._parent = parent
        parent:addObserver(self)
    end
end

function TypographyScheme:getPresets(view)
    local results = {
        TypographyScheme:get('basic'),
        TypographyScheme:get('light'),
        TypographyScheme:get('dense'),
        TypographyScheme:get('serif'),
        TypographyScheme:get('heavy'),
        TypographyScheme:get('fancy'),
        TypographyScheme:get('fancy2'),
        TypographyScheme:get('casual'),
    }
    if not view:getTypographyScheme():getName() then
        results[#results + 1] = false
        results[#results + 1] = view:getTypographyScheme()
    end
    return results
end

local _default
function TypographyScheme:default()
    if not _default then
        _default = TypographyScheme:get('basic')
    end
    return _default
end

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

local function roundHalf(value)
    return math.floor(value * 2 + 0.5) / 2
end

function TypographyScheme:load(presetName)
    self._loadingHook:setValue(true)
    if presetName == TypographyScheme.INHERIT then
        for name in pairs(self.fonts) do
            self:setFont(name, nil)
        end
        presetName = nil
    else
        local fonts = PRESETS[presetName] or {}
        for name, fontParams in pairs(fonts) do
            self:setFontParams(name, fontParams)
        end
    end
    self._loadingHook:setValue(false)
    self:setArchiveName(presetName)
end

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

function TypographyScheme:getName()
    local name = self:getProperty('name')
    if name == nil then
        local isCustom = false
        for fontName, fontHook in pairs(self.fonts) do
            if fontHook:getValue() ~= nil then
                isCustom = true
            end
        end
        if not isCustom then
            name = TypographyScheme.INHERIT
        end
    end
    return name
end

function TypographyScheme:setFontParams(name, params)
    local hook = self.fonts[name]
    if not hook then
        hook = PropertyHook:new()
        hook:addObserver(self._fontChangeObserver)
        self.fonts[name] = hook
    end
    hook:setValue(params)
end

function TypographyScheme:setFont(name, font, scale)
    local _, params
    if font then
        _, params = font:archive()
        scale = scale or (BASE_SIZE / self:getSize())
        params.size = params.size * scale
    end
    self:setFontParams(name, params)
end

function TypographyScheme:getExplicitFont(name, size)
    local hook = self.fonts[name]
    if hook then
        local fontParams = hook:getValue()
        if fontParams then
            local scale = (size or BASE_SIZE) / BASE_SIZE
            local scaledSize = roundHalf(fontParams.size * scale)
            fontParams = setmetatable({ size = scaledSize }, { __index = fontParams })
            return Font.get(fontParams)
        end
    end
end

function TypographyScheme:getFont(name, size)
    size = size or self:getSize()
    return self:getExplicitFont(name, size)
        or (self._parent and self._parent:getFont(name, size))
        or TypographyScheme:default():getExplicitFont(name, size)
end

function TypographyScheme:setSize(size)
    self:setProperty('size', size)
end

function TypographyScheme:getSize()
    return self:getProperty('size')
        or (self._parent and self._parent:getSize())
        or BASE_SIZE
end

return TypographyScheme
