local super = require "NumericAxis"

LogarithmicAxis = super:new()

local defaults = {
}

local nilDefaults = {
    'base',
}

local tonumber = tonumber
local _min = math.min
local _max = math.max
local _log = math.log

local validator = function(value)
    return value == nil or (type(value) == 'number' and (value > 0) and (value < math.huge))
end

local function logb(base, value)
    return _log(value) / _log(base)
end

function LogarithmicAxis: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
    self:getPropertyHook('min'):setValidator(validator)
    self:getPropertyHook('max'):setValidator(validator)
    self:getPropertyHook('base'):setValidator(validator)
    
    self.valueInspectorInfo = { min = 'min', max = 'max', step = 'base' }
    self:setAutoRange(1, 1000)
    self._autoBase = 10
    
    return self
end

function LogarithmicAxis:getPreferredType()
    return 'number'
end

function LogarithmicAxis:allowsNumberValues()
    return true
end

function LogarithmicAxis:requiresNumberValues()
    return true
end

function LogarithmicAxis:setRange(min, max)
    local base = self:getProperty('base') or self._autoBase
    local snapMin = base ^ math.floor(0.5 + logb(base, min))
    local snapMax = base ^ math.floor(0.5 + logb(base, max))
    local tolerance = (max / min) ^ (1 / 64) - 1
    if math.abs(min / snapMin - 1) < tolerance then
        min = snapMin
    end
    if math.abs(max / snapMax - 1) < tolerance then
        max = snapMax
    end
    super.setRange(self, min, max)
end

function LogarithmicAxis:setAutoRangeForValueRange(min, max)
    local fixedMin, fixedMax = self:getExplicitRange()
    min = fixedMin or min
    max = fixedMax or max
    if min <= max then
        min = 10 ^ (math.ceil(math.log10(min)) - 1)
        max = 10 ^ (math.floor(math.log10(max)) + 1)
    else
        if fixedMin then
            max = 10 ^ (math.ceil(math.log10(fixedMin)) + 1)
        elseif fixedMax then
            min = 10 ^ (math.floor(math.log10(fixedMax)) - 1)
        else
            min = 1
            max = 1000
        end
    end
    self:setAutoRange(min, max)
    self._autoBase = 10
end

function LogarithmicAxis:getValueConverters()
    local positiveInfinity = math.huge
    return function(value)
        value = tonumber(value)
        if value and 0 < value and value < positiveInfinity then
            return value
        end
    end, function(value) return value end
end

function LogarithmicAxis:origin()
    return 1
end

function LogarithmicAxis:distribute(rect, crossing)
    local scaler = self:getScaler(rect)
    local min, max = self:getRange()
    local base = self:getProperty('base') or self._autoBase
    local newBase = base
    while math.floor(logb(newBase, max / min)) > 20 do
        newBase = newBase * base
    end
    base = newBase
    local minor
    if base <= 16 then
        minor = true
    end
    local origin = self:origin()
    local values = {}
    local positions = {}
    local minorPositions = {}
    if base > 1 then
        local iters = 0
        local magnitude = math.pow(base, math.floor(logb(base, min)))
        while magnitude <= max do
            local start = math.ceil(min / magnitude)
            local stop
            if minor then
                stop = _min(base - 1, math.floor(max / magnitude))
            else
                stop = 1
            end
            for i = start, stop do
                local value = magnitude * i
                if value ~= crossing then
                    if i > 1 then
                        minorPositions[#minorPositions + 1] = scaler(value)
                    else
                        values[#values + 1] = value
                        positions[#positions + 1] = scaler(value)
                    end
                end
            end
            iters = iters + 1
            magnitude = math.pow(base, math.floor(logb(base, min)) + iters)
        end
    end
    return values, positions, values, positions, minorPositions
end

function LogarithmicAxis:getScaler(rect)
    local min, max = self:getRange()
    local rectMin, rectSize
    if self:getOrientation() == Graph.horizontalOrientation then
        rectMin, rectSize = rect:minx(), rect:width()
    else
        rectMin, rectSize = rect:miny(), rect:height()
    end
    return LogarithmicScaler(min, max, rectMin, rectSize)
end

function LogarithmicAxis:scaled(rect, position)
    local min, max = self:getRange()
    local rectMin, rectSize
    if self:getOrientation() == Graph.horizontalOrientation then
        rectMin, rectSize = rect:minx(), rect:width()
    else
        rectMin, rectSize = rect:miny(), rect:height()
    end
    local fraction = ((position - rectMin) / rectSize)
    return min ^ (1 - fraction) * max ^ fraction
end

return LogarithmicAxis
