import dataclasses
import functools
from copy import deepcopy

import numpy as np
import common

ROBOT_MAPPING = {
    "ore": 0,
    "clay": 1,
    "obsidian": 2,
    "geode": 3
}

sample_input = """Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.
Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian."""

@dataclasses.dataclass
class Pointer:
    value_to_beat: int = 0

@dataclasses.dataclass
class Cache:
    lookup_dict: dict

VALUE_TO_BEAT = Pointer(value_to_beat=0)

def parse_blueprint(blueprint):
    robots = blueprint[:-1].split(": ")[1]
    robots = robots.split(". ")
    rob_map = {}
    for robot in robots:
        name, prices = robot.split(" costs ")
        name = name.split("Each ")[1]
        name = name.split(" robot")[0]

        blocks = prices.split(" and ")
        prices = {}
        for block in blocks:
            cost, type = block.split(" ")
            prices[type] = int(cost)

        rob_map[name] = prices

    return rob_map

def reduce_blueprint(blueprint):
    cost_array = np.zeros((4, 4), dtype=int)
    for robot_name, costs in blueprint.items():
        robot_index = ROBOT_MAPPING[robot_name]
        for to_buy, cost in costs.items():
            cost_index = ROBOT_MAPPING[to_buy]
            cost_array[robot_index, cost_index] = cost

    return cost_array

def evaluate_blueprint(
        n_remaining_steps,
        built_so_far,
        items_so_far,
        cost_array,
        best_at_time,
        best_path
):
    # Process the current minute
    print(best_at_time)

    theoretical = theoretical_max(
        n_remaining_steps,
        built_so_far,
        items_so_far,
        cost_array
    )
    if theoretical <= best_at_time[n_remaining_steps - 1]:
        return 0, best_at_time, []

    new_items = np.zeros(4, dtype=int)
    for idx, (i, b) in enumerate(zip(items_so_far, built_so_far)):
        new_items[idx] = i + b

    if n_remaining_steps == 1:
        return new_items[-1], best_at_time, best_path

    can_build = []
    for rob_idx in range(4):
        cost_line = cost_array[rob_idx]
        if np.all(cost_line <= items_so_far):
            can_build.append(rob_idx)

    can_build = can_build[::-1]

    if (2 not in can_build) or (3 not in can_build):
        new_best_path = best_path + [-1]
        best_n_geodes, best_at_time, final_best_path = evaluate_blueprint(
            n_remaining_steps - 1,
            built_so_far.copy(),
            new_items,
            cost_array,
            best_at_time,
            new_best_path
        )

    else:
        best_n_geodes = 0
        final_best_path = []

    if 3 in can_build:
        can_build = [3]

    for idx in can_build:
        new_built_so_far = built_so_far.copy()
        new_built_so_far[idx] += 1

        items_after_payment = new_items.copy()
        items_after_payment -= cost_array[idx]

        new_best_path = best_path + [idx]
        n_geodes, best_at_time, new_best_path = evaluate_blueprint(
            n_remaining_steps - 1,
            new_built_so_far,
            items_after_payment,
            cost_array,
            best_at_time,
            new_best_path
        )

        if n_geodes > best_n_geodes:
            best_n_geodes = n_geodes
            final_best_path = new_best_path

    if best_n_geodes > best_at_time[n_remaining_steps - 1]:
        best_at_time[n_remaining_steps - 1] = best_n_geodes

    return best_n_geodes, best_at_time, final_best_path

@functools.cache
def theoretical_max(
        n_remaining_steps,
        built_so_far,
        items_so_far,
        cost_array,
        max_costs,
):
    ore_max = theoretical_ore(
        n_remaining_steps,
        built_so_far[0],
        items_so_far[0],
        cost_array[0][0],
        max_costs[0]
    )

    clay_max = theoretical_clay(
        n_remaining_steps,
        built_so_far[1],
        items_so_far[1],
        cost_array[1][0],
        max_costs[1],
        tuple(ore_max)
    )

    obsidian_max = theoretical_obsidian(
        n_remaining_steps,
        built_so_far[2],
        items_so_far[2],
        cost_array[2][0],
        cost_array[2][1],
        max_costs[2],
        tuple(ore_max),
        tuple(clay_max)
    )

    geode_max = theoretical_geode(
        n_remaining_steps,
        built_so_far[3],
        items_so_far[3],
        cost_array[3][0],
        cost_array[3][2],
        tuple(ore_max),
        tuple(obsidian_max)
    )

    return geode_max[-1]

@functools.cache
def theoretical_ore(
        n_remaining_steps,
        ore_mines,
        ore_so_far,
        ore_cost_in_ore,
        max_number_ore_mines
):
    ore = ore_so_far * np.ones(n_remaining_steps, dtype=int)
    n_added = 0
    for idx in range(n_remaining_steps):
        ore[idx] += (n_added + ore_mines) + ore[idx-1]

        if n_added + ore_mines == max_number_ore_mines:
            pass
        elif ore[idx-1] - n_added*ore_cost_in_ore >= ore_cost_in_ore:
            n_added += 1

    return ore

@functools.cache
def theoretical_clay(
        n_remaining_steps,
        clay_mines,
        clay_so_far,
        clay_cost_in_ore,
        max_number_clay_mines,
        max_ore_at_time
):
    clay = clay_so_far * np.ones(n_remaining_steps, dtype=int)
    n_added = 0
    for idx in range(n_remaining_steps):
        clay[idx] += (n_added + clay_mines) + clay[idx-1]

        if n_added + clay_mines == max_number_clay_mines:
            pass
        elif max_ore_at_time[idx] - n_added*clay_cost_in_ore >= clay_cost_in_ore:
            n_added += 1

    return clay

@functools.cache
def theoretical_obsidian(
        n_remaining_steps,
        obsidian_mines,
        obsidian_so_far,
        obsidian_cost_in_ore,
        obsidian_cost_in_clay,
        max_number_obsidian_mines,
        max_ore_at_time,
        max_clay_at_time
):
    obsidian = obsidian_so_far * np.ones(n_remaining_steps, dtype=int)
    n_added = 0
    for idx in range(n_remaining_steps):
        obsidian[idx] += (n_added + obsidian_mines) + obsidian[idx-1]

        if n_added + obsidian_mines == max_number_obsidian_mines:
            continue
        elif (
            max_ore_at_time[idx] - n_added*obsidian_cost_in_ore >= obsidian_cost_in_ore
        ) and (
            max_clay_at_time[idx] - n_added*obsidian_cost_in_clay >= obsidian_cost_in_clay
        ):
            n_added += 1

    return obsidian

@functools.cache
def theoretical_geode(
        n_remaining_steps,
        geode_mines,
        geode_so_far,
        geode_cost_in_ore,
        geode_cost_in_obsidian,
        max_ore_at_time,
        max_obsidian_at_time
):
    geode = geode_so_far * np.ones(n_remaining_steps, dtype=int)
    n_added = 0
    for idx in range(n_remaining_steps):
        geode[idx] += (n_added + geode_mines) + geode[idx - 1]

        if (
            max_ore_at_time[idx] - n_added*geode_cost_in_ore >= geode_cost_in_ore
        ) and (
            max_obsidian_at_time[idx] - n_added*geode_cost_in_obsidian >= geode_cost_in_obsidian
        ):
            n_added += 1

    return geode

def evaluate_blueprint_2(
        n_remaining_time,
        built_so_far,
        items_so_far,
        cost_array,
        max_costs
):
    best_geodes = 0

    # print(n_remaining_time, built_so_far, items_so_far, VALUE_TO_BEAT.value_to_beat)

    # Calculate theoretical maximum and short-circuit if need be
    # rem_geodes = items_so_far[-1] + built_so_far[-1] * n_remaining_time
    # poss_geodes = (n_remaining_time * n_remaining_time - 1) // 2
    # if rem_geodes + poss_geodes <= value_to_beat:
    #     return 0
    # if theoretical_max(
    #         n_remaining_time,
    #         built_so_far,
    #         items_so_far,
    #         cost_array,
    #         max_costs
    # ) <= VALUE_TO_BEAT.value_to_beat:
    #     return 0

    outer_state = (n_remaining_time,) + built_so_far
    inner_state = items_so_far

    if outer_state not in CACHE.lookup_dict.keys():
        CACHE.lookup_dict[outer_state] = {}

    best_so_far = CACHE.lookup_dict[outer_state].get(inner_state, None)

    # if best_so_far is None:
    #     for key, value in CACHE.lookup_dict[outer_state].items():
    #         if np.all(np.array(items_so_far) <= np.array(key)):
    #             # We've found a previous situation with more items built by
    #             # this stage - discard this point
    #             best_so_far = value
    #             # CACHE.lookup_dict[outer_state][inner_state] = best_geodes
    #             break

    if best_so_far is not None:
        return best_so_far

    built_so_far = np.array(built_so_far)
    items_so_far = np.array(items_so_far)

    for build_next in range(3, -1, -1):
        if (build_next != 3) \
                and (max_costs[build_next] <= built_so_far[build_next]):
            continue

        # Sometimes there is no point building the factory - we already have
        # enough to use arbitrarily
        if (build_next != 3) and (
                items_so_far[build_next]
                + n_remaining_time * built_so_far[build_next]
        ) // max_costs[build_next] >= n_remaining_time:
            continue

        build_cost = np.array(cost_array[build_next])
        if np.any(build_cost[built_so_far == 0]):
            continue

        remaining_materials = build_cost - items_so_far
        time_to_build = 1
        while np.any(remaining_materials > 0):
            remaining_materials -= built_so_far
            time_to_build += 1

        if n_remaining_time - time_to_build <= 0:
            items = items_so_far + n_remaining_time * built_so_far
            n_geodes = items[-1]
        else:
            n_geodes = evaluate_blueprint_2(
                n_remaining_time - time_to_build,
                tuple(built_so_far + np.eye(4, dtype=int)[build_next]),
                tuple(items_so_far + built_so_far * time_to_build - build_cost),
                cost_array,
                max_costs
            )

        if n_geodes > best_geodes:
            best_geodes = n_geodes

    CACHE.lookup_dict[outer_state][inner_state] = best_geodes

    # if best_geodes > VALUE_TO_BEAT.value_to_beat:
    #     VALUE_TO_BEAT.value_to_beat = best_geodes

    return best_geodes

if __name__ == "__main__":
    text = common.load_todays_input(__file__)
    blueprints = []
    for line in text.split("\n"):
        if line == "":
            continue
        else:
            blueprints.append(reduce_blueprint(parse_blueprint(line)))

    cumulative = 0
    start = (1, 0, 0, 0)
    cache_store = []
    for i, blueprint in enumerate(blueprints):
        print(f"Blueprint {i+1}")
        VALUE_TO_BEAT.value_to_beat = 0
        CACHE = Cache({})

        max_costs = tuple(
            max(blueprint[:, i]) for i in range(4)
        )
        cost_array = tuple(
            tuple(b) for b in blueprint
        )

        max_geodes = evaluate_blueprint_2(
            24,
            start,
            (0, 0, 0, 0),
            cost_array,
            max_costs
        )
        print(max_geodes)
        cumulative += (i+1) * max_geodes

        cache_store.append(deepcopy(CACHE))

    common.part(1, cumulative)

    cumulative = 1
    start = (1, 0, 0, 0)
    for i, blueprint in enumerate(blueprints[:3]):
        CACHE = Cache({})

        print(f"Blueprint {i+1}")
        max_costs = tuple(
            max(blueprint[:, i]) for i in range(4)
        )
        cost_array = tuple(
            tuple(b) for b in blueprint
        )

        max_geodes = evaluate_blueprint_2(
            32,
            start,
            (0, 0, 0, 0),
            cost_array,
            max_costs
        )
        print(max_geodes)
        cumulative *= max_geodes

    common.part(2, cumulative)


import numpy as np

import cudf
import clx.parsers.zeek as zeek

def test_parse_log_file(tmpdir):

    header = "#separator\t\\x09\n\
        #set_separator\t,\n\
        #empty_field\t(empty)\n\
        #unset_field\t-\n\
        #path\tconn\n\
        #open\t2015-01-24-16-49-04\n\
        #fields\tts\tuid\tid.orig_h\tid.orig_p\tid.resp_h\tid.resp_p\tproto\tservice\tduration\torig_bytes\tresp_bytes\tconn_state\tlocal_orig\tmissed_bytes\thistory\torig_pkts\torig_ip_bytes\tresp_pkts\tresp_ip_bytes\ttunnel_parents\n\
        #types\ttime\tstring\taddr\tport\taddr\tport\tenum\tstring\tinterval\tcount\tcount\tstring\tbool\tcount\tstring\tcount\tcount\tcount\tcount\tset[string]\n"

    actual = cudf.DataFrame()
    actual["ts"] = [1421927450.370337, 1421927658.777193]
    actual["ts"] = actual["ts"].astype("float64")
    actual["uid"] = ["CFlyqZgM1g71BYPB6", "CnKVxKIj403JsAK5k"]
    actual["id.orig_h"] = ["175.45.176.3", "175.45.176.1"]
    actual["id.orig_p"] = [7177, 24809]
    actual["id.orig_p"] = actual["id.orig_p"].astype("int64")
    actual["id.resp_h"] = ["149.171.126.16", "149.171.126.14"]
    actual["id.resp_p"] = [80, 443]
    actual["id.resp_p"] = actual["id.resp_p"].astype("int64")
    actual["proto"] = ["tcp", "tcp"]
    actual["service"] = ["http", "http"]
    actual["duration"] = [0.214392, 2.37679]
    actual["duration"] = actual["duration"].astype("float64")
    actual["orig_bytes"] = [194, 188]
    actual["orig_bytes"] = actual["orig_bytes"].astype("int64")
    actual["resp_bytes"] = [12282, 0]
    actual["resp_bytes"] = actual["resp_bytes"].astype("int64")
    actual["conn_state"] = ["SF", "SF"]
    actual["local_orig"] = [False, False]
    actual["missed_bytes"] = [12282, 0]
    actual["missed_bytes"] = actual["missed_bytes"].astype("int64")
    actual["history"] = ["ShADdFfa", "ShADFfa"]
    actual["orig_pkts"] = [12, 14]
    actual["orig_pkts"] = actual["orig_pkts"].astype("int64")
    actual["orig_ip_bytes"] = [900, 1344]
    actual["orig_ip_bytes"] = actual["orig_ip_bytes"].astype("int64")
    actual["resp_pkts"] = [24, 6]
    actual["resp_pkts"] = actual["resp_pkts"].astype("int64")
    actual["resp_ip_bytes"] = [25540, 256]
    actual["resp_ip_bytes"] = actual["resp_ip_bytes"].astype("int64")
    actual["tunnel_parents"] = ["(empty)", "(empty)"]

    footer = "#close^I2015-01-24-16-50-35"

    fname = tmpdir.mkdir("tmp_clx_zeek_test").join("tst_zeek_conn_log.csv")
    actual.to_csv(fname, sep="\t", index=False, header=False)

    with open(fname, "r+") as f:
        content = f.read()
        f.seek(0, 0)
        f.write(header + content + footer)

    parsed = zeek.parse_log_file(fname)
    assert np.allclose(parsed["ts"].values_host, actual["ts"].values_host)
    assert parsed["uid"].equals(actual["uid"])
    assert parsed["id.orig_h"].equals(actual["id.orig_h"])
    assert parsed["id.orig_p"].equals(actual["id.orig_p"])
    assert parsed["id.resp_h"].equals(actual["id.resp_h"])
    assert parsed["id.resp_p"].equals(actual["id.resp_p"])
    assert parsed["proto"].equals(actual["proto"])
    assert parsed["service"].equals(actual["service"])
    assert np.allclose(parsed["duration"].values_host, actual["duration"].values_host)
    assert parsed["orig_bytes"].equals(actual["orig_bytes"])
    assert parsed["resp_bytes"].equals(actual["resp_bytes"])
    assert parsed["conn_state"].equals(actual["conn_state"])
    assert parsed["local_orig"].equals(actual["local_orig"])
    assert parsed["missed_bytes"].equals(actual["missed_bytes"])
    assert parsed["history"].equals(actual["history"])
    assert parsed["orig_pkts"].equals(actual["orig_pkts"])
    assert parsed["orig_ip_bytes"].equals(actual["orig_ip_bytes"])
    assert parsed["resp_pkts"].equals(actual["resp_pkts"])
    assert parsed["resp_ip_bytes"].equals(actual["resp_ip_bytes"])
    assert parsed["tunnel_parents"].equals(actual["tunnel_parents"])


# These are constants that the API uses, and we define them in a single place
# REGIONS
AMERICAS = "us"
EUROPE = "eu"
KOREA = "kr"
CHINA = "cn"
JAPAN = "jp"
ANY = "any"

# PLATFORMS
PC = "pc"
XBOX = "xbl"
PLAYSTATION = "psn"

# MODES
QUICK = "quickplay"
COMP = "competitive"

heroes = {
    "ANA": "Ana",
    "BASTION": "Bastion",
    "DIVA": "DVa",
    "GENJI": "Genji",
    "HANZO": "Hanzo",
    "JUNKRAT": "Junkrat",
    "LUCIO": "Lucio",
    "MCCREE": "McCree",
    "MEI": "Mei",
    "MERCY": "Mercy",
    "ORISA": "Orisa",
    "PHARAH": "Pharah",
    "REAPER": "Reaper",
    "REINHARDT": "Reinhardt",
    "ROADHOG": "Roadhog",
    "SOLDIER_76": "Soldier76",
    "SOMBRA": "Sombra",
    "SYMMETRA": "Symmetra",
    "TORBJOERN": "Torbjoern",
    "TRACER": "Tracer",
    "WIDOWMAKER": "Widowmaker",
    "WINSTON": "Winston",
    "ZARYA": "Zarya",
    "ZENYATTA": "Zenyatta"
}


# coding=utf-8
'''
Created on 1 Feb 2011

@author: ekondrashev
'''
from __future__ import nested_scopes
import os

import logger
import errorobject
import errorcodes
from dbutils import SqlClient, SelectSqlBuilder, SelectLeftJoinSqlBuilder, and_, or_
from tcp import ConnectionDetails, ProcessDetails, Node, Interaction
from tcp_acceptor_plugin import ACCEPTOR_BUILDERS, AcceptorPluginEngine, buildDefaultAcceptorPluginEngine, \
    ServicesAcceptorBuilder, RangeAcceptorBuilder
from tcp_reporter_plugin import REPORTER_BUILDERS, ReporterPluginEngine, IpNodeReporterBuilder, \
    IpTrafficLinkReporterBuilder, NodeDependencyLinkReporterBuilder, \
    ServerRunningSoftwareReporterBuilder
from tcp_approach_plugin import APPROACH_BUILDERS, ApproachPluginEngine

from java.util import HashSet, Arrays
from com.hp.ucmdb.discovery.library.common import CollectorsParameters
from appilog.common.system.types.vectors import ObjectStateHolderVector
from com.hp.ucmdb.discovery.library.scope import DomainScopeManager
from xmlutils import Objectifier as XMLObjectifier
import tcp_descriptor
import ip_addr

class Dao:
    def __init__(self, sqlClient):
        self._sqlClient = sqlClient

class ConnectionDao(Dao):

    def getUniqueSrcIps(self, protocol=6):
        uniqueIps = HashSet()
        srcAddrSqlBuilder = SelectSqlBuilder('Agg_V5', 'srcAddr as ip', distinct=1)
        srcAddrSqlBuilder.where('prot=%d' % protocol)
        srcIps = self._sqlClient.execute(srcAddrSqlBuilder)
        if srcIps:
            uniqueIps.addAll(Arrays.asList([ipEntry.ip for ipEntry in srcIps]))

        return uniqueIps.toArray()

    def getConnections(self, srcIps=None, hostId=None, protocol=6):
        srcIps = srcIps or []

        selectBuilder = SelectSqlBuilder('Agg_V5', 'hostId',
                                            'srcAddr as srcIp', 'srcPort',
                                            'dstAddr as dstIp', 'dstPort',
                                            'prot as protocol',
                                            'dPkts as packetCount',
                                            'dOctets as octetCount',
                                            dataObjectClass=ConnectionDetails)
        andClauses = []

        ipsClause = ','.join(["'%s'" % ip for ip in srcIps])
        srcIps and andClauses.append('srcAddr in (%s)' % ipsClause)
        andClauses.append('prot=%d' % protocol)
        hostId and andClauses.append("hostId='%s'" % hostId)

        selectBuilder.where(and_(*andClauses))

        connectionDetails = self._sqlClient.execute(selectBuilder)
        return connectionDetails

class ProcessDao(Dao):

    def getProcesses(self, ips=None, hostId=None):

        args = ['Port_Process.ipaddress as ip', 'Port_Process.port as port', 'Port_Process.Protocol as protocol', 'Port_Process.listen as isListen',
                  'Processes.pid as pid', 'Processes.name as name', 'cmdline', 'params', 'path', 'owner', 'startuptime', 'Port_Process.hostid as hostId']
        kwargs = {'dataObjectClass':ProcessDetails}
        portProcessSqlBuilder = SelectSqlBuilder('Port_Process', *args, **kwargs)

        joinConditions = ['Port_Process.hostid=Processes.hostid', 'Port_Process.pid=Processes.pid']
        selectJoinBuilder = SelectLeftJoinSqlBuilder(portProcessSqlBuilder, 'Processes', and_(*joinConditions))

        andClauses = []

        ips and andClauses.append('Port_Process.ipaddress in (%s)' % ','.join(["'%s'" % ip for ip in ips]))
        hostId and andClauses.append("hostId='%s'" % hostId)

        selectJoinBuilder.where(and_(*andClauses))
        entries = self._sqlClient.execute(selectJoinBuilder)

        processes = {}
        for process in entries:
            logger.debug('Got process: ', (process.ip, process.port))
            processes[(process.ip, process.port)] = process

        return processes

class DiscoveryContext:
    def __init__(self, connectionDao, processDao, servers=None, clients=None, scope=None):
        self.connectionDao = connectionDao
        self.processDao = processDao

def getProbeRanges(probeName):
    return DomainScopeManager.getProbeRanges(probeName, None)

def _getConfigFilePath(fileName):
    return ''.join(CollectorsParameters.BASE_PROBE_MGR_DIR,
            CollectorsParameters.getDiscoveryConfigFolder(),
            CollectorsParameters.FILE_SEPARATOR, fileName)

class PluginBuilderContext:
    def __init__(self):
        self.knownPortsConfigFile = None
        self.probeRanges = None
        self.probeIp = None
        self.registerDiscoveryHandlerCallback = None
        self.acceptorBuilders = {}
        self.reporterBuilders = {}

def getConfigFilePath(fileName):
    return ''.join((CollectorsParameters.BASE_PROBE_MGR_DIR,
            CollectorsParameters.getDiscoveryConfigFolder(),
            CollectorsParameters.FILE_SEPARATOR, fileName))

def _parseBoolean(value):
    if not value:
        return None
    from java.lang import Boolean
    return Boolean.valueOf(value)

def splitToPages(items, maxPageSize):
    return [items[i:i + maxPageSize] for i in xrange(0, len(items), maxPageSize)]

MAX_IPS_PAGE_SIZE = 100
MAX_CONNECTIONS_COUNT_IN_PAGE = 30000

def _is_ipv4(address):
    try:
        return ip_addr.IPAddress(address).version == 4
    except:
        return False

def DiscoveryMain(Framework):
    try:
        reportIpTrafficLink = _parseBoolean(Framework.getParameter('reportIpTrafficLink'))
        reportNodeDependencyLink = _parseBoolean(Framework.getParameter('reportNodeDependencyLink'))
        reportServerRunningSoftware = _parseBoolean(Framework.getParameter('reportServerRunningSoftware'))
        acceptedServices = Framework.getParameter('acceptedServices')
        includeOutscopeServers = _parseBoolean(Framework.getParameter('includeOutscopeServers'))
        includeOutscopeClients = _parseBoolean(Framework.getParameter('includeOutscopeClients'))

        descriptorFilePath = Framework.getParameter('discoveryDescriptorFile')
        descriptorFilePath = descriptorFilePath.replace(r'%%PROBE_MGR_CONFIGFILES_DIR%%', CollectorsParameters.PROBE_MGR_CONFIGFILES_DIR)

        if not os.path.isabs(descriptorFilePath):
            descriptorFilePath = getConfigFilePath(descriptorFilePath)
        logger.debug("Tcp discoveryDescriptor file path: %s" % descriptorFilePath)

        descriptorFile = open(descriptorFilePath)
        discoveryDescriptor = XMLObjectifier(descriptorFile.read()).makeInstance()
        descriptorFile.close()

        connection = Framework.getProbeDatabaseConnection('TCPDISCOVERY')
        sqlClient = SqlClient(connection)
        processDao = ProcessDao(sqlClient)
        connectionDao = ConnectionDao(sqlClient)

        discoveryContext = DiscoveryContext(connectionDao, processDao)

        probeName = Framework.getDestinationAttribute('probeName')
        probeIp = Framework.getDestinationAttribute('probeIp')

        pluginBuilderContext = PluginBuilderContext()
        pluginBuilderContext.knownPortsConfigFile = Framework.getConfigFile(CollectorsParameters.KEY_COLLECTORS_SERVERDATA_PORTNUMBERTOPORTNAME)
        pluginBuilderContext.probeRanges = getProbeRanges(probeName)
        pluginBuilderContext.probeIp = probeIp

        #true - report false - do not report, None - check TCP descriptor configuration
        pluginBuilderContext.reporterBuilders['ipTrafficLink'] = reportIpTrafficLink and IpTrafficLinkReporterBuilder()
        pluginBuilderContext.reporterBuilders['nodeDependencyLink'] = reportNodeDependencyLink and NodeDependencyLinkReporterBuilder()
        pluginBuilderContext.reporterBuilders['serverRunningSoftware'] = reportServerRunningSoftware and ServerRunningSoftwareReporterBuilder()#

        if acceptedServices:
            serviceAcceptorBuilder = ServicesAcceptorBuilder()
            serviceAcceptorBuilder.includeServices.extend(acceptedServices.split(','))
            pluginBuilderContext.acceptorBuilders['services'] = serviceAcceptorBuilder

        if includeOutscopeServers is not None or includeOutscopeClients is not None:
            rangeAcceptorBuilder = RangeAcceptorBuilder()
            rangeAcceptorBuilder.includeRanges.extend([range.toRangeString() for range in pluginBuilderContext.probeRanges])

            if includeOutscopeServers is not None:
                outscopeServers = 'outscope_servers'
                if includeOutscopeServers:
                    rangeAcceptorBuilder.includeRanges.append(outscopeServers)
                else:
                    rangeAcceptorBuilder.excludeRanges.append(outscopeServers)

            if includeOutscopeClients is not None:
                outscopeClients = 'outscope_clients'
                if includeOutscopeClients:
                    rangeAcceptorBuilder.includeRanges.append(outscopeClients)
                else:
                    rangeAcceptorBuilder.excludeRanges.append(outscopeClients)

            pluginBuilderContext.acceptorBuilders['ranges'] = rangeAcceptorBuilder

        discoveryScopes = tcp_descriptor.Parser(pluginBuilderContext
                ).parseDiscoveryScopes(
                    discoveryDescriptor,
                    ACCEPTOR_BUILDERS, REPORTER_BUILDERS, APPROACH_BUILDERS)

        try:
            ips = connectionDao.getUniqueSrcIps()
            ips = filter(_is_ipv4, ips)
            logger.debug("Unique ip count: %s" % len(ips))
            ipChunks = splitToPages(ips, MAX_IPS_PAGE_SIZE)
            logger.debug("page count %s" % len(ipChunks))

            is_ipv4_connection = lambda connection: _is_ipv4(connection.dstIp)
            for discoveryScope in discoveryScopes:
                for nr, ipChunk in enumerate(ipChunks):
                    logger.debug('page number:%s' % nr)
                    try:
                        connections = discoveryContext.connectionDao.getConnections(srcIps=ipChunk)
                        connections = filter(is_ipv4_connection, connections)
                        for connections in splitToPages(connections,
                                                     MAX_CONNECTIONS_COUNT_IN_PAGE):

                            OSHVResult = _discoverChunk(connections, discoveryScope, discoveryContext)
                            Framework.sendObjects(OSHVResult)
                            Framework.flushObjects()
                            discoveryScope.reset()
                    except:
                        logger.warnException('')

            logger.debug("Query count: %d" % sqlClient.queryCount)
        finally:
            sqlClient.close()
    except:
        errorMessage = 'Internal error. Please, see the logs for details'
        logger.debugException(errorMessage)
        errobj = errorobject.createError(errorcodes.INTERNAL_ERROR, [], errorMessage)
        logger.reportErrorObject(errobj)

def __getConnectionsIps(connections):
    res = HashSet()
    for connection in connections:
        res.add(connection.srcIp)
        res.add(connection.dstIp)
    return res.toArray()

def _discoverChunk(connections, discoveryScope, discoveryContext):
    OSHVResult = ObjectStateHolderVector()
#    servers, clients = discoveryContext.servers, discoveryContext.clients

    ips = __getConnectionsIps(connections)

    processes = discoveryContext.processDao.getProcesses(ips=ips)
    logger.debug("processes count %s" % len(processes))
    interactions = []
    for connection in connections:
        srcNode = Node()
        srcNode.ip = connection.srcIp

        dstNode = Node()
        dstNode.ip = connection.dstIp

        srcNode.port = connection.srcPort
        srcNode.process = processes.get(connection.getSrcId())

        dstNode.port = connection.dstPort
        dstNode.process = processes.get(connection.getDstId())

        if discoveryScope.acceptorPluginEngine.accepts(srcNode) and discoveryScope.acceptorPluginEngine.accepts(dstNode):
            interaction = Interaction()
            interaction.srcNode = srcNode
            interaction.dstNode = dstNode
            interaction.connection = connection
            interactions.append(interaction)

            discoveryScope.notifyInteractionDiscovered(interaction)

    for interaction in interactions:
        discoveryScope.approachPluginEngine.resolveRelation(interaction)
        discoveryScope.reporterPluginEngine.report(OSHVResult, interaction)
    return OSHVResult

def _reportInteraction(scope, OSHVResult, interaction):
    for reporter in scope.reporting:
        reporter.report(OSHVResult, interaction)