package org.v8LogScanner.logsCfg;

import org.v8LogScanner.commonly.Constants;
import org.v8LogScanner.commonly.ExcpReporting;
import org.v8LogScanner.commonly.fsys;
import org.v8LogScanner.logs.LogsOperations;
import org.v8LogScanner.rgx.RegExp;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.Provider;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * <p>Create a reactive object representation of the logcfg.xml that can be aplied to 1c enterprise platform</p>
 * <p>
 * <p>Note that LogBuilder requires installed 1c enterpise in order to read and write existing logcxfg.xml files
 *
 * @author R.I.P.real
 * @see <a href="https://github.com/ripreal/>repository with examples</a>
 */
public class LogBuilder implements Serializable{
    /**
     * It maintains structured <events> block of a cfg file
     */
    private Set<LogEvent> events = new HashSet<>();

    /**
     * It maintains structured <log> block of a cfg file
     */
    private List<LogLocation> locations = new ArrayList<>();

    /**
     * It maintains structured <property> block of a cfg file
     */
    private List<LogProperty> properties = new ArrayList<>();

    /**
     * It maintains structured <dump> tag of a cfg file
     */
    private LogDump dumps = new LogDump();

    /**
     * The file paths which logcfg.xml file save to
     */
    private List<Path> cfgPaths = null;

    /**
     * It keeps <plansql> tag
     */
    private boolean allowPLanSql = false;

    /**
     * enables recording infos about circular refs as propert event
     */
    private boolean scriptCircRefs = false;

    /**
     * Stores reactive xml-DOM representaion of a LOgBuilder object model
     */
    private String content = "";

    private final String DUMMY = "dummy";

    private final LogLocation DEFAULT_LOG_LOC = new LogLocation();

    // INTERFACE

    /**
     * If this constructor is used then logcfg.xml paths
     * will be taken from default 1c enterpise catalogs
     */
    public LogBuilder() {
        cfgPaths = LogsOperations.scanCfgPaths();
        List<String> v8Dirs = Constants.V8_Dirs();
        for (String dir : v8Dirs) {
            String defCfgDir = dir + "\\conf";
            if (Files.exists(Paths.get(dir))
                    && !cfgPaths.contains(Paths.get(defCfgDir + "\\" + LogConfig.LOG_CFG_NAME))){

                cfgPaths.add(Paths.get(defCfgDir + "\\" + LogConfig.LOG_CFG_NAME));
            }
        }
    }

    /**
     * Create new builder from desired logcfg.xml paths. Default 1c enterprise paths will be ignored
     *
     * @param cfgPaths
     */
    public LogBuilder(List<Path> cfgPaths) {
        this.cfgPaths = cfgPaths;
    }

    /**
     * Construct a logcfg.xml file from the object model and save it to specified
     * location (put in the conctructor or a setter)
     *
     * @return written file by the specified location
     */
    public File writeToXmlFile() throws IOException {
        File logFile = null;
        for (Path path : cfgPaths) {
            logFile = new File(path.toUri());
            fsys.writeInNewFile(content, path.toString());
        }
        return logFile;
    }

    public void deleteXmlFile() {
        File logFile = null;
        for (Path path : cfgPaths) {
            logFile = new File(path.toUri());
            fsys.deleteFile(logFile.getAbsolutePath());
        }
    }

    /**
     * Parse file located by cfg path specified as costrunctor parameter or in setter
     *
     * @return itself
     */
    public LogBuilder readCfgFile() {

        clearAll();

        for (Path path : cfgPaths) {
            File logFile = new File(path.toUri());
            if (!logFile.exists()) {
                //"file does not exists!"
                continue;
            }
            try {
                parseCfg((factory) -> {
                    try {
                        return factory.newDocumentBuilder().parse(logFile);
                    } catch (SAXException | IOException | ParserConfigurationException e) {
                        ExcpReporting.LogError(this, e);
                        return null;
                    }
                });
            } catch (CfgParsingError e) {
                ExcpReporting.LogError(this, e);
            }
        }
        return updateContent();
    }

    public LogBuilder readCfgFile(String input){
        readCfgFile(input, (info) -> {});
        return this;
    }

    public LogBuilder readCfgFile(String input, Consumer<String> info){
        try {
            parseCfg((factory) -> {
                try {
                    String fileName = fsys.createTempFile(input);
                    Document doc = factory.newDocumentBuilder().parse(fileName);
                    fsys.deleteFile(fileName);
                    return doc;
                } catch (SAXException | IOException | ParserConfigurationException e) {
                    info.accept(e.getMessage());
                    return null;
                }
            });
        } catch (CfgParsingError e) {
            info.accept(e.getMessage());
        }
        return updateContent();
    }

    /**
     * Contruct log file for analyzing all events logged by 1c
     *
     * @return prepared but not written text file with settings
     */
    public LogBuilder buildAllEvents() {
        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(LogEvent.LogEventComparisons.ne, RegExp.PropTypes.Event, "")
                .updateContent();
    }

    /**
     * Construct log file for analyzing dbmsql, dbpostgrs, db2, dboracle database events.
     * To analyse deadlocks and other sql errors additional excp event is added.
     * Note you need to add the location tag in on order to get *.log file working.
     *
     * @return prepared but not written text file with settings.
     */
    public LogBuilder buildSQlEvents() {
        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, LogConfig.DBMSQL)
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, LogConfig.DBPOSTGRS)
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, LogConfig.DB2)
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "sdbl")
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, LogConfig.DBORACLE)
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "excp")
                .updateContent();
    }

    /**
     * Construct the *.log file for analyzing excp events
     * Note you need to add the location tag in on order to get *.log file working.
     *
     * @return prepared but not written text file with settings
     */
    public LogBuilder buildExcpEvents() {
        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "excp")
                .updateContent();
    }

    /**
     * Construct the *.log file for analyzing long any events
     * Note you need to add the location tag in on order to get *.log file working.
     *
     * @return prepared but not written text file with settings
     */
    public LogBuilder buildLongEvents() {

        LogEvent longEv = new LogEvent();

        longEv.setProp(RegExp.PropTypes.Event);
        longEv.setVal(RegExp.PropTypes.Event, "");
        longEv.setComparison(RegExp.PropTypes.Event, LogEvent.LogEventComparisons.ne);

        longEv.setProp(RegExp.PropTypes.Duration);
        longEv.setVal(RegExp.PropTypes.Duration, "20000000");
        longEv.setComparison(RegExp.PropTypes.Duration, LogEvent.LogEventComparisons.ge);

        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(longEv)
                .updateContent();
    }

    /**
     * Construct the *.log file for analyzing managed locks put by 1c Enterprise
     * Note you need to add the location tag in on order to get *.log file working.
     *
     * @return prepared but not written text file with settings
     */
    public LogBuilder build1cLocksEvents() {
        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, RegExp.EventTypes.TLOCK.toString())
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, RegExp.EventTypes.TTIMEOUT.toString())
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, RegExp.EventTypes.TDEADLOCK.toString())
                .updateContent();
    }

    /**
     * Construct the *.log file for analyzing all DB MSQL database queries with the filter by user and infobase.
     * Note sql plan also included.
     * Note you need to add the location tag in on order to get *.log file working.
     *
     * @param user          - user descr field in 1c enterpise
     * @param infobase_name name of base in conection string
     * @return prepared but not written text file with settings
     */
    public LogBuilder buildInvestigateSQlQueries(String user, String infobase_name) {

        LogEvent event = new LogEvent();

        event.setProp(RegExp.PropTypes.Event);
        event.setVal(RegExp.PropTypes.Event, RegExp.EventTypes.DBMSSQL.toString());
        event.setComparison(RegExp.PropTypes.Event, LogEvent.LogEventComparisons.eq);

        if (infobase_name != null) {
            event.setProp(RegExp.PropTypes.ProcessName);
            event.setVal(RegExp.PropTypes.ProcessName, "%" + infobase_name + "%");
            event.setComparison(RegExp.PropTypes.ProcessName, LogEvent.LogEventComparisons.like);
        }
        if (user != null) {
            event.setProp(RegExp.PropTypes.Usr);
            event.setVal(RegExp.PropTypes.Usr, user);
            event.setComparison(RegExp.PropTypes.Usr, LogEvent.LogEventComparisons.eq);
        }

        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(event)
                .setPlanSql(true)
                .updateContent();
    }

    /**
     * Construct the *.log file for analyzing excp, conn, proc, admin, sesn, clstr events.
     * Note you need to add the location tag in on order to get *.log file working.
     *
     * @return prepared but not written text file with settings
     */
    public LogBuilder buildEverydayEvents() {

        RegExp.EventTypes[] events = {RegExp.EventTypes.SDBL, RegExp.EventTypes.DBMSSQL,
                RegExp.EventTypes.SDBL, RegExp.EventTypes.DBV8DBEng, RegExp.EventTypes.DBORACLE,
                RegExp.EventTypes.DBPOSTGRS};

        for(RegExp.EventTypes eventType : events) {
            LogEvent slowSql = new LogEvent();
            slowSql.setProp(RegExp.PropTypes.Event);
            slowSql.setComparison(RegExp.PropTypes.Event, LogEvent.LogEventComparisons.eq);
            slowSql.setVal(RegExp.PropTypes.Event, eventType.toString());

            slowSql.setProp(RegExp.PropTypes.Duration);
            slowSql.setComparison(RegExp.PropTypes.Duration, LogEvent.LogEventComparisons.gt);
            slowSql.setVal(RegExp.PropTypes.Duration, "10000");

            addEvent(slowSql);
        }

        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "excp")
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "excpcntx")
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "PROC")
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "ADMIN")
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "SESN")
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "QERR")
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "CLSTR")
                .setPlanSql(true)
                .updateContent();
    }

    /**
     * Construct the *.log file for analyzing sql deadlocks and timeouts locks occuring on sqml, oracle, db2, postgres db.
     * Note you need to add the location tag in on order to get *.log file working.
     *
     * @return prepared but not written text file with settings
     */
    public LogBuilder buildSqlDeadLocksEvents() {
        List<String> texts = org.v8LogScanner.logsCfg.LogConfig.sql_lock_texts();

        texts.forEach((sql_text) -> {
            LogEvent event = new LogEvent();
            event.setProp(RegExp.PropTypes.Event);
            event.setComparison(RegExp.PropTypes.Event, LogEvent.LogEventComparisons.eq);
            event.setVal(RegExp.PropTypes.Event, "excp");

            event.setProp(RegExp.PropTypes.Descr);
            event.setComparison(RegExp.PropTypes.Descr, LogEvent.LogEventComparisons.like);
            event.setVal(RegExp.PropTypes.Descr, "%" + sql_text + "%");
            addEvent(event);
        });
        return this;
    }

    /**
     * Construct the *.log file to filter any db msql events containg table name (objectId) specified by user
     * Note you need to add the location tag in on order to get *.log file working.
     *
     * @param objectId - id of the table name in msql database
     * @return prepared but not written text file with settings
     */
    public LogBuilder buildFindTableByObjectID(String objectId) {

        String[] dbNames = {LogConfig.DBMSQL, LogConfig.DBORACLE, LogConfig.DBPOSTGRS, LogConfig.DB2};

        for (String dbName : dbNames) {
            LogEvent event = new LogEvent();
            event.setProp(RegExp.PropTypes.Event);
            event.setComparison(RegExp.PropTypes.Event, LogEvent.LogEventComparisons.eq);
            event.setVal(RegExp.PropTypes.Event, dbName);

            event.setProp(RegExp.PropTypes.Sql);
            event.setComparison(RegExp.PropTypes.Sql, LogEvent.LogEventComparisons.like);
            event.setVal(RegExp.PropTypes.Sql, "%" + objectId + "%");

            addEvent(event);
        }

        return this
                .addLocLocation()
                .addLogProperty()
                .updateContent();
    }

    public LogBuilder buildSlowQueriesWithTABLE_SCAN() {
        LogEvent event = new LogEvent();

        event.setProp(RegExp.PropTypes.Event);
        event.setVal(RegExp.PropTypes.Event, RegExp.EventTypes.DBMSSQL.toString());
        event.setComparison(RegExp.PropTypes.Event, LogEvent.LogEventComparisons.eq);

        event.setProp(RegExp.PropTypes.Duration);
        event.setComparison(RegExp.PropTypes.Duration, LogEvent.LogEventComparisons.ge);
        event.setVal(RegExp.PropTypes.Duration, "3000000");

        event.setProp(RegExp.PropTypes.PlanSQLText);
        event.setComparison(RegExp.PropTypes.PlanSQLText, LogEvent.LogEventComparisons.like);
        event.setVal(RegExp.PropTypes.PlanSQLText, "%TABLE SCAN%");

        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(event)
                .setPlanSql(true)
                .updateContent();
    }

    public LogBuilder buildCircularRefsAndEXCP() {

        return this
                .addLocLocation()
                .addLogProperty()
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, LogConfig.SCRIPT_CIRC_REFS_TAG_NAME)
                .addEvent(LogEvent.LogEventComparisons.eq, RegExp.PropTypes.Event, "Excp")
                .setScriptcircrefs(true)
                .updateContent();
    }

    /**
     * add user defined log location inside <log> tag. Path is not checked.
     *
     * @param location - folder that is used to create logs
     * @param history  - amount of hours in which file will be stored on a disk.
     * @return itself
     */
    public LogBuilder addLocLocation(String location, String history) {
        locations.remove(DEFAULT_LOG_LOC);
        locations.add(new LogLocation(location, history));
        return this.addLogProperty().updateContent();
    }

    /**
     * Add default cfg location and also new <log> tag which is located </>on c:\\v8logs\. Path allowance is not checked.
     *
     * @return itself
     */
    public LogBuilder addLocLocation() {
        if (locations.size() == 0)
            locations.add(DEFAULT_LOG_LOC);
        return updateContent();
    }

    /**
     * add new <event> tag to the <log> with attributes defined by user.
     *
     * @param comparison tag comparison
     * @param event prop to add to event
     * @param val prop value to add to event
     * @return self
     */
    public LogBuilder addEvent(LogEvent.LogEventComparisons comparison, RegExp.PropTypes event, String val) {
        events.add(new LogEvent(comparison, event, val));
        return this
                .addLocLocation()
                .addLogProperty()
                .updateContent();
    }

    /**
     * @param event
     * @return
     */
    public LogBuilder addEvent(LogEvent event) {
        events.add(event);
        return this
                .addLocLocation()
                .addLogProperty()
                .updateContent();
    }

    public LogBuilder addEvent() {
        events.add(new LogEvent(LogEvent.LogEventComparisons.ne, RegExp.PropTypes.Event, ""));
        return this
                .addLocLocation()
                .addLogProperty()
                .updateContent();
    }

    public void removeLogEvent(LogEvent event) {
        events.remove(event);
        updateContent();
    }

    public LogBuilder setScriptcircrefs(boolean enableCheckScriptCircularRefs) {
        this.scriptCircRefs = enableCheckScriptCircularRefs;
        return this;
    }

    public boolean getScriptcircrefs() {
        return scriptCircRefs;
    }

    LogBuilder addLogProperty() {
        if (properties.size() == 0) {
            properties.add(new LogProperty());
        }
        return this
                .addLocLocation()
                .updateContent();
    }

    public Set<LogEvent> getLogEvents() {
        return events;
    }

    public LogBuilder setPlanSql(boolean allow) {
        allowPLanSql = allow;
        return this
                .updateContent();
    }

    public boolean getPlanSql() {
        return allowPLanSql;
    }

    public List<Path> getCfgPaths() {
        return cfgPaths;
    }

    public LogBuilder setCfgPaths(Path cfgPaths) {
        this.cfgPaths.clear();
        this.cfgPaths.add(cfgPaths);
        return this;
    }

    public List<String> getLocations() {
        List<String> res = new ArrayList<>();
        locations.forEach((item) -> res.add(item.getLocation()));
        return res;
    }

    public String getHistory() {
        return locations.size() == 0 ? "" : locations.get(0).getHistory();
    }

    public LogBuilder setHistory(int history) {
        locations.forEach((loc) ->  {
            loc.setHistory(history);
        });

        return this
            .updateContent();
    }

    public void clearAll() {
        events.clear();
        locations.clear();
        properties.clear();
        dumps = new LogDump();
        updateContent();
    }

    /**
     * Recieve a actual xml-DOM representation of a object model logcfg.xml
     *
     * @return xml-DOM of a logcfg.xml
     */
    public String getContent() {
        return content;
    }

    // PRIVATE

    private LogBuilder addLogProperty(String name) {
        LogProperty prop = new LogProperty(name);
        if (!properties.contains(prop))
            properties.add(prop);
        return this
                .addLocLocation()
                .updateContent();
    }

    private LogBuilder setCreateDumps(boolean create) {
        dumps.setCreate(create);
        return this.updateContent();
    }

    private LogBuilder setCreateDumps(String create) {
        setCreateDumps(Boolean.parseBoolean(create));
        return this.updateContent();
    }

    private LogBuilder updateContent() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        buildAndWrite(baos);
        String res = baos.toString();
        content = removeNodeElement(res.split("[\\r]"), "(?s).*" + DUMMY + ".*");

        return this;
    }

    /**
     * Construct a text of a logcfg.xml file from the object model and pass it to the stream
     *
     * @param stream - a stream for the output of the logcfg.xml content
     */
    private void buildAndWrite(OutputStream stream) {
        try {
            Transformer transformer = initTransformer();
            StreamResult streamresult = new StreamResult(stream);
            transformer.transform(buildXmlDoc(), streamresult);

        } catch (TransformerException | ParserConfigurationException e) {
            ExcpReporting.LogError(this, e);
        }
    }

    private Transformer initTransformer() throws TransformerConfigurationException {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        return transformer;
    }

    private DOMSource buildXmlDoc() throws ParserConfigurationException {
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        Document doc = docFactory.newDocumentBuilder().newDocument();

        // root elements
        Element root = doc.createElement(LogConfig.CONFIG_TAG_NAME);
        root.setAttribute(LogConfig.CONFIG_PROP_NAME, LogConfig.CONFIG_PROP_VAL);
        doc.appendChild(root);

        Element dumpEl = doc.createElement(LogConfig.DUMP_TAG_NAME);
        dumpEl.setAttribute(LogConfig.CREATE_NAME, dumps.getCreate());
        root.appendChild(dumpEl);

        Element logEl = doc.createElement(LogConfig.LOC_TAG_NAME);
        for (LogLocation loc : locations) {

            logEl.setAttribute(LogConfig.LOC_HISTORY_NAME, loc.getHistory());
            logEl.setAttribute(LogConfig.LOC_LOCATION_NAME, loc.getLocation());

            events.forEach((event) -> {
                Element eventEl = doc.createElement(LogConfig.EVENT_TAG_NAME);
                for (LogEvent.EventRow eventRow : event) {
                    Element eventPropEl = doc.createElement(eventRow.getComparison());
                    eventPropEl.setAttribute(LogConfig.PROP_NAME, eventRow.getKey());
                    eventPropEl.setAttribute(LogConfig.VAL_NAME, eventRow.getVal());
                    eventEl.appendChild(eventPropEl);
                }
                logEl.appendChild(eventEl);
            });

            properties.forEach((prop) -> {
                Element propEl = doc.createElement(LogConfig.PROP_NAME);
                propEl.setAttribute(LogConfig.PROPERTY_PROP_NAME, prop.getName());

                Element dummyEl = doc.createElement(DUMMY);
                propEl.appendChild(dummyEl);

                logEl.appendChild(propEl);
            });

            root.appendChild(logEl);
        }

        if (allowPLanSql) {
            Element planEl = doc.createElement(LogConfig.PLAN__SQL_TAG_NAME);
            root.appendChild(planEl);
        }
        if (scriptCircRefs) {
            Element scriptcircrefs = doc.createElement(LogConfig.SCRIPT_CIRC_REFS_TAG_NAME);
            root.appendChild(scriptcircrefs);
        }

        return new DOMSource(doc);
    }

    private String removeNodeElement(String[] text, String pattern) {

        List<String> textResult = new ArrayList<>(text.length);
        for(String textStroke : text) {
            if (!textStroke.matches(pattern))
                textResult.add(textStroke);
        }
        return String.join("", textResult);
    }

    private void parseCfg(Function<DocumentBuilderFactory, Document> docProvider) throws CfgParsingError {
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        try {
            Document doc = docProvider.apply(docFactory);
            if (doc == null || !doc.getDocumentElement().getTagName().matches(LogConfig.CONFIG_TAG_NAME))
                throw new CfgParsingError(String.format(
                        "Wrong file format. Root element must start with <%s> tag", LogConfig.CONFIG_TAG_NAME));

            parseLogEvents(doc.getFirstChild().getChildNodes());
        } catch (CfgParsingError e) {
            ExcpReporting.LogError(this, e);
        }
    }

    private void parseLogEvents(NodeList nodeList) throws CfgParsingError {

        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);

            String nodeName = node.getNodeName();
            if (nodeName.matches("(?i)" + LogConfig.DUMP_TAG_NAME)) {
                setCreateDumps(node.getAttributes().getNamedItem(LogConfig.CREATE_NAME).getNodeValue());
            } else if (nodeName.matches("(?i)" + LogConfig.LOC_TAG_NAME)) {
                NamedNodeMap attributes = node.getAttributes();
                addLocLocation(
                        attributes.getNamedItem(LogConfig.LOC_LOCATION_NAME).getNodeValue().toString(),
                        attributes.getNamedItem(LogConfig.LOC_HISTORY_NAME).getNodeValue().toString()
                );
                parseLogEvents(node.getChildNodes());

            } else if (nodeName.matches("(?i)" + LogConfig.EVENT_TAG_NAME)) {
                NodeList propList = node.getChildNodes();
                LogEvent event = new LogEvent();
                for (int j = 0; j < propList.getLength(); j++) {
                    Node propNode = propList.item(j);
                    NamedNodeMap attributes = propNode.getAttributes();
                    if (attributes != null) { // tag with non emty attribute means prop tag found
                        try {
                            RegExp.PropTypes propName = LogEvent.parseProp(attributes.getNamedItem(LogConfig.PROP_NAME).getNodeValue());
                            event.setProp(propName);
                            event.setVal(propName, attributes.getNamedItem(LogConfig.VAL_NAME).getNodeValue());
                            event.setComparison(propName, propNode.getNodeName());
                        } catch (RuntimeException e) {
                            throw new CfgParsingError("Error. Cannont parse one or more <event> attributes. Non-parseable attributes set by default"
                                    + "\njava exception: " + e.getMessage());
                        }
                    }
                }
                events.add(event);
            } else if (nodeName.matches("(?i)" + LogConfig.PROPERTY_TAG_NAME)) {
                addLogProperty(node.getAttributes().getNamedItem(LogConfig.PROPERTY_PROP_NAME).getNodeValue());
            } else if (nodeName.matches("(?i)" + LogConfig.PLAN__SQL_TAG_NAME)) {
                allowPLanSql = true;
            } else if (nodeName.matches("(?i)" + LogConfig.SCRIPT_CIRC_REFS_TAG_NAME)) {
                scriptCircRefs = true;
            }
        }
    }

}