local builder = ll.class(ll.ContainerNodeBuilder)

builder.name = 'lluvia/imgproc/ImagePyramid_r8ui'
builder.doc = [[
Creates an image pyramid from an input gray scale image.

For `levels >= 2`, the pyramid is generated by concatenating a series of
ImageDownsampleX_r8ui and ImageDownsampleY_r8ui nodes.

Parameters
----------
levels : int. Defaults to 1.
    The number of levels to create. If the value is 1, the in_gray input is bound
    as out_gray in the output, without any memory copy.

Inputs
------
in_gray : ImageView.
    r8ui image.

Outputs
-------
out_gray_0 : ImageView.
    r8ui image. The base level of the pyramid. This corresponds to in_gray.
    Other levels are named `out_gray_1`, to `out_gray_{levels - 1}`

out_gray : ImageView
    r8ui image. The output at the top level of the pyramid. This is equivalent
    to `out_gray_{levels - 1}`

]]

function builder.newDescriptor()

    local desc = ll.ContainerNodeDescriptor.new()

    desc.builderName = builder.name

    local in_gray = ll.PortDescriptor.new(0, 'in_gray', ll.PortDirection.In, ll.PortType.ImageView)
    in_gray:checkImageChannelCountIs(ll.ChannelCount.C1)
    in_gray:checkImageChannelTypeIs(ll.ChannelType.Uint8)

    desc:addPort(in_gray)

    -- parameter with default value
    desc:setParameter('levels', 1)

    return desc
end


function builder.onNodeInit(node)

    local levels = node.descriptor:getParameter('levels')
    ll.logd(builder.name, 'onNodeInit: levels:', levels)

    -- in_gray should have been bound before calling init()
    local in_gray = node:getPort('in_gray')
    node:bind(string.format('out_gray_0', i), in_gray)

    if levels < 1 then
        error(builder.name .. ': levels must be greater or equal 1, got: ' .. levels)
    end

    -- Pass through the input to the output
    if levels == 1 then
        node:bind('out_gray_0', in_gray)
        node:bind('out_gray', in_gray)
    end

    for i = 1, levels -1 do
        ll.logd(builder.name, 'onNodeInit: level:', i)
        
        local downX = ll.createComputeNode('lluvia/imgproc/ImageDownsampleX_r8ui')
        local downY = ll.createComputeNode('lluvia/imgproc/ImageDownsampleY_r8ui')

        downX:bind('in_gray', in_gray)
        downX:init()

        downY:bind('in_gray', downX:getPort('out_gray'))
        downY:init()

        in_gray = downY:getPort('out_gray')

        -- bind the output
        if i == levels -1 then
            node:bind('out_gray', downY:getPort('out_gray'))
        end

        ll.logd(builder.name, 'onNodeInit: level:', i, 'binding node')
        node:bindNode(string.format('ImageDownsampleX_r8ui_%d', i), downX)
        node:bindNode(string.format('ImageDownsampleY_r8ui_%d', i), downY)

        -- outputs of each level
        node:bind(string.format('out_gray_%d', i), downY:getPort('out_gray'))
    end

end


function builder.onNodeRecord(node, cmdBuffer)

    ll.logd(node.descriptor.builderName, 'onNodeRecord')

    local levels = node.descriptor:getParameter('levels')

    for i = 1, levels -1 do
        ll.logd(node.descriptor.builderName, 'onNodeRecord: level:', i)

        local downX = node:getNode(string.format('ImageDownsampleX_r8ui_%d', i))
        local downY = node:getNode(string.format('ImageDownsampleY_r8ui_%d', i))

        downX:record(cmdBuffer)
        cmdBuffer:memoryBarrier()
        downY:record(cmdBuffer)
        cmdBuffer:memoryBarrier()
    end

    ll.logd(node.descriptor.builderName, 'onNodeRecord: finish')
end


ll.registerNodeBuilder(builder)
