bool ReadString(XmlAttributeCollection attrs, string name, out string value, ParseLog parseLog)
        {
            value = "";

            XmlAttribute attr = attrs[name];

            if (attr == null)
            {
                parseLog.AddError(String.Format("attribute {0} not found", name));
                return(false);
            }
            value = attr.Value;
            parseLog.Add(String.Format("{0}={1}", name, value));
            return(true);
        }
        public bool LoadConfig(string configFileName)
        {
            XmlDocument doc = new XmlDocument();

            try
            {
                doc.Load(configFileName);
            }
            catch
            {
                // File doesn't exist?
                return(true);
            }

            bool alwaysSaveLog = false;

            ParseLog parseLog = new ParseLog();

            XmlNodeList list = doc.GetElementsByTagName("fanControllers");

            foreach (XmlNode node in list)
            {
                XmlNode parent = node.ParentNode;
                if (parent != null && parent.Name == "configuration" && parent.ParentNode is XmlDocument)
                {
                    parseLog.Begin("fanControllers");

                    alwaysSaveLog = ReadOptionalString(node.Attributes, "alwaysSaveParsingLog", "no", parseLog) == "yes";

                    this.isLogEnabled = ReadOptionalString(node.Attributes, "enableLog", "no", parseLog) == "yes";

                    if (this.isLogEnabled)
                    {
                        log.TraceInformation("----------------- started -----------------");
                        log.TraceInformation("LoadConfig: loading {0}", configFileName);
                    }

                    foreach (XmlNode fcNode in node.ChildNodes)
                    {
                        if (fcNode.Name == "fanController")
                        {
                            parseLog.Begin("fanController");

                            XmlAttributeCollection fcAttrs = fcNode.Attributes;

                            if (!ReadString(fcAttrs, "controlledSensorId", out string controlledSensorId, parseLog) ||
                                !ReadInt(fcAttrs, "minTarget", out int minTarget, parseLog) ||
                                !ReadInt(fcAttrs, "maxTarget", out int maxTarget, parseLog) ||
                                !ReadInt(fcAttrs, "tickIntervalSeconds", out int tickIntervalSeconds, parseLog))
                            {
                                continue;
                            }

                            bool fcLogEnabled = ReadOptionalString(fcAttrs, "enableLog", "no", parseLog) == "yes";

                            SlowTargetController slowTargetController = null;

                            List <ControlSignal> signals = new List <ControlSignal>();

                            foreach (XmlNode signalNode in fcNode.ChildNodes)
                            {
                                if (signalNode.Name == "controlSignal")
                                {
                                    parseLog.Begin("controlSignal");

                                    XmlAttributeCollection signalAttrs = signalNode.Attributes;

                                    if (!ReadString(signalAttrs, "id", out string signalId, parseLog) ||
                                        !ReadInt(signalAttrs, "smaPeriod", out int smaPeriod, parseLog))
                                    {
                                        continue;
                                    }

                                    bool signalLogEnabled = ReadOptionalString(signalAttrs, "enableLog", "no", parseLog) == "yes";

                                    List <ControlSignal.Point> points = new List <ControlSignal.Point>();

                                    foreach (XmlNode pointNode in signalNode.ChildNodes)
                                    {
                                        if (pointNode.Name == "point")
                                        {
                                            parseLog.Begin("point");

                                            XmlAttributeCollection pointAttrs = pointNode.Attributes;

                                            if (ReadFloat(pointAttrs, "value", out float pvalue, parseLog) &&
                                                ReadFloat(pointAttrs, "target", out float ptarget, parseLog))
                                            {
                                                points.Add(new ControlSignal.Point {
                                                    value = pvalue, target = ptarget
                                                });
                                            }
                                            else
                                            {
                                                continue;
                                            }

                                            parseLog.End("point");
                                        }
                                    }

                                    if (points.Count == 0)
                                    {
                                        parseLog.AddError(@"Points are not defined (<point> elements)");
                                        continue;
                                    }
                                    else if (points.Count == 1)
                                    {
                                        parseLog.AddWarning(@"Only single point defined. Signal target will be fixed.");
                                        continue;
                                    }

                                    signals.Add(new ControlSignal(signalId, points, smaPeriod, signalLogEnabled));

                                    parseLog.End("controlSignal");
                                }
                                else if (signalNode.Name == "slowTargetController")
                                {
                                    parseLog.Begin("slowTargetController");

                                    if (slowTargetController != null)
                                    {
                                        parseLog.AddError("slowTargetController already defined for current fanController.");
                                        continue;
                                    }

                                    XmlAttributeCollection stcAttrs = signalNode.Attributes;

                                    if (ReadInt(stcAttrs, "smaPeriod", out int smaPeriod, parseLog) &&
                                        ReadInt(stcAttrs, "delayPeriod", out int delayPeriod, parseLog))
                                    {
                                        bool logEnabled = ReadOptionalString(stcAttrs, "enableLog", "no", parseLog) == "yes";
                                        slowTargetController = new SlowTargetController(smaPeriod, delayPeriod, minTarget, logEnabled);
                                    }

                                    parseLog.End("slowTargetController");
                                }
                            }

                            if (signals.Count == 0)
                            {
                                parseLog.AddError(@"Control signals are not defined (<controlSignal> elements).");
                                continue;
                            }
                            fanControllers.Add(new FanController(computer, controlledSensorId, signals,
                                                                 minTarget, maxTarget, tickIntervalSeconds, slowTargetController, fcLogEnabled));

                            parseLog.End("fanController");
                        }
                    }
                    parseLog.End("fanControllers");
                }
        string ReadOptionalString(XmlAttributeCollection attrs, string name, string defaultValue, ParseLog parseLog)
        {
            string value = defaultValue;

            XmlAttribute attr = attrs[name];

            if (attr == null)
            {
                return(defaultValue);
            }
            value = attr.Value;
            parseLog.Add(String.Format("{0}={1}", name, value));
            return(value);
        }