
local xml = require('pl.xml')
local class = require('pl.class')
local Set = require('pl.Set')
local dir = require('pl.dir')
local path = require('pl.path')

local Magic = require('luamods.modbus_scan.Magic')

local TemplateCollection = class()

function TemplateCollection:_init()
    self.template_table = {}
end

function TemplateCollection:load_template_file(template_dir, file)
    local tree, err = xml.parse(file, true)
    if err then
        return nil, err
    end

    local details = tree:child_with_name('details')
    if not details then
        return nil, nil
    end

    -- Get all necessary details in table form.
    local details_table = {}
    details_table.magics = self:extract_magics(details)
    details_table.make = (details:child_with_name('make') or { nil })[1]
    details_table.type = (details:child_with_name('type') or { nil })[1]
    details_table.version = (details:child_with_name('version') or { nil })[1]
    details_table.model = (details:child_with_name('model') or { nil })[1]
    -- Save original file path and the path relative to the template dir.
    details_table.file_path = file
    details_table.template_file = path.relpath(path.abspath(file), path.abspath(template_dir))

    return details_table, nil
end

function TemplateCollection:load_template_file_list(template_dir)
    local files = dir.getallfiles(template_dir, '*.xml')

    local errors = {}

    for _, file in ipairs(files) do
        local template, err = self:load_template_file(template_dir, file)
        if err then
            table.insert(errors, err)
        else
            -- Filter out templates with no magic.
            if template and #template.magics > 0 then
                table.insert(self.template_table, template)
            end
        end
    end

    if #errors > 0 then
        return errors
    else
        return nil
    end
end

function TemplateCollection:test_with(tester)
    local passed_tests = Set()
    local passed_templates = {}

    for _, template in ipairs(self.template_table) do
        local has_failed = false

        for _, magic in ipairs(template.magics) do
            if not passed_tests[magic] then -- Do we need to perform this test?
                local is_test_passed, err = tester(magic)
                if err then
                    -- Errors should abort the whole test. Possibly no slave connected.
                    return passed_templates
                end

                if is_test_passed then
                    passed_tests = passed_tests + magic
                else
                    -- This template is not for us.
                    has_failed = true
                    break
                end
            end
        end

        if not has_failed then
            table.insert(passed_templates, template)
        end
    end

    return passed_templates
end

function TemplateCollection:extract_magics(details)
    local magics = {}
    local scan_rules = details:child_with_name('scan')
    if scan_rules then
        for c in scan_rules:childtags() do
            if c.tag == 'magic' then
                local magic = Magic(
                    c.attr.addr,
                    c.attr.len,
                    c.attr.type,
                    c[1]
                )
                table.insert(magics, magic)
            end
        end
    end

    return magics
end

return TemplateCollection
