require("luamods/cgi")
require("luamods/layout")
require("luamods/command")
require("luamods/db")
require("config/rsm_conf")
require("config/ccd_conf")

header = layout.getHeader()
footer = layout.getFooter()

userLevel = db.getPriv(layout.username) or 1

if (userLevel == 1) then
    buttonControl = " disabled"
    inputControl = " readonly"
    scriptControl = "//"
else
    buttonControl = ""
    inputControl = ""
    scriptControl = ""
end

local escape_lua_pattern
do
    local matches =
    {
        ["^"] = "%^";
        ["$"] = "%$";
        ["("] = "%(";
        [")"] = "%)";
        ["%"] = "%%";
        ["."] = "%.";
        ["["] = "%[";
        ["]"] = "%]";
        ["*"] = "%*";
        ["+"] = "%+";
        ["-"] = "%-";
        ["?"] = "%?";
        ["\0"] = "%z";
    }

    escape_lua_pattern = function(s)
        return (s:gsub(".", matches))
    end
end

response = command.ex(rsm_conf.rsmc_path.." GET: TAG_MSG_INFO TAG_MSG_INFO_IDX_ONLY TAG_DEV_CFG") or ""
devices = {}
if (response ~= "") then
    for id in response:gmatch("TAG_DEV_CFG%s+TAG_DEV_ID=(%x+)%s-") do
        devCfg = command.ex(rsm_conf.rsmc_path.." GET: TAG_DEV_CFG TAG_DEV_ID="..id)
        if (devCfg ~= nil) then
            device = {}
            device.id = id
            device.label = devCfg:match([[TAG_DEV_LABEL=%"(.-)%"]]) or "unknown"
            device.inputs = {}
            device.outputs = {}
            device.analogs = {}
            device.analogOutputs = {}
            
            devCfg = command.ex(rsm_conf.rsmc_path.." GET: TAG_IP_CFG TAG_DEV_ID="..id)
            if (devCfg ~= nil) then
                for inputCfg in devCfg:gmatch("TAG_IP_CFG%s+(.-)%s+TAG_IO_TYPE=01") do
                    input = {}
                    input.devID = id
                    input.pnum = string.format("%02d", tonumber(inputCfg:match("TAG_IO_PNUM=(%x+)"),16))
                    input.label = inputCfg:match([[TAG_IO_LABEL=%"(.-)%"]]) or "unknown"
                    table.insert(device.inputs, input)
                end
            end
            devCfg = command.ex(rsm_conf.rsmc_path.." GET: TAG_OP_CFG TAG_DEV_ID="..id)
            if (devCfg ~= nil) then
                for outputCfg in devCfg:gmatch("TAG_OP_CFG%s+(.-)%s+TAG_IO_TYPE=02") do
                    output = {}
                    output.devID = id
                    output.pnum = string.format("%02d", tonumber(outputCfg:match("TAG_IO_PNUM=(%x+)"),16))
                    output.label = outputCfg:match([[TAG_IO_LABEL=%"(.-)%"]]) or "unknown"
                    table.insert(device.outputs, output)
                end
            end
            devCfg = command.ex(rsm_conf.rsmc_path.." GET: TAG_AI_CFG TAG_DEV_ID="..id)
            if (devCfg ~= nil) then
                for analogCfg in devCfg:gmatch("TAG_AI_CFG%s+(.-)%s+TAG_IO_TYPE=03") do
                    analog = {}
                    analog.devID = id
                    analog.pnum = string.format("%02d", tonumber(analogCfg:match("TAG_IO_PNUM=(%x+)"),16))
                    analog.label = analogCfg:match([[TAG_IO_LABEL=%"(.-)%"]]) or "unknown"
                    table.insert(device.analogs, analog)
                end
            end
            devCfg = command.ex(rsm_conf.rsmc_path.." GET: TAG_AO_CFG TAG_DEV_ID="..id)
            if (devCfg ~= nil) then
                for analogOutCfg in devCfg:gmatch("TAG_AO_CFG%s+(.-)%s+TAG_IO_TYPE=04") do
                    analogOut = {}
                    analogOut.devID = id
                    analogOut.pnum = string.format("%02d", tonumber(analogOutCfg:match("TAG_IO_PNUM=(%x+)"),16))
                    analogOut.label = analogOutCfg:match([[TAG_IO_LABEL=%"(.-)%"]]) or "unknown"
                    table.insert(device.analogOutputs, analogOut)
                end
            end            
            table.insert(devices, device)
        end
    end
end

if (response == "") then
    -- Bail upon error --
    content = [[
                    <div id="info"><center><H1>Climate Control Profile</H1></center></div>
                    <div id="info"><center><h2>RSMC Error</h2><B>"]]..rsm_conf.rsmc_path..[[" is missing, not executable or not responding!</B></center><br></div>]]
    html = header..content..footer
    print(html)
    os.exit()
end

defaultInputOptions = [[<option value='' selected>(none) Select Input</option>]]
defaultOutputOptions = [[<option value='' selected>(none) Select Output</option>]]
defaultAnalogOptions = [[<option value='' selected>(none) Select Analog</option>]]
defaultAnalogOutputOptions = [[<option value='' selected>(none) Select Analog Output</option>]]

for idx, dev in ipairs(devices) do
    foundInputs = 0
    for idx = #(dev.inputs),1,-1 do
        if foundInputs == 0 then
            foundInputs = 1
            defaultInputOptions = defaultInputOptions..[[<optgroup label=']]..dev.id.." / "..dev.label..[['>]]
        end
        defaultInputOptions = defaultInputOptions..[[<option value=']]..dev.inputs[idx].label..[['>]]..dev.inputs[idx].pnum.." / "..dev.inputs[idx].label..[[</option>]]
    end
    if (foundInputs == 1) then
        defaultInputOptions = defaultInputOptions..[[</optgroup>]]
    end
    
    foundOutputs = 0
    for idx = #(dev.outputs),1,-1 do
        if foundOutputs == 0 then
            foundOutputs = 1
            defaultOutputOptions = defaultOutputOptions..[[<optgroup label=']]..dev.id.." / "..dev.label..[['>]]
        end
        defaultOutputOptions = defaultOutputOptions..[[<option value=']]..dev.outputs[idx].label..[['>]]..dev.outputs[idx].pnum.." / "..dev.outputs[idx].label..[[</option>]]
    end
    if (foundOutputs == 1) then
        defaultOutputOptions = defaultOutputOptions..[[</optgroup>]]
    end
    
    foundAnalogs = 0
    for idx = #(dev.analogs),1,-1 do
        if foundAnalogs == 0 then
            foundAnalogs = 1
            defaultAnalogOptions = defaultAnalogOptions..[[<optgroup label=']]..dev.id.." / "..dev.label..[['>]]
        end
        defaultAnalogOptions = defaultAnalogOptions..[[<option value=']]..dev.analogs[idx].label..[['>]]..dev.analogs[idx].pnum.." / "..dev.analogs[idx].label..[[</option>]]
    end
    if (foundAnalogs == 1) then
        defaultAnalogOptions = defaultAnalogOptions..[[</optgroup>]]
    end
    
    foundAnalogOutputs = 0
    for idx = #(dev.analogOutputs),1,-1 do
        if foundAnalogOutputs == 0 then
            foundAnalogOutputs = 1
            defaultAnalogOutputOptions = defaultAnalogOutputOptions..[[<optgroup label=']]..dev.id.." / "..dev.label..[['>]]
        end
        defaultAnalogOutputOptions = defaultAnalogOutputOptions..[[<option value=']]..dev.analogOutputs[idx].label..[['>]]..dev.analogOutputs[idx].pnum.." / "..dev.analogOutputs[idx].label..[[</option>]]
    end
    if (foundAnalogOutputs == 1) then
        defaultAnalogOutputOptions = defaultAnalogOutputOptions..[[</optgroup>]]
    end    
end

profile = {}
profile.varSpeed = false;
saved = 0

filename = cgi.post.filename or ""
if (filename ~= "") then
    edit = 1
else
    edit = 0
end

if (cgi.post.deleteButton == "Delete") and (userLevel ~= 1) then
    filename = filename:gsub(".xml","")
    filename = filename:gsub("[^%w]","")..".xml"
    if (filename ~= "") then
        command.ex("rm "..ccd_conf.profiles_path..filename)
        content = [[
                    <div id="info">
                        <center><h1>Edit Climate Control Profile</h1></center>
                    </div>            
                    <div id="info">
                        <center>
                            <h2>Profile Deleted</h2>
                            <b>The profile "]]..filename..[[" has been deleted...</b><br><br>
                            <A HREF="ccd_profiles.lua">Back to Climate Control Profiles</A>
                        </center>
                    </div>
]]
        print(header..content..footer)
        os.exit()
    end
end

if cgi.post.saveButton == "Save" then
    edit = 1
    if (userLevel ~= 1) then
        new_name = cgi.post.name or "unknown"
        new_name = new_name:gsub("^%s*(.-)%s*$", "%1")
        if new_name == "" then new_name = "unknown" end
        if (filename == "") then
            filename = string.lower(new_name)
        end
        filename = filename:gsub(".xml","")
        filename = filename:gsub("[^%w]","")..".xml"

        new_temperature = tonumber(cgi.post.temperature) or 20
        new_range = tonumber(cgi.post.range) or 2
        
        if (cgi.post.internalProbe ~= nil) then
            label = cgi.post.internalProbe or ""
            if (label ~= "") then
                internalProbe = label
            else
                -- Bail upon error --
                content = [[<div id="info"><center><H1>Climate Control Profile</H1></center></div>
                            <div id="info"><center><BR><B>You have submitted invalid form data.</B><BR><BR>Please make sure your browser has Javascript enabled, and trying entering the data again.
                            <BR><BR>
                            <A HREF="ccd_profiles.lua">Back to Climate Control Profiles</A></center><br></div>]]
                html = header..content..footer

                print(html)
                os.exit()
            end
        end

        if (cgi.post.externalProbe ~= nil) then
            label = cgi.post.externalProbe or ""
            if (label ~= "") then
                externalProbe = label
            end
        end
        
        if (cgi.post.enableDelta ~= nil) then
            deltaT = tonumber(cgi.post.deltaT) or 0
            deltaMinimum = tonumber(cgi.post.deltaMinimum) or 0
            if (deltaT > 3) then
                enableDelta = 1
            end
        else
            enableDelta = 0
        end
        
        aircons = {}
        fans = {}
        overrides = {}
        ACmonitor = cgi.post.ACmonitor or ""
        threshold = tostring(math.floor(tonumber(cgi.post.threshold or "0")))
        
        if (cgi.post["fan1_output"] ~= nil) then
            for count = 1,8 do
                if (cgi.post["fan"..count.."_output"] ~= nil) then
                    fans[count] = cgi.post["fan"..count.."_output"]
                end
            end
        else
            for count = 1,8 do
                if (cgi.post["fan"..count.."_analogOutput"] ~= nil) then
                    fans[count] = cgi.post["fan"..count.."_analogOutput"]
                    profile.varSpeed = true
                end
            end
        end

        for count = 1,4 do
            if (cgi.post["aircon_"..count.."_power"] ~= nil) then
                aircons[count] = {}
                aircons[count].power = cgi.post["aircon_"..count.."_power"]
                aircons[count].failure = cgi.post["aircon_"..count.."_fail"]
                aircons[count].probe = cgi.post["aircon_"..count.."_probe"]
            end
        end

        for count = 1,10 do
            if (cgi.post["override"..count] ~= nil) then
                label = cgi.post["override"..count] or ""
                if (label ~= "") then
                    overrides[count] = label
                end
            end
        end
        
        newxml = [[
<?xml version="1.0"?>
<profile name="]]..new_name..[[">
    <settings>
        <temperature>
            <ideal>]]..new_temperature..[[</ideal>
            <range>]]..new_range..[[</range>
]]
        if (threshold ~= "0") then
            newxml = newxml .. [[
            <threshold>]]..threshold..[[</threshold>
]]                
        end
        newxml = newxml .. [[
        </temperature>
]]
        if (enableDelta == 1) then
            newxml = newxml .. [[
        <deltacooling>
            <delta>]]..deltaT..[[</delta>
            <minimum>]]..deltaMinimum..[[</minimum>
        </deltacooling>
]]
        end
newxml = newxml .. [[
    </settings>
    <inputs>
        <probes>
            <internal>
                <analog>]]..internalProbe..[[</analog>
            </internal>
]]

        if (externalProbe ~= nil) then
            newxml = newxml .. [[
            <external>
                <analog>]]..externalProbe..[[</analog>
            </external>
]]
        end
        newxml = newxml .. [[
        </probes>
]]
        if (#(overrides) > 0) then
            newxml = newxml .. [[
        <overrides>
]]
            for num, o in pairs(overrides) do
                newxml = newxml .. [[
            <input>]]..o..[[</input>
]]
            end
            newxml = newxml .. [[
        </overrides>
]]
        end
        if (ACmonitor ~= "") then
            newxml = newxml .. [[
        <ACmonitor>
            <input>]]..ACmonitor..[[</input>
        </ACmonitor>
]]
        end
        newxml = newxml .. [[
    </inputs>
    <outputs>
]]

        for num, f in pairs(fans) do
            if (f ~= "") then 
                newxml = newxml .. [[
        <fan>
]]
                if (profile.varSpeed == true) then
                newxml = newxml .. [[
            <analogOutput>]]..f..[[</analogOutput>                    
]]
                else
                newxml = newxml .. [[
            <output>]]..f..[[</output>                    
]]                
                end
                newxml = newxml .. [[
        </fan>
]]
            end
        end


        for num, a in pairs(aircons) do
            if (a.power ~= nil) and (a.power ~= "") then
                newxml = newxml .. [[
        <aircon>
]]
            
                newxml = newxml .. [[
            <power>]]..a.power..[[</power>
]]            
                if (a.failure ~= nil) and (a.failure ~= "") then
                    newxml = newxml .. [[
            <failure>]]..a.failure..[[</failure>
]]            
                end
                if (a.probe ~= nil) and (a.probe ~= "") then
                    newxml = newxml .. [[
            <probe>]]..a.probe..[[</probe>
]]            
                end            
            newxml = newxml .. [[
        </aircon>
]]
            end
        end

        newxml = newxml .. [[
    </outputs>
</profile>

]]
    command.writefile(ccd_conf.profiles_path..filename, newxml)
    --print(newxml)
    saved = 1
    end
end

if (edit == 1) then
    content = [[
                    <div id="info">
                        <center><h1>Edit Climate Control Profile</h1></center>
                    </div>
]]
    xml = command.ex("cat "..ccd_conf.profiles_path..filename) or ""
    if (xml == "") then
        -- Bail on error, fuck these clowns
        content = content .. [[
                    <div id="info">
                        <center>
                            <h2><img src="images/warning.png"/> Invalid profile</h2>
                            The file "]]..ccd_conf.profiles_path..filename..[[" is either unreadable or not a valid climate profile.<br><br>
                            <a href="ccd_profiles.lua">Back to climate profiles</a>
                        </center>
                    </div>
]]
        print(header..content..footer)
        os.exit()
    else
        -- Let's parse them thangs
        profile.name = xml:match([[<profile%s+name="%s-(.-)%s-"%s->]]) or "unknown"
        profile.temperature = xml:match("<ideal>%s-(.-)%s-</ideal>") or "20.0"
        profile.range = xml:match("<range>%s-(.-)%s-</range>") or "2.0"
        
        profile.deltaT = xml:match("<delta>%s-(.-)%s-</delta>")
        profile.deltaMinimum = xml:match("<minimum>%s-(.-)%s-</minimum>")
        
        if (profile.deltaT ~= nil) and (profile.deltaMinimum ~= nil) then
            profile.enableDelta = "checked"
        end
        
        profile.internal = xml:match("<internal>%s-<analog>%s-(.-)%s-</analog>%s-</internal>")
        profile.external = xml:match("<external>%s-<analog>%s-(.-)%s-</analog>%s-</external>")
        profile.threshold = xml:match("<threshold>%s-(.-)%s-</threshold>")
        profile.overrides = {}
        profile.fans = {}
        profile.varSpeed = false
        profile.aircons = {}
        profile.ACmonitor = xml:match("<ACmonitor>%s-<input>%s-(.-)%s-</input>%s-</ACmonitor>")
        
        overrides = xml:match("<overrides>%s-(.-)%s-</overrides>") or ""
        for o in overrides:gmatch("<input>%s-(.-)%s-</input>") do
            table.insert(profile.overrides, o)
        end
        
        for f in xml:gmatch("<fan>%s-(.-)%s-</fan>") do
            local fan = {}
            fan.output = f:match("<output>%s-(.-)%s-</output>")
            fan.analogOutput = f:match("<analogOutput>%s-(.-)%s-</analogOutput>")
            if (fan.analogOutput ~= nil) then
                profile.varSpeed = true
            end
            table.insert(profile.fans, fan)
        end
        for a in xml:gmatch("<aircon>(.-)</aircon>") do
            local aircon = {}
            aircon.output = a:match("<power>%s-(.-)%s-</power>")
            aircon.failure = a:match("<failure>%s-(.-)%s-</failure>")
            aircon.probe = a:match("<probe>%s-(.-)%s-</probe>")
            table.insert(profile.aircons, aircon)
        end
    end
else
    content = [[
                    <div id="info">
                        <center><h1>Add new Climate Control Profile</h1></center>
                    </div>
]]
end

if (userLevel == 1) then
    content = content .. [[
                    <center><b>Note:</b> Editing this section requires administrator privileges.</center><br>
]]
end

inputOptions = defaultInputOptions
outputOptions = defaultOutputOptions
analogOptions = defaultAnalogOptions
analogOutputOptions = defaultAnalogOutputOptions

-- Create the HTML
content = content .. [[
                    <div class="formLayoutfull">

                        <script language="Javascript" type="text/javascript">
                        <!--
]]

if (profile.varSpeed == true) then
    content = content .. [[
                            var varSpeed = true;
]]
else
    content = content .. [[
                            var varSpeed = false;
]]    
end

content = content .. [[

                            // There is some disagreement as to whether or not the same I/O may belong to more than one CCD component.
                            // For now, only outputs are checked for duplicates and inputs are largely ignored.
    
                            function checkTemperatureProbe()
                            {
                                var field_area = document.getElementById("analog");
                                var all_inputs = field_area.getElementsByTagName("select"); //Get all the input fields in the given area.
                                
                                var good = (all_inputs[0].selectedIndex > 0);
                                
                                if (!good)
                                {
                                    var warning = document.getElementById("internalProbeWarning");
                                    if (!warning)
                                    {
                                        warning = document.createElement("b");
                                        warning.style.color = "red";
                                        warning.id = "internalProbeWarning";
                                        warning.innerHTML = " <img src='images/warning.png' alt='Invalid probe' title='Invalid probe'/>";
                                        all_inputs[0].parentNode.appendChild(warning);
                                    }
                                }
                                
                                return good;
                            }
                            
                            function disableStuff(what)
                            {
                                if (what == "delta") 
                                {
                                    var enableDelta = document.getElementById("enableDelta");
                                    var deltaT = document.getElementById("deltaT");
                                    var deltaMinimum = document.getElementById("deltaMinimum");
                                    var deltaDiv = document.getElementById("deltaDiv");
                                    if (enableDelta.checked)
                                    {
                                        deltaT.disabled = false;
                                        deltaMinimum.disabled = false;
                                        deltaDiv.style.display = "inline-block";
                                    }
                                    else
                                    {
                                        deltaT.disabled = true;
                                        deltaMinimum.disabled = true;
                                        deltaDiv.style.display = "none";
                                    }
                                }
                            }
                            
                            function clearDuplicates(type)
                            {
                                var all_selects = document.getElementsByClassName(type); //Get all the input fields in the given area.            
                                var loop;
                                for (loop = 0; loop < all_selects.length; loop++)
                                {
                                    var obj = all_selects[loop];
                                    obj.parentNode.removeAttribute("duplicate");
                                    var text = document.getElementById(type+"_dup_"+loop);
                                    if (text)
                                    {
                                        text.parentNode.removeChild(text);
                                    }
                                }                                
                            }

                            function checkDuplicates(type)
                            {
                                var all_selects = document.getElementsByClassName(type); //Get all the input fields in the given area.            
                                var loop;
                                var numDups = 0;
                                for (loop = 0; loop < all_selects.length; loop++)
                                {
                                    var obj = all_selects[loop];
                                    var innerloop;
                                    for (innerloop = loop; innerloop < all_selects.length; innerloop++)
                                    {
                                        var innerobj = all_selects[innerloop];
                                        if ((obj.selectedIndex > 0) && (innerloop != loop) && (obj.options[obj.selectedIndex].text == innerobj.options[innerobj.selectedIndex].text))
                                        {
                                            if ((!innerobj.parentNode.getAttribute("duplicate")) && (!obj.parentNode.getAttribute("duplicate")))
                                            {
                                                numDups++;
                                            }
                                            if (!innerobj.parentNode.getAttribute("duplicate"))
                                            {
                                                innerobj.parentNode.setAttribute("duplicate", numDups);
                                            }
                                            if (!obj.parentNode.getAttribute("duplicate"))
                                            {
                                                obj.parentNode.setAttribute("duplicate", numDups);
                                            }
                                        }
                                    }
                                    var bleep;
                                    if (bleep = obj.parentNode.getAttribute("duplicate"))
                                    {
                                        var text = document.createElement("b");
                                        text.style.color = "red";
                                        text.id = type+"_dup_"+loop;
                                        text.innerHTML = " <img src='images/warning.png' alt='Duplicate' title='Duplicate'/> #"+bleep+" ("+type+")";
                                        obj.parentNode.appendChild(text);
                                    }
                                }
                                return numDups;
                            }
                            
                            function trim (str) {
                                return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
                            }                            

                            var doValidate = 0;
                            function validateForm(f)
                            {
                                if (f.getAttribute('submitted')) return false;
                                
                                if (!doValidate)
                                {
                                    form.setAttribute('submitted','true');
                                    return true;
                                }
                                
                                doValidate = 0;
                                
                                var warning = document.getElementById("valueWarning");
                                if (warning)
                                {
                                    warning.parentNode.removeChild(warning);
                                }
                                
                                clearDuplicates("output");
                                clearDuplicates("analogOutput");
                                
                                //clearDuplicates("inputs");
                                //clearDuplicates("analog");
                                
                                var valueWarning = document.createElement("b");
                                valueWarning.style.color = "red";
                                valueWarning.id = "valueWarning";
                                valueWarning.innerHTML = " <img src='images/warning.png' alt='Invalid value' title='Invalid value'/>";                                
                                
                                var profilename = document.getElementById("profilename");
                                if (trim(profilename.value) == "")
                                {
                                    profilename.parentNode.appendChild(valueWarning);
                                    alert("Please enter a valid profile name.");
                                    return false;
                                }
                                
                                var temperature = document.getElementById("temperature");
                                temperature.value = parseFloat(temperature.value);
                                if ((temperature.value) == "NaN")
                                {
                                    temperature.parentNode.appendChild(valueWarning);
                                    alert("Temperature settings:\n\nPlease enter a valid ideal temperature.");
                                    return false;
                                }
                                
                                var warning = document.getElementById("externalProbeWarning");
                                if (warning)
                                {
                                    warning.parentNode.removeChild(warning);
                                }
                                
                                var warning = document.getElementById("internalProbeWarning");
                                if (warning)
                                {
                                    warning.parentNode.removeChild(warning);
                                }                                

                                if (!checkTemperatureProbe())
                                {
                                    alert("Temperature probes:\n\nPlease select a valid analog input for the internal temperature probe.");
                                    return false;
                                }

                                var enableDelta = document.getElementById("enableDelta");
                                if (enableDelta.checked)
                                {
                                    
                                    var deltaT = document.getElementById("deltaT");
                                    deltaT.value = parseFloat(deltaT.value);
                                    if ((deltaT.value == "NaN") || (deltaT.value < 3.0))
                                    {
                                        deltaT.parentNode.appendChild(valueWarning);
                                        alert("Delta cooling:\n\nPlease specify a valid temperature difference of at least 3 degrees.");
                                        return false;
                                    }
                                    
                                    var deltaMinimum = document.getElementById("deltaMinimum");
                                    deltaMinimum.value = parseFloat(deltaMinimum.value);
                                    if ((deltaMinimum.value) == "NaN")
                                    {
                                        deltaMinimum.parentNode.appendChild(valueWarning);
                                        alert("Delta cooling:\n\nPlease specify a valid minimum temperature.");
                                        return false;
                                    }                                    
                                    
                                    var externalProbe = document.getElementById("externalProbe");
                                    if (externalProbe.selectedIndex == 0)
                                    {
                                        var warning = document.getElementById("externalProbeWarning");
                                        if (!warning)
                                        {
                                            warning = document.createElement("b");
                                            warning.style.color = "red";
                                            warning.id = "externalProbeWarning";
                                            warning.innerHTML = " <img src='images/warning.png' alt='Invalid probe' title='Invalid probe'/>";
                                            externalProbe.parentNode.appendChild(warning);
                                        }                                        
                                        alert("Temperature probes:\n\nPlease select a valid analog input for the external temperature probe (or disable delta cooling).");
                                        return false;
                                    }
                                    
                                    if (!checkFans())
                                    {
                                        alert("Delta cooling:\n\nPlease select at least one valid DC fan output (or disable delta cooling).");
                                        return false;
                                    }
                                }
                                
                                if ((!checkFans()) && (!checkAircons()))
                                {
                                    alert("Please select at least one valid aircon or DC fan output.");
                                    return false;
                                }
                                
                                var numDups = 0;
                                
                                numDups = numDups + checkDuplicates("output");
                                numDups = numDups + checkDuplicates("analogOutput");
                                //numDups = numDups + checkDuplicates("inputs");
                                //numDups = numDups + checkDuplicates("analog");
                                
                                if (numDups)
                                {
                                    alert("All output labels must be unique per profile.\nPlease fix the indicated duplicates.");
                                    return false;
                                }                                

                                f.setAttribute('submitted','true');
                            }
                            
                            function removeField(node,field)
                            {
                                node.parentNode.parentNode.removeChild(node.parentNode);
                                var field_area = document.getElementById(field+"_list");
                                var all_inputs = field_area.getElementsByTagName("select"); //Get all the input fields in the given area.
                                var loop;
                                if (field == "aircon")
                                {
                                    var count = (all_inputs.length / 3);
                                    for (loop = 0; loop < count; loop++)
                                    {
                                        var num = loop*3;
                                        all_inputs[num].name = "aircon_"+(loop+1)+"_power";
                                        all_inputs[num+1].name = "aircon_"+(loop+1)+"_fail";
                                        all_inputs[num+2].name = "aircon_"+(loop+1)+"_probe";
                                    }
                                    var list_items = field_area.getElementsByTagName("b");
                                    for (loop = 0; loop < list_items.length; loop++)
                                    {
                                        list_items[loop].innerHTML = "Aircon "+(loop+1)+" :";
                                    }
                                }
                                else if (field == "fan")
                                {
                                    for (loop = 0; loop < all_inputs.length; loop++)
                                    {
                                        if (varSpeed)
                                        {
                                            all_inputs[loop].name = "fan"+(loop+1)+"_analogOutput";
                                        }
                                        else
                                        {
                                            all_inputs[loop].name = "fan"+(loop+1)+"_output";
                                        }
                                    }                                    
                                }
                            }
                            
                            function checkFans()
                            {
                                var field_area = document.getElementById("fan_list");
                                var all_inputs = field_area.getElementsByTagName("select"); //Get all the input fields in the given area.
                                var loop;

                                for (loop = 0; loop < all_inputs.length; loop++)
                                {
                                    if (all_inputs[loop].selectedIndex > 0)
                                    {
                                        return true;
                                    }
                                }
                                return false;
                            }
                            
                            function deleteFans()
                            {
                                var field_area = document.getElementById("fan_list");
                                while (field_area.firstChild)
                                {
                                    field_area.removeChild(field_area.firstChild);
                                }                                
                            }
                            
                            function checkAircons()
                            {
                                var field_area = document.getElementById("aircon_list");
                                var all_inputs = field_area.getElementsByTagName("select"); //Get all the input fields in the given area.
                                var loop;
                                var count = 0;
                                for (loop = 0; loop < all_inputs.length; loop++)
                                {
                                    if (all_inputs[loop].selectedIndex > 0)
                                    {
                                        count++;
                                    }
                                }
                                return count;
                            }

                            //Add more fields dynamically.
                            function addField(field,limit)
                            {
                                if(!document.getElementById) return; //Prevent older browsers from getting any further.
                                var field_area = document.getElementById(field+"_list");
                                var all_inputs = field_area.getElementsByTagName("select"); //Get all the input fields in the given area.
                                var count = all_inputs.length;
                                if(count+1 > limit && limit > 0) return;
                                
                                if (document.createElement)
                                {
                                    var li = document.createElement("li");
                                    if (field == "override")
                                    {
                                        li.innerHTML = "<select name=\""+(field+(count+1))+"\" style=\"width:300px;\">]]..inputOptions..[[</select><a style=\"cursor:pointer;color:blue;\" onclick=\"]]..scriptControl..[[removeField(this, '"+field+"');\"> <img src=\"images/trash.png\" alt=\"Delete\" title=\"Delete\"/></a>";
                                    }
                                    else if (field == "fan")
                                    {
                                        if (varSpeed)
                                        {
                                            li.innerHTML = "<select class=\"analogOutput\" name=\""+(field+(count+1))+"_analogOutput\" style=\"width:300px;\">]]..analogOutputOptions..[[</select><a style=\"cursor:pointer;color:blue;\" onclick=\"]]..scriptControl..[[removeField(this, \'fan\');\"> <img src=\"images/trash.png\" alt=\"Delete\" title=\"Delete\"/></a>"
                                        }
                                        else
                                        {
                                            li.innerHTML = "<select class=\"output\" name=\""+(field+(count+1))+"_output\" style=\"width:300px;\">]]..outputOptions..[[</select><a style=\"cursor:pointer;color:blue;\" onclick=\"]]..scriptControl..[[removeField(this, \'fan\');\"> <img src=\"images/trash.png\" alt=\"Delete\" title=\"Delete\"/></a>"
                                        }
                                    }
                                    else if (field == "aircon")
                                    {
                                        
                                        // -------------------------------------------------------------------------------------------------------------
                                        // At this point, you may be questioning the life choices that lead to you having to look at this source code.
                                        // Ride on, you son of a gun.
                                        // -------------------------------------------------------------------------------------------------------------

                                        count = count / 3; // 3 Outputs per aircon
                                        li.style.marginBottom = "15px";
                                        li.id = "aircon_"+(count+1)+"_li";
                                        li.innerHTML = "<b>Aircon "+(count+1)+" :</b> <a style=\"cursor:pointer;color:blue;\" onclick=\"]]..scriptControl..[[removeField(this, '"+field+"');\"> <img src=\"images/trash.png\" alt=\"Delete\" title=\"Delete\"/></a><br>";
                                        li.innerHTML += "\
                                                <span style=\"float: left; width: 200px;\">Power output:</span>\
                                                <span>\
                                                    <select class=\"output\" name=\"aircon_"+(count+1)+"_power\" style=\"width: 300px;\" ]]..buttonControl..[[>\
]]..outputOptions..[[\
                                                    </select>\
                                                </span><br>\
                                                <span style=\"float: left; width: 200px;\">Failure output:</span>\
                                                <span>\
                                                    <select class=\"output\" name=\"aircon_"+(count+1)+"_fail\" style=\"width: 300px;\" ]]..buttonControl..[[>\
]]..outputOptions..[[\
                                                    </select>\
                                                </span><br>\
                                                <span style=\"float: left; width: 200px;\">Temperature probe:</span>\
                                                <select name=\"aircon_"+(count+1)+"_probe\" style=\"width: 300px;\" ]]..buttonControl..[[>\
]]..analogOptions..[[\
                                                </select>";
                                    }
                                    field_area.appendChild(li);
                                }
                            }
                            
                            var oldIndex;
                            function confirmFanChange()
                            {
                                var box = document.getElementById("fanType");
                                if (checkFans())
                                {
                                    if (!confirm("Changing between fan types will delete the current fan configuration.\n\nProceed with this change?"))
                                    {
                                        box.selectedIndex = oldIndex;
                                        return;
                                    }
                                }
                                clearDuplicates("output");
                                deleteFans();
                                if (varSpeed)
                                {
                                    varSpeed = false;
                                    addField("fan", 8);                                    
                                }
                                else
                                {
                                    varSpeed = true;
                                    addField("fan", 8);
                                }
                            }
                        
                        //-->
                        </script>    

                        <form action="ccd_edit.lua#saved" method="post" onsubmit="return validateForm(this);">
                            <span>
                                <label style="float: left; width: 250px;">Profile name:</label><input type="text" id="profilename" name="name" style="width: 300px;" ]]..inputControl..[[/ value="]]..(profile.name or "")..[[">
                            </span>
                            <BR><BR><HR>
                            <H3>Temperature settings:</H3>
                            <span>
                                <label style="float: left; width: 250px;">Ideal temperature:</label><input type="text" id="temperature" name="temperature" value="]]..(profile.temperature or "20.0")..[[" style="width: 50px;"]]..inputControl..[[/> °C
                            </span><BR>
                            <label style="float: left; width: 250px;">Range:</label>
                            <select name="range" ]]..buttonControl..[[ style="width: 50px;">
]]
rangeOptions = [[
                                <option value="1">± 1</option>
                                <option value="2">± 2</option>
                                <option value="3">± 3</option>
                                <option value="4">± 4</option>
                                <option value="5">± 5</option>
]]
if (profile.range ~= nil) then
    for option,value in rangeOptions:gmatch([[(<option value="(%d+)">.-</option>)]]) do
        if (value == profile.range) then
            rangeOptions = rangeOptions:gsub(option, option:gsub([[">]], [[" selected>]]))
        end
    end
end
content = content .. rangeOptions .. [[
                            </select> °C
                            <BR><BR><HR>
                            <H3>Delta cooling settings:</H3>
                            <label for="enableDelta">Enable delta cooling</label><input type="checkbox" id="enableDelta" name="enableDelta" onchange="]]..scriptControl..[[disableStuff('delta');"]]..(profile.enableDelta or "")..inputControl..[[/><BR><BR>
                            <div id="deltaDiv" style="display: none;">
                                <span>
                                    <label style="float: left; width: 350px;">Temperature difference (Ti - Te):</label><input type="text" maxlength="2" id="deltaT" name="deltaT" value="]]..(profile.deltaT or "")..[[" style="width: 50px;"]]..inputControl..[[/> °C
                                </span><BR>
                                <span>
                                    <label style="float: left; width: 350px;">Disable below temperature:</label><input type="text" maxlength="3" id="deltaMinimum" name="deltaMinimum" value="]]..(profile.deltaMinimum or "")..[[" style="width: 50px;"]]..inputControl..[[/> °C
                                </span><BR><BR>
                                <B>Notes:</B> 
                                <ul>
                                    <li>Delta cooling requires an external temperature probe and at least one DC fan.</li>
                                    <li>Unless an override is active, delta cooling <u>always</u> takes preference when its conditions are met.</li>
                                    <li>When the internal temperature exceeds the external temperature by (at least) the specified number, DC fans will be switched on at full power.</li>
                                    <li>Fans will remain on until the internal temperature cools to the specified minimum, or the external temperature reaches or exceeds the internal temperature.</li>
                                </ul>
                                <BR>
                            </div>
                            <HR>
                            <div id="analog">
                                <H3>Temperature probe inputs:</H3>
                                <label style="float: left; width: 250px;">Internal:</label>
                                <span>
                                    <select name="internalProbe" style="width: 300px;" ]]..buttonControl..[[>
]]                                    
if (profile.internal) then
    a = analogOptions:match([[(<option value=']]..escape_lua_pattern(profile.internal)..[['>.-</option>)]])
    if (a) then
        analogOptions = analogOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
    end
end            

content = content .. analogOptions
analogOptions = defaultAnalogOptions
content = content .. [[

                                    </select>
                                </span>
                                <BR>
                                <label style="float: left; width: 250px;">External:</label>
                                <span>
                                    <select name="externalProbe" id="externalProbe" style="width: 300px;" ]]..buttonControl..[[>
]]                                    
if (profile.external) then
    a = analogOptions:match([[(<option value=']]..escape_lua_pattern(profile.external)..[['>.-</option>)]])
    if (a) then
        analogOptions = analogOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
    end
end            

content = content .. analogOptions
analogOptions = defaultAnalogOptions
content = content .. [[

                                    </select>
                                </span>
                            </div>
                            <BR>
                            <HR>
                            <div id="inputs">
                                <H3>AC power monitor:</H3>
                                <label style="float: left; width: 250px;">Monitor input:</label>
                                <span>
                                    <select name="ACmonitor" style="width: 300px;" ]]..buttonControl..[[>
]]                                    
if (profile.ACmonitor) then
    a = inputOptions:match([[(<option value=']]..escape_lua_pattern(profile.ACmonitor)..[['>.-</option>)]])
    if (a) then
        inputOptions = inputOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
    end
end            
content = content .. inputOptions
inputOptions = defaultInputOptions
content = content .. [[

                                    </select>
                                </span><BR><BR>
                                <B>Note:</B> If an AC power monitor is installed, aircon cooling will take preference and DC fans will only be switched on when AC power loss is detected, or delta cooling is enabled.
                                <BR><BR><HR>
                                <H3>Override inputs:</H3>
                                <ol id="override_list">
]]                                
if (profile.overrides) and (#(profile.overrides) > 0) then
    for idx, override in pairs(profile.overrides) do
        content = content .. [[
                                    <li>
                                        <select name="override]]..idx..[[" style="width: 300px;" ]]..buttonControl..[[>
]]
        a = inputOptions:match([[(<option value=']]..escape_lua_pattern(override)..[['>.-</option>)]])
        if (a) then
            inputOptions = inputOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
        end

        content = content .. inputOptions
        content = content .. [[
                                        </select>
                                        <a style="cursor:pointer;color:blue;" onclick="]]..scriptControl..[[removeField(this, 'override');"> <img src="images/trash.png" alt="Delete" title="Delete"/></a>
                                    </li>
]]
        inputOptions = defaultInputOptions
    end
else
    content = content .. [[
                                    <li>
                                        <select name="override1" style="width: 300px;" ]]..buttonControl..[[>
]]..inputOptions..[[

                                        </select>
                                        <a style="cursor:pointer;color:blue;" onclick="]]..scriptControl..[[removeField(this, 'override');"> <img src="images/trash.png" alt="Delete" title="Delete"/></a>
                                    </li>
]]
end
content = content .. [[
                                </ol>
                                <input type="button" value="Add new override" onclick="addField('override', 10);" ]]..buttonControl..[[/>
                            </div>
                            <BR><HR>
                            <div id="outputs">
                                <H3>DC fans:</H3>
                                <label style="float: left; width: 250px;">Type of fans installed:</label>
                                <select id="fanType" onfocus="]]..scriptControl..[[oldIndex=this.selectedIndex;" onchange="]]..scriptControl..[[confirmFanChange();"]]..buttonControl..[[>
                                    <option>Single speed fans</option>
                                    <option]] if (profile.varSpeed == true) then content = content .. [[ selected]] end content = content .. [[>Variable speed fans</option>
                                </select><BR>
                                <ol id="fan_list">
]]

analogOutputOptions = defaultAnalogOutputOptions
outputOptions = defaultOutputOptions
if  (profile.fans) and (#(profile.fans) > 0) then
    for idx, fan in pairs(profile.fans) do
        if (profile.varSpeed) then
            content = content .. [[
                                    <li>
                                        <select class="analogOutput" name="fan]]..idx..[[_analogOutput" style="width:300px;"]]..buttonControl..[[>
]]
                    if (fan.analogOutput ~= nil) then
                        a = analogOutputOptions:match([[(<option value=']]..escape_lua_pattern(fan.analogOutput)..[['>.-</option>)]])
                        if (a) then
                            analogOutputOptions = analogOutputOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
                        end
                    end
                    content = content .. analogOutputOptions
                    analogOutputOptions = defaultAnalogOutputOptions                    
                    content = content .. [[
                                        </select><a style="cursor:pointer;color:blue;" onclick="]]..scriptControl..[[removeField(this, 'fan');"> <img src="images/trash.png" alt="Delete" title="Delete"/></a>
                                    </li>
]]                                        
        else
            content = content .. [[
                                    <li>
                                        <select class="output" name="fan]]..idx..[[_output" style="width:300px;"]]..buttonControl..[[>
]]
                    if (fan.output ~= nil) then
                        a = outputOptions:match([[(<option value=']]..escape_lua_pattern(fan.output)..[['>.-</option>)]])
                        if (a) then
                            outputOptions = outputOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
                        end
                    end
                    content = content .. outputOptions
                    outputOptions = defaultOutputOptions                    
                    content = content .. [[
                                        </select><a style="cursor:pointer;color:blue;" onclick="]]..scriptControl..[[removeField(this, 'fan');"> <img src="images/trash.png" alt="Delete" title="Delete"/></a>
                                    </li>
]]                                        
        end
    end
else
    content = content .. [[
                                    <li>
                                        <select class="output" name="fan1_output" style="width:300px;"]]..buttonControl..[[>]]..outputOptions..[[</select>
                                        <a style="cursor:pointer;color:blue;" onclick="]]..scriptControl..[[removeField(this, 'fan');"> <img src="images/trash.png" alt="Delete" title="Delete"/></a>
                                    </li>
]]
end
content = content .. [[
                                </ol>
                                <input type="button" value="Add new DC fan" onclick="addField('fan', 8);" ]]..buttonControl..[[/>
                                <BR>
                                <BR>
                                <HR>
                                <H3>Air Conditioning:</H3>
                                <label style="float: left; width: 250px;">Failure threshold time:</label><input type="text" id="threshold" name="threshold" value="]]..(profile.threshold or "10")..[[" style="width: 40px;"]]..inputControl..[[/> minutes
                                <br>
                                <ul id="aircon_list">
]]                                
if (profile.aircons) and (#(profile.aircons) > 0) then
    for idx, aircon in pairs(profile.aircons) do
        content = content .. [[
                                    <li style="margin-bottom: 15px;"> <b>Aircon ]]..idx..[[ :</b> <a style="cursor:pointer;color:blue;" onclick="]]..scriptControl..[[removeField(this, 'aircon');"> <img src="images/trash.png" alt="Delete" title="Delete"/></a> <br>
                                        <span style="float: left; width: 200px;">Power output:</span>
                                        <span>
                                            <select class="output" name="aircon_]]..idx..[[_power" style="width: 300px;" ]]..buttonControl..[[>
]]                                        
                    if (aircon.output ~= nil) then
                        a = outputOptions:match([[(<option value=']]..escape_lua_pattern(aircon.output)..[['>.-</option>)]])
                        if (a) then
                            outputOptions = outputOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
                        end
                    end
                    content = content .. outputOptions
                    outputOptions = defaultOutputOptions                    
                    content = content .. [[
                                            </select>
                                        </span><br>

                                        <span style="float: left; width: 200px;">Failure output:</span>
                                        <span>
                                            <select class="output" name="aircon_]]..idx..[[_fail" style="width: 300px;" ]]..buttonControl..[[>
]]                                        
                    if (aircon.failure ~= nil) then
                        a = outputOptions:match([[(<option value=']]..escape_lua_pattern(aircon.failure)..[['>.-</option>)]])
                        if (a) then
                            outputOptions = outputOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
                        end
                    end
                    content = content .. outputOptions
                    outputOptions = defaultOutputOptions                    
                    content = content .. [[
                                            </select>
                                        </span></<br>

                                        <span style="float: left; width: 200px;">Temperature probe:</span>
                                        <select name="aircon_]]..idx..[[_probe" style="width: 300px;" ]]..buttonControl..[[>
]]                                        
                    if (aircon.probe ~= nil) then
                        a = analogOptions:match([[(<option value=']]..escape_lua_pattern(aircon.probe)..[['>.-</option>)]])
                        if (a) then
                            analogOptions = analogOptions:gsub(escape_lua_pattern(a), a:gsub("'>", "' selected>"))
                        end
                    end
                    content = content .. analogOptions
                    analogOptions = defaultAnalogOptions                    
                    content = content .. [[
                                        </select>
                                    </li>
]]
    end
else
    content = content .. [[
                                    <li style="margin-bottom: 15px;"> <b>Aircon 1 :</b> <a style="cursor:pointer;color:blue;" onclick="]]..scriptControl..[[removeField(this, 'aircon');"> <img src="images/trash.png" alt="Delete" title="Delete"/></a> <br>

                                        <span style="float: left; width: 200px;">Power output:</span>
                                        <span>
                                            <select class="output" name="aircon_1_power" style="width: 300px;" ]]..buttonControl..[[>
]]..outputOptions..[[
                                            </select>
                                        </span></<br>

                                        <span style="float: left; width: 200px;">Failure output:</span>
                                        <span>
                                            <select class="output" name="aircon_1_fail" style="width: 300px;" ]]..buttonControl..[[>
]]..outputOptions..[[
                                            </select>
                                        </span></<br>

                                        <span style="float: left; width: 200px;">Temperature probe:</span>
                                        <select name="aircon_1_probe" style="width: 300px;" ]]..buttonControl..[[>
]]..analogOptions..[[
                                        </select>
                                    </li>
]]
end
content = content .. [[
                                </ul>
                                <input type="button" value="Add new aircon" onclick="addField('aircon', 12);" ]]..buttonControl..[[/>
                            </div>
                            <BR>
                            <HR>
                            <BR>
]]                            
if (saved == 1) then
    content = content .. [[
                            <b><a name="saved"><img src="images/ok.png"/> Changes saved.</b></a><br><br>
]]
end
content = content .. [[     
                            <input name="saveButton" value="Save" type="submit" ]]..buttonControl..[[ onclick="doValidate = 1;"/>
                            <input name="Cancel" value="Back to Profiles" type="button" onclick="location.href='ccd_profiles.lua';"]]..buttonControl..[[/>                            
                            <input type="hidden" name="filename" value="]]..(filename or "")..[["/>
]]
if (edit == 1) then
    content = content .. [[
                            <input name="deleteButton" value="Delete" type="submit" ]]..buttonControl..[[ onclick="return confirm('Are you sure you want to delete this profile?');"/>
]]
end
content = content .. [[
                        </form>
                    </div>
]]

if (edit == 1) then
    xml = xml:gsub("<","&lt;")
    xml = xml:gsub(">","&gt;")    
    content = content .. [[
                    <script>
                        disableStuff("delta");
                        function toggleVis(target)
                        {
                            if (target.style.display == "none")
                            {
                                target.style.display = "inline-block";
                            }
                            else
                            {
                                target.style.display = "none";
                            }
                            return false;
                        }
                    </script>
                    <br><img src="images/magnify.png"/> <a href="javascript:void(0)" onclick="toggleVis(document.getElementById('profile'));">View ]]..ccd_conf.profiles_path..filename..[[</a></a><br>
        
                    <div id="profile" style="display: none;">
                        <pre class="formLayoutfull">
]]..xml..[[
                        </pre>
                    </div>
]]
else
    content = content .. [[
                    <script>
                        disableStuff("delta");
                    </script>
]]
end

html = header
html = html..content
html = html..footer

print(html)

