class Logger

    require 'thread'

    LOG_DEPTH_INC_RESC = 1 # Depth increase of method scope for rescue
    LOG_DEPTH_INC_LOOP = 2 # Depth increase of method scope for loop

    def self.log_error(msg, depth=0)
        _methodname = getMethodname(depth)
        _message = "[#{_methodname}]:#{msg}"
        $log.error "[NPMD]:#{_message}"
    end
    def self.log_info(msg, depth=0)
        _methodname = getMethodname(depth)
        $log.info "[NPMD]:[#{_methodname}]:#{msg}"
    end
    def self.log_warn(msg, depth=0)
        _methodname = getMethodname(depth)
        $log.warn "[NPMD]:[#{_methodname}]:#{msg}"
    end

    class << self
        alias_method :logError, :log_error
        alias_method :logInfo,  :log_info
        alias_method :logWarn,  :log_warn
    end

    private

    def self.getMethodname(depth)
        _depth = depth > 0 ? depth : 0
        begin
            caller_locations(2 + _depth, 1)[0].label
        rescue
            caller_locations(2 + LOG_DEPTH_INC_RESC, 1)[0].label
        end
    end

    def self.loop
        LOG_DEPTH_INC_LOOP
    end

    def self.resc
        LOG_DEPTH_INC_RESC
    end
end

# Module to parse config received from DSC and generate Agent Configuration
module NPMDConfig

    require 'rexml/document'
    require 'json'
    require 'ipaddr'
    require 'socket'

    # Need to have method to get the subnetmask
    class ::IPAddr
        def getNetMaskString
            _to_string(@mask_addr)
        end
    end

    # This class holds the methods for creating
    # a config understood by NPMD Agent from a hash
    class AgentConfigCreator
        public

        # Variables for tracking errors
        @@agent_ip_drops = 0
        @@agent_drops = 0
        @@network_subnet_drops = 0
        @@network_drops = 0
        @@rule_subnetpair_drops = 0
        @@rule_drops = 0

        # Strings utilized in drop summary
        DROP_IPS        = "Agent IPs"
        DROP_AGENTS     = "Agents"
        DROP_SUBNETS    = "Network subnets"
        DROP_NETWORKS   = "Networks"
        DROP_SUBNETPAIRS= "Rule subnetpairs"
        DROP_RULES      = "Rules"

        # Reset error checking
        def self.resetErrorCheck
            @@agent_ip_drops = 0
            @@agent_drops = 0
            @@network_subnet_drops = 0
            @@network_drops = 0
            @@rule_subnetpair_drops = 0
            @@rule_drops = 0
        end

        # Generating the error string
        def self.getErrorSummary
            _agentIpDrops=""
            _agentDrops=""
            _networkSNDrops=""
            _networkDrops=""
            _ruleSNPairDrops=""
            _ruleDrops=""

            if @@agent_ip_drops != 0
                _agentIpDrops = "#{DROP_IPS}=#{@@agent_ip_drops}"
            end
            if @@agent_drops != 0
                _agentDrops= "#{DROP_AGENTS}=#{@@agent_drops}"
            end
            if @@network_subnet_drops != 0
                _networkSNDrops = "#{DROP_SUBNETS}=#{@@network_subnet_drops}"
            end
            if @@network_drops != 0
                _networkDrops = "#{DROP_NETWORKS}=#{@@network_drops}"
            end
            if @@rule_subnetpair_drops != 0
                _ruleSNPairDrops = "#{DROP_SUBNETPAIRS}=#{@@rule_subnetpair_drops}"
            end
            if @@rule_drops != 0
                _ruleDrops = "#{DROP_RULES}=#{@@rule_drops}"
            end
            _str =  _agentIpDrops + " " + _agentDrops + " " +
                    _networkSNDrops + " " + _networkDrops + " " +
                    _ruleSNPairDrops + " " + _ruleDrops
        end

        # Only accessible method
        def self.createJsonFromUIConfigHash(configHash)
            begin
                if configHash == nil
                    Logger::logError "Config received is NIL"
                    return nil
                end
                _subnetInfo = getProcessedSubnetHash(configHash["Subnets"])
                _doc = {"Configuration" => {}}
                _doc["Configuration"] ["Metadata"] = createMetadataElements(configHash["Metadata"])
                _doc["Configuration"] ["Agents"] = createAgentElements(configHash["Agents"], _subnetInfo["Masks"])
                _doc["Configuration"] ["Networks"] = createNetworkElements(configHash["Networks"], _subnetInfo["IDs"])
                _doc["Configuration"] ["Rules"] = createRuleElements(configHash["Rules"], _subnetInfo["IDs"]) unless !configHash.has_key?("Rules")
                _doc["Configuration"] ["Epm"] = createEpmElements(configHash["Epm"]) unless !configHash.has_key?("Epm")
                _doc["Configuration"] ["ConnectionMonitorConfiguration"] = configHash["ConnectionMonitorConfiguration"] unless !configHash.has_key?("ConnectionMonitorConfiguration")
                _doc["Configuration"] ["ER"] = createERElements(configHash["ER"]) unless !configHash.has_key?("ER")

                _configJson = _doc.to_json
                _configJson
            rescue StandardError => e
                Logger::logError "Got error creating JSON from UI Hash: #{e}", Logger::resc
                raise "Got error creating AgentJson: #{e}"
            end
        end

        private

        def self.getNetMask(ipaddrObj)
            _tempIp = IPAddr.new(ipaddrObj.getNetMaskString)
            _tempIp.to_s
        end

        def self.getProcessedSubnetHash(subnetHash)
            _h = Hash.new
            _h["Masks"] = Hash.new
            _h["IDs"] = Hash.new
            begin
                subnetHash.each do |key, value|
                    _tempIp = IPAddr.new(value)
                    _h["Masks"][key] = getNetMask(_tempIp)
                    _h["IDs"][key] = value
                end
                _h
            rescue StandardError => e
                Logger::logError "Got error while creating subnet hash: #{e}", Logger::resc
                nil
            end
        end

        def self.createMetadataElements(metadataHash)
            _metadata = Hash.new
            _metadata["Version"] = metadataHash.has_key?("Version") ? metadataHash["Version"] : String.new
            _metadata["Protocol"] = metadataHash.has_key?("Protocol") ? metadataHash["Protocol"] : String.new
            _metadata["SubnetUid"] = metadataHash.has_key?("SubnetUid") ? metadataHash["SubnetUid"] : String.new
            _metadata["AgentUid"] = metadataHash.has_key?("AgentUid") ? metadataHash["AgentUid"] : String.new
            _metadata[:"WorkspaceResourceId"] = metadataHash["WorkspaceResourceID"] if metadataHash.has_key?("WorkspaceResourceID")
            _metadata[:"WorkspaceId"] = metadataHash["WorkspaceID"] if metadataHash.has_key?("WorkspaceID")
            _metadata[:"LastUpdated"] = metadataHash["LastUpdated"] if metadataHash.has_key?("LastUpdated")
            return _metadata
        end

        def self.createAgentElements(agentArray, maskHash)
            _agents = Array.new
            agentArray.each do |x|
                _agent = Hash.new
                _agent["Name"] = x["Guid"];
                _agent["Capabilities"] = x["Capability"].to_s;
                _agent["ResourceId"] = x["ResourceId"].to_s;
                _agent["IPConfiguration"] = [];

                x["IPs"].each do |ip|
                    _ipConfig = Hash.new
                    _ipConfig["IP"] = ip["IP"];
                    _subnetMask = maskHash[ip["SubnetName"]];
                    if _subnetMask.nil?
                        Logger::logWarn "Did not find subnet mask for subnet name #{ip["SubnetName"]} in hash", 2*Logger::loop
                        @@agent_ip_drops += 1
                    else
                        _ipConfig["Mask"] = maskHash[ip["SubnetName"]];
                    end
                    _agent["IPConfiguration"].push(_ipConfig);
                end
                _agents.push(_agent);
                if _agents.empty?
                    @@agent_drops += 1
                end
            end
            _agents
        end

        def self.createNetworkElements(networkArray, subnetIdHash)
            _networks = Array.new
            networkArray.each do |x|
                _network = Hash.new
                _network["Name"] = x["Name"];
                _network["Subnet"] = Array.new
                x["Subnets"].each do |sn|
                    _subnet = Hash.new
                    _subnetId = subnetIdHash[sn]
                    if _subnetId.nil?
                        Logger::logWarn "Did not find subnet id for subnet name #{sn} in hash", 2*Logger::loop
                        @@network_subnet_drops += 1
                    else
                        _subnet["ID"] = subnetIdHash[sn];
                        _subnet["Disabled"]  = "False" # TODO
                        _subnet["Tag"]  = "" # TODO
                    end
                    _network["Subnet"].push(_subnet);
                end
                _networks.push(_network);
                if _networks.empty?
                    @@network_drops += 1
                end
            end
            _networks
        end

        def self.createActOnElements(elemArray, subnetIdHash)
            _networkTestMatrix = Array.new
            elemArray.each do |a|
                _sSubnetId = "*"
                _dSubnetId = "*"
                if a["SS"] != "*" and a["SS"] != ""
                    _sSubnetId = subnetIdHash[a["SS"].to_s]
                end
                if a["DS"] != "*" and a["DS"] != ""
                    _dSubnetId = subnetIdHash[a["DS"].to_s]
                end
                if _sSubnetId.nil?
                    Logger::logWarn "Did not find subnet id for source subnet name #{a["SS"].to_s} in hash", 2*Logger::loop
                    @@rule_subnetpair_drops += 1
                elsif _dSubnetId.nil?
                    Logger::logWarn "Did not find subnet id for destination subnet name #{a["DS"].to_s} in hash", 2*Logger::loop
                    @@rule_subnetpair_drops += 1
                else
                    # Process each subnetpair
                    _snPair = Hash.new
                    _snPair["SourceSubnet"] = _sSubnetId
                    _snPair["SourceNetwork"] = a["SN"]
                    _snPair["DestSubnet"] = _dSubnetId
                    _snPair["DestNetwork"] = a["DN"]
                    _networkTestMatrix.push(_snPair);
                end
            end
            _networkTestMatrix
        end

        def self.createRuleElements(ruleArray, subnetIdHash)
            _rules = Array.new
            ruleArray.each do |x|
                _rule = Hash.new
                _rule["Name"] = x["Name"];
                _rule["Description"] = x["Description"]
                _rule["Protocol"] = x["Protocol"].to_s;
                _rule["NetworkTestMatrix"] = createActOnElements(x["Rules"], subnetIdHash);
                _rule["AlertConfiguration"] = Hash.new;
                _rule["Exceptions"] = createActOnElements(x["Exceptions"], subnetIdHash);
                _rule["DiscoverPaths"] = x["DiscoverPaths"].to_s
                _rule["Port"] = x["Port"].to_s
                _rule["ListenOnPort"] = x["ListenOnPort"]
                _rule["BiDirectional"] = x["BiDirectional"]
                _rule["TestFrequencyInSec"] = x["Frequency"]
                _rule["CMResourceId"] = x.has_key?("CMResourceId") ? x["CMResourceId"] : String.new
                _rule["IngestionWorkspaceId"] = x.has_key?("IngestionWorkspaceId") ? x["IngestionWorkspaceId"] : String.new
                _rule["WorkspaceAlias"] = x.has_key?("WorkspaceAlias") ? x["WorkspaceAlias"] : String.new
                _rule["WorkspaceResourceId"] = x["WorkspaceResourceId"]

                if _rule["NetworkTestMatrix"].empty?
                    Logger::logWarn "Skipping rule #{x["Name"]} as network test matrix is empty", Logger::loop
                    @@rule_drops += 1
                else
                    # Alert Configuration
                    _rule["AlertConfiguration"]["ChecksFailedPercent"]  = x["LossThreshold"].to_s
                    _rule["AlertConfiguration"]["RoundTripTimeMs"]  = x["LatencyThreshold"].to_s
                end
                if !_rule.empty?
                    _rules.push(_rule)
                end
            end
            _rules
        end

        def self.createEpmElements(epmHash)
            return if epmHash.nil?
            _epmRules = Hash.new
            _rule = Array.new
            epmHash.each do |key, rules|
                for i in 0..rules.length-1
                    _ruleHash = Hash.new
                    _iRule = rules[i] # get individual rule
                    _ruleHash["ID"] = _iRule["ID"]
                    _ruleHash["Name"] = _iRule["Name"]
                    _ruleHash["CMResourceId"] = _iRule.has_key?("CMResourceId") ? _iRule["CMResourceId"] : String.new
                    _ruleHash["IngestionWorkspaceId"] = _iRule.has_key?("IngestionWorkspaceId") ? _iRule["IngestionWorkspaceId"] : String.new
                    _ruleHash["WorkspaceAlias"] = _iRule.has_key?("WorkspaceAlias") ? _iRule["WorkspaceAlias"] : String.new
                    _ruleHash["Redirect"] = "false"
                    _ruleHash["WorkspaceResourceId"] = _iRule["WorkspaceResourceId"]
                    _ruleHash["DiscoverPaths"] = _iRule.has_key?("DiscoverPaths") ? _iRule["DiscoverPaths"].to_s : "true"
                    _ruleHash["NetTests"] = (_iRule["NetworkThresholdLoss"].to_i >= -2 and _iRule["NetworkThresholdLatency"].to_i >= -2) ? "true" : "false"
                    _ruleHash["AppTests"] = (_iRule["AppThresholdLatency"].to_i >= -2) ? "true" : "false"
                    _ruleHash["ValidStatusCodeRanges"] = _iRule.has_key?("ValidStatusCodeRanges") ? _iRule["ValidStatusCodeRanges"] : nil;
                    _ruleHash["SourceAgentIds"] = _iRule["SourceAgentIds"]

                    if (_ruleHash["NetTests"] == "true")
                        _ruleHash["NetworkThreshold"] = {"ChecksFailedPercent" => _iRule["NetworkThresholdLoss"].to_s, "RoundTripTimeMs" => _iRule["NetworkThresholdLatency"].to_s}
                    end

                    if (_ruleHash["AppTests"] == "true")
                        _ruleHash["AppThreshold"] = {"ChecksFailedPercent" => (_iRule.has_key?("AppThresholdLoss") ? _iRule["AppThresholdLoss"].to_s : nil), "RoundTripTimeMs" => _iRule["AppThresholdLatency"].to_s}
                    end

                    # Fill endpoints
                    _epList = _iRule["Endpoints"]
                    _endpointList = Array.new
                    for j in 0.._epList.length-1
                        _epHash = Hash.new
                        _epHash["Name"] = _epList[j]["Name"]
                        _epHash["ID"] = _epList[j]["Id"]
                        _epHash["DestAddress"] = _epList[j]["URL"]
                        _epHash["DestPort"] = _epList[j]["Port"].to_s
                        _epHash["TestProtocol"] = _epList[j]["Protocol"]
                        _epHash["MonitoringInterval"] = _iRule["Poll"].to_s
                        _epHash["TimeDrift"] = _epList[j]["TimeDrift"].to_s
                        _epHash["Type"] = _epList[j]["Type"].to_s
                        _epHash["ListenOnPort"] = _epList[j]["ListenOnPort"]
                        _endpointList.push(_epHash)
                    end
                    _ruleHash["Endpoints"] = _endpointList
                    _rule.push(_ruleHash)
                end
            end
            _epmRules["Rules"] = _rule
            _epmRules
        end

        def self.createERElements(erHash)
            return if erHash.nil?
            _er = Hash.new
            erHash.each do |key, rules|
                # Fill Private Peering Rules
                if key == "PrivatePeeringRules"
                    _ruleList = Array.new
                    for i in 0..rules.length-1
                        _pvtRule = Hash.new
                        _iRule = rules[i]
                        _pvtRule["Name"] = _iRule["Name"]
                        _pvtRule["ConnectionResourceId"] = _iRule["ConnectionResourceId"]
                        _pvtRule["CircuitResourceId"] = _iRule["CircuitResourceId"]
                        _pvtRule["CircuitName"] = _iRule["CircuitName"]
                        _pvtRule["VirtualNetworkName"] = _iRule["vNetName"]
                        _pvtRule["Protocol"] = _iRule["Protocol"].to_s

                        #Thresholds
                        _thresholdMap = Hash.new
                        _thresholdMap["ChecksFailedPercent"] = _iRule["LossThreshold"].to_s
                        _thresholdMap["RoundTripTimeMs"] = _iRule["LatencyThreshold"].to_s
                        _pvtRule["AlertConfiguration"] = _thresholdMap

                        #OnPremAgents
                        _onPremAgents = Array.new
                        _onPremAgentList = _iRule["OnPremAgents"]
                        for j in 0.._onPremAgentList.length-1
                            _onPremAgents.push(_onPremAgentList[j])
                        end
                        _pvtRule["OnPremAgents"] = _onPremAgents

                        #AzureAgents
                        _azureAgents = Array.new
                        _azureAgentsList = _iRule["AzureAgents"]
                        for k in 0.._azureAgentsList.length-1
                            _azureAgents.push(_azureAgentsList[k])
                        end
                        _pvtRule["AzureAgents"] = _azureAgents
                        _ruleList.push(_pvtRule)
                    end
                    _er[:"PrivateRules"] = _ruleList if !_ruleList.empty?
                end

                # Fill MS Peering Rules
                if key == "MSPeeringRules"
                    _ruleList = Array.new
                    for i in 0..rules.length-1
                        _msRule = Hash.new
                        _iRule = rules[i]
                        _msRule["Name"] = _iRule["Name"]
                        _msRule["CircuitName"] = _iRule["CircuitName"]
                        _msRule["Protocol"] = _iRule["Protocol"].to_s
                        _msRule["CircuitResourceId"] = _iRule["CircuitResourceId"]

                        #Thresholds
                        _thresholdMap = Hash.new
                        _thresholdMap["ChecksFailedPercent"] = _iRule["LossThreshold"].to_s
                        _thresholdMap["RoundTripTimeMs"] = _iRule["LatencyThreshold"].to_s
                        _msRule["AlertConfiguration"] = _thresholdMap

                        #OnPremAgents
                        _onPremAgents = Array.new
                        _onPremAgentList = _iRule["OnPremAgents"]
                        for j in 0.._onPremAgentList.length-1
                            _onPremAgents.push(_onPremAgentList[j])
                        end
                        _msRule["OnPremAgents"] = _onPremAgents

                        #Urls
                        _urls = Array.new
                        _urlList = _iRule["UrlList"]
                        for k in 0.._urlList.length-1
                            _urlHash = Hash.new
                            _urlHash["Target"] = _urlList[k]["url"]
                            _urlHash["Port"] = _urlList[k]["port"].to_s
                            _urls.push(_urlHash)
                        end
                        _msRule["URLs"] = _urls
                        _ruleList.push(_msRule)
                    end
                    _er[:"MSPeeringRules"] = _ruleList if !_ruleList.empty?
                end
            end
            _er
        end

    end

    # This class holds the methods for parsing
    # a config sent via DSC into a hash
    class UIConfigParser
        public

        # Only accessible method
        def self.parse(string)
            begin
                _doc = REXML::Document.new(string)
                if _doc.elements.empty? or _doc.root.nil?
                    Logger::logWarn "UI config string converted to nil/empty rexml doc"
                    return nil
                end

                _configVersion = _doc.elements[RootConfigTag].attributes[Version].to_i
                unless _configVersion == 3
                    Logger::logWarn "Config version #{_configVersion} is not supported"
                    return nil
                else
                    Logger::logInfo "Supported version of config #{_configVersion} found"
                end

                _config = _doc.elements[RootConfigTag + "/" + SolnConfigV3Tag]

                if _config.nil? or _config.elements.empty?
                    Logger::logWarn "found nothing for path #{RootConfigTag}/#{SolnConfigV3Tag} in config string"
                    return nil
                end

                @agentData = JSON.parse(_config.elements[AgentInfoTag].text())
                @metadata = JSON.parse(_config.elements[MetadataTag].text())

                _h = Hash.new
                _h[KeyMetadata] = @metadata
                _h[KeyNetworks] = getNetworkHashFromJson(_config.elements[NetworkInfoTag].text())
                _h[KeySubnets]  = getSubnetHashFromJson(_config.elements[SubnetInfoTag].text())
                _h[KeyAgents]   = getAgentHashFromJson(_config.elements[AgentInfoTag].text())
                _h[KeyRules]    = getRuleHashFromJson(_config.elements[RuleInfoTag].text()) unless _config.elements[RuleInfoTag].nil?
                _h[KeyEpm]      = getEpmHashFromJson(_config.elements[EpmInfoTag].text()) unless _config.elements[EpmInfoTag].nil?
                _h[KeyCM]       = getCMHashFromJson(_config.elements[EpmInfoTag].text()) unless _config.elements[EpmInfoTag].nil?
                _h[KeyER]       = getERHashFromJson(_config.elements[ERInfoTag].text()) unless _config.elements[ERInfoTag].nil?
                _h = nil if (_h[KeyNetworks].nil? or _h[KeySubnets].nil? or _h[KeyAgents].nil?)
                if _h == nil
                    Logger::logError "UI Config parsed as nil"
                end
                return _h

            rescue REXML::ParseException => e
                Logger::logError "Got XML parse exception at #{e.line()}, #{e.position()}", Logger::resc
                raise "Got XML parse exception at #{e.line()}, #{e.position()}"
            end
            nil
        end

        private

        RootConfigTag           = "Configuration"
        SolnConfigV3Tag         = "NetworkMonitoringAgentConfigurationV3"
        MetadataTag             = "Metadata"
        NetworkInfoTag          = "NetworkNameToNetworkMap"
        SubnetInfoTag           = "SubnetIdToSubnetMap"
        AgentInfoTag            = "AgentFqdnToAgentMap"
        RuleInfoTag             = "RuleNameToRuleMap"
        EpmInfoTag              = "EPMConfiguration"
        EpmTestInfoTag          = "TestIdToTestMap"
        EpmCMInfoTag            = "ConnectionMonitorIdToInfoMap"
        EpmEndpointInfoTag      = "EndpointIdToEndpointMap"
        EpmAgentInfoTag         = "AgentIdToTestIdsMap"
        ERInfoTag               = "erConfiguration"
        ERPrivatePeeringInfoTag = "erPrivateTestIdToERTestMap";
        ERMSPeeringInfoTag      = "erMSTestIdToERTestMap";
        ERCircuitInfoTag        = "erCircuitIdToCircuitResourceIdMap";
        Version                 = "Version"
        KeyMetadata             = "Metadata"
        KeyNetworks             = "Networks"
        KeySubnets              = "Subnets"
        KeyAgents               = "Agents"
        KeyRules                = "Rules"
        KeyEpm                  = "Epm"
        KeyER                   = "ER"
        KeyCM                   = "ConnectionMonitorConfiguration"

        # Hash of {AgentID => {AgentContract}}
        @agentData = {}

        # Hash of Metadata
        @metadata = {}

        def self.getCurrentAgentId()
            begin
                _agentId = ""
                _ips = []
                addr_infos = Socket.getifaddrs
                addr_infos.each do |addr_info|
                    if addr_info.addr and (addr_info.addr.ipv4? or addr_info.addr.ipv6?)
                        _ips.push(addr_info.addr.ip_address)
                    end
                end

                @agentData.each do |key, value|
                    next if value.nil? or !(value["IPs"].is_a?Array)
                    value["IPs"].each do |ip|
                        for ipAddr in _ips
                            if ip["Value"] == ipAddr
                                _agentId = key
                                break
                            end
                        end
                    end
                end
                return _agentId
            end
        end

        def self.getNetworkHashFromJson(text)
            begin
                _h = JSON.parse(text)
                _a = Array.new
                _h.each do |key, value|
                    next if value.nil? or value["Subnets"].nil?
                    _network = Hash.new
                    _network["Name"] = key
                    _network["Subnets"] = value["Subnets"]
                    _a << _network
                end
                _a
            rescue JSON::ParserError => e
                Logger::logError "Error in Json Parse in network data: #{e}", Logger::resc
                nil
            end
        end

        def self.getSubnetHashFromJson(text)
            begin
                _h = JSON.parse(text)
            rescue JSON::ParserError => e
                Logger::logError "Error in Json Parse in subnet data: #{e}", Logger::resc
                nil
            end
        end

        def self.getAgentHashFromJson(text)
            begin
                _h = JSON.parse(text)
                _a = Array.new
                _h.each do |key, value|
                    next if value.nil? or !(value["IPs"].is_a?Array)
                    _agent = Hash.new
                    _agent["Guid"] = key
                    _agent["Capability"] = value["Protocol"] unless value["Protocol"].nil?
                    _agent["ResourceId"] = value["ResourceId"] unless value["ResourceId"].nil?
                    _agent["IPs"] = Array.new
                    value["IPs"].each do |ip|
                        _tempIp = Hash.new
                        _tempIp["IP"] = ip["Value"]
                        # Store agent subnet name as string
                        _tempIp["SubnetName"] = ip["Subnet"].to_s
                        _agent["IPs"] << _tempIp
                    end
                    _a << _agent
                end
                _a
            rescue JSON::ParserError => e
                Logger::logError "Error in Json Parse in agent data: #{e}", Logger::resc
                nil
            end
        end

        def self.getRuleHashFromJson(text)
            begin
                _h = JSON.parse(text)
                _a = Array.new
                _h.each do |key, value|
                    next if value.nil? or
                        !(value["ActOn"].is_a?Array) or
                        !(value["Exceptions"].is_a?Array)
                    _rule = Hash.new
                    _rule["Name"] = key
                    _rule["LossThreshold"] = (!value["Threshold"].nil? and value["Threshold"].has_key?("Loss")) ? value["Threshold"]["Loss"] : "-2"
                    _rule["LatencyThreshold"] = (!value["Threshold"].nil? and value["Threshold"].has_key?("Latency")) ? value["Threshold"]["Latency"] : "-2.0"
                    _rule["Protocol"] = value["Protocol"] unless value["Protocol"].nil?
                    _rule["Rules"] = value["ActOn"]
                    _rule["Exceptions"] = value["Exceptions"]
                    _rule["DiscoverPaths"] = value.has_key?("DiscoverPaths") ? value["DiscoverPaths"].to_s : "true"
                    _rule["Description"] = value["Description"]
                    _rule["Enabled"] = value["Enabled"]
                    _rule["Port"] = value["port"]
                    _rule["ListenOnPort"] = value["listenOnPort"]
                    _rule["BiDirectional"] = value["bidirectional"]
                    _rule["Frequency"] = value["frequency"]
                    _rule["WorkspaceResourceId"] = @metadata.has_key?("WorkspaceResourceID") ? @metadata["WorkspaceResourceID"] : String.new
                    _connectionMonitorId = value.has_key?("ConnectionMonitorId") ? value["ConnectionMonitorId"].to_s : String.new

                    # Iterate over ConnectionMonitorInfoMap to get following info
                    if !_connectionMonitorId.empty?
                        _cmMap = _h.has_key?(EpmCMInfoTag) ? _h[EpmCMInfoTag] : Hash.new
                        if !_cmMap.empty?
                            _cmId = _cmMap[_connectionMonitorId.to_s]
                            _rule["CMResourceId"] = _cmId["resourceId"]
                            _rule["IngestionWorkspaceId"] = _cmId["ingestionWorkspaceId"]
                            _rule["WorkspaceAlias"] = _cmId["workspaceAlias"]
                        end
                    end

                    _a << _rule
                end
                _a
            rescue JSON::ParserError => e
                Logger::logError "Error in Json Parse in rule data: #{e}", Logger::resc
                nil
            end
        end

        def self.getCMHashFromJson(text)
            begin
                _h = JSON.parse(text)
                _cmConfiguration = _h[KeyCM]
            rescue JSON::ParserError => e
                Logger::logError "Error in Json Parse in ConnectionMonitorConfiguration data: #{e}", Logger::resc
                nil
            end
        end

        def self.getEpmHashFromJson(text)
            begin
                _h = JSON.parse(text)
                _epmRules = {"Rules" => []}
                _testAgentMap = getTestAgents(_h[EpmAgentInfoTag])
                _testIds = _h[EpmTestInfoTag]
                _testIds.each_key do |testId|
                    _test = _h[EpmTestInfoTag][testId]
                    _rule = Hash.new
                    _rule["ID"] = testId
                    _rule["Name"] = _test["Name"]
                    _rule["Poll"] = _test["Poll"]
                    _rule["WorkspaceResourceId"] = @metadata.has_key?("WorkspaceResourceID") ? @metadata["WorkspaceResourceID"] : String.new
                    _rule["DiscoverPaths"] = _test.has_key?("DiscoverPaths") ? _test["DiscoverPaths"].to_s : "true"
                    _rule["AppThresholdLoss"] = _test["AppThreshold"].nil? ? "-3" : (_test["AppThreshold"].has_key?("Loss") ? _test["AppThreshold"]["Loss"] : "-2")
                    _rule["AppThresholdLatency"] = _test["AppThreshold"].nil? ? "-3.0" : (_test["AppThreshold"].has_key?("Latency") ? _test["AppThreshold"]["Latency"] : "-2.0")
                    _rule["NetworkThresholdLoss"] = _test["NetworkThreshold"].nil? ? "-3" : (_test["NetworkThreshold"].has_key?("Loss") ? _test["NetworkThreshold"]["Loss"] : "-2")
                    _rule["NetworkThresholdLatency"] = _test["NetworkThreshold"].nil? ? "-3.0" : (_test["NetworkThreshold"].has_key?("Latency") ? _test["NetworkThreshold"]["Latency"] : "-2.0")
                    _rule["ValidStatusCodeRanges"] = _test.has_key?("ValidStatusCodeRanges") ? _test["ValidStatusCodeRanges"] : nil
                    _rule["SourceAgentIds"] = _testAgentMap[testId]
                    _connectionMonitorId = _test.has_key?("ConnectionMonitorId") ? _test["ConnectionMonitorId"].to_s : String.new

                    # Iterate over ConnectionMonitorInfoMap to get following info
                    if !_connectionMonitorId.empty?
                        _cmMap = _h.has_key?(EpmCMInfoTag) ? _h[EpmCMInfoTag] : Hash.new
                        if !_cmMap.empty?
                            _cmId = _cmMap[_connectionMonitorId.to_s]
                            _rule["CMResourceId"] = _cmId["resourceId"]
                            _rule["IngestionWorkspaceId"] = _cmId["ingestionWorkspaceId"]
                            _rule["WorkspaceAlias"] = _cmId["workspaceAlias"]
                        end
                    end

                    # Collect endpoints details
                    _rule["Endpoints"] = []

                    # Get the list of endpoint ids
                    _endpoints = _test["Endpoints"]
                    _endpoints.each do |ep|
                        _endpointHash = Hash.new
                        _endpoint = _h[EpmEndpointInfoTag][ep]
                        _endpointHash["Id"] = ep
                        _endpointHash["Name"] = _endpoint.has_key?("name") ? _endpoint["name"] : String.new
                        _endpointHash["URL"] = _endpoint["url"]
                        _endpointHash["Port"] = _endpoint["port"]
                        _endpointHash["Protocol"] = _endpoint["protocol"]
                        _endpointHash["ListenOnPort"] = _endpoint.has_key?("listenOnPort") ? _endpoint["listenOnPort"] : String.new
                        _endpointHash["Type"] = _endpoint.has_key?("type") ? _endpoint["type"] : String.new
                        _endpointHash["TimeDrift"] = getEndpointTimedrift(testId, ep, _test["Poll"], getWorkspaceId()) #TODO
                        _rule["Endpoints"].push(_endpointHash)
                    end
                    _epmRules["Rules"].push(_rule)
                end

                                _epmRules
            rescue JSON::ParserError => e
                Logger::logError "Error in Json Parse in EPM data: #{e}", Logger::resc
                raise "Got exception in EPM parsing: #{e}"
                nil
            end
        end

        def self.getTestAgents(agentTestIdMap)
            begin
                h = agentTestIdMap.each_with_object({}) { |(k,v),g| (v.each{ |item| g.has_key?(item) ? g[item].push(k.to_i) : g[item] ||= [] << k.to_i}) }
                return h
            end
        end

        def self.getWorkspaceId()
            begin
                _workspaceId = @metadata.has_key?("WorkspaceID") ? @metadata["WorkspaceID"] : String.new
                return _workspaceId
            end
        end

        def self.getEndpointTimedrift(testId, endpointId, monitoringInterval, workspaceId)
            begin
                hashString = testId + endpointId + workspaceId
                monIntervalInSecs = monitoringInterval * 60
                hashCode = getHashCode(hashString)
                timeDrift = hashCode % monIntervalInSecs
                return timeDrift.to_s
            end
        end

        def self.getHashCode(str)
            result = 0
            mul = 1
            max_mod = 2**31 - 1

            str.chars.reverse_each do |c|
              result += mul * c.ord
              result %= max_mod
              mul *= 31
            end
            result
        end

        def self.getERHashFromJson(text)
            begin
                _h = JSON.parse(text)
                _agentId = getCurrentAgentId()

                if _agentId.empty?
                    return nil
                else
                    _erRules = {"PrivatePeeringRules" => [], "MSPeeringRules" => []}
                    # Iterate over OnPrem and Azure Agent Lists to check if this agent is part of this test
                    _privateTestMap = _h[ERPrivatePeeringInfoTag]
                    _microsoftTestMap = _h[ERMSPeeringInfoTag]
                    _circuitIdMap = _h[ERCircuitInfoTag]

                    if _privateTestMap.empty? && _microsoftTestMap.empty?
                        Logger::logInfo "ER configuration is not present"
                    end

                    # Private Peering Rules
                    if !_privateTestMap.empty?
                        _privateTestMap.each do |key, value|
                            # Get list of onPremAgents in this test
                            _isAgentPresent = false
                            _privateRule = Hash.new
                            _onPremAgents = value["onPremAgents"]
                            _onPremAgents.each do |x|
                                if x == _agentId
                                    # Append this test to ER Config
                                    _isAgentPresent = true
                                    _privateRule = getERPrivateRuleFromUIConfig(key, value, _circuitIdMap)
                                    break;
                                end
                            end
                            if !_isAgentPresent
                                _azureAgents = value["azureAgents"]
                                _azureAgents.each do |x|
                                    if x == _agentId
                                        _isAgentPresent = true
                                        _privateRule = getERPrivateRuleFromUIConfig(key, value, _circuitIdMap)
                                        break;
                                    end
                                end
                            end
                            if !_privateRule.empty?
                                _erRules["PrivatePeeringRules"].push(_privateRule)
                            end
                        end
                    end

                    # MS Peering Rules
                    if !_microsoftTestMap.empty?
                        _microsoftTestMap.each do |key, value|
                            _microsoftRule = Hash.new
                            _onPremAgents = value["onPremAgents"]
                            _onPremAgents.each do |x|
                                if x == _agentId
                                    # Append this test to ER Config
                                    _isAgentPresent = true
                                    _microsoftRule = getERMicrosoftRuleFromUIConfig(key, value, _circuitIdMap)
                                    break;
                                end
                            end
                            if !_microsoftRule.empty?
                                _erRules["MSPeeringRules"].push(_microsoftRule)
                            end
                        end
                    end
                    _erRules
                end
            rescue JSON::ParserError => e
                Logger::logError "Error in Json Parse in ER data: #{e}", Logger::resc
                nil
            end
        end

        def self.getERPrivateRuleFromUIConfig(key, value, _circuitIdMap)
            _ruleHash = Hash.new
            _ruleHash["Name"] = key
            _ruleHash["Protocol"] = value["protocol"]
            _ruleHash["CircuitId"] = value["circuitId"]
            _ruleHash["LossThreshold"] = (!value["threshold"].nil? and value["threshold"].has_key?("loss")) ? value["threshold"]["loss"] : "-2"
            _ruleHash["LatencyThreshold"] = (!value["threshold"].nil? and value["threshold"].has_key?("latency")) ? value["threshold"]["latency"] : "-2.0"
            _ruleHash["CircuitName"] = value["circuitName"]
            _ruleHash["vNetName"]= value["vNet"]
            _ruleHash["ConnectionResourceId"]= value["connectionResourceId"]
            _ruleHash["CircuitResourceId"] = _circuitIdMap[value["circuitId"]]
            _ruleHash["OnPremAgents"] = value["onPremAgents"]
            _ruleHash["AzureAgents"] = value["azureAgents"]
            return _ruleHash
        end

        def self.getERMicrosoftRuleFromUIConfig(key, value, _circuitIdMap)
            _ruleHash = Hash.new
            _ruleHash["Name"] = key
            _ruleHash["CircuitName"] = value["circuitName"]
            _ruleHash["CircuitId"] = value["circuitId"]
            _ruleHash["Protocol"] = value["protocol"]
            _ruleHash["CircuitResourceId"] = _circuitIdMap[value["circuitId"]]
            _ruleHash["LossThreshold"] = (!value["threshold"].nil? and value["threshold"].has_key?("loss")) ? value["threshold"]["loss"] : "-2"
            _ruleHash["LatencyThreshold"] = (!value["threshold"].nil? and value["threshold"].has_key?("latency")) ? value["threshold"]["latency"] : "-2.0"
            _ruleHash["UrlList"] = value["urlList"]
            _ruleHash["OnPremAgents"] = value["onPremAgents"]
            return _ruleHash
        end
    end

    # Only function needed to be called from this module
    def self.GetAgentConfigFromUIConfig(uiXml)
        _uiHash = UIConfigParser.parse(uiXml)
        AgentConfigCreator.resetErrorCheck()
        _agentJson = AgentConfigCreator.createJsonFromUIConfigHash(_uiHash)
        _errorStr = AgentConfigCreator.getErrorSummary()
        return _agentJson, _errorStr
    end

end

# NPM Contracts verification for data being uploaded
module NPMContract
    DATAITEM_AGENT                    = "agent"
    DATAITEM_PATH                     = "path"
    DATAITEM_DIAG                     = "diagnostics"
    DATAITEM_ENDPOINT_HEALTH          = "endpointHealth"
    DATAITEM_ENDPOINT_MONITORING      = "endpointMonitoringData"
    DATAITEM_ENDPOINT_DIAGNOSTICS     = "endpointDiagnostics"
    DATAITEM_EXROUTE_MONITORING       = "expressrouteMonitoringData"
    DATAITEM_CONNECTIONMONITOR_TEST   = "connectionMonitorTestResult"
    DATAITEM_CONNECTIONMONITOR_PATH   = "connectionMonitorPathData"
    DATAITEM_AGENT_DIAGNOSTICS        = "agentDiagnostics"


    DATAITEM_VALID = 1
    DATAITEM_ERR_INVALID_FIELDS = 2
    DATAITEM_ERR_INVALID_TYPE = 3

    CONTRACT_AGENT_DATA_KEYS = ["AgentFqdn",
                                "AgentIP",
                                "AgentCapability",
                                "SubnetId",
                                "PrefixLength",
                                "AddressType",
                                "SubType",
                                "TimeGenerated",
                                "OSType",
                                "NPMAgentEnvironment"]

    CONTRACT_AGENT_DIAGNOSTICS_KEYS = ["SubType",
                                       "TimeGenerated",
                                       "NotificationCode",
                                       "NotificationType",
                                       "Computer"]

    CONTRACT_PATH_DATA_KEYS  = ["SourceNetwork",
                                "SourceNetworkNodeInterface",
                                "SourceSubNetwork",
                                "DestinationNetwork",
                                "DestinationNetworkNodeInterface",
                                "DestinationSubNetwork",
                                "RuleName",
                                "TimeSinceActive",
                                "LossThreshold",
                                "LatencyThreshold",
                                "LossThresholdMode",
                                "LatencyThresholdMode",
                                "SubType",
                                "HighLatency",
                                "MedianLatency",
                                "LowLatency",
                                "LatencyHealthState",
                                "Loss",
                                "LossHealthState",
                                "Path",
                                "Computer",
                                "TimeGenerated",
                                "Protocol",
                                "MinHopLatencyList",
                                "MaxHopLatencyList",
                                "AvgHopLatencyList",
                                "TraceRouteCompletionTime"]

    CONTRACT_DIAG_DATA_KEYS  = ["SubType",
                                "Message"]

    CONTRACT_ENDPOINT_HEALTH_DATA_KEYS  =  ["SubType",
                                            "TestName",
                                            "ServiceTestId",
                                            "ConnectionMonitorResourceId",
                                            "Target",
                                            "Port",
                                            "EndpointId",
                                            "Protocol",
                                            "TimeSinceActive",
                                            "ServiceResponseTime",
                                            "ServiceLossPercent",
                                            "ServiceLossHealthState",
                                            "ServiceResponseHealthState",
                                            "ResponseCodeHealthState",
                                            "ServiceResponseThresholdMode",
                                            "ServiceResponseThreshold",
                                            "ServiceResponseCode",
                                            "Loss",
                                            "LossHealthState",
                                            "LossThresholdMode",
                                            "LossThreshold",
                                            "NetworkTestEnabled",
                                            "MedianLatency",
                                            "HighLatency",
                                            "LowLatency",
                                            "LatencyThresholdMode",
                                            "LatencyThreshold",
                                            "LatencyHealthState",
                                            "TimeGenerated",
                                            "Computer"]

    CONTRACT_ENDPOINT_PATH_DATA_KEYS = ["SubType",
                                        "TestName",
                                        "ServiceTestId",
                                        "ConnectionMonitorResourceId",
                                        "Target",
                                        "Port",
                                        "TimeSinceActive",
                                        "EndpointId",
                                        "SourceNetworkNodeInterface",
                                        "DestinationNetworkNodeInterface",
                                        "Path",
                                        "Loss",
                                        "NetworkTestEnabled",
                                        "HighLatency",
                                        "MedianLatency",
                                        "LowLatency",
                                        "LossHealthState",
                                        "LatencyHealthState",
                                        "LossThresholdMode",
                                        "LossThreshold",
                                        "LatencyThresholdMode",
                                        "LatencyThreshold",
                                        "Computer",
                                        "Protocol",
                                        "MinHopLatencyList",
                                        "MaxHopLatencyList",
                                        "AvgHopLatencyList",
                                        "TraceRouteCompletionTime",
                                        "TimeGenerated"]

    CONTRACT_ENDPOINT_DIAG_DATA_KEYS = ["SubType",
                                        "TestName",
                                        "ServiceTestId",
                                        "ConnectionMonitorResourceId",
                                        "Target",
                                        "NotificationCode",
                                        "EndpointId",
                                        "TimeGenerated",
                                        "Computer"]

    CONTRACT_EXROUTE_MONITOR_DATA_KEYS =   ["SubType",
                                            "TimeGenerated",
                                            "Circuit",
                                            "ComputerEnvironment",
                                            "vNet",
                                            "Target",
                                            "PeeringType",
                                            "CircuitResourceId",
                                            "ConnectionResourceId",
                                            "Path",
                                            "SourceNetworkNodeInterface",
                                            "DestinationNetworkNodeInterface",
                                            "Loss",
                                            "HighLatency",
                                            "MedianLatency",
                                            "LowLatency",
                                            "LossHealthState",
                                            "LatencyHealthState",
                                            "RuleName",
                                            "TimeSinceActive",
                                            "LossThreshold",
                                            "LatencyThreshold",
                                            "LossThresholdMode",
                                            "LatencyThresholdMode",
                                            "Computer",
                                            "Protocol",
                                            "MinHopLatencyList",
                                            "MaxHopLatencyList",
                                            "AvgHopLatencyList",
                                            "TraceRouteCompletionTime",
                                            "DiagnosticHop",
                                            "DiagnosticHopLatency"]

    CONTRACT_CONNECTIONMONITOR_TEST_RESULT_KEYS =  ["SubType",
                                                    "RecordId",
                                                    "Computer",
                                                    "ConnectionMonitorResourceId",
                                                    "TimeCreated",
                                                    "TestGroupName",
                                                    "TestConfigurationName",
                                                    "SourceType",
                                                    "SourceResourceId",
                                                    "SourceAddress",
                                                    "SourceName",
                                                    "SourceIP",
                                                    "SourceSubnet",
                                                    "DestinationType",
                                                    "DestinationResourceId",
                                                    "DestinationAddress",
                                                    "DestinationName",
                                                    "DestinationAgentId",
                                                    "Protocol",
                                                    "DestinationPort",
                                                    "DestinationIP",
                                                    "DestinationSubnet",
                                                    "ChecksTotal",
                                                    "ChecksFailed",
                                                    "ChecksFailedPercentThreshold",
                                                    "RoundTripTimeMsThreshold",
                                                    "MinRoundTripTimeMs",
                                                    "MaxRoundTripTimeMs",
                                                    "AvgRoundTripTimeMs",
                                                    "JitterMs",
                                                    "TestResult",
                                                    "TestResultCriterion",
                                                    "AdditionalData",
                                                    "IngestionWorkspaceResourceId"]

    CONTRACT_CONNECTIONMONITOR_PATH_DATA_KEYS =    ["SubType",
                                                    "RecordId",
                                                    "Computer",
                                                    "TopologyId",
                                                    "ConnectionMonitorResourceId",
                                                    "TimeCreated",
                                                    "TestGroupName",
                                                    "TestConfigurationName",
                                                    "SourceType",
                                                    "SourceResourceId",
                                                    "SourceAddress",
                                                    "SourceName",
                                                    "SourceIP",
                                                    "SourceSubnet",
                                                    "DestinationType",
                                                    "DestinationResourceId",
                                                    "DestinationAddress",
                                                    "DestinationName",
                                                    "DestinationIP",
                                                    "DestinationSubnet",
                                                    "DestinationAgentId",
                                                    "ChecksTotal",
                                                    "ChecksFailed",
                                                    "ChecksFailedPercentThreshold",
                                                    "RoundTripTimeMsThreshold",
                                                    "MinRoundTripTimeMs",
                                                    "MaxRoundTripTimeMs",
                                                    "AvgRoundTripTimeMs",
                                                    "JitterMs",
                                                    "HopAddresses",
                                                    "HopTypes",
                                                    "HopLinkTypes",
                                                    "HopResourceIds",
                                                    "Issues",
                                                    "Hops",
                                                    "DestinationPort",
                                                    "Protocol",
                                                    "PathTestResult",
                                                    "PathTestResultCriterion",
                                                    "AdditionalData",
                                                    "IngestionWorkspaceResourceId",
                                                    "UploadDirectly"]

    def self.IsValidDataitem(item, itemType)
        _contract=[]

        if itemType == DATAITEM_AGENT
            _contract = CONTRACT_AGENT_DATA_KEYS
        elsif itemType == DATAITEM_PATH
            _contract = CONTRACT_PATH_DATA_KEYS
        elsif itemType == DATAITEM_DIAG
            _contract = CONTRACT_DIAG_DATA_KEYS
        elsif itemType == DATAITEM_ENDPOINT_HEALTH
            _contract = CONTRACT_ENDPOINT_HEALTH_DATA_KEYS
        elsif itemType == DATAITEM_ENDPOINT_MONITORING
            _contract = CONTRACT_ENDPOINT_PATH_DATA_KEYS
        elsif itemType == DATAITEM_ENDPOINT_DIAGNOSTICS
            _contract = CONTRACT_ENDPOINT_DIAG_DATA_KEYS
        elsif itemType == DATAITEM_EXROUTE_MONITORING
            _contract = CONTRACT_EXROUTE_MONITOR_DATA_KEYS
        elsif itemType == DATAITEM_CONNECTIONMONITOR_TEST
            _contract = CONTRACT_CONNECTIONMONITOR_TEST_RESULT_KEYS
        elsif itemType == DATAITEM_CONNECTIONMONITOR_PATH
            _contract = CONTRACT_CONNECTIONMONITOR_PATH_DATA_KEYS
        elsif itemType == DATAITEM_AGENT_DIAGNOSTICS
            _contract = CONTRACT_AGENT_DIAGNOSTICS_KEYS
        end

        return DATAITEM_ERR_INVALID_TYPE, nil if _contract.empty?

        item.keys.each do |k|
            return DATAITEM_ERR_INVALID_FIELDS, k if !_contract.include?(k)
        end

        return DATAITEM_VALID, nil
    end

end

