local super = require "Object"

GraphLayer = super:new()

function GraphLayer:new()
    self = super.new(self)
    
    self._datasetHook = PropertyHook:new()
    self._datasetHook:addObserver(self)
    self._orientationHook = PropertyHook:new(Graph.verticalOrientation)
    self._orientationHook:addObserver(self)
    
    self._colorSchemeHook = nil
    self._typographySchemeHook = nil
    self._hasDataset = true
    self._isPositionConstrained = false
    
    self._propertySequence = nil
    self._valueRanges = {}
    self._propertySequenceInvalidator = function()
        self._propertySequence = nil
        self._valueRanges = {}
        collectgarbage()
    end
    
    return self
end

function GraphLayer:mirror(object)
    self = super.mirror(self, object)
    if object and object._hasDataset then
        self:setDataset(object:getDataset())
    end
    if object and object:isIndependentlyOrientable() then
        self:setOrientation(object:getOrientation())
    end
    return self
end

function GraphLayer:unarchiveDataset(archived)
    local dataset = unarchive(archived)
    self:setDataset(dataset)
end

function GraphLayer:unarchiveOrientation(archived)
    local orientation = unarchive(archived)
    self:setOrientation(orientation)
end

function GraphLayer:peerPropertyKeyArtifactValues(class, propertyName)
    local values = {}
    local dataset = self:getDataset()
    if Object.isa(dataset, DataSource) then
        dataset = dataset:getDataset()
    end
    for layer in self:getParent():getLayerList():iter() do
        if layer == self then
            break
        end
        local layerDataset = layer:getDataset()
        if Object.isa(layerDataset, DataSource) then
            layerDataset = layerDataset:getDataset()
        end
        if Object.isa(layer, class) and layerDataset == dataset then
            local property = layer:getProperty(propertyName)
            if Object.isa(property, KeyArtifact) then
                values[#values + 1] = property:getInput()
            end
        end
    end
    return values
end

function GraphLayer:archive()
    local typeName, properties = super.archive(self)
    if self._hasDataset then
        properties.dataset = self:getDataset()
    end
    if self:isIndependentlyOrientable() then
        properties.orientation = self:getOrientation()
    end
    return typeName, properties
end

function GraphLayer:getSubclasses()
    return {
        {'ScatterGraphLayer', 'Scatter'},
        {'BubbleGraphLayer', 'Bubble'},
        {'LineGraphLayer', 'Line'},
        {'BarGraphLayer', 'Bar'},
        {'LabelGraphLayer', 'Text'},
        {'RangeGraphLayer', 'Range'},
        {'ReferenceGraphLayer', 'Reference'},
        {'ArrowGraphLayer', 'Arrow'},
        {'CandlestickGraphLayer', 'Candlestick'},
    }
end

function GraphLayer:isGroupable()
    return false
end

function GraphLayer:isOrientable()
    return false
end

function GraphLayer:isIndependentlyOrientable()
    local parent = self:getParent()
    return self:isOrientable() and (not parent or not parent:isOrientable())
end

function GraphLayer:draw(canvas, rect, propertySequence, xScaler, yScaler, intralayerState)
end

function GraphLayer:getInspectors()
    local list = List:new()
    local inspector, hook
    local layerList = self.parent:getLayerList()
    inspector = Inspector:new{
        title = 'Type',
        type = 'Class',
        constraint = function()
            return GraphLayer:getSubclasses()
        end,
    }
    hook = Hook:new(
        function()
            return self:class()
        end,
        function(value)
            local index = layerList:index(self)
            local clone = _G[value]:new():mirror(self)
            layerList:replace(self, clone)
            unarchived()
        end)
    inspector:addHook(hook)
    list:add(inspector)
    if self:isIndependentlyOrientable() then
        list:add(self:getOrientationInspector())
    end
    if self._hasDataset then
        inspector = Inspector:new{
            title = 'Dataset',
            type = 'Dataset',
        }
        inspector:addHook(self._datasetHook)
        inspector:addHook(Hook:new(self:isPositionConstrained()), 'ordered')
        list:add(inspector)
    end
    local inspectorInfo = self:getInspectorInfo(self:isPositionConstrained())
    for i = 1, #inspectorInfo do
        local info = inspectorInfo[i]
        list:add(self:createInspector(unpack(info)))
    end
    if self:isPositionConstrained() and self:isGroupable() then
        local parent = self:getParent()
        if parent.getGroupStyleInspector then
            list:add(parent:getGroupStyleInspector())
        end
    end
    return list
end

function GraphLayer:createInspector(inspectorType, hooks, title, undoTitle)
    local inspector = super.createInspector(self, inspectorType, hooks, title, undoTitle)
    inspector:addHook(self._datasetHook, 'dataSource')
    return inspector
end

function GraphLayer:getCustomPaintHook()
    local colorScheme = self._colorSchemeHook:getValue()
    local hook = Hook:new(
        function()
            return (self:getProperty('paint') ~= nil)
        end)
    colorScheme:addObserver(hook)
    return hook
end

function GraphLayer:getDefaultFontHook()
    local hook = Hook:new(
        function()
            return self.parent:getLayerLabelFont(self)
        end,
        function(value) end)
    if self._typographySchemeHook then
        self._typographySchemeHook:addObserver(hook)
    end
    return hook
end

function GraphLayer:setParent(parent)
    self.parent = parent
end

function GraphLayer:getParent()
    return self.parent
end

function GraphLayer:getPaint()
    return self:getProperty('paint') or self.parent:getLayerPaint(self)
end

function GraphLayer:setPaint(value)
    self:setProperty('paint', value)
end

function GraphLayer:hasExplicitPaint()
    return self:getProperty('paint') ~= nil
end

function GraphLayer:usesLayerPaint()
    return true
end

function GraphLayer:setPositionConstrained(value)
    self._isPositionConstrained = value
end

function GraphLayer:isPositionConstrained()
    return self._isPositionConstrained
end

function GraphLayer:getGetterPieceNames(constrained)
    return {}
end

function GraphLayer:getInspectorInfo(constrained)
    return {}
end

function GraphLayer:getTitle()
end

function GraphLayer:makePropertySequence()
    if not self._propertySequence then
        Profiler.time(self:class() .. ':makePropertySequence', function()
            self._datasetHook:addObserver(self._propertySequenceInvalidator)
            local sequences = {}
            local propertyNames = self:getGetterPieceNames(self:isPositionConstrained())
            local dataset = self:getDataset()
            local propertySequences = {}
            propertySequences['#'] = Sequence:new{
                getter = function(index)
                    return index
                end,
                length = function()
                    return dataset and dataset:entryCount() or 0
                end,
                cached = true
            }
            for index = 1, #propertyNames do
                local propertyName = propertyNames[index]
                if propertySequences[propertyName] == nil then
                    local propertyHook = self:getPropertyHook(propertyName)
                    if propertyHook then
                        propertyHook:addObserver(self._propertySequenceInvalidator) -- TODO: try to re-calculate only the affected sequences
                    end
                    propertySequences[propertyName] = self:getPropertySequence(propertyName, dataset)
                end
                sequences[#sequences + 1] = propertySequences[propertyName]
            end
            self._propertySequence = Sequence:newWithSequenceList(sequences)
        end)
    end
    return self._propertySequence
end

function GraphLayer:setOrientationHook(hook)
    self._orientationHook:removeObserver(self)
    self._orientationHook = hook
end

function GraphLayer:setOrientation(orientation)
    if self:isIndependentlyOrientable() then
        self._orientationHook:setValue(orientation)
    end
end

function GraphLayer:getOrientation()
    return self._orientationHook:getValue()
end

function GraphLayer:getOrientationInspector()
    local inspector = Inspector:new{
        title = 'Orientation',
        type = 'Orientation',
    }
    inspector:addHook(self._orientationHook)
    return inspector
end

function GraphLayer:setColorSchemeHook(hook)
    if self._colorSchemeHook ~= hook then
        hook:addObserver(self)
    end
    self._colorSchemeHook = hook
end

function GraphLayer:setTypographySchemeHook(hook)
    if self._typographySchemeHook ~= hook then
        hook:addObserver(self)
    end
    self._typographySchemeHook = hook
end

function GraphLayer:setDatasetHook(hook)
    self._datasetHook:removeObserver(self)
    self._hasDataset = false
    self._datasetHook = hook
end

function GraphLayer:setDataset(dataset)
    if self._hasDataset then
        self._datasetHook:setValue(dataset)
    end
end

function GraphLayer:getDataset(dataset)
    return self._datasetHook:getValue()
end

function GraphLayer:iterateValues(orientation, mapFunction, intralayerState)
end

function GraphLayer:cacheValueRange(orientation, min, max)
    self._valueRanges[orientation] = { min = min, max = max }
end

function GraphLayer:cachedValueRange(orientation)
    local cache = self._valueRanges[orientation]
    if cache then
        return cache.min, cache.max
    end
end

return GraphLayer
