------------------------------
-- Modified 16 October 2012 --
-- dylan@kses.net           --
-----------------------------------------------------------------------
-- This behemoth of a function reads the "temporary" iodriver xml    --
-- and parses it, outputting the final iodriver xml                  --
-- The temporary file stores each individual connection and has no   --
-- nested triggers. This is easier to parse (for now)                --
-- The final file will take connections with the same output action  --
-- and put them together in a single connection with nested triggers --
-----------------------------------------------------------------------
--module("io_parse", package.seeall)
local io_parse = {}
local command = require("luamods/command")
local gsmio_conf = require("config/gsmio_conf")

function io_parse.parseToFinalXML()

    connections={}
    counter = 0

    -- Read in the "temporary" file
    xml = command.returnFileString(gsmio_conf.iodriver_tmp)

    -- Read in the io_connections version
    version=xml:match("<io_connections ver=\"(.-)\">")

    -- Read the connection tags
    for conn_attr,conn in xml:gmatch("<connection([^>]*)>(.-)</connection>") do
        counter = counter + 1
        connections[counter] = {}
        connections[counter].id = conn_attr:match("id=\"(.-)\"") or "UNKNOWN"
        local types = { [ "00" ] = "IO", [ "01" ] = "AC", [ "02" ] = "ANALOG", [ "03" ] = "ANALOG_IO" }
        connections[counter].triggers = {}
        -- Read the trigger tags (inputs)
        for trigger_attr,trigger in conn:gmatch("<trigger([^>]*)>(.+)</trigger>") do
            connections[counter].trigger = {}
            connections[counter].trigger.details = {}
            -- Get operation from attribute
            trigger_operation = trigger_attr:match("operation=\"(.-)\"")
            detail_counter = 0
            -- Get dev_type and io_label from each detail tag
            for dev_type,io_label in trigger:gmatch("<detail%s+dev_type=\"(.-)\"%s+io_label=\"(.-)\"%s*/>") do
                detail_counter = detail_counter + 1
                connections[counter].trigger.details[detail_counter] = {}
                -- Substitute numeric dev_type with string representation ("02" -> "ANALOG", etc)
                --dev_type = dev_type:gsub("%d+", types)
                connections[counter].trigger.details[detail_counter].dev_type = dev_type
                connections[counter].trigger.details[detail_counter].io_label = io_label
            end
        end

        -- Get the action tags (output)
        -- We are reading all the action tags and all their outputs, but in reality we
        -- only ever use action[1].details[1]
        connections[counter].actions = {}
        for action_attr,action in conn:gmatch("<action([^>]*)>(.-)</action>") do
            connections[counter].action = {}
            connections[counter].action.details = {}
            -- Get operation from attribute. Could be nil or "INVERSE"
            connections[counter].action.operation = action_attr:match("operation=\"(.-)\"")
            -- Get each dev_type and io_label from each <detail ... />
            detail_counter = 0
            for dev_type,io_label in action:gmatch("<detail%s+dev_type=\"(.-)\"%s+io_label=\"(.-)\"%s*/>") do
                detail_counter = detail_counter + 1
                connections[counter].action.details[detail_counter] = {}
                -- Substitute numeric dev_type with string representation ("02" -> "ANALOG", etc)
                dev_type = dev_type:gsub("%d+", types)
                connections[counter].action.details[detail_counter].dev_type = dev_type
                connections[counter].action.details[detail_counter].io_label = io_label
            end
        end
    end

    -- Compare the outputs of all connections and find matches
    for con=1,#connections do
        outputOperation = connections[con].action.operation
        connections[con].matches = {}
        label = connections[con].action.details[1].io_label

        for con2=con+1,#connections do
            label2 = connections[con2].action.details[1].io_label
            -- Match if label is the same, operation is the same
            -- and hasn't been previously matched
            if label == label2
            and outputOperation == connections[con2].action.operation
            and connections[con2].matched == nil
            then
                -- Mark this one so it doesn't get matched again
                connections[con2].matched = true
                table.insert(connections[con].matches, con2)
            end
        end
    end

    -- Assemble the xml for the final iodriver file
    newXml = "<io_connections ver=\""..version.."\">\n\n"
    for con=1,#connections do
        n = (#(connections[con].matches))

        -- If connections share a matched output, nest all their triggers under
        -- a single connection with an "OR" trigger (other triggers are "AND")
        if n > 0 then
            newXml = newXml.."  <connection>\n"
            newXml = newXml.."    <trigger operation=\"OR\">\n"
            newXml = newXml.."      <trigger operation=\"AND\">\n"
            for k = 1,#(connections[con].trigger.details) do
                newXml = newXml.."        <detail dev_type=\""..connections[con].trigger.details[k].dev_type.."\" io_label=\""..connections[con].trigger.details[k].io_label.."\"/>\n"
            end
            newXml = newXml.."      </trigger>\n"
            for a,b in pairs(connections[con].matches) do
                newXml = newXml.."      <trigger operation=\"AND\">\n"
                for k = 1,#(connections[b].trigger.details) do
                    newXml = newXml.."        <detail dev_type=\""..connections[b].trigger.details[k].dev_type.."\" io_label=\""..connections[b].trigger.details[k].io_label.."\"/>\n"
                end
                newXml = newXml.."      </trigger>\n"
            end
            newXml = newXml.."    </trigger>\n"
            if connections[con].action.operation == "INVERSE" then
                newXml = newXml.."    <action operation=\"INVERSE\">\n"
            else
                newXml = newXml.."    <action>\n"
            end
            newXml = newXml.."      <detail dev_type=\""..connections[con].action.details[1].dev_type.."\" io_label=\""..connections[con].action.details[1].io_label.."\"/>\n"
            newXml = newXml.."    </action>\n"
            newXml = newXml.."  </connection>\n\n"
        -- This is a regular connection with no nested triggers
        elseif connections[con].matched ~= true then
            newXml = newXml.."  <connection>\n"
            newXml = newXml.."    <trigger operation=\"AND\">\n"
            for k = 1,#(connections[con].trigger.details) do
                newXml = newXml.."      <detail dev_type=\""..connections[con].trigger.details[k].dev_type.."\" io_label=\""..connections[con].trigger.details[k].io_label.."\"/>\n"
            end
            newXml = newXml.."    </trigger>\n"
            if connections[con].action.operation == "INVERSE" then
                newXml = newXml.."    <action operation=\"INVERSE\">\n"
            else
                newXml = newXml.."    <action>\n"
            end
            newXml = newXml.."      <detail dev_type=\""..connections[con].action.details[1].dev_type.."\" io_label=\""..connections[con].action.details[1].io_label.."\"/>\n"
            newXml = newXml.."    </action>\n"
            newXml = newXml.."  </connection>\n\n"
        end
    end
    newXml = newXml.."</io_connections>\n"
    -- Write that sucka
    command.writefile(gsmio_conf.iodriver_xml, newXml)
end

return io_parse