import acrobind
import acrort
import base64
import collections
import copy
import csv
import datetime
import json
import operator
import os.path
import prettytable
import re
import subprocess
import sys
import time
import traceback
import requests
import inspect
import platform
import urllib.parse
from contextlib import closing
from time import sleep
from xml.dom.minidom import parse
from uuid import UUID


INSTANCE_TYPES = {
    1: 'TYPE_MACHINE',
    2: 'TYPE_DB',
    3: 'TYPE_SHAREPOINT',
    4: 'TYPE_VIRTUAL_MACHINE',
    5: 'TYPE_VIRTUAL_SERVER',
    6: 'TYPE_EXCHANGE',
    7: 'TYPE_VIRTUAL_CLUSTER',
    8: 'TYPE_VIRTUAL_APPLIANCE',
    9: 'TYPE_VIRTUAL_APPLICATION',
    10: 'TYPE_VIRTUAL_RESOURCE_POOL',
    11: 'TYPE_VIRTUAL_CENTER',
    12: 'TYPE_DATASTORE',
    13: 'TYPE_DATASTORE_CLUSTER',
    14: 'TYPE_MSSQL',
    15: 'TYPE_VIRTUAL_NETWORK',
    16: 'TYPE_VIRTUAL_FOLDER',
    17: 'TYPE_VIRTUAL_DATACENTER',
    18: 'TYPE_SMB_SHARED_FOLDER',
    19: 'TYPE_MSSQL_INSTANCE',
    20: 'TYPE_MSSQL_DATABASE',
    21: 'TYPE_MSSQL_DATABASE_FOLDER',
    22: 'TYPE_MSEXCHANGE_DATABASE',
    23: 'TYPE_MSEXCHANGE_STORAGE_GROUP',
    24: 'TYPE_MSEXCHANGE_MAILBOX',
}

BACKUP_STATUSES = {
    0: 'STATUS_BACKUP_UNKNOWN',
    1: 'STATUS_BACKUP_NONE',
    2: 'STATUS_BACKUP_SUCCEEDED',
    3: 'STATUS_BACKUP_WARNING',
    4: 'STATUS_BACKUP_FAILED'
}

BACKUP_STATES = {
    0: 'BACKUP_STATE_IDLE',
    1: 'BACKUP_STATE_RUNNING',
}

INSTANCE_STATUSES = {
    0: 'INSTANCE_STATUS_UNKNOWN',
    1: 'INSTANCE_STATUS_NONE',
    2: 'INSTANCE_STATUS_SUCCEEDED',
    3: 'INSTANCE_STATUS_WARNING',
    4: 'INSTANCE_STATUS_FAILED'
}

INSTANCE_STATES = {
    0: 'IDLE',
    0x01: 'INTERACTION_REQUIRED',
    0x02: 'CANCELLING',
    0x04: 'RUNNING_BACKUP',
    0x08: 'RUNNING_RECOVER',
    0x10: 'RUNNING_INSTALL',
    0x20: 'RUNNING_REBOOT',
    0x40: 'RUNNING_FAILBACK',
    0x80: 'RUNNING_TEST',
    0x100: 'RUNNING_FROM_IMAGE',
    0x200: 'RUNNING_FINALIZE',
    0x400: 'RUNNING_FAILOVER',
    0x800: 'RUNNING_REPLICATION',
}


BM_MASTER_TENANT_ID = "EC22CEE6-FC80-4325-A10F-7EAA5F424759"

CLOUD_CONFIG_PATH = "/lib/Acronis/AMS/net.config"
DEFAULT_LINUX_CONFIG_PATH = "/usr/lib/Acronis/AMS/net.config"
LINUX_SECRET_CONFIG_PATH = "/var/lib/Acronis/AMS/user.config"
DEFAULT_WIN_CONFIG_PATH = "C:/Program Files/Acronis/AMS/net.config"
WIN_SECRET_CONFIG_PATH = "C:/ProgramData/Acronis/AMS/user.config"

class TokenSettings:
    bm_url = None
    config = None
    tm_url = None

Log = None
args = None


def format_backtrace(exception):
    exc_type, exc_value, exc_traceback = sys.exc_info()
    info = traceback.format_exception(exc_type, exc_value, exc_traceback)
    return ''.join(line for line in info)


def error_log(message):
    if Log:
        Log.write(message)
    else:
        print(message)


def print_bool_flag(flag):
    if flag is None:
      return '-'
    if not flag:
        return '-'
    return '+'


def get_optional(object, prop_name):
    val = None
    if prop_name in object:
        val = object[prop_name].ref
    return val


def describe_tenant(tenant, full_format=True):
    if tenant is None:
        return 'MISSING'

    if full_format:
        return '\'{0}\'(Name:\'{1}\', Locator: \'{2}\', Kind: \'{3}\')'.format(tenant.ID.ref if 'ID' in tenant else 'MISSING',\
            tenant.Name.ref if 'Name' in tenant else 'MISSING', tenant.Locator.ref if 'Locator' in tenant else 'MISSING',\
            tenant.Kind.ref if 'Kind' in tenant else 'MISSING')
    return '{}'.format(tenant.ID.ref if 'ID' in tenant else 'MISSING')


def get_tenant_string(object, full_format=True):
    if object is None or 'Tenant' not in object:
        return 'MISSING'

    return describe_tenant(object.Tenant, full_format)


def to_instance_state(state):
    if state == 0:
        return 'IDLE(0)'
    if state == 1:
        return 'RUNNING(1)'
    return 'UNKNOWN({})'.format(state)


def to_machine_status(status):
    if status == 0:
        return 'ONLINE'
    if status == 1:
        return 'OFFLINE'
    if status == 2:
        return 'FOREIGN'
    if status == 3:
        return 'EXPIRED'
    return 'UNKNOWN'


def drop_agent_connection(connection, host_id):
    try:
        argument = acrort.plain.Unit(flat=[('.ID', 'string', '{}'.format(host_id).upper())])
        activity_id = connection.tol.launch_command(command='651F1568-B113-479F-B578-A4167F9CA61B', argument=argument)
        Log.write('Waiting for command activity {} completion...'.format(activity_id), end='')
        result = connection.tol.get_result(activity_id)
        Log.write('done')
    except Exception as error:
        Log.write('Error: {}'.format(error))


def fix_item_protection_tenant(connection, ip_id, instance_id, host_id):
    print('Processing Gtob::Dto::ItemProtection with ID: {0}'.format(ip_id))
    print('Getting corresponding InstanceManagement::Instance with ID: {0}'.format(instance_id))
    (selected, instance) = acrobind.safe_select_object(connection, acrobind.create_viewspec_instance_by_id(instance_id))
    if selected and instance is None:
        print("Can't find instance with ID {0}, deleting wrong ItemProtection".format(instance_id))
        return
    print('Getting Msp::AMS::Dto::Machine for host ID: {0}'.format(host_id))

    msp_machine = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(host_id)))
    #print('msp_machine: {0}'.format(msp_machine))
    if msp_machine is None:
        print("Can't find Msp::AMS::Dto::Machine for host {0}, skipping".format(host_id))
        return
    tenant_id = msp_machine['OwnerID'].ref
    print('Getting Tenants::HierarchyNode with ID: {0}'.format(tenant_id))
    tenant = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', tenant_id))
    if tenant is None:
        print("Can't find Tenants::HierarchyNode with ID {0}, skipping".format(tenant_id))
        return
    print(tenant)
    #connection.dml.update(pattern=acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.ID', ip_id), diff={'Tenant': tenant})
    print('Gtob::Dto::ItemProtection with ID {0} is fixed.'.format(ip_id))


def fix_instance_tenant(connection, instance_id, host_id):
    print('Processing InstanceManagement::Instance with ID: {0}'.format(instance_id))
    print('Getting Msp::AMS::Dto::Machine for host ID: {0}'.format(host_id))
    msp_machine = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(host_id)))
    #print('msp_machine: {0}'.format(msp_machine))
    if msp_machine is None:
        print("Can't find Msp::AMS::Dto::Machine for host {0}, skipping".format(host_id))
        return
    tenant_id = msp_machine['OwnerID'].ref
    print('Getting Tenants::HierarchyNode with ID: {0}'.format(tenant_id))
    tenant = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', tenant_id))
    if tenant is None:
        print("Can't find Tenants::HierarchyNode with ID {0}, skipping".format(tenant_id))
        return
    print(tenant)
    #connection.dml.update(pattern=instance_spec(instance_id), diff={'Tenant': tenant})
    print('InstanceManagement::Instance with ID {0} is fixed.'.format(instance_id))


def fix_machine_tenant(connection, machine_id):
    print('Processing MachineManagement::Machine with ID: {0}'.format(machine_id))
    print('Getting Msp::AMS::Dto::Machine for host ID: {0}'.format(machine_id))
    msp_machine = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(host_id)))
    #print('msp_machine: {0}'.format(msp_machine))
    if msp_machine is None:
        print("Can't find Msp::AMS::Dto::Machine for host {0}, skipping".format(machine_id))
        return False
    tenant_id = msp_machine['OwnerID'].ref
    print('Getting Tenants::HierarchyNode with ID: {0}'.format(tenant_id))
    tenant = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', tenant_id))
    if tenant is None:
        print("Can't find Tenants::HierarchyNode with ID {0}, skipping".format(tenant_id))
        return False
    print(tenant)
    #connection.dml.update(pattern=acrobind.create_viewspec_by_is_and_guid_property('MachineManagement::Machine', '.ID', machine_id), diff={'Tenant': tenant})
    print('MachineManagement::Machine with ID {0} is fixed.'.format(machine_id))
    return True


def analyze_tenant(object):
    if 'Tenant' in object:
        locator = '-'
        if 'Locator' in object['Tenant']:
            locator = object.Tenant.Locator.ref
        return '\'{0}\'({1})'.format(object.Tenant.Name.ref, locator)
    return None


def wrap_safe_exec_result(result):
    if not result[0]:
        return 'UNKNOWN'
    return 'OK' if result[1] else 'MISSING'


def get_objects_count(connection, machine_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Dml::Sync::Caching::Registration', '.__source_machine', str(machine_id))
    return acrobind.count_objects_by_spec(connection, spec)


def get_objects_count_2(connection, machine_id):
    return acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_guid_property('Dml::Sync::Caching::Registration', '.__source_machine', str(machine_id)))


def delete_objects_count(dml, machine_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Dml::Sync::Caching::Registration', '.__source_machine', str(machine_id))
    dml.delete(pattern=spec.pattern)


def check_caching_registration(connection):
    print('Checking Dml::Sync::Caching::Registration')

    if args.machine_id is not None and args.machine_status is not None and args.machine_status == 0:


        print('Count: {0}'.format(get_objects_count(connection, args.machine_id)))

        objects = get_objects_count_2(connection, args.machine_id)
        print('Count2: {0}'.format(len(objects)))

        def caching_registration_spec(registration_id, machine_id):
            return acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is_and_guid_property('Dml::Sync::Caching::Registration', '.ID', registration_id), machine_id)

        object = acrobind.safe_select_object(connection, caching_registration_spec('CE030FCE-9241-400D-97C0-601610EDD186', args.machine_id))
        print('\tActivities: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('D0F82464-DD2F-4017-9745-DDEE4F44610A', args.machine_id))
        print('\tProtect command activities: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('8B94773D-6748-443B-8170-91426FD0EA98', args.machine_id))
        print('\tGtob::Protection::App::Machine: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('CC7554D7-62EA-4D57-8483-E6BFA12CDA72', args.machine_id))
        print('\tClusterManager::Cluster: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('D236945B-755A-4A79-A614-67BB674E011A', args.machine_id))
        print('\tGtob::Dto::Protection: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('54EB2BDC-1465-4C34-9C94-A08C843E6ED6', args.machine_id))
        print('\tGtob::Dto::ProtectionPlan: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('BFBFEE9D-551C-4737-BF43-06CB97B7FACA', args.machine_id))
        print('\tRunVmFromImage::VMResurrection: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('DE5FCF73-3A7E-4989-A869-CF61F509B0EB', args.machine_id))
        print('\tStatistics::Counters: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('B9D53478-4CB1-45C6-9EAF-91AB6DDD38CC', args.machine_id))
        print('\tReplication::Continuous::ProcessInfo: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('F47035AE-2057-4862-889A-40D6DADB7F9C', args.machine_id))
        print('\tLocal Gtob::Dto::ItemProtection: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('055F55CC-2F09-4FB8-A5E0-63EC1F186E0F', args.machine_id))
        print('\tCentralized Gtob::Dto::ItemProtection: {0}'.format(wrap_safe_exec_result(object)))

        object = acrobind.safe_select_object(connection, caching_registration_spec('323976BC-3CB2-4DD2-8736-9343A7B4C3DB', args.machine_id))
        print('\tLegacy Gtob::Dto::ItemProtection: {0}'.format(wrap_safe_exec_result(object)))

        objects = []
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is('Gtob::Dto::ItemProtection'), args.machine_id))
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is('InstanceManagement::Instance'), args.machine_id))
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is('Gtob::Dto::Protection'), args.machine_id))
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is('Gtob::Dto::ProtectionPlan'), args.machine_id))
        #objects = acrobind.select_objects(connection, acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.Environment.ProtectionPlanID', 'C3A38821-AF2D-05A0-21E2-23B2F5673916'), args.machine_id))
        for o in objects:
            print('--------------')
            print(o)

    elif args.machine_id is not None:
      print('Machine is OFFLINE.')
    else:
      print('No machine ID.')


def check_running_activities(connection, spec, machine_id=None):
    Log.write('Checking activities by spec: {0}'.format(spec.pattern))

    pattern1 = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.Tenant.ID', 'string', '{}'.format(args.tenant_id)),
            ('.State', 'dword', 0),
            ('.State^Less', 'dword', 5)]
    spec1 = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern1))

    local_count = acrobind.count_objects_by_spec(connection, spec)
    Log.write('AMS has \'{}\' activities.'.format(local_count))
    if local_count > 0:
        activities = acrobind.select_objects(connection, spec)
        list_activities(activities)

        if args.fix:
            print('Do you want to change state of these activities to completed?(y/n)?')
            if ask_user():
                for a in activities:
                    id_str = '{0}'.format(a.ID.ref)
                    pattern = [
                        ('', 'dword', 5),
                    ]
                    diff_unit={'State': acrort.plain.Unit(flat=pattern)}
                    connection.dml.update(pattern=acrobind.create_viewspec_by_is_and_guid_property('Tol::History::Plain::Activity', '.ID', id_str).pattern, diff=diff_unit)
                    print('Activity {} is fixed.'.format(id_str))
            else:
                print('skipped.')

    if machine_id:
        Log.write('Checking remote activities on agent \'{0}\'.'.format(machine_id))
        local_pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.State', 'dword', 0),
            ('.State^Less', 'dword', 5)]
        local_spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=local_pattern))
        remote_spec = acrobind.viewspec_apply_remote_host(local_spec, machine_id)
        count = acrobind.count_objects_by_spec(connection, remote_spec)
        Log.write('Agent \'{}\' has \'{}\' running activities.'.format(machine_id, count))
        if count > 0:
            activities = acrobind.select_objects(connection, remote_spec)
            list_activities(activities)


def list_activities(activities):
    table = prettytable.PrettyTable(["Name", "State", "ID", "Specific", "CommandID", "HostID"])
    table.align["Name"] = "l"
    table.padding_width = 1

    for a in activities:
        if args.extra:
            Log.write(a)
        name_str = '\'{0}\''.format(a.Name.ref)
        state_str = '{0}'.format(a.State.ref)
        id_str = '{0}'.format(a.ID.ref)
        command_id_str = '{0}'.format(a.Details.CommandID.ref)
        specific_id_str = '{0}'.format(a.Details.Specific.ref)
        host_id_str = acrobind.get_trait_value('Source', a)
        table.add_row([name_str, state_str, id_str, specific_id_str, command_id_str, host_id_str])
    Log.write(table.get_string(sortby="Name"))
    Log.write('')


def delete_plan_artifacts(connection, host_id, cplan_id):
    print('Do you want to cleanup synced Gtob::Dto::ProtectionPlan(y/n)?')
    if ask_user():
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', cplan_id).pattern)
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::CentralizedProtection', '.ID', cplan_id).pattern)
        connection.dml.delete(pattern=acrobind.viewspec_apply_source(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', cplan_id), host_id).pattern)
        print('deleted.')
    else:
        print('skipped.')


    print('Do you want to cleanup legacy  Gtob::Dto::ItemProtection(y/n)?')
    if ask_user() and host_id:
        remote_ips_spec = acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.Plan', cplan_id), host_id)
        objects = acrobind.select_objects(connection, remote_ips_spec)
        for o in objects:
            print('--------------')
            print(o)

        connection.dml.delete(pattern=remote_ips_spec.pattern)
        print('deleted.')
    else:
        print('skipped.')


def origin_to_str(origin):
    if origin == 1:
        return 'L'
    if origin == 2:
        return 'C'
    if origin ==3:
        return 'D'
    return 'U'


def redeploy_plan(connection, plan_id):
    selection_state_spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Gct::SelectionState', '.ID', plan_id)
    selection_state = acrobind.select_object(connection, selection_state_spec)
    print("Deleting Gtob::Gct::SelectionState: {}".format(selection_state))
    connection.dml.delete(pattern=selection_state_spec.pattern)
    digest_spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ResourceDigest', '.ID', plan_id)
    digest = acrobind.select_object(connection, digest_spec)
    Log.write("Deleting Gtob::Dto::ResourceDigest: {}".format(digest))
    connection.dml.delete(pattern=digest_spec.pattern)
    deployment_request = [
      ('^Is', 'string', 'Gtob::Dto::AutomaticDeploymentRequest'),
      ('.ID', 'guid', plan_id),
      ('.ID^PrimaryKey', 'nil', None),
      ('.ID^AutomaticDeploymentRequest', 'nil', None),
      ('.InitiatedBy', 'string', 'infraview'),
      ('.Fails', 'array', [])
    ]
    #deployment_request_unit = acrort.plain.Unit(flat=deployment_request)
    #Log.write('Creating deployment request: {0}'.format(deployment_request_unit))
    #connection.dml.create(deployment_request_unit, mode=acrort.dml.CREATE_OR_REPLACE)

    Log.write('Running protect command for plan: {0}'.format(plan_id))
    plan = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', plan_id))
    tenant_connection = acrort.connectivity.Connection('ams', client_session_data = {'tenant_id': '{}'.format(plan.Tenant.ID.ref)})

    activity_id = tenant_connection.tol.launch_command(command='41830509-FCA4-4B3A-9978-3D00462DE006', argument=plan)

    result = None
    try:
        result = tenant_connection.tol.get_result(activity_id)
        tenant_connection.close()
    except acrort.Exception as ex:
        if acrort.common.interrupt_sentinel:
             tenant_connection.tol.cancel_activity(activity_id)
        Log.write('Protect canceled.')
        tenant_connection.close()
        raise


def fix_centralized_protection(connection, plan_id):
    Log.write('Creating missing Gtob::Dto::Centralized::ItemProtection object for plan \'{}\'...'.format(plan_id))
    spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::Centralized::ItemProtection', '.Centralized.PlanID', plan_id)
    protections = acrobind.select_objects(connection, spec)

    if not protections:
        Log.write('There are no Gtob::Dto::Centralized::ItemProtection objects for this plan')
        return

    affected_machines = []
    protected_instances = []

    for protection in protections:
        affected_machines.append([('', 'guid', protection.HostID.ref)])
        protected_instances.append(('.ProtectedInstances.{}'.format(protection.InstanceID.ref), 'dword', 1))

    centralized_protection = acrort.plain.Unit(flat=[
        ('.ActivationStatus', 'dword', 1),
        ('.AffectedMachines', 'array', affected_machines),
        ('.AffectedMachines^IsContainer', 'string', 'vector'),
        ('.AffectedMachinesCount', 'dword', 1),
        ('.CurrentFrame.BackupNumber', 'dword', 0),
        ('.CurrentFrame.LastStartTime', 'sqword', 0),
        ('.CurrentFrame.SchemeDeploymentID', 'string', ''),
        ('.CurrentFrame^Is', 'string', 'Gtob::Dto::BackupFrameData'),
        ('.Host', 'guid', '00000000-0000-0000-0000-000000000000'),
        ('.ID', 'guid', plan_id),
        ('.ID^PrimaryKey', 'nil', None),
        ('.ID^Protection', 'nil', None),
        ('.LastResult', 'dword', 0),
        ('.ManualStartNumber', 'dword', 0),
        ('.Origin', 'dword', 2),
        ('.Owner', 'string', 'root'),
        ('.PlanDeploymentState', 'dword', 0),
        ('.PlanID', 'guid', plan_id),
        ('.PlanID^ForeignKey', 'nil', None),
        *protected_instances,
        ('.Status', 'dword', 0),
        ('^Is', 'string', 'Gtob::Dto::CentralizedProtection'),
        ('^Is', 'string', 'Gtob::Dto::Protection'),
    ])
    connection.dml.create(centralized_protection)
    Log.write('Creating object:\n', centralized_protection)


def describe_plans(connection):
    Log.write('[---Checking plans---]')
    plans = None
    if args.plan_name:
        plans = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_like('Gtob::Dto::ProtectionPlan', '.Name', args.plan_name))
    if args.plan_id:
        plans = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', args.plan_id))

    check_plan_list_internal(connection, plans)

    if args.plan_id and args.change_owner:
        new_tenant = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', args.change_owner))
        if new_tenant:
            diff = [
              ('^PackedDiff', 'nil', None),
              ('.DmlTimeStamp', 'sqword', 0),
              ('.DmlTimeStamp^Removed', 'nil', None),
            ]
            tenant_patch = new_tenant.merge(acrort.plain.Unit(flat=diff))
            new_owner_id = acrort.common.Guid(acrort.common.eval_md5('security-tenant-{}'.format(new_tenant.Locator.ref)))
            new_tenant_str = describe_tenant(new_tenant, full_format=True)
            plan = plans[0]
            p_tenant = get_tenant_string(plan, full_format=True)
            p_owner = get_optional(plan, "OwnerID")

            Log.write('Do you want to change owner of Gtob::Dto::ProtectionPlan from \'{0}({1})\' to \'{2}({3})\'?(y/n)'.format(p_tenant, p_owner, new_tenant_str, new_owner_id))
            if ask_user():
                owner_patch = acrort.plain.Unit({'OwnerID' : new_owner_id, 'Tenant' : tenant_patch})
                plan = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', args.plan_id))
                Log.write('Applying patch: {0}'.format(owner_patch))
                new_plan = plan.consolidate(owner_patch)
                connection.dml.create(new_plan, mode=acrort.dml.CREATE_OR_REPLACE)
                if args.extra:
                    Log.write(plan)
                Log.write('done')
        else:
            Log.write('Can\'t find tenant with ID \'{0}\''.format(args.change_owner))

    if args.plan_id and args.redeploy:
        Log.write('Do you want to redeploy Gtob::Dto::ProtectionPlan?(y/n)')
        if ask_user():
            result = redeploy_plan(connection, args.plan_id)
            Log.write(result)
            Log.write('done')

    if args.plan_id and args.fix:
        Log.write('Do you want to undeploy Gtob::Dto::ProtectionPlan?(y/n)')
        if ask_user():
            Log.write('Input host ID(for centralized plan leave it empty):')
            host_id = sys.stdin.readline()
            host_id = host_id.rstrip()

            cplan_id = None
            if not args.plan_id:
                Log.write('Input plan ID(empty ID will skip action):')
                cplan_id = sys.stdin.readline()
                cplan_id = cplan_id.rstrip()
            else:
                cplan_id = args.plan_id

            if cplan_id:
                check_plan = None
                if host_id:
                    check_plan = acrobind.select_object(connection, acrobind.viewspec_apply_source(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', cplan_id), host_id))
                else:
                    check_plan = acrobind.select_object(connection, acrobind.create_viewspec_deployed_protection_plan_by_id(cplan_id))

                force_undeploy = False
                if not check_plan:
                    Log.write('Do you want to force undeploy?(y/n)')
                    if ask_user():
                        force_undeploy = True

                if check_plan or force_undeploy:
                    arg = acrort.common.Guid(cplan_id)
                    if args.extra:
                       Log.write(check_plan)
                    Log.write('Running unprotect')
                    if host_id:
                        activity_id = connection.tol.launch_command(command='C006D24E-E6ED-494a-9789-237CD3A814E7', argument=arg, target_machine=host_id)
                    else:
                        activity_id = connection.tol.launch_command(command='C006D24E-E6ED-494a-9789-237CD3A814E7', argument=arg)

                    try:
                        if host_id:
                            result = connection.tol.get_result(activity_id, target_machine=host_id)
                        else:
                            result = connection.tol.get_result(activity_id)
                    except acrort.Exception as ex:
                        if acrort.common.interrupt_sentinel:
                            if host_id:
                                connection.tol.cancel_activity(activity_id, target_machine=host_id)
                            else:
                                connection.tol.cancel_activity(activity_id)
                        Log.write('Unprotect canceled.')
                        delete_plan_artifacts(connection, host_id, cplan_id)
                        raise
                    Log.write(result)
                    Log.write('done')

                    delete_plan_artifacts(connection, host_id, cplan_id)
                else:
                    Log.write('Plan not found. Skipped.')

                    delete_plan_artifacts(connection, host_id, cplan_id)
            else:
                Log.write('skipped')
        else:
            Log.write('skipped')

    if args.plan_id and args.fix_centralized_protection:
        plan = plans[0]
        is_centralized = (plan.Origin.ref == 2)
        cprotection = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::CentralizedProtection', '.ID', args.plan_id))
        if cprotection:
            Log.write('Gtob::Dto::CentralizedProtection already exist')
        else:
            if not is_centralized:
                Log.write('Gtob::Dto::CentralizedProtection can be created for centralized plans only')
            else:
                Log.write('Do you want to create missing Gtob::Dto::CentralizedProtection?(y/n)')
                if ask_user():
                    fix_centralized_protection(connection, args.plan_id)
                    Log.write('done')


def list_tenants(connection):
    Log.write('[---List of tenants that matches \'{}\'---]'.format(args.tenant_name))
    objects = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_like('Tenants::HierarchyNode', '.Name', args.tenant_name))

    table = prettytable.PrettyTable(["Name", "ID", "Locator", "Kind"])
    table.align["Name"] = "l"
    table.padding_width = 1

    for o in objects:
        if args.extra:
            Log.write(o)
        o_name = '\'{0}\''.format(o.Name.ref)
        o_id_str = '{0}'.format(o.ID.ref)
        o_locator_str = '{0}'.format(o.Locator.ref)
        o_kind_str = '{0}'.format(o.Kind.ref)
        table.add_row([o_name, o_id_str, o_locator_str, o_kind_str])
    Log.write(table.get_string(sortby="Name"))
    Log.write('')


def handle_remove_object_request(connection, spec):
    if args.delete:
        print('Do you want to delete object by spec(y/n):\n\'{0}\'?'.format(spec.pattern))
        if ask_user():
            connection.dml.delete(pattern=spec.pattern)
            print('done')
        else:
            print('skipped')


def fix_centralized_protections(connection, plans):
    centralized_plan_found = False
    for plan in plans:
        try:
            # Process centralized plans onnly
            if plan.Origin.ref != 2:
                continue

            centralized_plan_found = True
            cprotection_spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::CentralizedProtection', '.ID', plan.ID.ref)
            cprotection = acrobind.select_object(connection, cprotection_spec)

            if cprotection:
                Log.write('Gtob::Dto::CentralizedProtection for plan \'{}\' already exists, skipping'.format(plan.ID.ref))
            else:
                fix_centralized_protection(connection, plan.ID.ref)
        except Exception as e:
            error_log(format_backtrace(e))

    if not centralized_plan_found:
        Log.write('There are no centralized plans for current account, nothing to fix')
    Log.write('done')


def check_plan_list(connection):
    Log.write('[---Checking plans---]')
    plans = []

    ids = [
'9248C82C-3848-430B-A74E-A2B3490112B9',
'DC5BB2CF-F645-43A1-9F6D-F77EAC8DABCA',
'099E59F7-856B-47D6-B6C6-87F61B25E907',
'F1EFC07B-B6F7-45A6-8B0B-439ADBBCD73E',
'74C6AFDB-682D-454F-BCCA-F36656EBF991',
'0FD9477E-69ED-42C8-B7BE-B7F25C76814A',
'29FE3C53-C0BB-47B4-87E7-269030FBF754',
'5B01139F-97CF-4957-969D-AFFBA5FB2A13',
'D7DF4A24-00BB-407F-9AA1-A54476953466',
'8434793F-9745-4855-A38F-53F8F42B6674',
'7D0F79F7-0551-4154-A463-D689514D14F6',
'C382E30D-66B2-41D9-A9EA-7AA4C08405F7',
'7D5EA15A-F928-441E-960F-CAE5EBE96603',
'C47E4AB4-9D37-41AF-9C1D-4681A96892CC',
'127F8D2C-A4B2-4FED-9A52-0EBBA608D25B',
'F77882CF-4E45-4A95-B3E8-6061E7BE5A98',
'60B724D9-B0A2-4916-91E7-C31C72D31DDE',
'03E1BBA6-1F6C-4446-926D-E5D997E412A6',
'4F8F9FCE-299D-4493-8E97-4F98299D15BD',
'25921214-FF3C-4A4B-BA0E-D50D2E994E9E',
'26FE004C-4773-4D9E-9F55-257D3F8E10FA',
'807BDBD3-DDD1-4EEB-BC99-5C6B63453BBD',
'99A1B1C0-089F-490F-8865-DE38A82CF5BC',
'88C98A57-0537-4F55-A5E9-E21E3999516D',
'19E90A70-1BAB-4929-8BB2-2038AF58F718',
'998EE36E-4973-496D-B8A2-B14AA3ECD526',
'1584C104-F6B1-42A4-A62B-D1B1DDB23330',
'34742B08-07EE-488D-BB8C-67CE062EDC67',
'EB333349-4FA2-4822-ADBC-59E129AD02A3',
'3D382519-9E62-42A4-A302-EB42AB493856',
'34D86CC4-76B8-43C2-B87B-F29D660A3F1F',
'CF9B8B40-9AA5-450A-8A50-6C8987733558',
'49791B80-D074-488D-9578-A8B4FC81F483',
'E65223EC-BDAB-4EE0-BA8A-55EDF94AC0D7',
'952B2C76-B4AF-4636-B845-EDA9E7322940',
'CF7BFC0C-F2D0-424D-A4EA-2E36EC5604B5',
'7547E6FF-AE84-4EB4-A57A-E9BCB51AE8E6',
'1E150441-5993-45CF-85DA-100738092AEC',
'05FAB142-3952-4DB0-83A4-8140FEFEF498',
'DC7A5FE6-0E4D-4906-A990-0E3EED7FEF0C',
'BDDF59BE-4CB7-46F4-9B28-29EC5F735394',
'29DBFE77-7B92-4AEE-9633-F7A4417AF560',
'7518FB93-56F0-418D-AC70-645D0A005302',
'F9FF7DA1-D21D-459F-8ABA-D1C51C93ED04',
'13215CC3-10C2-43A6-B773-CABEE0E9BA0C',
'730B3DA3-E118-4C1A-8C12-2F4F9D246C83',
'781AF1F6-0EC4-48CD-A717-B07CEF53CF1A',
'1E48C701-548C-4D7D-9B1D-3E06D99AA86E'
    ]
    for id in ids:
        pattern = [
          ('^Is', 'string', 'Gtob::Dto::ProtectionPlan'),
          ('.ID', 'guid', id)
        ]
        object = acrobind.select_object(connection, acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern)))
        if object is not None:
            plans.append(object)

    check_plan_list_internal(connection, plans)


def check_plan_list_internal(connection, plans):

    plan_table = prettytable.PrettyTable(["Index", "Name/Instance", "ID", "Agents", "Status", "Tenant"])
    plan_table.align["Name/Instance"] = "l"
    plan_table.align["Status"] = "l"
    plan_table.padding_width = 1

    is_centralized = False
    p_owner = None
    p_tenant = None
    p_id = None
    affected_machines_str = '-'
    affected_machines_ids_str = None
    centralized_plan_id_str = '-'
    cprotection = None


    index = 0
    for p in plans:
        if args.extra:
            Log.write(p)
        p_id = p.ID.ref
        p_id_str = '{0}'.format(p_id)
        p_origin = origin_to_str(p.Origin.ref)
        p_name = '\'{0}\'({1})'.format(p.Name.ref, p_origin)

        p_owner = get_optional(p, "OwnerID")

        if p.Origin.ref == 2: #'CENTRALIZED'
            is_centralized = True

            affected_machine_count = None
            cprotection = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::CentralizedProtection', '.ID', p_id))

            if not cprotection:
                Log.write('Missing centralized protection for plan \'{0}\'!'.format(p_id))
                continue

            if args.extra:
                print(cprotection)

            affected_machines_str = 'MISSING'
            if 'AffectedMachines' in cprotection:
                affected_machines_str = '{0}'.format(len(cprotection.AffectedMachines))

                for (number, affected_machine) in cprotection.AffectedMachines:
                    if affected_machines_ids_str:
                        affected_machines_ids_str = '{0}, {1}'.format(affected_machines_ids_str, affected_machine.ref)
                    else:
                        affected_machines_ids_str = '{0}'.format(affected_machine.ref)
        else:
            centralized_plan_id_str = '{0}'.format(p.CentralizedProtectionPlan.ref)

        p_source = ''
        for tname, tunit in p.traits:
            if tname == 'Source':
                p_source = '{0}'.format(tunit.ref)
                break

        p_tenant = get_tenant_string(p, full_format=True)
        t = get_tenant_string(p, full_format=False)


        items = []
        if is_centralized:
            items = check_cplan_related_item_protections(connection, p_id)
        else:
            items = check_plan_related_item_protections(connection, p_id)

        plan_table.add_row([index, p_name, p_id_str, affected_machines_str, '', t])
        index += 1
        for item in items:
            #item['lfi_status'] = None
            #item['lfi_result'] = None
            #item['lfi_time'] = None
            #item['ns_time'] = None
            #item['lsi_time'] = None
            #item['cpi'] = '-'
            #item['lpi'] = '-'
            #item['legacy_pi'] = '-'

            errors_string = ''
            errors_count = len(item['lfi_errors'])
            warnings_string = ''
            warnings_count = len(item['lfi_warnings'])
            is_ok = True
            for error in item['lfi_errors']:
                if errors_string != '':
                    errors_string += '\n{}'.format(errors_string)
                else:
                    errors_string = error

            for warning in item['lfi_warnings']:
                if warnings_string != '':
                    warnings_string += '\n{}'.format(warnings_string)
                else:
                    warnings_string = warning

            last_result = ''
            if errors_count > 0:
                last_result = 'E({}): {}'.format(errors_count, errors_string)
            if warnings_count > 0:
                if errors_count > 0:
                    last_result += '\n'
                last_result += 'W({}): {}'.format(warnings_count, warnings_string)

            if errors_count == 0 and warnings_count == 0:
                last_result = 'OK'
            elif args.parameter1 == 'id':
                last_result = 'E:{},W:{}'.format(errors_count, warnings_count)
            status = '{}\nLFT: {}\nNST: {}'.format(last_result, item['lfi_time'], item['ns_time'])
            #item_tenant_str = '{}({})'.format(item['tenant_id'], item['tenant_name'])
            item_tenant_str = '{}'.format(item['tenant_id'])

            plan_table.add_row([index, '  |- {}'.format(item['instance_id']), item['id'], item['host_id'], status, item_tenant_str])
            index += 1

    #Log.write(plan_table.get_string(sortby="Index", fields=["Name/Instance", "ID" "Agents", "Status", "Tenant"]))
    if args.parameter1 == 'id':
        Log.write(plan_table.get_string(sortby="Index", fields=["Name/Instance", "ID", "Agents", "Status", "Tenant"]))
    else:
        Log.write(plan_table.get_string(sortby="Index", fields=["Name/Instance", "Status", "Tenant"]))
    #print(plan_table)



def check_tenant(connection):
    Log.write('[---Checking tenant \'{}\'---]'.format(args.tenant_id))

    tenant_spec = acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', args.tenant_id)
    t = acrobind.select_object(connection, tenant_spec)
    if args.extra:
        Log.write(t)

    if not t:
        Log.write('No such tenant.')
        return

    t_name = '\'{0}\'(ID:\'{1}\', Locator: \'{2}\', Kind: \'{3}\')'.format(t.Name.ref, t.ID.ref, t.Locator.ref, t.Kind.ref)
    Log.write('Tenant match: {0}'.format(t_name))

    handle_remove_object_request(connection, tenant_spec)

    if args.update:
        is_arr = [None, 'InstanceManagement::Instance', 'MachineManagement::Machine', 'Tol::History::Plain::Activity']
        base_pattern = [
            ('.Tenant.ID', 'string', t.ID.ref)
        ]
        raw_diff = acrort.plain.UnitDiff()
        raw_diff.add_patch('.Tenant.Name', t.Name.ref)
        for is_str in is_arr:
            pattern = base_pattern.copy()
            if is_str:
                pattern.append(('^Is', 'string', is_str))
            connection.dml.update(pattern=acrort.plain.Unit(flat=pattern), raw_diff=raw_diff)
        Log.write('All tenant sections is successfully updated.')

    if args.running_activities:
        pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.Tenant.ID', 'string', '{}'.format(args.tenant_id)),
            ('.State', 'dword', 0),
            ('.State^Less', 'dword', 5)]
        spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern))
        check_running_activities(connection, spec)

    if args.machine_statistics:
        stat = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('MachineManagement::Statistics', '.Tenant.ID', args.tenant_id))
        Log.write(stat)

    msp_machine_table = prettytable.PrettyTable(["AgentID", "OwnerID", "IsEnabled", "PublicKey"])
    msp_machine_table.align["AgentID"] = "l"
    msp_machine_table.padding_width = 1

    machine_table = prettytable.PrettyTable(["Name", "ID", "Status", "InsideVirtual", "Tenant"])
    machine_table.align["Name"] = "l"
    machine_table.padding_width = 1

    plan_table = prettytable.PrettyTable(["Name", "ID", "Origin", "Source", "Tenant"])
    plan_table.align["Name"] = "l"
    plan_table.padding_width = 1

    tenants = []
    if t.Kind.ref != -1:
        Log.write('Checking subtenants of group...')
        pattern = [
            ('^Is', 'string', 'Tenants::HierarchyNode'),
            ('.Locator', 'string', ''),
            ('.Locator^DirectRelative', 'string', t.Locator.ref)
        ]
        tenants.extend(acrobind.select_objects(connection, acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern))))
    else:
        tenants.append(t)

    all_plans = []
    for t in tenants:
        if t.ID.ref != args.tenant_id:
            t_name = '\'{0}\'(ID:\'{1}\', Locator: \'{2}\', Kind: \'{3}\')'.format(t.Name.ref, t.ID.ref, t.Locator.ref, t.Kind.ref)
            Log.write('Subtenant: {0}'.format(t_name))

        msp_machines = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.OwnerID', t.ID.ref))
        machines = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_string_property('MachineManagement::Machine', '.Tenant.ID', t.ID.ref))
        plans = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_string_property('Gtob::Dto::ProtectionPlan', '.Tenant.ID', t.ID.ref))
        all_plans.extend(plans)

        add_tenant_description(connection, t, msp_machine_table, msp_machines, machine_table, machines, plan_table, plans)

    Log.write(msp_machine_table)
    Log.write('')
    Log.write(machine_table)
    Log.write('')
    Log.write(plan_table.get_string(sortby="Name"))
    Log.write('')

    if args.fix_centralized_protection:
        Log.write('Do you want to fix Gtob::Dto::CentralizedProtection objects for account and its children?(y/n)')
        if not ask_user():
            return
        fix_centralized_protections(connection, all_plans)

    if args.check_multitenancy:
        Log.write('Check virtual instances with mixed ownership...')
        instances = acrobind.select_objects(connection, acrobind.create_viewspec_by_is_and_string_property('InstanceManagement::Instance', '.Tenant.ID', t.ID.ref))

        if not instances:
            Log.write('Tenant doesn\'t have virtual instances')
            return

        all_vcenters = set()
        all_hosts = set()

        hosts_to_select = set()
        vcenters_to_select = set()

        already_selected = {instance.ID.ref for instance in instances}

        # Collect IDs of hosts and vcenters referened in virtual machines and servers
        for instance in instances:
            instance_id = instance.ID.ref
            instance_type = INSTANCE_TYPES.get(instance.Type.ref)

            if instance_type == 'TYPE_VIRTUAL_MACHINE':
                if instance.get_branch('.Parameters.Server', None) is not None:
                    host_id = instance.Parameters.Server[0].ref
                    if host_id not in already_selected:
                        hosts_to_select.add(host_id)
                    all_hosts.add(host_id)

            if instance_type == 'TYPE_VIRTUAL_SERVER':
                all_hosts.add(instance.ID.ref)
                if instance.get_branch('.Parameters.VirtualCenterUUID', None) is not None:
                    vcenter_id = instance.Parameters.VirtualCenterUUID[0].ref
                    if vcenter_id not in already_selected:
                        vcenters_to_select.add(vcenter_id)
                    all_vcenters.add(vcenter_id)

            if instance_type == 'TYPE_VIRTUAL_CENTER':
                all_vcenters.add(instance.ID.ref)

        def values_pattern(property, values):
            return [
                ('^Is', 'string', 'InstanceManagement::Instance'),
                (property, 'guid', '00000000-0000-0000-0000-000000000000'),
                (property + '^ValueIn', 'complex_trait', [
                    ('', 'array', [[('', 'guid', value)] for value in values])
                ]),
            ]

        def searchable_values_pattern(property, values):
            return [
                ('^Is', 'string', 'InstanceManagement::Instance'),
                (property, 'array', []),
                (property + '^HasIntersection', 'complex_trait', [
                    ('', 'array', [[('', 'string', str(value))] for value in values]),
                ])
            ]

        instances = []

        # Select all vcenters referenced by tenant hosts
        if vcenters_to_select:
            instances.extend(acrobind.select_objects(connection, acrobind.create_viewspec_by_pattern(values_pattern('.ID', vcenters_to_select))))
        # Select all hosts that have reference to all found vcenters
        if all_vcenters:
            pattern = searchable_values_pattern('.Parameters.VirtualCenterUUID', all_vcenters)
            hosts_by_vcenters = acrobind.select_objects(connection, acrobind.create_viewspec_by_pattern(pattern))

            for host in hosts_by_vcenters:
                host_id = host.ID.ref
                all_hosts.add(host_id)
                if host_id in hosts_to_select:
                    hosts_to_select.remove(host_id)

            instances.extend(hosts_by_vcenters)

        # Select all hosts referenced by tenant vms
        if hosts_to_select:
            instances.extend(acrobind.select_objects(connection, acrobind.create_viewspec_by_pattern(values_pattern('.ID', hosts_to_select))))
        # Select all vms that have reference to all found hosts
        if all_hosts:
            pattern = searchable_values_pattern('.Parameters.Server', all_hosts)
            vms = acrobind.select_objects(connection, acrobind.create_viewspec_by_pattern(pattern))
            instances.extend(vms)

        found = False
        for instance in instances:
            if instance.Tenant.ID.ref != args.tenant_id:
                if not found:
                    Log.write('List of virtual instances with mixed ownership:\n')
                    found = True
                Log.write('ID: %s' % instance.ID.ref)
                Log.write('Name: %s' % instance.FullPath.ref)
                Log.write('Type: %s' % INSTANCE_TYPES.get(instance.Type.ref))
                Log.write('Owner ID: %s' % instance.Tenant.ID.ref)
                Log.write('Owner Name: %s\n' % instance.Tenant.Name.ref)

        if not found:
            Log.write('All virtual instances belong to the same tenant')


def add_tenant_description(connection, t, msp_machine_table, msp_machines, machine_table, machines, plan_table, plans):
    for msp in msp_machines:
        if args.extra:
            print(msp)
        public_key = '{}'.format(msp.PublicKey.ref) if 'PublicKey' in msp else 'Missing'
        msp_machine_table.add_row(['{}'.format(msp.AgentID.ref), '{}'.format(msp.OwnerID.ref), '{}'.format(msp.IsEnabled.ref), public_key])

    for m in machines:
        if args.extra:
            Log.write(m)
        tenant_info = get_tenant_string(m, full_format=False)
        is_inside_virtual = ''
        if 'IsInsideVirtual' in m.Info:
            is_inside_virtual = '{}'.format(m.Info.IsInsideVirtual.ref)
        machine_table.add_row([m.Info.Name.ref, '{}'.format(m.ID.ref), to_machine_status(m.Status.ref), is_inside_virtual, tenant_info])

    for p in plans:
        if args.extra:
            Log.write(p)
        p_name = '\'{0}\''.format(p.Name.ref)
        p_id = p.ID.ref
        p_id_str = '{0}'.format(p_id)
        p_origin = origin_to_str(p.Origin.ref)
        p_source = ''
        for tname, tunit in p.traits:
            if tname == 'Source':
                p_source = '{0}'.format(tunit.ref)
                break

        t_name = get_tenant_string(p, full_format=False)
        plan_table.add_row([p_name, p_id_str, p_origin, p_source, t_name])

def get_token(settings):
    Log.write("\nGetting token")
    cfg = settings.config
    key_secret = "{0}:{1}".format(
        cfg["client_id"], cfg["client_secret"]).encode("ascii")
    b64_encoded_key = base64.b64encode(key_secret).decode("ascii")

    auth_headers = {
        "Authorization": "Basic {0}".format(b64_encoded_key),
        "Content-Type": "application/x-www-form-urlencoded; charset=utf-8"
    }

    auth_data = {
        "grant_type": "client_credentials"
    }

    auth_resp = requests.post(
        cfg["idp_address"] + "/idp/token", headers=auth_headers, data=auth_data)

    auth_info = auth_resp.json()

    return auth_info["access_token"], auth_info["token_type"]


def read_config(path_to_config=""):
    settings = TokenSettings()
    
    def getElementsByTagNameDirect(dom, elementname):
      for node in dom.childNodes:
          if node.nodeName == elementname:
              return node
      return None

    def verify_customized_path(config_path):
        if os.path.exists(config_path):
            return config_path
        
        ## client installed the on-premise solution in a customized location
        else:
            acronis_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
            net_config_path = os.path.join(acronis_dir, "AMS", "net.config")
            
            if os.path.exists(net_config_path):
                return net_config_path
            else:
                raise FileNotFoundError("Reading Config Failed. '{0}' is not found".format(net_config_path))

    def is_msp():
        return os.path.exists(CLOUD_CONFIG_PATH) and os.path.exists(os.path.join(os.path.dirname(CLOUD_CONFIG_PATH), "msp_ams.config"))

    def get_config_path(config_path):
        os_type = platform.system()
        if config_path != "":
            return [config_path, config_path, os_type]
        if is_msp():
            return [CLOUD_CONFIG_PATH, CLOUD_CONFIG_PATH, os_type + "(msp cloud)"]
        if "Windows" in os_type:
            return [verify_customized_path(DEFAULT_WIN_CONFIG_PATH), WIN_SECRET_CONFIG_PATH, os_type]

        return [verify_customized_path(DEFAULT_LINUX_CONFIG_PATH), LINUX_SECRET_CONFIG_PATH, os_type]

    config_paths = get_config_path(path_to_config)
    Log.write("\nReading idp address config '{}', client secret config '{}', os_type '{}' ".format(*config_paths))

    config_dom = parse(config_paths[0])

    bm_dom = config_dom.getElementsByTagName("backup_manager")[0]
    bm_url_dom = bm_dom.getElementsByTagName("url")[0]
    settings.bm_url = bm_url_dom.childNodes[0].data

    tm_dom = config_dom.getElementsByTagName("task_manager")[0]
    tm_url_dom = tm_dom.getElementsByTagName("url")[0]
    settings.tm_url = tm_url_dom.childNodes[0].data

    settings.cred_url = 'http://credentials-store.service.consul'
    settings.acc_url = 'http://appaccountsrv.service.consul:30675/bc'

    cs_dom = config_dom.getElementsByTagName("cred_store")[0]
    cs_url_dom = cs_dom.getElementsByTagName("url")[0]
    settings.cred_url = cs_url_dom.childNodes[0].data

    acc_dom = config_dom.getElementsByTagName("account_server")[0]
    acc_url_dom = acc_dom.getElementsByTagName("url")[0]
    settings.acc_url = acc_url_dom.childNodes[0].data

    
    auth_dom = getElementsByTagNameDirect(config_dom.childNodes[0],"auth")
    
    idp_address_dom = auth_dom.getElementsByTagName("idp_address")[0]
    idp_address = idp_address_dom.childNodes[0].data

    ## Parse authentication token
    if not "msp cloud" in config_paths[2]:
        secret_config_dom = parse(config_paths[1])
        auth_dom = getElementsByTagNameDirect(secret_config_dom.childNodes[0],"auth")

    client_id_dom = auth_dom.getElementsByTagName("client_id")[0]
    client_id = client_id_dom.childNodes[0].data

    client_secret_dom = auth_dom.getElementsByTagName("client_secret")[0]
    client_secret = client_secret_dom.childNodes[0].data

    settings.config = dict({
        "idp_address": idp_address,
        "client_id": client_id,
        "client_secret": client_secret
    })

    return settings

def process_response(resp, failmsg, successmsg="Request succeeded"):
    if resp is None:
        Log.write(failmsg +". Reason: Response is None")
        return False
    elif resp.ok:
        Log.write(successmsg)
        return True
    else:
        Log.write(failmsg +". Reason: Code '{0}' Reason '{1}' Text '{2}'".format(resp.status_code, resp.reason, resp.text))
        return False



def quotas_reconcile(connection):
    def tail(file):
        while True:
            line = file.readline().strip()
            if not line:
                time.sleep(0.1)
                continue
            yield line

    def create_request():
        Log.write('Create request for quotas reconsiling.')
        request = acrort.plain.Unit(flat=[
            ('.Tenant.ID', 'string', args.tenant_id),
            ('.Tenant.ID^PrimaryKey', 'nil', None),
            ('.Tenant.ID^Type', 'string', 'Gtob::Protection::ResourceUsage::ForceAcquireRequest'),
            ('^Is', 'string', 'Gtob::Protection::ResourceUsage::ForceAcquireRequest')
        ])
        connection.dml.create(request)

    log_file = open('/var/lib/Acronis/AMS/logs/resource-usage.0.log')
    log_file.seek(0, 2)

    create_request()

    error_count = 0
    for line in tail(log_file):
        col = line.split(' ')
        if len(col) < 5:
            continue
        col[4] = ' '.join(col[4:])
        col = col[:5]

        reconcile_prefix = '[Reconcile'
        start_line = '[Reconcile] account: \'{}\'. Start.'.format(args.tenant_id)
        finish_line = '[Reconcile] account: \'{}\'. Finish.'.format(args.tenant_id)

        if col[4] == start_line:
            Log.write(' '.join([col[0], col[1], col[4]]))
        if reconcile_prefix in col[4] and col[3][0] == 'E':
            Log.write(' '.join([col[0], col[1], col[4]]))
            error_count += 1
        if col[4] == finish_line:
            Log.write(' '.join([col[0], col[1], col[4]]))
            break
    Log.write('\nScript finished. Error count: \'{}\''.format(error_count))




def _process_filters(filters):
    formatted_str = ""
    if not filters is None and isinstance(filters, dict) and len(filters) > 0:
        keys = list(filters)
        formatted_str = "?"
        for i in range(len(filters)):
            formatted_str += keys[i] + "=" + filters[keys[i]]
            if i < len(filters) - 1:
                formatted_str += "&"

    return formatted_str


def _validate_UUID(uuid_str):
    try:
        UUID(uuid_str)
        return True
    except Exception:
        return False


def _create_grpm_policy_selection(settings, access_token, token_type, policy_ids, tenant_id, log_name=""):
    log_name = inspect.stack()[1][3] if log_name == "" else log_name
    headers = dict({
        "X-Apigw-Tenant-Id": "{0}".format(tenant_id),
        "Authorization": "{0} {1}".format(token_type, access_token),
        "Content-Type": "application/json; charset=utf-8"
    })
    Log.write("\n[{0}] Creating policy selection with policy_ids '{1}' and tenant_id '{2}'".format(log_name, policy_ids, tenant_id))
    return requests.post(settings.bm_url + '/api/policy_management/v2/policy_selections',
                                    headers=headers, timeout=30, json={"items": policy_ids})


def _get_grpm_policy_selection(settings, access_token, token_type, policy_selection, tenant_id, log_name=""):
    log_name = inspect.stack()[1][3] if log_name == "" else log_name
    headers = dict({
        "X-Apigw-Tenant-Id": "{0}".format(tenant_id),
        "Authorization": "{0} {1}".format(token_type, access_token),
        "Content-Type": "application/json; charset=utf-8"
    })
    Log.write("\n[{0}] Getting policy selection with policy_selection id '{1}' and tenant_id '{2}'".format(log_name, policy_selection, tenant_id))
    return requests.get(settings.bm_url + '/api/policy_management/v2/policy_selections/' + policy_selection,
                                    headers=headers, timeout=30)


def _create_and_check_poilcy_selection(settings, access_token, token_type, policy_ids, tenant_id, log_name=""):
    log_name = inspect.stack()[1][3] if log_name == "" else log_name
    policy_selection = ""
    try:
        policy_selection_resp = _create_grpm_policy_selection(settings, access_token, token_type, policy_ids, tenant_id, log_name)
        if not process_response(policy_selection_resp, "\n[{0}] Failed to get policies(create policy_selection)".format(log_name)):
            raise ValueError()

        policy_selection = policy_selection_resp.json()["id"]
        if len(policy_selection) == 0 or not _validate_UUID(policy_selection):
            raise ValueError("Get policies(create policy_selection) response is not valid '{0}'".format(policy_selection))
        
        get_policy_selection_resp = _get_grpm_policy_selection(settings, access_token, token_type, policy_selection, tenant_id, log_name)
        if not process_response(get_policy_selection_resp, "\n[{0}] Failed to get policies(get policy_selection)".format(log_name)):
            raise ValueError()

        get_policy_selection_resp = get_policy_selection_resp.json()
        saved_policy_ids = get_policy_selection_resp["items"]
        saved_tenant_id = get_policy_selection_resp["tenantID"]
        if len(saved_policy_ids) == 0 or saved_tenant_id != tenant_id:
            raise ValueError("Get policies(get policy_selection) saved_policy_ids is empty OR saved_tenant_id '{0}' is not the same as input '{1}'".format(saved_tenant_id, tenant_id))
        
        if set(policy_ids) != set(saved_policy_ids):
            raise ValueError("Get policies(get policy_selection) input policy ids '{0}' are not the same as saved policy ids '{1}'".format(policy_ids, saved_policy_ids))
    
    except Exception as e:
        Log.write("\n[{0}] - Failed. _create_and_check_poilcy_selection failed with policy_id '{1}' and tenant_id '{2}'. Reason: '{3}'".format(log_name, policy_ids, tenant_id, e))
        return False
    
    return policy_selection


def _get_grpm_policies_with_filter(settings, access_token, token_type, filters, log_name=""):
    log_name = inspect.stack()[1][3] if log_name == "" else log_name
    if not isinstance(filters, dict):
        Log.write("\n[{0}] _get_grpm_policies_with_filter failed. The input filters is not in type dict. '{1}'".format(log_name, filters))
        return None
    if not "limit" in filters:
        filters.update({"limit":"1000"})
    fomatted_query = _process_filters(filters)

    headers = dict({
        "X-Apigw-Tenant-Id": "{0}".format(BM_MASTER_TENANT_ID),
        "Authorization": "{0} {1}".format(token_type, access_token),
        "Content-Type": "application/json; charset=utf-8"
    })
    Log.write("\n[{0}] Getting policies with filter '{1}' and tenant '{2}'".format(log_name, filters, BM_MASTER_TENANT_ID))
    return requests.get(settings.bm_url + '/api/policy_management/v2/policies{0}'.format(fomatted_query),
                                    headers=headers, timeout=30)


def _get_grpm_single_policy_with_filter(settings, access_token, token_type, policy_id, filters, log_name=""):
    log_name = inspect.stack()[1][3] if log_name == "" else log_name
    if not isinstance(filters, dict):
        Log.write("\n[{0}] _get_grpm_policies_with_filter failed. The input filters is not in type dict. '{1}'".format(log_name, filters))
        return None
    if not "limit" in filters:
        filters.update({"limit":"1000"})
    fomatted_query = _process_filters(filters)

    headers = dict({
        "X-Apigw-Tenant-Id": "{0}".format(BM_MASTER_TENANT_ID),
        "Authorization": "{0} {1}".format(token_type, access_token),
        "Content-Type": "application/json; charset=utf-8"
    })
    Log.write("\n[{0}] Getting policy with id {3} filter '{1}' and tenant '{2}'".format(log_name, filters, BM_MASTER_TENANT_ID, policy_id))
    return requests.get(settings.bm_url + '/api/policy_management/v2/policies/{0}{1}'.format(policy_id, fomatted_query),
                                    headers=headers, timeout=30)


def list_machines(connection):
    print('[---List of machines that match name \'{}\'---]'.format(args.machine_name))
    machine_spec = acrobind.create_viewspec_by_is_and_like('MachineManagement::Machine', '.Info.Name', args.machine_name)
    machines = acrobind.select_objects(connection, machine_spec)

    table = prettytable.PrettyTable(["Name", "ID", "Tenant"])
    table.align["Name"] = "l"
    table.padding_width = 1

    for m in machines:
        if args.extra:
            Log.write(m)
        m_name = '\'{0}\''.format(m.Info.Name.ref)
        m_id = m.ID.ref
        m_id_str = '{0}'.format(m_id)
        t = get_tenant_string(m)
        table.add_row([m_name, m_id_str, t])
    Log.write(table.get_string(sortby="Name"))
    Log.write('')


def list_instances(connection):
    Log.write('[---List of instances that match name \'{}\'---]'.format(args.instance_name))
    spec = acrobind.create_viewspec_by_is_and_like('InstanceManagement::Instance', '.FullPath', args.instance_name)
    objects = acrobind.select_objects(connection, spec)
    list_instances_internal(connection, objects)


def list_instances_internal(connection, objects):

    for o in objects:
        table = prettytable.PrettyTable(["Name", "Value", ""])

        table.align["Name"] = "l"
        table.align["Value"] = "l"
        table.padding_width = 1
        if args.extra:
            Log.write(o)
        o_name = '\'{0}\''.format(str(get_optional(o, 'FullPath')))
        o_id_str = '{0}'.format(o.ID.ref)
        o_host_id_str = '{0}'.format(o.HostID.ref)
        o_state_str = to_instance_state(get_optional(o, 'State'))
        o_backup_state_str = to_instance_state(o.BackupState.ref)
        o_status = '{0}'.format(o.Status.ref)
        o_backup_status_str = '{0}'.format(o.BackupStatus.ref)
        o_inside_virtual_str = 'MISSING'
        if 'HostInfo' in o:
            o_inside_virtual_str = '{0}'.format(get_optional(o.HostInfo, "IsInsideVirtual"))
        availability_str = 'MISSING'
        if 'Availability' in o:
            availability_str = '{0}'.format(to_machine_status(o.Availability.ref))
        last_backup_str = '-'
        if 'LastBackup' in o:
            last_backup_str = describe_time(o.LastBackup.ref)
        last_backup_try_str = '-'
        if 'LastBackupTry' in o:
            last_backup_try_str = describe_time(o.LastBackupTry.ref)
        next_backup_time_str = '-'
        if 'NextBackupTime' in o and 'Time' in o.NextBackupTime:
            next_backup_time_str = describe_time(o.LastBackup.ref)
        t = get_tenant_string(o, full_format=False)
        table.add_row(['Name', o_name, ''])
        table.add_row(['ID', o_id_str, ''])
        table.add_row(['HostID', o_host_id_str, ''])
        table.add_row(['State', o_state_str, ''])
        table.add_row(['Status', o_status, ''])
        table.add_row(['State(Backup)', o_backup_state_str, ''])
        table.add_row(['Status(Backup)', o_backup_status_str, ''])
        table.add_row(['LastBackup', last_backup_str, ''])
        table.add_row(['LastBackupTry', last_backup_try_str, ''])
        table.add_row(['NextBackupTime', next_backup_time_str, ''])
        table.add_row(['Availability', availability_str, ''])
        table.add_row(['InsideVirtual', o_inside_virtual_str, ''])
        table.add_row(['Tenant', t, ''])

        Log.write(table)
        Log.write('')


def check_instance(connection):
    Log.write('Checking instance \'{}\'...'.format(args.instance_id))

    local_spec = acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.ID', args.instance_id)
    Log.write('AMS instance:')
    object = acrobind.select_object(connection, local_spec)
    if object is not None:
        list_instances_internal(connection, [object])
    else:
        Log.write('Not found')
        return

    spec = acrobind.viewspec_apply_remote_host(local_spec, object.HostID.ref)
    Log.write('Agent instance:')
    try:
        objects = acrobind.select_objects(connection, spec)
        list_instances_internal(connection, objects)
    except Exception as e:
        if args.extra:
            Log.write('Failed to get instance from agent: {}'.format(e))
        else:
            Log.write('Failed to get instance from agent')
    items = check_instance_related_item_protections(connection, args.instance_id)
    #TODO:
    print(items)


def check_machine(connection):
    if not args.delete and args.break_connection and args.machine_id:
        drop_agent_connection(connection, args.machine_id)

    Log.write('[---Check machine with id \'{}\'---]'.format(args.machine_id))
    machine_spec = acrobind.create_viewspec_by_is_and_guid_property('MachineManagement::Machine', '.ID', args.machine_id)
    machine = acrobind.select_object(connection, machine_spec)

    if not machine:
        Log.write('Not found.')
        #return

    if args.extra:
        Log.write(machine)

    msp_machine = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(args.machine_id)))
    public_key = 'MISSING'
    is_enabled = 'MISSING'
    if msp_machine and 'PublicKey' in msp_machine:
        public_key = '{0}'.format(msp_machine.PublicKey.ref)
    if msp_machine and 'IsEnabled' in msp_machine:
        is_enabled = '{0}'.format(msp_machine.IsEnabled.ref)

    if args.extra:
        Log.write(msp_machine)

    machine_table = prettytable.PrettyTable(["Name", "Value", ""])

    machine_table.align["Name"] = "l"
    machine_table.align["Value"] = "l"
    machine_table.padding_width = 1

    name_str = '-'
    if machine and 'Info' in machine and 'Name' in machine.Info:
        name_str = '{}'.format(machine.Info.Name.ref)
    machine_table.add_row(['Name', name_str, ''])

    id_str = '-'
    if machine and 'ID' in machine:
        id_str = '{}'.format(machine.ID.ref)

    machine_table.add_row(['ID', id_str, ''])

    status_str = '-'
    if machine and 'Status' in machine:
        machine_statuses = {0: 'ONLINE (0)', 1: 'OFFLINE (1)'}
        if machine.Status.ref in machine_statuses:
            status_str = machine_statuses[machine.Status.ref]
        else:
            status_str = str(machine.Status.ref)

    machine_table.add_row(['Status', status_str, ''])
    machine_table.add_row(['PublicKey', public_key, ''])
    machine_table.add_row(['IsEnabled', is_enabled, ''])
    is_inside_virtual_str = '-'

    if machine and 'Info' in machine and 'IsInsideVirtual' in machine.Info:
        is_inside_virtual_str = '{}'.format(machine.Info.IsInsideVirtual.ref)

    machine_table.add_row(['IsInsideVirtual', is_inside_virtual_str, ''])
    machine_table.add_row(['Tenant', get_tenant_string(machine), ''])

    current_version = 'Unknown'
    try:
        if 'Info' in machine:
            current_version = machine.Info.Agents[0].Version.ref
    except:
        pass

    installed_agents = 'unknown'
    if machine and 'Info' in machine:
        if is_mobile_agent(machine.Info.Agents):
            installed_agents = 'mobile {}'.format(installed_agents)

        if is_ad_agent(machine.Info.Agents):
            installed_agents = 'ad {}'.format(installed_agents)

        if is_ati_agent(machine.Info.Agents):
            installed_agents = 'ati {}'.format(installed_agents)

        if is_win_agent(machine.Info.Agents):
            installed_agents = 'win {}'.format(installed_agents)

        if is_sql_agent(machine.Info.Agents):
            installed_agents = 'sql {}'.format(installed_agents)

        if is_esx_agent(machine.Info.Agents):
            installed_agents = 'esx {}'.format(installed_agents)

        if is_hyperv_agent(machine.Info.Agents):
            installed_agents = 'hyperv {}'.format(installed_agents)

        if is_exchange_agent(machine.Info.Agents):
            installed_agents = 'exchange {}'.format(installed_agents)

        if not installed_agents:
          Log.write(machine.Info.Agents)

    machine_table.add_row(['Current version', '{} | {}'.format(current_version, installed_agents), ''])

    update_desc = '-'
    if machine and 'UpdateState' in machine and 'UpdateIsAvailable' in machine.UpdateState:
        if machine.UpdateState.UpdateIsAvailable.ref:
            update_desc = '{}'.format(machine.UpdateState.UpdateVersion.ref)
    update_url = ''
    if machine and 'UpdateState' in machine and 'UpdateUrl' in machine.UpdateState:
        update_url = '{}'.format(machine.UpdateState.UpdateUrl.ref)
    machine_table.add_row(['Available update', '{}'.format(update_desc), update_url])
    Log.write(machine_table)

    if machine and 'Tenant' not in machine and args.fix:
        Log.write('Do you want to fix Tenant info for machine object with ID \'{0}\'(y/n)?'.format(args.machine_id))
        if ask_user():
            fix_machine_tenant(connection, args.machine_id)
            Log.write('done')
        else:
            Log.write('skipped')

    Log.write('Instances:')
    instances_spec = acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.HostID', args.machine_id)
    objects = acrobind.select_objects(connection, instances_spec)
    list_instances_internal(connection, objects)

    #check_caching_registration(connection)

    if args.running_activities:
        pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('^Source', 'string', '{}'.format(args.machine_id)),
            ('.State', 'dword', 0),
            ('.State^Less', 'dword', 5)]
        spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern))
        check_running_activities(connection, spec, args.machine_id)

    if args.reset_update:
        Log.write('Reseting update status for MachineManagement::Machine with ID {0}.'.format(args.machine_id))
        pattern = [
            ('.MachineIsProcessed', 'bool', False),
        ]
        diff_unit={'UpdateState': acrort.plain.Unit(flat=pattern)}
        #diff_unit={'Status': 1}
        connection.dml.update(pattern=acrobind.create_viewspec_by_is_and_guid_property('MachineManagement::Machine', '.ID', args.machine_id).pattern, diff=diff_unit)

    if args.delete:
        do_deletion(connection)


def ask_user():
    answer = sys.stdin.readline()
    if answer.startswith('y') or answer.startswith('Y'):
        return True
    else:
        return False

    return False

def wrap_error(error):
    if error == acrort.common.SUCCESS:
        return 'OK'
    return 'E'


def describe_time(time):
    return datetime.datetime.fromtimestamp(time).strftime('%Y-%m-%d %H:%M:%S')


def check_instance_related_item_protections(connection, instance_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.InstanceID', instance_id)
    return check_item_protection_objects_internal(connection, spec)

def check_plan_related_item_protections(connection, plan_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.Local.PlanID', plan_id)
    return check_item_protection_objects_internal(connection, spec)


def check_cplan_related_item_protections(connection, cplan_id):
    spec = acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.Centralized.PlanID', cplan_id)
    return check_item_protection_objects_internal(connection, spec)


def check_item_protection_objects_internal(connection, spec):
    ips = acrobind.select_objects(connection, spec)
    items = []
    if not (len(ips) > 0):
        return items

    for ip in ips:
        if args.extra:
            print(ip)

        item = {}
        item['tenant_id'] = get_tenant_string(ip, full_format=False)
        item['tenant'] = get_tenant_string(ip, full_format=True)
        item['tenant_name'] = ip.Tenant.Name.ref
        item['id'] = '{0}'.format(ip.ID.ref)
        item['instance_id'] = '{0}'.format(ip.InstanceID.ref)
        item['host_id'] = '-'
        if 'HostID' in ip:
            item['host_id'] = '{0}'.format(ip.HostID.ref)

        instance_spec = acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.ID', ip.InstanceID.ref)
        instance_spec = acrobind.viewspec_apply_mask(instance_spec, acrobind.create_mask3('.ID', '.FullPath', '.Type'))
        instance = acrobind.select_object(connection, instance_spec)
        if instance:
            #print(instance)
            item['instance_id'] = '{}({})'.format(instance.FullPath.ref, instance.Type.ref)

        item['lfi_status'] = None
        item['lfi_result'] = None
        item['lfi_result_full'] = None
        item['lfi_errors'] = []
        item['lfi_warnings'] = []
        item['lfi_time'] = None
        item['ns_time'] = None
        item['lsi_time'] = None
        item['cpi'] = '-'
        item['lpi'] = '-'
        item['legacy_pi'] = '-'

        if 'LastFinishInfo' in ip:
            item['lfi_status'] = '{0}'.format(ip.LastFinishInfo.Status.ref)
            item['lfi_result'] = '-'
            if 'CompletionResult' in ip.LastFinishInfo:
                item['lfi_result'] =  '{0}'.format(wrap_error(ip.LastFinishInfo.CompletionResult))
                item['lfi_result_full'] =  '{0}'.format(ip.LastFinishInfo.CompletionResult.ref)

            item['lfi_time'] =  '{0}'.format(describe_time(ip.LastFinishInfo.Time.ref))

            str_limit = 70
            if 'Errors' in ip.LastFinishInfo:
                for index, error in ip.LastFinishInfo.Errors:
                    full_error_str = '{}'.format(error.Error.ref)
                    last_error_pos = full_error_str.rfind('| error')
                    last_error_pos_2 = full_error_str.find('\n', last_error_pos)
                    error_str = full_error_str[last_error_pos:last_error_pos_2 if last_error_pos_2 - last_error_pos < str_limit else last_error_pos + str_limit]

                    last_function_pos = full_error_str.rfind('| $module:')
                    last_function_pos_2 = full_error_str.find('\n', last_function_pos)
                    function_str = full_error_str[last_function_pos:last_function_pos_2 if last_function_pos_2 - last_function_pos < str_limit else last_function_pos + str_limit]

                    #print(error_str)
                    #item['lfi_errors'].append('{}({})'.format(error_str, function_str))
                    item['lfi_errors'].append('{}'.format(error_str))

            if 'Warnings' in ip.LastFinishInfo:
                for index, warning in ip.LastFinishInfo.Warnings:
                    full_error_str = '{}'.format(warning.Error.ref)
                    last_error_pos = full_error_str.rfind('| error')
                    last_error_pos_2 = full_error_str.find('\n', last_error_pos)
                    error_str = full_error_str[last_error_pos:last_error_pos_2 if last_error_pos_2 - last_error_pos < str_limit else last_error_pos + str_limit]

                    last_function_pos = full_error_str.rfind('| $module:')
                    last_function_pos_2 = full_error_str.find('\n', last_function_pos)
                    function_str = full_error_str[last_function_pos:last_function_pos_2 if last_function_pos_2 - last_function_pos < str_limit else last_function_pos + str_limit]

                    #item['lfi_warnings'].append('{}({})'.format(error_str, function_str))
                    item['lfi_warnings'].append('{}'.format(error_str))

        if 'LastStartInfo' in ip and 'Time' in ip.LastStartInfo:
            item['lsi_time'] =  '{0}'.format(describe_time(ip.LastStartInfo.Time.ref))

        if 'NextBackupTime' in ip and 'Time' in ip.NextBackupTime:
            item['ns_time'] =  '{0}'.format(describe_time(ip.NextBackupTime.Time.ref))

        if 'Centralized' in ip:
            item['cpi'] = '{0}'.format(ip.Centralized.PlanID.ref)

        if 'Local' in ip:
            item['lpi'] = '{0}'.format(ip.Local.PlanID.ref)

        item['legacy_pi'] = '{0}'.format(ip.Plan.ref) if 'Plan' in ip else '-'

        items.append(item)
    return items


def do_deletion(connection):

    print('Do you want to delete MachineManagement::Machine related to this machine? (y/n)')
    if ask_user():
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('MachineManagement::Machine', '.ID', args.machine_id).pattern)
        print('deleted.')
    else:
        print('skipped.')

    if args.instance_id:
        print('Do you want to delete all Gtob::Dto::ItemProtection related to this machine? (y/n)')
        if ask_user():
            connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ItemProtection', '.InstanceID', args.instance_id).pattern)
            print('deleted.')
        else:
            print('skipped.')

    print('Do you want to delete Msp::AMS::Dto::Machine related to this machine? (y/n)')
    if ask_user():
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_string_property('Msp::AMS::Dto::Machine', '.AgentID', '{}'.format(args.machine_id)).pattern)
        print('deleted.')
    else:
        print('skipped.')


    print('Do you want to delete all InstanceManagement::Instance objects related to this machine? (y/n)')
    if ask_user():
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::InstanceAspect', '.Key.HostID', args.machine_id).pattern)
        connection.dml.delete(pattern=acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.HostID', args.machine_id).pattern)
        print('deleted.')
    else:
        print('skipped.')


def undeploy_local_plan(connection, pp_info, plan_id):
    try:
        host_id = pp_info[plan_id]['source']

        if not host_id:
            Log.write('Can\'t find host_id for plan {0}'.format(plan_id))
            return

        if 'status' in pp_info[plan_id] and pp_info[plan_id]['status'] != 0:
            Log.write('Can\'t undeploy plan {0} because agent ({1}) is OFFLINE'.format(plan_id, host_id))
            return

        Log.write('Trying to undeploy Gtob::Dto::ProtectionPlan ({0}) from host ({1})...'.format(plan_id, host_id), end='')

        arg = acrort.common.Guid(plan_id)
        if host_id:
            activity_id = connection.tol.launch_command(command='C006D24E-E6ED-494a-9789-237CD3A814E7', argument=arg, target_machine=host_id)
        else:
            activity_id = connection.tol.launch_command(command='C006D24E-E6ED-494a-9789-237CD3A814E7', argument=arg)

        try:
            if host_id:
                result = connection.tol.get_result(activity_id, target_machine=host_id)
            else:
                result = connection.tol.get_result(activity_id)
        except acrort.Exception as ex:
            if acrort.common.interrupt_sentinel:
                if host_id:
                    connection.tol.cancel_activity(activity_id, target_machine=host_id)
                else:
                    connection.tol.cancel_activity(activity_id)
            Log.write('canceled')
        Log.write('done')

        Log.write('Removing synced Gtob::Dto::ProtectionPlan ({0})...'.format(plan_id), end='')
        connection.dml.delete(pattern=acrobind.viewspec_apply_source(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.ID', plan_id), host_id).pattern)
        Log.write('done')

        Log.write('Removing Gtob::Dto::ItemProtection from host({0})...'.format(host_id), end='')
        bytes_arg = None
        with open("a_injection.py", "rb") as tol_command:
            bytes_arg = acrort.common.Blob(bytes=tol_command.read())

        run_request = [
            ('.Script.Location', 'string', "attachment:a_injection.py?delete_legacy_item_protections"),
            ('.Script.Argument', 'string', plan_id),
            ('.Script.Body', 'blob', bytes_arg)
        ]
        request_unit = acrort.plain.Unit(flat=run_request)
        activity_id = connection.tol.launch_command(command=acrort.remoting.RUN_SCRIPT_COMMAND_ID_BUSINESS, argument=request_unit, target_machine=host_id)
        result = connection.tol.get_result(activity_id, target_machine=host_id)
        Log.write('done')
    except Exception as e:
        Log.write('Error: {0}'.format(e))


def _init_counters(connection, data_cache, entry_id, spec):
    entry_id_internal = '_{}'.format(entry_id)
    data_cache[entry_id_internal] = {}
    data_cache[entry_id_internal]['wrong'] = 0
    data_cache[entry_id_internal]['ok'] = 0
    data_cache[entry_id_internal]['total'] = acrobind.count_objects_by_spec(connection, spec)
    data_cache[entry_id_internal]['counter_reset'] = data_cache[entry_id_internal]['total'] / 10
    data_cache[entry_id_internal]['counter'] = data_cache[entry_id_internal]['counter_reset']


def _update_counters_and_file(data_cache, entry_id, filename, wrong_item_id, error_string):
    entry_id_internal = '_{}'.format(entry_id)

    if error_string:
        data_cache[entry_id_internal]['wrong'] = data_cache[entry_id_internal]['wrong'] + 1
        with open(filename, "a") as myfile:
            myfile.write('{0}: {1}\n'.format(wrong_item_id, error_string))
    else:
        data_cache[entry_id_internal]['ok'] = data_cache[entry_id_internal]['ok'] + 1
    data_cache[entry_id_internal]['counter'] = data_cache[entry_id_internal]['counter'] - 1

    if not data_cache['specific_tenant'] and data_cache[entry_id_internal]['counter'] <= 0:
        Log.write('-', end='')
        data_cache[entry_id_internal]['counter'] = data_cache[entry_id_internal]['counter_reset']


def _do_check(connection, data_cache, entry_id, filename, spec, callback):
    entry_id_internal = '_{}'.format(entry_id)
    with open(filename, "w") as myfile:
        myfile.truncate()
    if not data_cache['specific_tenant']:
        Log.write('[----------][{}]'.format(data_cache[entry_id_internal]['total']))
        Log.write('[', end='')
    else:
        Log.write('Objects count: {}'.format(data_cache[entry_id_internal]['total']))
    start = time.time()
    acrobind.enumerate_objects(connection, spec, callback, error_log)
    if not data_cache['specific_tenant']:
        Log.write('-]')
    Log.write('OK: {0} WRONG: {1}. Elapsed: {2:.2f} s'.format(data_cache[entry_id_internal]['ok'], data_cache[entry_id_internal]['wrong'], time.time() - start))


def check_tenants_consistency(connection, data_cache):
    data_cache['tenants'] = {}
    spec = acrobind.create_viewspec_by_is('Tenants::HierarchyNode')
    filename = 'amsctl_wrong_tenants.txt'
    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'tenants', spec)

    def tenants_callback(connection, cache, file, object):
        tenant_id = '{0}'.format(object.ID.ref)

        parent_exists = False
        error_string = ''
        if 'ParentID' in object:
            parent_exists = True
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_parent_id_field')

        locator_exists = False
        if 'Locator' in object:
            locator_exists = True
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_locator')

        kind_exists = False
        if 'Kind' in object:
            kind_exists = True
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_kind')

        data_cache['tenants'][tenant_id] = {}
        data_cache['tenants'][tenant_id]['parent_exists'] = parent_exists
        data_cache['tenants'][tenant_id]['locator_exists'] = locator_exists
        data_cache['tenants'][tenant_id]['kind_exists'] = kind_exists
        data_cache['tenants'][tenant_id]['used'] = False

        _update_counters_and_file(data_cache, 'tenants', file, tenant_id, error_string)

    callback = lambda x: tenants_callback(connection, data_cache, filename, x)
    Log.write('Checking Tenants::HierarchyNode consistency. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'tenants', filename, spec, callback)


def check_orphan_msp_machines(connection, data_cache):
    data_cache['msp_machines'] = {}
    data_cache['to_delete']['msp_machines'] = []
    spec = acrobind.create_viewspec_by_is('Msp::AMS::Dto::Machine')
    filename = 'amsctl_wrong_msp_machines.txt'
    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Owner', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'msp_machines', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask4('.AgentID', '.IsEnabled', '.OwnerID', '.PublicKey'))

    def msp_machine_callback(connection, cache, file, object):
        host_id_str = '{}'.format(object.AgentID.ref)
        cache['msp_machines'][host_id_str] = {}
        cache['msp_machines'][host_id_str]['used'] = False
        cache['msp_machines'][host_id_str]['public_key_exists'] = False
        cache['msp_machines'][host_id_str]['is_enabled'] = False
        cache['msp_machines'][host_id_str]['is_enabled_field_exists'] = False
        cache['msp_machines'][host_id_str]['owner_id'] = ''
        cache['msp_machines'][host_id_str]['owner_id_field_exists'] = False
        cache['msp_machines'][host_id_str]['owner_exists'] = False

        error_string = ''
        if 'PublicKey' in object:
            cache['msp_machines'][host_id_str]['public_key_exists'] = True
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_public_key')

        if 'IsEnabled' in object:
            cache['msp_machines'][host_id_str]['is_enabled_field_exists'] = True
            cache['msp_machines'][host_id_str]['is_enabled'] = object.IsEnabled.ref
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_is_enabled')

        if 'OwnerID' in object:
            cache['msp_machines'][host_id_str]['owner_id_field_exists'] = True
            cache['msp_machines'][host_id_str]['owner_id'] = object['OwnerID'].ref
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_owner_id')

        if cache['msp_machines'][host_id_str]['owner_id'] in data_cache['tenants']:
            data_cache['tenants'][cache['msp_machines'][host_id_str]['owner_id']]['used'] = True
            cache['msp_machines'][host_id_str]['owner_exists'] = True
        else:
            error_string = '{0} {1}'.format(error_string, 'no_owner(d)')
            data_cache['to_delete']['msp_machines'].append(host_id_str)

        _update_counters_and_file(data_cache, 'msp_machines', file, host_id_str, error_string)

    callback = lambda x: msp_machine_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Msp::Agent::Dto::Machine. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'msp_machines', filename, spec, callback)


def is_mobile_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}':
            return True
    return False


def is_ad_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{9F148D12-3653-478B-A039-239BE36950AB}':
            return True
    return False


def is_ati_agent(agent_info):
    for index, a in agent_info:
        agent_type_id = a.Id.ref
        if agent_type_id == '{8397FC51-C78E-4E26-9860-6A4A8622DF7C}':
            return True
        #Home agent have no Version AgentInfo and have empty Id in AgentInfo
        if len(agent_info) == 1 and agent_type_id == '':
            return True
        else:
            return False
    return False


def is_win_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{BA262882-C484-479A-8D07-B0EAC55CE27B}':
            return True
    return False

def is_sql_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{C293F7DD-9531-4916-9346-12B0F3BFCCAA}':
            return True
    return False

def is_esx_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{EC3CF038-C08A-4341-82DF-F75133705F63}':
            return True
    return False

def is_hyperv_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{87671A98-2B47-4D4C-98FB-490CA111E7A7}':
            return True
    return False

def is_exchange_agent(agent_info):
    for index, a in agent_info:
        if a.Id.ref == '{30BAF589-C02A-463F-AB37-5A9193375700}':
            return True
    return False


def check_if_tenant_is_valid(object, data_cache, item_type, item_id):
    tenant_id = '-'
    data_cache[item_type][item_id]['has_tenant'] = False

    error_string = ''
    if 'Tenant' in object:
        tenant_id = object.Tenant.ID.ref
        data_cache[item_type][item_id]['has_tenant'] = True
    else:
        error_string = '{0} {1}'.format(error_string, 'missing_tenant_field')

    data_cache[item_type][item_id]['tenant'] = tenant_id

    if data_cache['resolve_links']:
        data_cache[item_type][item_id]['has_existing_tenant'] = False
        if data_cache[item_type][item_id]['has_tenant']:
            if tenant_id in data_cache['tenants']:
                data_cache[item_type][item_id]['has_existing_tenant'] = True
            else:
                error_string = '{0} {1}'.format(error_string, 'no_tenant(d)')
                data_cache['to_delete'][item_type].append(item_id)
        else:
            data_cache[item_type][item_id]['has_existing_tenant'] = True
    return error_string


def check_orphan_agents(connection, data_cache):
    data_cache['agents'] = {}
    data_cache['to_delete']['agents'] = []
    spec = acrobind.create_viewspec_machines_by_role(0)
    filename = 'amsctl_wrong_agents.txt'
    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'agents', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask4('.ID', '.Role', '.Tenant', '.Info.Agents'))

    def agents_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)

        data_cache['agents'][id_str] = {}
        data_cache['agents'][id_str]['used'] = False
        data_cache['agents'][id_str]['has_existing_msp_machine'] = False

        error_string = ''
        #Mobile agents don't have Msp::Agent::Dto::Configuration
        if not is_mobile_agent(object.Info.Agents):
            if id_str in data_cache['msp_machines']:
                data_cache['agents'][id_str]['has_existing_msp_machine'] = True
                data_cache['msp_machines'][id_str]['used'] = True
            else:
                error_string = '{0} {1}'.format(error_string, 'no_msp_machine')

        tenant_error = check_if_tenant_is_valid(object, data_cache, 'agents', id_str)
        if tenant_error:
            error_string = '{0} {1}'.format(error_string, tenant_error)

        _update_counters_and_file(data_cache, 'agents', file, id_str, error_string)

    callback = lambda x: agents_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan MachineManagement::Machine. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'agents', filename, spec, callback)


def check_orphan_instances(connection, data_cache):
    data_cache['instances'] = {}
    data_cache['to_delete']['instances'] = []
    spec = acrobind.create_viewspec_by_is('InstanceManagement::Instance')
    filename = 'amsctl_wrong_instances.txt'
    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)
    _init_counters(connection, data_cache, 'instances', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.HostID', '.Tenant'))

    def instance_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)

        data_cache['instances'][id_str] = {}
        data_cache['instances'][id_str]['has_host_id'] = False
        data_cache['instances'][id_str]['host_id'] = '-'
        data_cache['instances'][id_str]['has_existing_host'] = False
        data_cache['instances'][id_str]['has_existing_tenant'] = False
        data_cache['instances'][id_str]['has_tenant'] = False
        data_cache['instances'][id_str]['is_deprecated'] = False

        error_string = ''

        if is_deprecated_vm_instance(id_str):
            error_string = '{0} {1}'.format(error_string, 'deprecated_vm_instance(d)')
            data_cache['instances'][id_str]['is_deprecated'] = True
            data_cache['to_delete']['instances'].append(id_str)
        else:
            if 'HostID' in object:
                data_cache['instances'][id_str]['has_host_id'] = False
                data_cache['instances'][id_str]['host_id'] = '{}'.format(object.HostID.ref)
            else:
                error_string = '{0} {1}'.format(error_string, 'missing_host_id_field')

            host_id_str = data_cache['instances'][id_str]['host_id']
            if host_id_str in data_cache['agents']:
                data_cache['instances'][id_str]['has_existing_host'] = True
                data_cache['agents'][host_id_str]['used'] = True
            else:
                error_string = '{0} {1}'.format(error_string, 'no_host')

            tenant_error = check_if_tenant_is_valid(object, data_cache, 'instances', id_str)
            if tenant_error:
                error_string = '{0} {1}'.format(error_string, tenant_error)

        _update_counters_and_file(data_cache, 'instances', file, id_str, error_string)

    callback = lambda x: instance_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan InstanceManagement::Instance. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'instances', filename, spec, callback)


def check_orphan_plans(connection, data_cache):
    data_cache['plans'] = {}
    data_cache['to_delete']['plans'] = []
    spec = acrobind.create_viewspec_by_is('Gtob::Dto::ProtectionPlan')
    filename = 'amsctl_wrong_plans.txt'

    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'plans', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.Origin', '.Tenant'))

    def plan_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)

        data_cache['plans'][id_str] = {}
        data_cache['plans'][id_str]['origin'] = '-'

        error_string = ''

        if 'Origin' in object:
            data_cache['plans'][id_str]['status'] = object.Origin.ref
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_origin_field')

        tenant_error = check_if_tenant_is_valid(object, data_cache, 'plans', id_str)
        if tenant_error:
            error_string = '{0} {1}'.format(error_string, tenant_error)

        _update_counters_and_file(data_cache, 'plans', file, id_str, error_string)

    callback = lambda x: plan_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Gtob::Dto::ProtectionPlan. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'plans', filename, spec, callback)


def check_deployment_fact_consistency(connection, data_cache):
    data_cache['deployment_facts'] = {}
    data_cache['to_delete']['deployment_facts'] = []
    spec = acrobind.create_viewspec_by_is('Gtob::Protection::PlanDeploymentFact')
    filename = 'amsctl_wrong_deployment_facts.txt'

    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.PlanObject.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'deployment_facts', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.PlanObject.CentralizedProtectionPlan', '.PlanObject.Tenant'))

    def fact_callback(connection, cache, file, object):
        host_id_str = '{}'.format(object.ID.Host.ref)
        plan_id_str = '{}'.format(object.ID.Plan.ref)
        id_str = '{0}+{1}'.format(host_id_str, plan_id_str)

        data_cache['deployment_facts'][id_str] = {}
        data_cache['deployment_facts'][id_str]['plan'] = plan_id_str
        data_cache['deployment_facts'][id_str]['host'] = host_id_str
        data_cache['deployment_facts'][id_str]['need_redeploy'] = False
        data_cache['deployment_facts'][id_str]['exists'] = True

        source_id = acrobind.get_trait_value('Source', object)

        error_string = ''
        if source_id is None:
            error_string = '{0} {1}'.format(error_string, 'missing_source_trait')

        if source_id is not None and str(source_id) != host_id_str :
            error_string = '{0} {1}'.format(error_string, 'source_differes_from_host')

        if plan_id_str not in data_cache['plans']:
            error_string = '{0} {1}'.format(error_string, 'missing_plan(d)')
            data_cache['to_delete']['deployment_facts'].append(plan_id_str)

        if 'PlanObject' in object:
            tenant_error = check_if_tenant_is_valid(object.PlanObject, data_cache, 'deployment_facts', id_str)
            if tenant_error:
                error_string = '{0} {1}'.format(error_string, tenant_error)
        else:
            error_string = '{0} {1}'.format(error_string, 'missing_plan_object_field')

        _update_counters_and_file(data_cache, 'deployment_facts', file, id_str, error_string)

    callback = lambda x: fact_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Gtob::Protection::PlanDeploymentFact. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'deployment_facts', filename, spec, callback)


def check_protections_consistency(connection, data_cache):
    if args.parameter1 is not None:
        Log.write('Can\'t calculate protection for specific tenant.')
        #return

    data_cache['protections'] = {}
    data_cache['to_delete']['protections'] = []
    spec = acrobind.create_viewspec_by_is('Gtob::Dto::CentralizedProtection')
    filename = 'amsctl_wrong_protections.txt'

    _init_counters(connection, data_cache, 'protections', spec)

    spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask2('.ID', '.AffectedMachines'))

    def protection_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)
        plan_id_str = id_str

        data_cache['protections'][id_str] = {}

        error_string = ''
        if plan_id_str not in data_cache['plans']:
            error_string = '{0} {1}'.format(error_string, 'missing_plan(d)')
            data_cache['to_delete']['protections'].append(id_str)
        else:
            for (number, affected_machine) in object.AffectedMachines:
                fact_id = '{0}+{1}'.format(affected_machine.ref, plan_id_str)

                no_fact = fact_id not in data_cache['deployment_facts']
                if no_fact:
                    error_string = '{0} {1}'.format(error_string, 'missing_deployment_fact')
                    data_cache['deployment_facts'][fact_id] = {}
                    data_cache['deployment_facts'][fact_id]['plan'] = plan_id_str
                    data_cache['deployment_facts'][fact_id]['host'] = affected_machine.ref
                    data_cache['deployment_facts'][fact_id]['exists'] = False

                    remote_spec = acrobind.viewspec_apply_remote_host(acrobind.create_viewspec_by_is_and_guid_property('Gtob::Dto::ProtectionPlan', '.CentralizedProtectionPlan', plan_id_str), affected_machine.ref)
                    remote_spec = acrobind.viewspec_apply_mask(remote_spec, acrobind.create_mask2('.ID', '.CentralizedProtectionPlan'))
                    (mms_plan_selected, mms_plan) = acrobind.safe_select_object(connection, remote_spec)
                    if mms_plan_selected:
                        if mms_plan is None:
                            error_string = '{0} {1}'.format(error_string, 'plan_missing_on_agent(r)')
                            data_cache['deployment_facts'][fact_id]['need_redeploy'] = True
                            data_cache['to_redeploy'].append(plan_id_str)
                    else:
                        if mms_plan is None:
                            error_string = '{0} {1}'.format(error_string, 'unknown_plan_state_on_agent')
                            data_cache['deployment_facts'][fact_id]['need_redeploy'] = True
                            data_cache['unknown_to_redeploy'].append(plan_id_str)

        _update_counters_and_file(data_cache, 'protections', file, id_str, error_string)

    callback = lambda x: protection_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Gtob::Dto::CentralizedProtection. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'protections', filename, spec, callback)


def check_orphan_item_protections(connection, data_cache):
    data_cache['item_protections'] = {}
    data_cache['to_delete']['item_protections'] = []
    data_cache['item_protections']['stat'] = {}

    data_cache['item_protections']['stat']['is_legacy'] = 0
    data_cache['item_protections']['stat']['missing_tenant_field'] = 0
    data_cache['item_protections']['stat']['total'] = 0
    data_cache['item_protections']['stat']['error_item_type'] = 0
    data_cache['item_protections']['stat']['error_item_id'] = 0
    data_cache['item_protections']['stat']['local_item_protection'] = 0
    data_cache['item_protections']['stat']['missing_centralized'] = 0
    data_cache['item_protections']['stat']['missing_local'] = 0
    data_cache['item_protections']['stat']['missing_item_type'] = 0
    data_cache['item_protections']['stat']['missing_item_id'] = 0

    spec = acrobind.create_viewspec_by_is('Gtob::Dto::ItemProtection')
    filename = 'amsctl_wrong_item_protections.txt'

    def apply_limits(filename, spec):
        if args.parameter1 is not None:
            filename = '{0}_{1}'.format(args.parameter1, filename)
            limit_pattern = [
                ('.Tenant.ID', 'string', '{}'.format(args.parameter1))
            ]
            return filename, acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return filename, spec

    filename, spec = apply_limits(filename, spec)

    _init_counters(connection, data_cache, 'item_protections', spec)

    def ip_callback(connection, cache, file, object):
        id_str = '{}'.format(object.ID.ref)
        is_centralized = [unit.ref for name, unit in object.traits if unit.ref == "Gtob::Dto::Centralized::ItemProtection"]

        stat = data_cache['item_protections']['stat']
        data_cache['item_protections'][id_str] = {}
        data_cache['item_protections'][id_str]['cplan'] = '-'
        data_cache['item_protections'][id_str]['lplan'] = '-'

        error_string = ''

        stat['total'] = stat['total'] + 1

        is_legacy = False
        if 'Instance' in object:
            is_legacy = True
            stat['is_legacy'] = stat['is_legacy'] + 1

        tenant_id = ''
        tenant_error = check_if_tenant_is_valid(object, data_cache, 'item_protections', id_str)
        if tenant_error:
            error_string = '{0} {1}'.format(error_string, tenant_error)

        has_centralized = False
        if 'Centralized' in object:
            has_centralized = True

        has_local = False
        if 'Local' in object:
            has_local = True

        local_item_protection = False
        if 'LastStartInfo' in object and 'BackupFrame' in object.LastStartInfo:
            start_frame = object.LastStartInfo.BackupFrame.ref
            if start_frame.startswith('LOCAL'):
                local_item_protection = not is_centralized
                if local_item_protection:
                    stat['local_item_protection'] = stat['local_item_protection'] + 1

        item_type = get_optional(object, 'ItemType')
        wrong_item_type = False
        if 'ItemType' in object and item_type == 0:
            stat['error_item_type'] = stat['error_item_type']  + 1
            error_string = '{0} {1}'.format(error_string, 'error_item_type')

        item_id = get_optional(object, 'ItemID')

        if 'ItemID' in object and str(item_id) == '00000000-0000-0000-0000-000000000000':
            stat['error_item_id'] = stat['error_item_id']  + 1
            error_string = '{0} {1}'.format(error_string, 'error_item_id')
            data_cache['item_protections'][id_str]['wrong_item_id'] = True

        if cache['resolve_links']:
            if object is not None and 'Centralized' in object and 'PlanID' in object.Centralized:
                cplan_id = '{0}'.format(object.Centralized.PlanID.ref)
                data_cache['item_protections'][id_str]['cplan'] = cplan_id
                if cplan_id not in data_cache['plans']:
                    error_string = '{0} {1}'.format(error_string, 'link_to_missing_centralized_plan')

            if object is not None and 'Local' in object and 'PlanID' in object.Local:
                lplan_id = '{0}'.format(object.Local.PlanID.ref)
                data_cache['item_protections'][id_str]['lplan'] = lplan_id
                if lplan_id not in data_cache['plans']:
                    error_string = '{0} {1}'.format(error_string, 'link_to_missing_local_plan')

        if not is_legacy:
            if not has_local:
                any_problem_exists = True
                stat['missing_local'] = stat['missing_local'] + 1
                error_string = '{0} {1}'.format(error_string, 'missing_local')

            if not has_centralized and is_centralized:
                any_problem_exists = True
                stat['missing_centralized'] = stat['missing_centralized'] + 1
                error_string = '{0} {1}'.format(error_string, 'missing_centralized')

            if not local_item_protection and (item_type or item_id):
                if item_type is None:
                    stat['missing_item_type'] = stat['missing_item_type'] + 1
                    error_string = '{0} {1}'.format(error_string, 'missing_item_type')
                    any_problem_exists = True

                if item_id is None:
                    stat['missing_item_id'] = stat['missing_item_id'] + 1
                    error_string = '{0} {1}'.format(error_string, 'missing_item_id')
                    any_problem_exists = True

        #log_str = '{0}: {1}'.format(id_str, error_string)
        #if not cache['resolve_links']:
        #    log_str = '{0}\n{1}'.format(error_string, object)
        log_str = '{0}\n{1}'.format(error_string, object)
        _update_counters_and_file(data_cache, 'item_protections', file, id_str, error_string)

    callback = lambda x: ip_callback(connection, data_cache, filename, x)
    Log.write('Checking orphan Gtob::Dto::ItemProtection. Logging wrong item ids to \'{}\'.'.format(filename))
    _do_check(connection, data_cache, 'item_protections', filename, spec, callback)
    stat = data_cache['item_protections']['stat']
    for key in sorted(stat):
        Log.write('{0}: {1}'.format(key, stat[key]))


def _log_for_unused_objects(connection, cache, entry_id, filename):
    with open(filename, "w") as myfile:
        myfile.truncate()

    counter = 0
    for id, value in cache[entry_id].items():
        if not value['used']:
            counter = counter + 1
            with open(filename, "a") as myfile:
                myfile.write('{}\n'.format(id))
    Log.write('Unused {}: {}'.format(entry_id, counter))


def check_for_unused_objects(connection, cache):
    _log_for_unused_objects(connection, cache, 'tenants', 'amsctl_unused_tenants.txt')
    _log_for_unused_objects(connection, cache, 'msp_machines', 'amsctl_unused_msp_machines.txt')
    _log_for_unused_objects(connection, cache, 'agents', 'amsctl_unused_agents.txt')


def consistency_redeploy_plans(connection, ids):
    creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    log_file_name = 'amsctl_redeploy_{}.txt'.format(creation_time)
    Log.write('Plans to redeploy: {0}'.format(len(ids)))

    start = time.time()

    for plan_id in ids:
        Log.write('Redeploying \'{0}\''.format(plan_id))
        try:
            redeploy_plan(connection, plan_id)
        except Exception as error:
            Log.write('Skipping because of error: {0}'.format(error))

    Log.write('elapsed: {0:.2f} s.'.format(time.time() - start))

def delete_obsolete_data(connection, cache):
    creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    log_file_name = 'amsctl_deleted_objects_{}.txt'.format(creation_time)
    Log.write('Removing wrong objects...')

    start = time.time()
    def handle_ids(connection, spec, filename):

        def store_object(object, filename):
            with open(filename, "a") as myfile:
                myfile.write('{}\n'.format(object))

        callback = lambda x: store_object(x, filename)
        acrobind.enumerate_objects(connection, spec, callback, error_log)
        connection.dml.delete(pattern=spec.pattern)

    for type, ids in cache['to_delete'].items():
        Log.write('To delete \'{0}\': {1}'.format(type, len(ids)))

        if len(ids) <= 0:
            continue
        if type == 'agents':
            spec = acrobind.create_viewspec_by_is('MachineManagement::Machine')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)

        if type == 'instances':
            spec = acrobind.create_viewspec_by_is('InstanceManagement::Instance')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)
        if type == 'deployment_facts':
            spec = acrobind.create_viewspec_by_is('Gtob::Protection::PlanDeploymentFact')
            ids_pattern = []
            for id in ids:
                ids_pattern.append([('', 'guid', id)])

            pattern = [
                ('.ID.Plan', 'guid', '00000000-0000-0000-0000-000000000000'),
                ('.ID.Plan^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]),
            ]
            spec = acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=pattern)), spec.options)
            handle_ids(connection, spec, log_file_name)
        if type == 'plans':
            spec = acrobind.create_viewspec_by_is('Gtob::Dto::ProtectionPlan')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)
        if type == 'item_protections':
            spec = acrobind.create_viewspec_by_is('Gtob::Dto::ItemProtection')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)
        if type == 'protections':
            spec = acrobind.create_viewspec_by_is('Gtob::Dto::CentralizedProtection')
            spec = acrobind.viewspec_apply_ids(spec, ids)
            handle_ids(connection, spec, log_file_name)
        if type == 'msp_machines':
            spec = acrobind.create_viewspec_by_is('Msp::AMS::Dto::Machine')

            ids_pattern = []
            for id in ids:
                ids_pattern.append([('', 'string', id)])

            pattern = [
                ('.AgentID', 'string', '00000000-0000-0000-0000-000000000000'),
                ('.AgentID^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]),
            ]
            spec = acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=pattern)), spec.options)
            handle_ids(connection, spec, log_file_name)

    Log.write('elapsed: {0:.2f} s.'.format(time.time() - start))


def obsolete_data(connection):

    cache = {}
    cache['resolve_links'] = True
    cache['specific_tenant'] = False
    cache['to_delete'] = {}
    cache['to_redeploy'] = []
    cache['unknown_to_redeploy'] = []
    action = 'all'

    if args.parameter2 is not None and args.parameter2.startswith('ip'):
        action = 'item_protection_only'
        cache['resolve_links'] = False
    elif args.parameter2 is not None and args.parameter2.startswith('protection'):
        action = 'protection_plan_only'
        cache['resolve_links'] = False

    if args.parameter1 is not None:
        cache['specific_tenant'] = True

    Log.write('Action: {}'.format(action))
    Log.write('Resolve links between objects: {}'.format(cache['resolve_links']))

    if action in ['all']:
        check_tenants_consistency(connection, cache)
        check_orphan_msp_machines(connection, cache)
        check_orphan_agents(connection, cache)
        check_orphan_instances(connection, cache)

    if action in ['protection_plan_only', 'all']:
        check_orphan_plans(connection, cache)
        check_deployment_fact_consistency(connection, cache)
        check_protections_consistency(connection, cache)
    if action in ['item_protection_only', 'all']:
        check_orphan_item_protections(connection, cache)

    if action in ['all']:
        check_for_unused_objects(connection, cache)

    for type, ids in cache['to_delete'].items():
        Log.write('To delete \'{0}\': {1}'.format(type, len(ids)))
    Log.write('To redeploy: {0}'.format(len(cache['to_redeploy'])))
    Log.write('Unknown deployment status: {0}'.format(len(cache['unknown_to_redeploy'])))

    if args.fix:
        print('Do you want to redeploy wrong protection plans {0}? (y/n)'.format(len(cache['to_redeploy'])))
        if ask_user():
            consistency_redeploy_plans(connection, cache['to_redeploy'])
        print('Do you want to redeploy unknown protection plans {0}? (y/n)'.format(len(cache['unknown_to_redeploy'])))
        if ask_user():
            consistency_redeploy_plans(connection, cache['unknown_to_redeploy'])
    if args.delete:
        print('Do you want to delete obsolete data? (y/n)')
        if ask_user():
            delete_obsolete_data(connection, cache)


def init_agent_stat(stat):
    stat['by_version'] = {}
    stat['by_type'] = {}
    stat['by_type']['mac'] = {}
    stat['by_type']['lin'] = {}
    stat['by_type']['win'] = {}
    stat['by_type']['exc'] = {}
    stat['by_type']['vmw'] = {}
    stat['by_type']['mob'] = {}
    stat['by_type']['sql'] = {}
    stat['by_type']['oth'] = {}


def map_agent_type(id):
    if id == '':
        return 'Version'

    if id == '{BA262882-C484-479A-8D07-B0EAC55CE27B}':
        return 'Agent for Windows'

    if id == '{C293F7DD-9531-4916-9346-12B0F3BFCCAA}':
        return 'Agent for SQL'

    if id == '{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}':
        return 'Agent for Mobile'

    if id == '{EC3CF038-C08A-4341-82DF-F75133705F63}':
        return 'Agent for VMware'

    if id == '{87671A98-2B47-4D4C-98FB-490CA111E7A7}':
        return 'Agent for Hyper-V'

    if id == '{30BAF589-C02A-463F-AB37-5A9193375700}':
        return 'Agent for Exchange'

    if id == '{CDC40814-3769-4D13-B222-629463D3F5CA}':
        return 'Agent for ESX (Appliance)'

    if id == '{09726067-5E56-4775-8D3B-FD9110BCAAD1}':
        return 'Agent for ARX single pass'

    if id == '{383CD411-7A5D-4658-B184-3B651A6E12BB}':
        return 'Agent for Online exchange agent'

    if id == '{9F148D12-3653-478B-A039-239BE36950AB}':
        return 'Agent for AD'

    if id == '{8397FC51-C78E-4E26-9860-6A4A8622DF7C}':
        return 'Agent for Home'

    return 'oth'


def increment_counter(stat, counter_name):
    if counter_name not in stat:
        stat[counter_name] = 1
        return
    stat[counter_name] += 1


def init_agents_info(data):
    data[map_agent_type('')] = ''
    data[map_agent_type('{BA262882-C484-479A-8D07-B0EAC55CE27B}')] = ''
    data[map_agent_type('{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}')] = ''
    data[map_agent_type('{EC3CF038-C08A-4341-82DF-F75133705F63}')] = ''
    data[map_agent_type('{87671A98-2B47-4D4C-98FB-490CA111E7A7}')] = ''
    data[map_agent_type('{C293F7DD-9531-4916-9346-12B0F3BFCCAA}')] = ''
    data[map_agent_type('{30BAF589-C02A-463F-AB37-5A9193375700}')] = ''
    data[map_agent_type('{CDC40814-3769-4D13-B222-629463D3F5CA}')] = ''
    data[map_agent_type('{09726067-5E56-4775-8D3B-FD9110BCAAD1}')] = ''
    data[map_agent_type('{383CD411-7A5D-4658-B184-3B651A6E12BB}')] = ''
    data[map_agent_type('{9F148D12-3653-478B-A039-239BE36950AB}')] = ''
    #home
    data[map_agent_type('{8397FC51-C78E-4E26-9860-6A4A8622DF7C}')] = ''

def map_os_type(type):
    if type == 1:
        return 'Unknown'

    if type == 2:
        return 'Windows9x'

    if type == 3:
        return 'Windows'

    if type == 4:
        return 'Linux'

    if type == 5:
        return 'MacOs'

    return '{0}'.format(value)


def map_os_caps(value):
    result = []
    if value & 1:
        result.append('BOOTMEDIA')

    if value & 2:
        result.append('SERVER')

    if value & 4:
        result.append('APPLIANCE')

    if value & 8:
        result.append('LINUX_RAMDISK')

    if value & 16:
        result.append('HYPERV')

    if value & 32:
        result.append('DR_APPLIANCE')

    return '|'.join(result)


def map_architecture_edition(value):
    if value == 0:
        return 'UNKNOWN'

    if value == 1:
        return 'X86'

    if value == 2:
        return 'X64'

    return '{0}'.format(value)


def get_app_stat(info, host_id):
    if host_id not in info['apps']:
        info['apps'][host_id] = {}
        info['apps'][host_id]['exchange'] = {}
        info['apps'][host_id]['exchange']['2003'] = 0
        info['apps'][host_id]['exchange']['2007'] = 0
        info['apps'][host_id]['exchange']['2010'] = 0
        info['apps'][host_id]['exchange']['2013'] = 0
        info['apps'][host_id]['exchange']['-'] = 0
        info['apps'][host_id]['mssql'] = {}
        info['apps'][host_id]['mssql']['SQL Server 2005'] = 0
        info['apps'][host_id]['mssql']['SQL Server 2008'] = 0
        info['apps'][host_id]['mssql']['SQL Server 2008R2'] = 0
        info['apps'][host_id]['mssql']['SQL Server 2012'] = 0
        info['apps'][host_id]['mssql']['SQL Server 2014'] = 0
        info['apps'][host_id]['mssql']['SQL Server Unknown'] = 0

    return info['apps'][host_id]


def map_sql_server_version(version):
    if version.startswith('9.0'):
        return 'SQL Server 2005'
    if version.startswith('10.0'):
        return 'SQL Server 2008'
    if version.startswith('10.50'):
        return 'SQL Server 2008R2'
    if version.startswith('11.0'):
        return 'SQL Server 2012'
    if version.startswith('12.0'):
        return 'SQL Server 2014'
    return 'SQL Server Unknown'


def map_exchange_server_version(version):
    if version == '-':
        return 'Exchange Server Unknown'
    return 'Exchange Server {0}'.format(version)


def collect_summary_info(connection, info, object):
    is_online = (object.Status.ref == 0)
    is_mobile = is_mobile_agent(object.Info.Agents)
    is_ati = is_ati_agent(object.Info.Agents)

    info["total"]['total'] = info["total"]['total'] + 1
    if is_online:
        info["total"]['total_online'] = info["total"]['total_online'] + 1

    if is_mobile:
        info["total"]['mobile'] = info["total"]['mobile'] + 1
        if is_online:
            info["total"]['mobile_online'] = info["total"]['mobile_online'] + 1
    elif is_ati:
        info["total"]['home'] = info["total"]['home'] + 1
        if is_online:
            info["total"]['home_online'] = info["total"]['home_online'] + 1
    else:
        info['total']['abr'] = info['total']['abr'] + 1
        if is_online:
            info["total"]['abr_online'] = info["total"]['abr_online'] + 1


def collect_online_info(connection, info, object):
    is_mobile = is_mobile_agent(object.Info.Agents)
    is_ati = is_ati_agent(object.Info.Agents)

    info["total"]['total'] = info["total"]['total'] + 1

    if is_mobile:
        info["total"]['mobile'] = info["total"]['mobile'] + 1
    elif is_ati:
        info["total"]['home'] = info["total"]['home'] + 1
    else:
        info['total']['abr'] = info['total']['abr'] + 1


def process_agents(connection, info, object):
    agent_id = '{0}'.format(object.ID.ref)
    info[agent_id] = {}
    data = info[agent_id]

    tenant = ''
    if 'Tenant' in object:
        tenant = object.Tenant.Name.ref
    data['Tenant'] = tenant
    init_agents_info(data)

    is_online = (object.Status.ref == 0)
    is_mobile = is_mobile_agent(object.Info.Agents)
    is_ati = is_ati_agent(object.Info.Agents)

    def get_version(agents_info):
        for index, a in object.Info.Agents:
            version = a.Version.ref
            return version

    if is_mobile:
        agent_type = map_agent_type('{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}')
        data[agent_type] = get_version(object.Info.Agents)
    elif is_ati:
        agent_type = map_agent_type('{8397FC51-C78E-4E26-9860-6A4A8622DF7C}')
        data[agent_type] = get_version(object.Info.Agents)
    else:
        for index, a in object.Info.Agents:
            version = a.Version.ref
            agent_type = map_agent_type(a.Id.ref)
            data[agent_type] = version

            if agent_type == 'oth':
                Log.write('UnknownType: {0}, {1}'.format(a.Name.ref, a.Id.ref))

    data['ProcessorArchitecture'] = object.Info.Architecture.ref
    #if 'Hardware' in object.Info:
    #    data['MemorySize'] = object.Info.Hardware.MemorySize.ref
    #    data['ProcessorFrequency'] = object.Info.Hardware.ProcessorFrequency.ref
    #    data['ProcessorName'] = object.Info.Hardware.ProcessorName.ref
    #else: #For Mobile
    #    data['MemorySize'] = '-'
    #    data['ProcessorFrequency'] = '-'
    #    data['ProcessorName'] = ''

    #data['Name'] = object.Info.Name.ref
    data['Status'] = object.Status.ref
    data['OSArchitectureEdition'] = map_architecture_edition(object.Info.OS.ArchitectureEdition.ref)
    data['OSName'] = object.Info.OS.Name.ref
    data['OSCaps'] = map_os_caps(object.Info.OS.OSCaps.ref)
    data['OSType'] = map_os_type(object.Info.OS.OSType.ref)
    data['OSVersionMajor'] = object.Info.OS.VersionMajor.ref
    data['OSVersionMinor'] = object.Info.OS.VersionMinor.ref
    if 'TimezoneOffsetInMinutes' in object.Info:
        data['TimezoneOffsetInMinutes'] = object.Info.TimezoneOffsetInMinutes.ref
    else: #For Mobile
        data['TimezoneOffsetInMinutes'] = '-'
    data['LastConnectionTime'] = '-'
    if 'LastConnectionTime' in object:
        data['LastConnectionTime'] = object.LastConnectionTime.ref

    if 'Tenant' in object:
        data['TenantName'] = object.Tenant.Name.ref
        data['TenantID'] = object.Tenant.ID.ref
        data['TenantLocator'] = object.Tenant.Locator.ref
    else:
        data['TenantName'] = '-'
        data['TenantID'] = '-'
        data['TenantLocator'] = '-'
    if 'UpdateState' in object:
        data['UpdateIsAvailable'] = object.UpdateState.UpdateVersion.ref
    else: #For Mobile
        data['UpdateIsAvailable'] = '-'

    app_stat = get_app_stat(info, agent_id)
    for app_version, count in app_stat['mssql'].items():
        data[app_version] = count
    for app_vesion, count in app_stat['exchange'].items():
        data[map_exchange_server_version(app_version)] = count


def print_agent_stat(stat):
    Log.write('By version:')
    sorted_items = sorted(stat['by_version'].items(), key=operator.itemgetter(1))
    sorted_items.reverse()
    for version, count in sorted_items:
        Log.write('\t{0}: {1}'.format(version, count))

    Log.write('By type:')
    for type, data in stat['by_type'].items():
        Log.write('\t{0}:'.format(type))
        sorted_items = sorted(data.items(), key=operator.itemgetter(1))
        sorted_items.reverse()
        for version, count in sorted_items:
            Log.write('\t\t{0}: {1}'.format(version, count))


def process_mssql_instances(connection, info, object):
    host_id = '{0}'.format(object.HostID.ref)
    stat = get_app_stat(info, host_id)
    if 'SqlServerVersion' not in object.Parameters:
        stat['mssql'][map_sql_server_version('-')] += 1
        return
    for index, value in object.Parameters.SqlServerVersion:
        #Log.write('Found mssql version \'{0}\''.format(value.ref))
        app_version = map_sql_server_version(value.ref)
        if app_version not in stat['mssql']:
            stat['mssql'][map_sql_server_version('-')] += 1
        else:
            stat['mssql'][app_version] += 1


def process_exchange_instances(connection, info, object):
    host_id = '{0}'.format(object.HostID.ref)
    stat = get_app_stat(info, host_id)
    #print(object)
    if 'ExchangeServerVersion' not in object.Parameters:
        stat['exchange']['-'] += 1
        return
    for index, value in object.Parameters.ExchangeServerVersion:
        #Log.write('Found exchange version \'{0}\''.format(value.ref))
        if value.ref not in stat['exchange']:
            stat['exchange']['-'] += 1
        else:
            stat['exchange'][value.ref] += 1


def collect_agents_statistics(connection):

    info = {}
    info['total'] = {}
    info['total']['home'] = 0
    info['total']['mobile'] = 0
    info['total']['abr'] = 0
    info['total']['total'] = 0
    info['apps'] = {}

    if args.parameter1 != 'summary' and args.parameter1 != 'online':
        #prefetch application instance info
        Log.write('Collecting information applications...')
        start = time.time()
        callback = lambda x: process_exchange_instances(connection, info, x)
        #INSTANCE_TYPE_EXCHANGE = 6,
        pattern = [
            ('^Is', 'string', 'InstanceManagement::Instance'),
            ('.Type', 'dword', 6),
        ]
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))
        acrobind.enumerate_objects(connection, spec, callback, error_log)

        callback = lambda x: process_mssql_instances(connection, info, x)
        #INSTANCE_TYPE_MSSQL = 19,
        pattern = [
            ('^Is', 'string', 'InstanceManagement::Instance'),
            ('.Type', 'dword', 19),
        ]
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))
        acrobind.enumerate_objects(connection, spec, callback, error_log)
        Log.write('done ({0:.2f} s)'.format(time.time() - start))

    #determine all agents type
    Log.write('Collecting information about agents...')
    start = time.time()
    spec = acrobind.create_viewspec_machines_by_role(0)

    if args.parameter1 == 'summary':
        info['total']['home_online'] = 0
        info['total']['mobile_online'] = 0
        info['total']['abr_online'] = 0
        info['total']['total_online'] = 0
        spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.Status', '.Info'))
        agents_callback = lambda x: collect_summary_info(connection, info, x)
    elif args.parameter1 == 'online':
        pattern = [
            ('^Is', 'string', 'MachineManagement::Machine'),
            ('.Info.Role', 'dword', 0),
            ('.Status', 'dword', 0),
        ]
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))
        spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.Status', '.Info'))
        agents_callback = lambda x: collect_online_info(connection, info, x)
    else:
        info['total']['home_online'] = 0
        info['total']['mobile_online'] = 0
        info['total']['abr_online'] = 0
        info['total']['total_online'] = 0
        agents_callback = lambda x: process_agents(connection, info, x)

    acrobind.enumerate_objects(connection, spec, agents_callback, error_log)
    Log.write('done ({0:.2f} s)'.format(time.time() - start))

    if args.parameter1 == 'summary' or args.parameter1 == 'online':
        print(info['total'])
        return

    info.pop('apps', None)
    info.pop('total', None)
    print(json.dumps(info))
    #print_agent_stat(info)

    #for tenant, data in info['by_tenant'].items():
    #    Log.write("Tenant: {0}".format(tenant))
    #    print_agent_stat(data)


def map_usert_agent_type(id):

    if id == '{BA262882-C484-479A-8D07-B0EAC55CE27B}':
        return 'Workstation'

    if id == '{C293F7DD-9531-4916-9346-12B0F3BFCCAA}':
        return 'SQL'

    if id == '{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}':
        return 'Mobile'

    if id == '{EC3CF038-C08A-4341-82DF-F75133705F63}':
        return 'VMware'

    if id == '{87671A98-2B47-4D4C-98FB-490CA111E7A7}':
        return 'Hyper-V'

    if id == '{30BAF589-C02A-463F-AB37-5A9193375700}':
        return 'Exchange'

    if id == '{CDC40814-3769-4D13-B222-629463D3F5CA}':
        return 'ESX (Appliance)'

    if id == '{09726067-5E56-4775-8D3B-FD9110BCAAD1}':
        return 'ARX single pass'

    if id == '{383CD411-7A5D-4658-B184-3B651A6E12BB}':
        return 'Online ARX'

    if id == '{9F148D12-3653-478B-A039-239BE36950AB}':
        return 'AD'

    if id == '{8397FC51-C78E-4E26-9860-6A4A8622DF7C}':
        return 'Home'

    if id == 'home':
        return 'Home'

    return 'unknown'

def init_users_info(data):
    data[map_usert_agent_type('{BA262882-C484-479A-8D07-B0EAC55CE27B}')] = {}
    data[map_usert_agent_type('{AC127296-8E50-41DE-8EFE-853C4B5CD8A8}')] = {}
    data[map_usert_agent_type('{EC3CF038-C08A-4341-82DF-F75133705F63}')] = {}
    data[map_usert_agent_type('{87671A98-2B47-4D4C-98FB-490CA111E7A7}')] = {}
    data[map_usert_agent_type('{C293F7DD-9531-4916-9346-12B0F3BFCCAA}')] = {}
    data[map_usert_agent_type('{30BAF589-C02A-463F-AB37-5A9193375700}')] = {}
    data[map_usert_agent_type('{CDC40814-3769-4D13-B222-629463D3F5CA}')] = {}
    data[map_usert_agent_type('{09726067-5E56-4775-8D3B-FD9110BCAAD1}')] = {}
    data[map_usert_agent_type('{383CD411-7A5D-4658-B184-3B651A6E12BB}')] = {}
    data[map_usert_agent_type('{9F148D12-3653-478B-A039-239BE36950AB}')] = {}
    data[map_usert_agent_type('home')] = {}
    data[map_usert_agent_type('unknown')] = {}


def process_users(connection, info, object):
    #print(object)
    #print(len(object.Info.Agents))

    if 'Tenant' not in object:
        #Log.write("Object without tenant info:\n{0}".format(object))
        return

    tenant_id = object.Tenant.ID.ref

    for index, a in object.Info.Agents:
        data = None
        agent_type = None
        #Home agent have no Version AgentInfo and have empty Id in AgentInfo
        if len(object.Info.Agents) == 1 and a.Id.ref == '':
            agent_type = map_usert_agent_type('home')
            #print('Home detected {0}'.format(object))
        elif a.Id.ref:
            agent_type = map_usert_agent_type(a.Id.ref)

        if not agent_type:
            continue

        data = info[agent_type]
        if data is None:
            Log.write('UnknownType: {0}, {1}'.format(a.Name.ref, a.Id.ref))
            print(agent_type)
            print(info)
            continue

        if tenant_id in data:
            #Log.write('Already calculated')
            return

        #Log.write(agent_type)
        data[tenant_id] = {}
        data[tenant_id]["ID"] = object.Tenant.ID.ref
        data[tenant_id]["Name"] = object.Tenant.Name.ref
        data[tenant_id]["Locator"] = object.Tenant.Locator.ref
        data[tenant_id]["ParentID"] = object.Tenant.ParentID.ref
        data[tenant_id]["Kind"] = object.Tenant.Kind.ref


def collect_users_info(connection):

    info = {}
    init_users_info(info)

    #determine all agents type
    Log.write('Collecting information about agents...')
    start = time.time()
    agents_callback = lambda x: process_users(connection, info, x)
    spec = acrobind.create_viewspec_machines_by_role(0)
    #spec = acrobind.viewspec_apply_mask(spec, acrobind.create_mask3('.ID', '.Tenant', '.Info.Agents', '.Info.OS'))
    acrobind.enumerate_objects(connection, spec, agents_callback, error_log)
    Log.write('done ({0:.2f} s)'.format(time.time() - start))
    print(json.dumps(info))


def process_autoupdate(connection, object, table):
    if args.extra:
        Log.write(object)

    if table:
        table.add_row(['{}'.format(object.Version.ref), '{}'.format(object.OS.ref), '{}'.format(object.Arch.ref), '{}'.format(object.Locale.ref), '{}'.format(object.BuildUrl.ref)])

    if args.fix:
        print('Do you want to delete this object(y/n)?')
        if ask_user():
            object_pattern = [
                ('^Is', 'string', 'AutoUpdate::Dto::Update'),
                ('.BuildUrl', 'string', object.BuildUrl.ref),
                ('.ID.ID', 'guid', object.ID.ID.ref),
                ('.ID.SequencedID', 'qword', object.ID.SequencedID.ref),
            ]
            connection.dml.delete(pattern=acrort.plain.Unit(flat=object_pattern))
            print('deleted.')
        else:
            print('skipped.')


def analyze_updates(connection):
    build_url = None
    if args.build_url:
        build_url = args.build_url

    update_id = None
    if args.update_id:
        update_id = args.update_id


    Log.write('Listing updates for build url {0} and update id {1}'.format(build_url if build_url else "All", update_id if update_id else "All"))

    table = prettytable.PrettyTable(["Version", "OS", "Arch", "Locale", "BuildUrl"])
    table.align["Version"] = "l"
    table.padding_width = 1
    callback = lambda x: process_autoupdate(connection, x, table)
    pattern = [
        ('^Is', 'string', 'AutoUpdate::Dto::Update'),
    ]
    if build_url:
        pattern.append(('.BuildUrl', 'string', build_url))
        pattern.append(('.BuildUrl^Like', 'string', '%{0}%'.format(build_url)))

    if update_id:
        pass
        #pattern.append(('.ID.ID', 'guid', update_id))

    spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))
    acrobind.enumerate_objects(connection, spec, callback, error_log)
    print(table.get_string(sortby="Version"))
    print('')


def counter_mode(connection):
    statistics_views = {'machine-statistics': 'Statistics::Agents', 'backup-statistics': 'Statistics::Resources'}
    if args.count in statistics_views:
        stat = acrobind.select_object(connection, acrobind.create_viewspec_by_is_and_string_property(statistics_views[args.count], '.Tenant.ID', "all"))
        if not stat:
            Log.write("Failed to select statistics object.")
            return
        print(stat)
    else:
        Log.write("Trying to count objects: {}".format(args.count))
        count = acrobind.count_objects(connection, args.count)
        print("{0}: {1}".format(args.count, count))

#BEGIN --perf_stat analyze--
def get_perf_stat_folder():
    return os.path.join(acrort.fs.APPDATA_COMMON, acrort.common.BRAND_NAME, 'AMS', 'perf_stats')


def init_dml_perf_stat(stat, time_range):
    stat[time_range] = {}
    stat[time_range]['report_count_{}'.format(time_range)] = {}
    stat[time_range]['report_time_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_create_count_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_create_time_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_delete_count_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_delete_time_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_update_count_{}'.format(time_range)] = {}
    stat[time_range]['subscribe_update_time_{}'.format(time_range)] = {}


def load_dml_stat(file_name, stat):
    with open(file_name, 'r') as csvfile:
        try:
            reader = csv.reader(csvfile, 'ams')
            for row in reader:
                stat[row[0]] = int(row[1])
        except Exception as error:
            Log.write('Failed to process stat file \'{0}\', reason: {1}.'.format(file_name, error))


def perf_stat():
    Log.write('DML perf stat')
    dml_perf_stat()


def dml_perf_stat():
    csv.register_dialect('ams', delimiter=';', skipinitialspace=True, quoting=csv.QUOTE_NONE)
    perf_folder = get_perf_stat_folder()
    stat = {}

    desc = {}
    init_dml_perf_stat(stat, 'total')
    init_dml_perf_stat(stat, '1_minute')

    for i in range(1, 10):
        core_id = '0_{}'.format(i)
        desc_file = '{0}/{1}_desc.log'.format(perf_folder, core_id)
        try:
            with open(desc_file, 'r') as csvfile:
                try:
                    reader = csv.reader(csvfile, 'ams')
                    for row in reader:
                        desc[row[0]] = '{}:{}'.format(row[2], row[1])
                except Exception as error:
                    Log.write('Failed to process file \'{0}\', reason: {1}.'.format(desc_file, error))

            for key, value in stat.items():
                for stat_id in value:
                    stat_file_name = '{0}/{1}_{2}.log'.format(perf_folder, stat_id, core_id)
                    load_dml_stat(stat_file_name, stat[key][stat_id])
        except Exception as error:
            Log.write('Failed to process file \'{0}\', reason: {1}. Skipping'.format(desc_file, error))


    for key, stat_pack in stat.items():
        table = prettytable.PrettyTable([key, "value", "file"])

        table.align[key] = "l"
        table.align["value"] = "l"
        table.align["file"] = "l"
        table.padding_width = 1

        last_minute_filter = 100
        total_minute_filter = 1000
        if args.parameter1 is not None:
            last_minute_filter = int(args.parameter1)

        if args.parameter2 is not None:
            total_minute_filter = int(args.parameter2)
        filter = last_minute_filter if key == '1_minute' else total_minute_filter
        for stat_id, state_value in stat_pack.items():
            sorted_stat = reversed(sorted(state_value.items(), key=lambda x:x[1]))
            top_str = ''
            column_name = stat_id
            for i, v in sorted_stat:
                if v > filter:
                    table.add_row([column_name, v, desc[i]])
                    column_name = ''

        Log.write(table)

#END --perf_stat analyze--


def dml_stat():
    Log.write('DML perf stat')
    csv.register_dialect('ams', delimiter=';', skipinitialspace=True, quoting=csv.QUOTE_NONE)
    perf_folder = get_perf_stat_folder()
    stat = {}
    stat['recent'] = {}
    stat['total'] = {}

    stat['result'] = {}
    stat['result']['recent'] = {}
    stat['result']['total'] = {}
    try:
        with open('dml_perf_last_1_minute.log', 'r') as csvfile:
            try:
                reader = csv.reader(csvfile, 'ams')
                for row in reader:
                    stat['recent'][int(row[0])] =  int(row[1])
            except Exception as error:
                Log.write('Failed to process file \'{0}\', reason: {1}.'.format('dml_perf_last_1_minute.log', error))

        with open('dml_perf_total.log', 'r') as csvfile:
            try:
                reader = csv.reader(csvfile, 'ams')
                for row in reader:
                    stat['total'][int(row[0])] =  int(row[1])
            except Exception as error:
                Log.write('Failed to process file \'{0}\', reason: {1}.'.format('dml_perf_total.log', error))
    except Exception as error:
        Log.write('Failed to process file, reason: {0}. Skipping'.format(error))

    def load_stat(stat, type):
        for key, value in stat[type].items():
            temp = key
            counter_id = temp % 100
            temp = temp // 100

            op_id = temp % 100
            temp = temp // 100

            core_id = temp % 100
            temp = temp // 100

            dispatcher_id = temp % 100
            counter_class = temp // 100

            if dispatcher_id not in stat['result'][type]:
                stat['result'][type][dispatcher_id] = {}
            dispatcher_stat = stat['result'][type][dispatcher_id]

            if counter_class > 1:
                core_id = core_id - 1

            if core_id not in dispatcher_stat:
                dispatcher_stat[core_id] = {}
            core_stat = dispatcher_stat[core_id]

            unified_key = key

            if counter_class > 1:
                unified_key = key % 10000000
                unified_key = 100000000 + dispatcher_id * 1000000 + core_id * 10000 + op_id * 100 + counter_id
                #continue
            #if key == 100000001:
            #    print(unified_key)

            if op_id not in core_stat:
                core_stat[op_id] = {}
                def init_op_stat(stat, op_id):
                    stat[op_id] = {}
                    stat[op_id]['key'] = unified_key
                    stat[op_id]['val'] = 0

                init_op_stat(core_stat[op_id], 0)
                init_op_stat(core_stat[op_id], 1)
                init_op_stat(core_stat[op_id], 2)
                init_op_stat(core_stat[op_id], 5)
                init_op_stat(core_stat[op_id], 6)

            op_stat = core_stat[op_id]

            c_id = (counter_class - 1)* 100 + counter_id
            if c_id not in op_stat:
                op_stat[c_id] = {}
                op_stat[c_id]['key'] = {}
                op_stat[c_id]['val'] = {}
            op_stat[c_id]['val'] = value
            op_stat[c_id]['key'] = unified_key

            #Log.write('{} - {}:{}:{}:{}'.format(key, dispatcher_id, core_id, op_id, counter_id))

    load_stat(stat, 'recent')
    load_stat(stat, 'total')

    table = prettytable.PrettyTable(['id', 'core + op', "calls=sucess+failed (time)", "calls=sucess+failed (time)", "avg_rate/cur_rate", "pending", "batch+sql+completion", "batch(elems)/register"])
    for dispatcher, dispatcher_stat in stat['result']['total'].items():
        for core_id, core_stat in dispatcher_stat.items():
            for op_id, op_stat in core_stat.items():

                op_name = ''
                if op_id == 0:
                    op_name = 'create'
                elif op_id == 1:
                    op_name = 'update'
                elif op_id == 2:
                    op_name = 'delete'
                elif op_id == 3:
                    op_name = 'tr'
                elif op_id == 4:
                    op_name = 'tu'
                elif op_id == 5:
                    op_name = 'report'
                elif op_id == 6:
                    op_name = 'subscribe'

                time_val = 0
                calls_val = 0
                s_calls_val = 0
                f_calls_val = 0
                p_calls_val = 0
                b_processing_time = '-'
                sql_exec_time = '-'
                completion_exec_time = '-'
                batch_count = '-'
                batch_elements = '-'
                register_counts = '-'

                key = 0
                for c_id, c_stat in op_stat.items():
                    key = c_stat['key']
                    if c_id == 0:
                        time_val = c_stat['val']
                    elif c_id == 1:
                        calls_val = c_stat['val']
                    elif c_id == 2:
                        s_calls_val = c_stat['val']
                    elif c_id == 3:
                        f_calls_val = c_stat['val']
                    elif c_id == 4:
                        p_calls_val = c_stat['val']
                    elif c_id == 100: #batch processing time
                        b_processing_time = c_stat['val']
                    elif c_id == 101: #sql execution  time
                        sql_exec_time = c_stat['val']
                    elif c_id == 102: #completion  time
                        completion_exec_time = c_stat['val']
                    elif c_id == 103: #batch count
                        batch_count = c_stat['val']
                    elif c_id == 104: #batch elements
                        batch_elements = c_stat['val']
                    elif c_id == 105: #register count
                        register_counts = c_stat['val']
                key = key // 100
                #print(key)
                #if key == 100000001:
                #    print(unified_key)
                cur_time_val = 0
                cur_calls_val = 0
                cur_s_calls_val = 0
                cur_f_calls_val = 0
                cur_p_calls_val = 0

                cur_b_processing_time = 0
                cur_sql_exec_time = 0
                cur_completion_exec_time = 0
                cur_batch_count = 0
                cur_batch_elements = 0
                cur_register_counts = 0

                if dispatcher in stat['result']['recent'] and core_id in stat['result']['recent'][dispatcher] and op_id in stat['result']['recent'][dispatcher][core_id]:
                    for c_id, c_stat in stat['result']['recent'][dispatcher][core_id][op_id].items():
                        if c_id == 0:
                            cur_time_val = c_stat['val']
                        elif c_id == 1:
                            cur_calls_val = c_stat['val']
                        elif c_id == 2:
                            cur_s_calls_val = c_stat['val']
                        elif c_id == 3:
                            cur_f_calls_val = c_stat['val']
                        elif c_id == 4:
                            cur_p_calls_val = c_stat['val']
                        elif c_id == 100: #batch processing time
                            cur_b_processing_time = c_stat['val']
                        elif c_id == 101: #sql execution  time
                            cur_sql_exec_time = c_stat['val']
                        elif c_id == 102: #completion  time
                            cur_completion_exec_time = c_stat['val']
                        elif c_id == 103: #batch count
                            cur_batch_count = c_stat['val']
                        elif c_id == 104: #batch elements
                            cur_batch_elements = c_stat['val']
                        elif c_id == 105: #register count
                            cur_register_counts = c_stat['val']
                cur_process_rate_val = 0
                cur_rate_val = 0
                if cur_time_val != '-' and cur_calls_val != '-' and cur_p_calls_val != '-' and cur_time_val != 0:
                    cur_process_rate_val = cur_calls_val
                    cur_rate_val = cur_p_calls_val + cur_calls_val
                rate = cur_rate_val - cur_process_rate_val
                rate_sgn = ''
                if rate > 0:
                    rate_sgn = '+'
                if rate < 0:
                    rate_sgn = '-'
                #Log.write('{}:{}:{} = {}'.format(dispatcher, core_id, op_name, val_str))

                table.add_row([key, '{:<2}:{:<2}:{:>10}'.format(dispatcher, core_id, op_name),\
                    '{:10} = {:8} + {:<4} ({:<8})'.format(calls_val, s_calls_val, f_calls_val, time_val), \
                    '{:5} = {:3} + {:<3} ({:<6})'.format(cur_calls_val, cur_s_calls_val, cur_f_calls_val, cur_time_val), \
                    '{:8.2f} / {:<8.2f}'.format(0 if time_val <= 0 else calls_val/time_val, 0 if cur_time_val <= 0 else cur_calls_val/cur_time_val), \
                    '{} ({}{})'.format(p_calls_val, rate_sgn, cur_p_calls_val), \
                    '{:1} + {:4} + {:<2}'.format(cur_b_processing_time, cur_sql_exec_time, cur_completion_exec_time), \
                    '{:4} ({:4}) / {:<2}'.format(cur_batch_count, cur_batch_elements, cur_register_counts) \
                    ])

    table.align['core + op'] = "l"
    table.align['time'] = "l"
    table.align['calls'] = "l"
    table.align['success'] = "l"
    table.align['failed'] = "l"
    table.align['rate'] = "l"
    table.align['pending'] = "l"
    table.padding_width = 1
    Log.write(table.get_string(sortby='id', reversesort=False))
    #print(table)
    return
    #continue
    table = prettytable.PrettyTable([key, "value", "file"])



    last_minute_filter = 100
    total_minute_filter = 1000
    if args.parameter1 is not None:
        last_minute_filter = int(args.parameter1)

    if args.parameter2 is not None:
        total_minute_filter = int(args.parameter2)
    filter = last_minute_filter if key == '1_minute' else total_minute_filter
    for stat_id, state_value in stat_pack.items():
        sorted_stat = reversed(sorted(state_value.items(), key=lambda x:x[1]))
        top_str = ''
        column_name = stat_id
        for i, v in sorted_stat:
            if v > filter:
                table.add_row([column_name, v, desc[i]])
                column_name = ''

    #Log.write(table)


#BEGIN --fix instance status--
def getEpochTime(hours_ago):
    ago = datetime.datetime.now() - datetime.timedelta(hours=hours_ago)
    return int(time.mktime(ago.timetuple()))


def fix_instances(connection):
    creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    Log.write('[FIX_INSTANCES]: {}'.format(creation_time))

    days = 1
    if args.parameter1:
        days = int(args.parameter1)

    hours = 24
    if args.parameter2:
        hours = int(args.parameter2)
    hours_ago = hours * days
    epochTime = getEpochTime(hours_ago)
    activities_pattern = [
        ('^Is', 'string', 'Tol::History::Plain::Activity'),
        ('.State', 'dword', 5),
        ('.Details.CommandID', 'guid', '8F01AC13-F59E-4851-9204-DE1FD77E36B4'),
        ('.Period.FinishTime', 'sqword', epochTime),
        ('.Period.FinishTime^Greater', 'sqword', epochTime)
    ]

    if args.fix_instances != 'all':
        activities_pattern.append(('.Tenant.ID', 'string', args.fix_instances))

    activities_options = [
        ('.Mask.ID', 'nil', None),
        ('.Mask.Details.CommandID', 'nil', None),
        ('.Mask.Environment.InstanceID', 'nil', None),
        ('.Mask.Environment.ProtectionPlanName', 'nil', None),
        ('.Mask.Tenant', 'nil', None)
    ]

    spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=activities_pattern), options=acrort.plain.Unit(flat=activities_options))
    cache = {}
    table = prettytable.PrettyTable(['id', 'plan', 'host', 'instance', 'tenant'])
    table.padding_width = 1

    def process_activity(connection, activity, cache, act_table):
        source_id = acrobind.get_trait_value('Source', activity)
        id = '{}'.format(activity.ID.ref)
        instance_id = '{}'.format(activity.Environment.InstanceID.ref)
        tenant_id = '{}'.format(activity.Tenant.ID.ref)
        plan_name = ''
        if 'ProtectionPlanName' in activity.Environment:
            plan_name = '{}'.format(activity.Environment.ProtectionPlanName.ref)
        cache[id] = {}
        cache[id]['host_id'] = source_id
        cache[id]['tenant_id'] = tenant_id
        cache[id]['instance_id'] = instance_id

        act_table.add_row([id, plan_name, source_id, instance_id, get_tenant_string(activity)])

    callback = lambda x: process_activity(connection, x, cache, table)
    acrobind.enumerate_objects(connection, spec, callback, error_log)
    if args.extra:
        Log.write(table)
    update_instance_state(connection, cache)


def drop_orphaned_machines(connection):
    locator = '/{}/'.format(args.tenant_id) if args.tenant_id else None
    tenant_locator_to_machines = collections.defaultdict(list)

    def collect_tenant_locators(object):
        Log.write('-', end='')
        tenant_locator_to_machines[object.Tenant.Locator.ref].append(object)

    def tenant_exists(locator):
        Log.write('-', end='')
        spec = acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.Locator', locator)
        return acrobind.count_objects_by_spec(connection, spec) > 0

    spec_m = acrobind.create_viewspec_by_is('MachineManagement::Machine')
    spec_m = acrobind.viewspec_apply_tenant_locator(spec_m, locator)
    mask = acrobind.create_mask2('.Info.Name', '.Tenant')

    Log.write('Collect all MachineManagement::Machine...')
    acrobind.enumerate_objects(connection, acrobind.viewspec_apply_mask(spec_m, mask), collect_tenant_locators, error_log)
    Log.write('\nFound {} tenants'.format(len(tenant_locator_to_machines)))

    Log.write('Search missed Tenants::HierarchyNode...')
    tenant_locator_to_machines = {k: v for k, v in tenant_locator_to_machines.items() if not tenant_exists(k)}

    Log.write('\nFound {} missed Tenants::HierarchyNode.'.format(len(tenant_locator_to_machines)))
    if not tenant_locator_to_machines:
        return

    table = prettytable.PrettyTable(['ID', 'Name', 'Tenant', 'Locator'])
    table.align = 'l'
    table.padding_width = 1
    for key, values in tenant_locator_to_machines.items():
        for v in values:
            tenant = v.get_branch('.Tenant', None)
            table.add_row([str(v.ID.ref), v.Info.Name.ref, tenant.ID.ref if 'ID' in tenant else '-', tenant.Locator.ref])

    Log.write('Orphaned machines:\n{}\nDelete entries?'.format(table.get_string(sortby='Locator')))
    if not ask_user():
        return

    for _, values in tenant_locator_to_machines.items():
        for v in values:
            Log.write('-', end='')
            connection.dml.delete(key=acrort.dml.get_object_key(acrort.plain.Unit(v)))
    Log.write('\nDone.')

    if not args.drop_orphaned_instances:
        Log.write('You deleted orphaned machines. Do you want to delete orphaned instances? (recommended)')
        if ask_user():
            drop_orphaned_instances(connection)

def drop_orphaned_instances(connection):
    host_id_to_keys = collections.defaultdict(list)
    locator = '/{}/'.format(args.tenant_id) if args.tenant_id else None

    def is_aspect(object):
        return acrobind.get_trait_value('Is', object) == 'InstanceManagement::InstanceAspect'

    def collect_host_ids(object):
        Log.write('-', end='')
        host_id = object.get_branch('.Key.HostID' if is_aspect(object) else '.HostID').ref
        host_id_to_keys[str(host_id)].append(object)

    def machine_exists(host_id):
        Log.write('-', end='')
        spec = acrobind.create_viewspec_by_is_and_id('MachineManagement::Machine', host_id)
        return acrobind.count_objects_by_spec(connection, spec) > 0

    spec_i = acrobind.create_viewspec_by_is('InstanceManagement::Instance')
    spec_i = acrobind.viewspec_apply_tenant_locator(spec_i, locator)
    spec_ia = acrobind.create_viewspec_by_is('InstanceManagement::InstanceAspect')
    spec_ia = acrobind.viewspec_apply_tenant_locator(spec_ia, locator)
    mask = acrobind.create_mask4('.HostID', '.FullPath', '.Type', '.Tenant')

    Log.write('Collect all InstanceManagement::Instance...')
    acrobind.enumerate_objects(connection, acrobind.viewspec_apply_mask(spec_i, mask), collect_host_ids, error_log)
    Log.write('\nCollect all InstanceManagement::InstanceAspect...')
    acrobind.enumerate_objects(connection, acrobind.viewspec_apply_mask(spec_ia, mask), collect_host_ids, error_log)
    Log.write('\nFound {} hosts'.format(len(host_id_to_keys)))

    Log.write('Search missed MachineManagement::Machine...')
    host_id_to_keys = {k: v for k, v in host_id_to_keys.items() if not machine_exists(k)}

    Log.write('\nFound {} missed MachineManagement::Machine.'.format(len(host_id_to_keys)))
    if not host_id_to_keys:
        return

    table = prettytable.PrettyTable(['HostID', 'Is', 'InstanceID', 'FullPath', 'Type', 'Tenant', 'Locator'])
    table.align = 'l'
    table.padding_width = 1
    for key, values in host_id_to_keys.items():
        for v in values:
            is_trait = acrobind.get_trait_value('Is', v)
            instance_id = str(v.get_branch('.Key.ID' if is_aspect(v) else '.ID').ref)
            tenant = v.get_branch('.Tenant', None)
            table.add_row([key, is_trait, instance_id, v.FullPath.ref, v.Type.ref, tenant.ID.ref if 'ID' in tenant else '-', tenant.Locator.ref])

    Log.write('Orphaned instances:\n{}\nDelete entries?'.format(table.get_string(sort_key=operator.itemgetter(0, 1))))
    if not ask_user():
        return

    for _, values in host_id_to_keys.items():
        for v in values:
            Log.write('-', end='')
            connection.dml.delete(key=acrort.dml.get_object_key(acrort.plain.Unit(v)))
    Log.write('\nDone.')


def update_instance_state(connection, data):
    Log.write('Fixing instance statuses for tenant: \'{}\'.'.format(args.fix_instances))

    mask = acrort.plain.Unit(flat=[
        ('.Mask.FullPath', 'nil', None),
        ('.Mask.Tenant', 'nil', None),
        ('.Mask.BackupState', 'nil', None),
        ('.Mask.BackupStatus', 'nil', None),
        ('.Mask.State', 'nil', None),
        ('.Mask.Status', 'nil', None),
        ('.Mask.Availability', 'nil', None),
        ('.Mask.LastBackup', 'nil', None),
        ('.Mask.LastBackupTry', 'nil', None),
        ('.Mask.NextBackupTime', 'nil', None)
    ])

    def set_ams_instance_availability(connection, ams_instance_spec, availability):
        diff = [
            ('', 'dword', availability),
        ]
        diff_unit={'Availability': acrort.plain.Unit(flat=diff)}
        connection.dml.update(pattern=ams_instance_spec.pattern, diff=diff_unit)

    def update_ams_instance_state(connection, ams_instance_spec, mms_instance, ams_instance, diff_next_start_time):
        status = mms_instance['Status']
        if (ams_instance.Status.ref == 1 and mms_instance.Status.ref == 1) or (ams_instance.Status.ref == 0 and mms_instance.Status.ref == 0):
            status = [
            ('', 'dword', 2)
            ]
            status = acrort.plain.Unit(flat=status)
        diff_unit={
        'BackupState': mms_instance['BackupState'],
        'BackupStatus': mms_instance['BackupStatus'],
        'State': mms_instance['State'],
        'Status': status,
        'LastBackup': mms_instance['LastBackup'],
        'LastBackupTry': mms_instance['LastBackupTry'],
        'NextBackupTime': diff_next_start_time,
        }
        Log.write('Applying patch:')
        for name, value in diff_unit.items():
            Log.write('{}: {}'.format(name, str(value)))

        connection.dml.update(pattern=ams_instance_spec.pattern, diff=diff_unit)

    #creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    #fixed_instances_log = 'fixed_instances_log_{}.txt'.format(creation_time)
    #with open(fixed_instances_log, "w") as myfile:
    #    myfile.truncate()

    processed_instances = []
    for activity_id, value in data.items():
        if value['instance_id'] in processed_instances:
            #Log.write('Skipping already processed instance \'{}\' on host \'{}\' (account \'{}\')'.format(value['instance_id'], value['host_id'], value['tenant_id']))
            continue
        else:
            processed_instances.append(value['instance_id'])
            Log.write('Checking instance \'{}\' on host \'{}\' (account \'{}\')'.format(value['instance_id'], value['host_id'], value['tenant_id']))

        ams_instance_spec = acrobind.create_viewspec_by_is_and_guid_property('InstanceManagement::Instance', '.ID', value['instance_id'])
        ams_instance_spec = acrobind.viewspec_apply_mask(ams_instance_spec, mask)

        ams_instance = acrobind.select_object(connection, ams_instance_spec)
        if ams_instance is None:
            Log.write('AMS doesn\'t have such instance. Skipping.')
            continue

        availability = 13
        if 'Availability' in ams_instance:
            availability = ams_instance.Availability.ref
        mms_instance_spec = acrobind.viewspec_apply_remote_host(ams_instance_spec, value['host_id'])
        mms_instance = None
        try:
            mms_instance = acrobind.select_object(connection, mms_instance_spec)
        except Exception as error:
            #Log.write('Couldn\'t get instance from host {}'.format(value['host_id']))
            if availability != 1:
                Log.write('Reseting Availability property for instance {} to 1.'.format(value['instance_id']))
                if args.fix:
                    set_ams_instance_availability(connection, ams_instance_spec, 1)
            continue

        #print(ams_instance)
        if mms_instance is None:
            Log.write('Instance {} is missing on host {}'.format(value['instance_id'], value['host_id']))
            continue

        if availability != 0:
            Log.write('Reseting Availability property for instance {} to 0.'.format(value['instance_id']))
            if args.fix:
                set_ams_instance_availability(connection, ams_instance_spec, 0)

        table = prettytable.PrettyTable(['Property', 'ams', 'mms', 'equal'])
        table.padding_width = 1
        def process_property(prop_name, ams_object, mms_object, table):
            ams_prop_state = None
            if prop_name in ams_object:
                ams_prop_state = ams_object[prop_name]
            mms_prop_state = None
            if prop_name in mms_object:
                mms_prop_state = mms_object[prop_name]

            equal = (ams_prop_state == mms_prop_state)
            ams_prop_state_str = '-'
            if ams_prop_state is not None and ams_prop_state.is_composite():
                ams_prop_state_str = '{}'.format(ams_prop_state)
            else:
                ams_prop_state_str = '{}'.format(ams_prop_state.ref)
            mms_prop_state_str = '-'
            if mms_prop_state is not None and mms_prop_state.is_composite():
                mms_prop_state_str = '{}'.format(mms_prop_state)
            else:
                mms_prop_state_str = '{}'.format(mms_prop_state.ref)
            table.add_row([prop_name, ams_prop_state_str, mms_prop_state_str, '+' if equal else '-'])
            return equal

        instance_name = ''
        tenant_str = get_tenant_string(ams_instance)
        if 'FullPath' in ams_instance:
            instance_name = ams_instance.FullPath.ref

        if 'FullPath' not in mms_instance or \
            'BackupState' not in mms_instance or \
            'BackupStatus' not in mms_instance or \
            'State' not in mms_instance or \
            'Status' not in mms_instance or \
            'LastBackup' not in mms_instance or \
            'LastBackupTry' not in mms_instance or \
            'NextBackupTime' not in mms_instance:
            Log.write('CORRUPTED MMS INSTANCE PROBLEM detected: {}, {}, {}'.format(value['instance_id'], instance_name, tenant_str))
            #print(mms_instance)
            continue

        instance_ok = True
        instance_ok = instance_ok and process_property('FullPath', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('BackupState', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('BackupStatus', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('State', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('Status', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('LastBackup', ams_instance, mms_instance, table)
        instance_ok = instance_ok and process_property('LastBackupTry', ams_instance, mms_instance, table)

        diff_next_start_time_unit = None
        if 'NextBackupTime' in mms_instance and 'Time' in mms_instance.NextBackupTime and mms_instance.NextBackupTime.Time.ref is not None:
            mms_next_start_time = mms_instance.NextBackupTime.Time.ref
            ams_next_start_time = 0
            if 'NextBackupTime' in ams_instance and 'Time' in ams_instance.NextBackupTime and ams_instance.NextBackupTime.Time.ref is not None:
                ams_next_start_time = ams_instance.NextBackupTime.Time.ref
                time_equal = (mms_next_start_time == ams_next_start_time)
                instance_ok = instance_ok and time_equal
                #print('{} {}'.format(mms_next_start_time, ams_next_start_time))
            else:
                #print('FALSE')
                time_equal = False
                instance_ok = False

            table.add_row(['NextStartTime', '{}'.format(ams_next_start_time), '{}'.format(mms_next_start_time), '+' if time_equal else '-'])
            diff_next_start_time = [
                ('.Time', 'sqword', mms_next_start_time),
                ('^Is', 'string', 'Gtob::Dto::NextExecutionTime')
            ]
            if 'Trigger' in mms_instance.NextBackupTime and mms_instance.NextBackupTime.Trigger.ref is not None:
                diff_next_start_time.append(('.Trigger', 'dword', mms_instance.NextBackupTime.Trigger.ref))
            diff_next_start_time_unit = acrort.plain.Unit(flat=diff_next_start_time)
        else:
            diff_next_start_time_unit = ams_instance.NextBackupTime

        if not instance_ok:
            Log.write('SYNC PROBLEM detected: {}, {}, {}'.format(value['instance_id'], instance_name, tenant_str))
            Log.write(table)
            if args.fix:
                update_ams_instance_state(connection, ams_instance_spec, mms_instance, ams_instance, diff_next_start_time_unit)
            #with open(fixed_instances_log, "a") as myfile:
            #    myfile.write('{}\n'.format(value['instance_id']))

        if instance_ok:
            if (ams_instance.Status.ref == 1 and mms_instance.Status.ref == 1) or (ams_instance.Status.ref == 0 and mms_instance.Status.ref == 0):
                Log.write('STATUS PROBLEM detected: {}, {}, {}'.format(value['instance_id'], instance_name, tenant_str))
                Log.write(table)
                if args.fix:
                    update_ams_instance_state(connection, ams_instance_spec, mms_instance, ams_instance, diff_next_start_time_unit)

        if 'LastBackup' in mms_instance and 'NextBackupTime' in mms_instance and 'Time' in mms_instance.NextBackupTime and mms_instance.NextBackupTime.Time.ref is not None:
            try:
                if mms_instance.LastBackup.ref > mms_instance.NextBackupTime.Time.ref:
                    Log.write('SCHEDULE PROBLEM detected: {}, {}, {}'.format(value['instance_id'], instance_name, tenant_str))
                    Log.write(table)
            except Exception as error:
                print(mms_instance.NextBackupTime)
                print(error)
#END --fix instance status--


def check_sync(connection):
    creation_time = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
    Log.write('[FIX_INSTANCES]: {}'.format(creation_time))

    agents_pattern = [
        ('^Is', 'string', 'MachineManagement::Machine'),
        ('.Info.Role', 'dword', 0),
        ('.Status', 'dword', 0)
    ]

    if args.check_sync != 'all':
        agents_pattern.append(('.Tenant.ID', 'string', args.check_sync))

    options = [
        ('.Mask.ID', 'nil', None),
        ('.Mask.Info.Name', 'nil', None),
        ('.Mask.Tenant', 'nil', None)
    ]

    spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=agents_pattern), options=acrort.plain.Unit(flat=options))

    processed_agents = []
    flag = False
    def process_agent(connection, cache, wait_flag, agent):
        #print(agent)
        host_id = agent.ID.ref
        if host_id in cache:
            Log.write('.', end='')
            wait_flag = True
            return
        else:
            if wait_flag:
                Log.write('.')
                wait_flag = False
            cache.append(host_id)

        name_str = '-'
        try:
            name_str = agent.Info.Name.ref
        except:
            pass

        if 'Tenant' not in agent:
            Log.write('MISSING tenant in {}, {}, {}'.format(name_str, host_id, get_tenant_string(agent)))
            return

        tenant_id = '{}'.format(agent.Tenant.ID.ref)
        #print(host_id)

        ams_activities_pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.State', 'dword', 5),
            #('^Source', 'string', '{}'.format(host_id)),
            ('.Details.MachineName', 'string', name_str),
            ('.Tenant.ID', 'string', tenant_id),
            ('.Details.Specific', 'string', 'Business'),
        ]

        agent_activities_pattern = [
            ('^Is', 'string', 'Tol::History::Plain::Activity'),
            ('.__source_machine', 'guid', agent.ID.ref),
            ('.State', 'dword', 5),
            ('.Details.Specific', 'string', 'Business'),
        ]

        options = [
            ('.SortingOptions.Period.FinishTime', 'sqword', 0),
            ('.SortingOptions.Period.FinishTime^Descending', 'nil', None),
            ('.LimitOptions', 'dword', 1),
            ('.Mask.ID', 'nil', None),
            ('.Mask.Tenant', 'nil', None),
            ('.Mask.Details.MachineName', 'nil', None),
            ('.Mask.Period.FinishTime', 'nil', None)
            ]

        ams_act_spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=ams_activities_pattern), options=acrort.plain.Unit(flat=options))
        mms_act_spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=agent_activities_pattern), options=acrort.plain.Unit(flat=options))

        ams_act = acrobind.select_object(connection, ams_act_spec)
        #print(ams_act)
        ams_act_time = 0
        if ams_act is not None and 'Period' in ams_act and 'FinishTime' in ams_act.Period:
            ams_act_time = ams_act.Period.FinishTime.ref

        mms_act = None
        try:
            mms_act = acrobind.select_object(connection, mms_act_spec)
        except Exception as error:
            #Log.write('Couldn\'t get instance from host {}'.format(value['host_id']))
            pass

        mms_act_time = 0
        if mms_act is not None and 'Period' in mms_act and 'FinishTime' in mms_act.Period:
            mms_act_time = mms_act.Period.FinishTime.ref

        if ams_act_time < mms_act_time:
            Log.write('NEED RESYNC: {}, {}, {}, {}, {}'.format(ams_act_time, mms_act_time, name_str, host_id, get_tenant_string(agent)))
            if args.fix:
                if args.parameter1:
                    psql_command = []
                    psql_command.append('psql')
                    psql_command.append('acronis_cms')
                    psql_command.append('-c')
                    psql_command.append('update attachedmachines set lastoperationid=1 where machineid=\'{}\''.format(host_id))
                    ret = subprocess.call(psql_command)
                    Log.write('Update last operation id: {}'.format(ret))

                drop_agent_connection(connection, host_id)
        else:
            Log.write('OK: {}, {}, {}, {}, {}'.format(ams_act_time, mms_act_time, name_str, host_id, get_tenant_string(agent)))

    callback = lambda x: process_agent(connection, processed_agents, flag, x)
    acrobind.enumerate_objects(connection, spec, callback, error_log)


def sync_monitor(connection):

    sync_monitor_for = args.sync_monitor
    names = {
      '7': 'Instance',
      '77': 'Aspect',
      '9': 'Machine',
      '15': 'Plan',
      '17': 'CentralizedItemProtection',
      '18': 'LocalItemProtection',
      '29': 'Activity',
      '101': 'Configuration',
      '102': 'Applications',
      '103': 'Cluster',
      '104': 'VmRessurection',
      '105': 'Archive',
      '106': 'Slice',
      '107': 'Vault',
      '108': 'Location',
      '109': 'Alert',
      '110': 'Notification',
      '111': 'Autoupdate',
      '112': 'Counter',
      '113': 'ProcessInfo',
      '114': 'UpgradeEvent11',
      '115': 'LocalPlan',
      '116': 'LocalProtectionObject',
      '117': 'Migration'
    }

    def format_epoch(x):
        if x is None:
            return 'Never'
        if x == 0:
            return 'Never'
        t = time.localtime(x)
        return time.strftime('%Y-%m-%d %H:%M:%S', t)

    def print_process_report(data):
        result = prettytable.PrettyTable(["ID", "Commits", "Reads", "RR", "Changed", "News", "Completions", "Objects", "Expired", "Fails"])
        result.align = "r"
        result.align["ID"] = "l"

        for x in data:
            is_summary = [unit.ref for name, unit in x.traits if unit.ref == "Sync::Replication::Monitoring::SummaryCounter"]
            if not is_summary and 'Processes' in x:
                for name, p in x.Processes:
                    if name in names:
                        session_id = '-'
                        if 'SessionID' in x:
                            session_id = str(x.SessionID.ref)
                        result.add_row([
                            "/{}/{}/{}".format(str(x.MachineID.ref), session_id, names[name]),
                            p.CommitCount.ref,
                            p.ReadCount.ref,
                            p.RemoteReadCount.ref,
                            format_epoch(p.SourceChanged.ref),
                            get_optional(p, 'ReadNewsCount'),
                            get_optional(p, 'CompletionCount'),
                            p.ObjectCount.ref,
                            get_optional(p, 'ExpiredObjectsCount'),
                            p.FailCount.ref
                        ])
        Log.write(result.get_string(sortby="ID", reversesort=False))

    def print_session_report(data):
        result = prettytable.PrettyTable(["ID", "StartTime", "InPackets", "InSize/K", "OutPackets", "OutSize/K",
                                          "Commits", "CommitTime", "Reads", "ReadTime", "RR", "RRTime",
                                          "Changed", "Recon", "Fails"])
        result.float_format["InSize/K"] = ".2"
        result.float_format["OutSize/K"] = ".2"
        result.align = "r"
        result.align["ID"] = "l"

        for x in data:
            is_summary = [unit.ref for name, unit in x.traits if unit.ref == "Sync::Replication::Monitoring::SummaryCounter"]
            if is_summary:
                result.add_row([
                    'Summary',
                    '-',
                    x.InCount.ref,
                    x.InSize.ref / 1024,
                    x.OutCount.ref,
                    x.OutSize.ref / 1024,
                    x.CommitCount.ref,
                    x.CommitTime.ref / 1000,
                    x.ReadCount.ref,
                    x.ReadTime.ref / 1000,
                    '-',
                    '-',
                    '-',
                    '-',
                    x.FailCount.ref
                ])
            else:
                result.add_row([
                    "/{}/{}".format(str(x.MachineID.ref), str(get_optional(x, 'SessionID'))),
                    format_epoch(get_optional(x, 'StartTime')),
                    x.InCount.ref,
                    x.InSize.ref / 1024,
                    x.OutCount.ref,
                    x.OutSize.ref / 1024,
                    x.CommitCount.ref,
                    x.CommitTime.ref / 1000,
                    x.ReadCount.ref,
                    x.ReadTime.ref / 1000,
                    get_optional(x, 'RemoteReadCount'),
                    '-' if get_optional(x, 'RemoteReadTime') is None else str(get_optional(x, 'RemoteReadTime') / 1000),
                    format_epoch(get_optional(x, 'Changed')),
                    get_optional(x, 'ReconcileTime'),
                    x.FailCount.ref
                ])

        Log.write(result.get_string(sortby="ID", reversesort=False))


    def apply_limits(spec):
        if args.parameter1 is not None and args.parameter2 is not None:
            limit_pattern = [
                ('.{0}'.format(args.parameter1), 'dword', int(args.parameter2))
            ]
            return acrort.dml.ViewSpec(spec.pattern.consolidate(acrort.plain.Unit(flat=limit_pattern)), spec.options)
        return spec


    if sync_monitor_for == 'summary':
        pattern = [
            ('^Is', 'string', 'Sync::Replication::Monitoring::Counter'),
            ('^Is', 'string', 'Sync::Replication::Monitoring::SummaryCounter')
        ]
        data = acrobind.select_objects(connection, apply_limits(acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=pattern))))
        print_session_report(data)

    elif sync_monitor_for == 'all':
        spec = apply_limits(acrobind.create_viewspec_by_is('Sync::Replication::Monitoring::Counter'))
        print(spec.pattern)
        data = acrobind.select_objects(connection, spec)
        print_session_report(data)
        print_process_report(data)
    else:
        data = acrobind.select_objects(connection, apply_limits(acrobind.create_viewspec_by_is_and_guid_property('Sync::Replication::Monitoring::Counter', '.MachineID', sync_monitor_for)))
        print_session_report(data)
        print_process_report(data)


def is_deprecated_vm_instance(id):
    deprecated_ids = [
        '98259016-909E-48dd-A240-EE97209F545C',
        'E7F120F4-5479-4C91-AEA0-ACE049E8F4CC',
        '1052D468-8EA9-6C59-A0DB-9E56FC6A23C6',
        'ADFC498F-C6A4-AF0B-0476-277362346360',
        'B7A68552-D940-4781-B4CD-95F178DA7B2C'
    ]
    return id in deprecated_ids;


def check_deprecated_vms(connection):
    deprecated_ids = [
        '98259016-909E-48dd-A240-EE97209F545C',
        'E7F120F4-5479-4C91-AEA0-ACE049E8F4CC',
        '1052D468-8EA9-6C59-A0DB-9E56FC6A23C6',
        'ADFC498F-C6A4-AF0B-0476-277362346360',
        'B7A68552-D940-4781-B4CD-95F178DA7B2C'
    ]

    instances_spec = acrobind.create_viewspec_by_is('InstanceManagement::Instance')
    instances_spec = acrobind.viewspec_apply_ids(instances_spec, deprecated_ids)

    instance_aspects_spec = acrobind.create_viewspec_by_is('InstanceManagement::InstanceAspect')

    ids_pattern = []
    for id in deprecated_ids:
        ids_pattern.append([('', 'guid', id)])

    pattern = [
        ('.Key.ID', 'guid', '00000000-0000-0000-0000-000000000000'),
        ('.Key.ID^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]),
    ]

    instance_aspects_spec = acrort.dml.ViewSpec(instance_aspects_spec.pattern.consolidate(acrort.plain.Unit(flat=pattern)), instance_aspects_spec.options)

    objects = acrobind.select_objects(connection, instances_spec)
    #dump instances
    for i in objects:
        Log.write(i)

    aspects = acrobind.select_objects(connection, instance_aspects_spec)
    for i in aspects:
        Log.write(i)

    if args.fix:
        Log.write('Removing deprecated aspects and instances...')
        start = time.time()
        connection.dml.delete(pattern=instance_aspects_spec.pattern)
        connection.dml.delete(pattern=instances_spec.pattern)
        Log.write('Elapsed: {0:.2f} s'.format(time.time() - start))


def check_status(connection_args):
    Log.write('')
    start = time.time()
    connection = acrort.connectivity.Connection(*connection_args)
    Log.write('Connection time: {0:.2f} s'.format(time.time() - start))

    table = prettytable.PrettyTable(["DB", "Connection Time (s)"])
    table.align["DB"] = "l"
    table.align["Connection Time (s)"] = "l"
    table.padding_width = 1

    def check_db_connection(connection, object, table):
        options = [('.LimitOptions', 'dword', 1)]
        start = time.time()
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=[('^Is', 'string', object)]), options=acrort.plain.Unit(flat=options))
        objects = acrobind.select_objects(connection, spec)
        table.add_row([object, '{0:.2f}'.format(time.time() - start)])

    check_db_connection(connection, 'Tenants::HierarchyNode', table)
    check_db_connection(connection, 'Gtob::Dto::ItemProtection', table)
    check_db_connection(connection, 'Tol::History::Plain::Activity', table)
    check_db_connection(connection, 'MachineManagement::Machine', table)
    check_db_connection(connection, 'InstanceManagement::Instance', table)
    check_db_connection(connection, 'Agent::Configuration', table)
    check_db_connection(connection, 'Tenant::Configuration', table)
    check_db_connection(connection, 'GroupManagement::Group', table)
    check_db_connection(connection, 'ArchiveManagement::Archive', table)
    check_db_connection(connection, 'Msp::AMS::Dto::Machine', table)

    Log.write(table.get_string(sortby="Connection Time (s)", reversesort=True))
    Log.write('')

    if args.extra:
        spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=[('^Is', 'string', 'Msp::Agent::Dto::Configuration')]))
        object = acrobind.select_object(connection, spec)
        table = prettytable.PrettyTable(["Name", "Value"])
        table.align["Name"] = "l"
        table.padding_width = 1
        table.add_row(["AgentID", str(get_optional(object, 'AgentID'))])
        table.add_row(["ZmqPublicKey", str(get_optional(object.Zmq, 'ZmqPublicKey'))])
        table.add_row(["Uplink", '{0}:{1}'.format(str(get_optional(object.Uplink.Address, 'Address')), str(get_optional(object.Uplink.Address, 'Port')))])
        Log.write(table.get_string(sortby="Name"))
        Log.write('')


def check_notifications():
    '''
    Print statistics about backup notifications
    '''
    def group(name, pattern):
        return '(?P<{}>{})'.format(name, pattern)


    def convert_date(date):
        return datetime.datetime.strptime(date, '%Y-%m-%d %H:%M:%S:%f')


    def calculate_average(avg, current, count):
        if count == 1:
            return avg + current
        else:
            return avg * (count - 1) / count + current / count


    date_pattern = r'\d[\d-]+ \d[\d:]+'
    thread_id_pattern = r'\d+'
    level_pattern = r'[\w]\d+'

    log_header_pattern = '^{} {} {}:'.format(group('date', date_pattern), thread_id_pattern, level_pattern)

    guid_pattern = '[0-9A-F-]+'
    activity_header_pattern = r'\[Activity\] ID: {}'.format(group('activity_id', guid_pattern))

    start_string = 'Backup reports: processing completed activity:'
    end_string = 'Backup reports: MarkProcessedAndFlushActivities'

    activity_start_line = '{} {} {}'.format(log_header_pattern, start_string, activity_header_pattern)
    activity_end_line = '{} {} {}'.format(log_header_pattern, end_string, activity_header_pattern)

    log_file = os.path.join(acrort.fs.APPDATA_COMMON, acrort.common.BRAND_NAME, 'AMS', 'backup_notifications.0.log')

    with open(log_file, encoding='latin-1') as log:
        activities = {}
        durations = []

        avg_time = datetime.timedelta()
        completed_count = 0

        line_number = 0
        for line in log:
            line_number += 1
            r = re.search(activity_start_line, line)
            if r:
                activity_id = r.group('activity_id')
                activities[activity_id] = {'id': activity_id, 'start': convert_date(r.group('date'))}
            r = re.search(activity_end_line, line)
            if r:
                activity_id = r.group('activity_id')
                data = activities.get(activity_id)
                if data:
                    completed_count += 1
                    data['end'] = convert_date(r.group('date'))
                    data['duration'] = data['end'] - data['start']

                    avg_time = calculate_average(avg_time, data['duration'], completed_count)

                    durations.append(data)

        s = sorted(durations, key=lambda x: x['duration'])

        Log.write('Slowest processed activities:')
        for idx, record in enumerate(reversed(s[-5:])):
            Log.write(idx + 1, 'time:', str(record['duration']), ' activity:', record['id'])
        Log.write()

        Log.write('Fastest processed activities:')
        for idx, record in enumerate(s[:5]):
            Log.write(idx + 1, 'time:', str(record['duration']), ' activity:', record['id'])
        Log.write()

        Log.write('Average activity processing time:', avg_time)
        Log.write('Count of analysed activities: ', completed_count)


def delete_object(connection, args):
    spec = acrort.plain.Unit(flat=[('.ID', 'guid', args.delete_object)])
    connection.dml.delete(pattern=spec)


def fix_all_centralized_protections(connection):
    Log.write('Do you want to fix Gtob::Dto::CentralizedProtection objects for all tenants?(y/n)')
    if not ask_user():
        return

    plans_pattern = [
        ('^Is', 'string', 'Gtob::Dto::ProtectionPlan'),
        ('.Origin', 'dword', 2), # Centralized plans only
    ]

    options = [
        ('.Mask.ID', 'nil', None),
        ('.Mask.Origin', 'nil', None),
    ]

    spec = acrort.dml.ViewSpec(pattern=acrort.plain.Unit(flat=plans_pattern), options=acrort.plain.Unit(flat=options))

    plans = []
    acrobind.enumerate_objects(connection, spec, lambda x: plans.append(x), error_log)
    Log.write('Going to check {} plans'.format(len(plans)))

    fix_centralized_protections(connection, plans)


def report_all_instances(connection):
    Log.write(';'.join(['ID', 'BackupState', 'BackupStatus', 'FullPath', 'HostID', 'Mobile', 'AD', 'ATIH', 'BackupAgent', 'SQL', 'Exchange', 'ESX', 'Hyper-V', 'LastBackup', 'NextBackup', 'State', 'Status', 'Type']))

    pattern = acrort.plain.Unit(flat=[
        ('^Is', 'string', 'InstanceManagement::Instance'),
    ])
    options = acrort.plain.Unit(flat=[
        ('.Mask.ID', 'nil', None),
        ('.Mask.BackupState', 'nil', None),
        ('.Mask.BackupStatus', 'nil', None),
        ('.Mask.FullPath', 'nil', None),
        ('.Mask.HostID', 'nil', None),
        ('.Mask.LastBackup', 'nil', None),
        ('.Mask.NextBackupTime.Time', 'nil', None),
        ('.Mask.State', 'nil', None),
        ('.Mask.Status', 'nil', None),
        ('.Mask.Type', 'nil', None),
        ('.Mask.HostInfo.Agents', 'nil', None),
    ])
    spec = acrort.dml.ViewSpec(pattern, options)

    def print_instance(instance):
        is_esx = False
        is_hyperv = False
        is_mobile = False
        is_ad = False
        is_ati = False
        is_win = False
        is_sql = False
        is_exchange = False
        is_hyperv = False
        if 'HostInfo' in instance and 'Agents' in instance.HostInfo:
            is_mobile = is_mobile_agent(instance.HostInfo.Agents)
            is_ad = is_ad_agent(instance.HostInfo.Agents)
            is_ati = is_ati_agent(instance.HostInfo.Agents)
            is_win = is_win_agent(instance.HostInfo.Agents)
            is_sql = is_sql_agent(instance.HostInfo.Agents)
            is_exchange = is_exchange_agent(instance.HostInfo.Agents)
            is_esx = is_esx_agent(instance.HostInfo.Agents)
            is_hyperv = is_hyperv_agent(instance.HostInfo.Agents)

        last_backup_stamp = instance.LastBackup.ref if instance.LastBackup.ref else 0
        next_backup_stamp = instance.NextBackupTime.Time.ref if instance.NextBackupTime.Time.ref else 0

        record = [
            instance.ID.ref,
            BACKUP_STATES.get(instance.BackupState.ref),
            BACKUP_STATUSES.get(instance.BackupStatus.ref),
            instance.FullPath.ref,
            instance.HostID.ref,
            '+' if is_mobile else '-',
            '+' if is_ad else '-',
            '+' if is_ati else '-',
            '+' if is_win else '-',
            '+' if is_sql else '-',
            '+' if is_exchange else '-',
            '+' if is_esx else '-',
            '+' if is_hyperv else '-',
            datetime.datetime.utcfromtimestamp(last_backup_stamp),
            datetime.datetime.utcfromtimestamp(next_backup_stamp),
            INSTANCE_STATES.get(instance.State.ref) if 'State' in instance else None,
            INSTANCE_STATUSES.get(instance.Status.ref),
            INSTANCE_TYPES.get(instance.Type.ref),
        ]

        print(';'.join([str(field) for field in record]).encode('utf-8').strip())

    acrobind.enumerate_objects(connection, spec, print_instance, error_log)


def format_epoch(x):
    t = time.localtime(x)
    return time.strftime('%Y-%m-%d %H:%M:%S', t)


def collect_protections(connection, tenants):
    pattern = acrort.plain.Unit(flat=[
        ('^Is', 'string', 'Gtob::Dto::Centralized::ItemProtection'),
    ])
    options = acrort.plain.Unit(flat=[
        ('.Mask.ID', 'nil', None),
        ('.Mask.Centralized.PlanID', 'nil', None),
        ('.Mask.HostID', 'nil', None),
    ])
    spec = acrort.dml.ViewSpec(pattern, options)
    spec = acrobind.viewspec_apply_tenants(spec, tenants)
    stat = {}
    protections = [x for x in acrobind.dml_utils.enum_objects(connection, spec)]

    for x in protections:
        agent = ''

        if 'HostID' in x:
            agent = str(x.HostID.ref)
        else:
            for name, trait_value in x.traits:
                if 'name' == 'Source':
                    agent = str(trait_value.ref)

        if agent in stat:
            stat[agent] += 1
        else:
            stat[agent] = 1
    return stat


def collect_tenants(connection, tenants):
    pattern = [
        ('^Is', 'string', 'Tenants::HierarchyNode'),
    ]

    if tenants:
        ids_pattern = []
        for tenant_id in tenants:
            ids_pattern.append([('', 'string', tenant_id)])

        pattern.append(('.Tenant.ID', 'string', ''))
        pattern.append(('.Tenant.ID^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]))

    options = acrort.plain.Unit(flat=[
        ('.Mask.ID', 'nil', None),
        ('.Mask.Name', 'nil', None),
        ('.Mask.Locator', 'nil', None),
        ('.Mask.Parent', 'nil', None),
    ])
    spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern), options)
    tenants = { str(x.ID.ref): x for x in acrobind.dml_utils.enum_objects(connection, spec) }
    return { id: tenant.Name.ref for id, tenant in tenants.items() }


def collect_public_keys(connection, tenants):
    pattern = [
        ('^Is', 'string', 'Msp::AMS::Dto::Machine'),
    ]

    if tenants:
        ids_pattern = []
        for tenant_id in tenants:
            ids_pattern.append([('', 'string', tenant_id)])

        pattern.append(('.OwnerID', 'string', ''))
        pattern.append(('.OwnerID^ValueIn', 'complex_trait', [('', 'array', ids_pattern)]))

    options = acrort.plain.Unit(flat=[
        ('.Mask.AgentID', 'nil', None),
        ('.Mask.IsEnabled', 'nil', None),
        ('.Mask.OwnerID', 'nil', None),
        ('.Mask.PublicKey', 'nil', None),
        ('.Mask.LastSeenTime', 'nil', None),
    ])
    spec = acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern), options)
    return { str(x.AgentID.ref): x for x in acrobind.dml_utils.enum_objects(connection, spec)  }


def collect_offline_machines(connection, tenants):
    pattern = acrort.plain.Unit(flat=[
        ('^Is', 'string', 'MachineManagement::Machine'),
        ('.Info.Role', 'dword', 0),
    ])
    options = acrort.plain.Unit(flat=[
        ('.Mask.ID', 'nil', None),
        ('.Mask.Info.Agents', 'nil', None),
        ('.Mask.Info.Hardware.MemorySize', 'nil', None),
        ('.Mask.Info.Hardware.ProcessorName', 'nil', None),
        ('.Mask.Info.Hardware.ProcessorFrequency', 'nil', None),
        ('.Mask.Info.Name', 'nil', None),
        ('.Mask.Info.OS.ArchitectureEdition', 'nil', None),
        ('.Mask.Info.OS.Name', 'nil', None),
        ('.Mask.Info.OS.OSCaps', 'nil', None),
        ('.Mask.Info.OS.OSType', 'nil', None),
        ('.Mask.Info.OS.ProductType', 'nil', None),
        ('.Mask.Info.ResidentialAddresses', 'nil', None),
        ('.Mask.LastConnectionTime', 'nil', None),
        ('.Mask.MachineAddress', 'nil', None),
        ('.Mask.Status', 'nil', None),
        ('.Mask.Tenant', 'nil', None),
        ('.Mask.UpdateState.UpdateIsAvailable', 'nil', None)
    ])
    spec = acrort.dml.ViewSpec(pattern, options)
    spec = acrobind.viewspec_apply_tenants(spec, tenants)
    return { str(x.ID.ref): x for x in acrobind.dml_utils.enum_objects(connection, spec) }


def compare_numbers(lhs, rhs):
    if lhs > rhs:
        return 1
    elif lhs == rhs:
        return 0
    else:
        return -1


def compare_versions(lhs, rhs):
    major, minor, build = lhs.split('.')
    major2, minor2, build2 = rhs.split('.')
    if major == major2:
        if minor == minor2:
            return compare_numbers(build, build2)
        else:
            return compare_numbers(minor, minor2)
    return compare_numbers(major, major2)


def get_product_type(build_number):
    if len(build_number) <= 2:
        return "mobile"
    versions = build_number.split('.')
    if int(versions[0]) < 11:
        return "mobile"
    elif int(versions[0]) == 1:
        return "msp"
    elif int(versions[2]) < 100:
        return "home"
    else:
        return "msp"


def get_agent_version(machine):
    version = machine.Info.Agents[0].Version.ref
    for agent in machine.Info.Agents:
        agent = agent[1]
        agent_version = agent.Version.ref
        if agent_version.count('.') != 2:
            continue
        if compare_versions(agent_version, version) > 0:
            version = agent_version
    return version


def get_os_caps(value):
    if value & 4:
        return 'APPLIANCE'
    if value & 1:
        return 'BOOTMEDIA'
    if value & 8:
        return 'LINUX_RAMDISK'
    if value & 16:
        return 'HYPERV'
    if value & 32:
        return 'DR_APPLIANCE'
    if value & 2:
        return 'SERVER'
    return ''


def get_additional_info(value):
    if value & 2:
        return 'DOMAIN CONTROLLER'
    if value & 3:
        return 'SERVER'
    if value & 1:
        return 'WORKSTATION'
    return ""


def report_all_machines(connection):
    tenants = []
    if args.tenant_id:
        tenants.append(args.tenant_id)
        Log.write('Checking subtenants of {0}'.format(args.tenant_id))
        tenant_spec = acrobind.create_viewspec_by_is_and_string_property('Tenants::HierarchyNode', '.ID', args.tenant_id)
        t = acrobind.select_object(connection, tenant_spec)
        if t:
            pattern = [
                ('^Is', 'string', 'Tenants::HierarchyNode'),
                ('.Locator', 'string', ''),
                ('.Locator^DirectRelative', 'string', t.Locator.ref)
            ]
            tenants_objects = acrobind.select_objects(connection, acrort.dml.ViewSpec(acrort.plain.Unit(flat=pattern)))
            for ten in tenants_objects:
              tenants.append(ten.ID.ref)

    protections = collect_protections(connection, tenants)
    tenant_names = collect_tenants(connection, tenants)
    public_keys = collect_public_keys(connection, tenants)
    offline_machines = collect_offline_machines(connection, tenants)

    print(';'.join(["Account", "EUC", "AccountName", "AgentID", "MachineName", "IsEnabled", "PublicKey", "DmlTimeStamp", "OSName", "OS", "Architecture", "OSCaps", "OSInfo", "Memory",
      "ProcessorName", "ProcessorFrequency", "IP", "Build", "Product", "IsUpdateAvailable", "PlanCount", "LastConnectionTimeOld", "DaysOfflineOld", "LastConnectionTime", "DaysOffline", "Status"]))
    report = []
    for agent_id, machine in offline_machines.items():
        agent_version = get_agent_version(machine)
        product = get_product_type(agent_version)

        machine_name = machine.Info.Name.ref
        last_connection_time_old = 0
        sec_offline_old = 0
        if 'LastConnectionTime' in machine:
            last_connection_time_old = format_epoch(machine.LastConnectionTime.ref)
            sec_offline_old = int(time.time()) - machine.LastConnectionTime.ref

        if agent_id not in public_keys:
            continue
        key = public_keys.get(agent_id, None)
        if not key:
            print('Cannot find key for {}({})'.format(machine_name, agent_id))
            continue
        if 'OwnerID' in key:
            owner_id = str(key.OwnerID.ref)
        if 'Tenant' not in machine:
            print('Cannot find account for {}({}), owner={}, name={}'.format(machine_name, agent_id, owner_id, tenant_names.get(owner_id, 'Not found')))
            continue

        last_connection_time = 0
        sec_offline = 0
        if 'LastSeenTime' in key:
            last_connection_time = format_epoch(key.LastSeenTime.ref)
            sec_offline = int(time.time()) - key.LastSeenTime.ref

        stamp = key.DmlTimeStamp.ref
        if 'Status' in machine:
            is_offline = machine.Status.ref == 1
        else:
            is_offline = True
        is_enabled = key.IsEnabled.ref
        os = {1: "Unknown", 2: "Windows", 3: "Windows", 4: "Linux", 5: "MacOS"}[machine.Info.OS.OSType.ref]
        os_name = machine.Info.OS.Name.ref
        architecture = {0: "Unknown", 1: "x86", 2: "x64"}[machine.Info.OS.ArchitectureEdition.ref]
        os_caps = ""
        if 'OSCaps' in machine.Info.OS:
            os_caps = get_os_caps(machine.Info.OS.OSCaps.ref)
        os_info = ""
        if 'ProductType' in machine.Info.OS:
            os_info = get_additional_info(machine.Info.OS.ProductType.ref)
        memory = 0
        processor = ""
        if 'Hardware' in machine.Info:
            memory = round(machine.Info.Hardware.MemorySize.ref / 1024 / 1024)
            processor = machine.Info.Hardware.ProcessorName.ref
            frequency = machine.Info.Hardware.ProcessorFrequency.ref
        update_available = False
        if 'UpdateState' in machine:
            update_available = machine.UpdateState.UpdateIsAvailable.ref

        address = ','.join([y.ref for i, y in machine.Info.ResidentialAddresses])
        public_key = ''
        plan_count = protections.get(agent_id, 0)
        if 'PublicKey' in key:
            public_key = str(key.PublicKey.ref)

        account = machine.Tenant.Locator.ref
        account_name = tenant_names.get(machine.Tenant.ID.ref, 'Not found')
        euc_name = tenant_names.get(machine.Tenant.ParentID.ref, 'Not found')
        report.append([account, euc_name, account_name, agent_id, machine_name, is_enabled, public_key, stamp, os_name, os, architecture, os_caps, os_info, memory, processor,
            frequency, address, agent_version, product, update_available, plan_count, last_connection_time_old, round(sec_offline_old / 86400), last_connection_time, round(sec_offline / 86400), is_offline])
    for r in report:
        print(';'.join([str(c) for c in r]))



def repair_credentials_internal(plan_id):
    def get_credential(access_token, token_type, tenant_id, cred_id):
        headers = dict({
            "Authorization": "{0} {1}".format(token_type, access_token),
            "X-Apigw-Tenant-Id": "{0}".format(tenant_id),
            #"X-Apigw-Tenant-Name"  tenant.Name;
            #"X-Apigw-Tenant-Locator": tenant.Locator;
        })
        cred_id = urllib.parse.quote_plus(cred_id)
        Log.write("Getting cred '{}' for tenant '{}'".format(cred_id, tenant_id))
        return requests.get(settings.cred_url + '/api/credentials_store/v2/credentials/' + cred_id + '?include_secret=true',
                                      headers=headers, timeout=30)

    def create_credential(access_token, token_type, tenant_id, cred_data):
        headers = dict({
            "Authorization": "{0} {1}".format(token_type, access_token),
            "X-Apigw-Tenant-Id": "{0}".format(tenant_id),
            #"X-Apigw-Tenant-Name"  tenant.Name;
            #"X-Apigw-Tenant-Locator": tenant.Locator;
        })
        Log.write("Adding cred  for tenant '{}'".format(tenant_id))
        return requests.post(settings.cred_url + '/api/credentials_store/v2/credentials',
                                      headers=headers, data=json.dumps(cred_data), timeout=30)



    def resolve_tenant(access_token, token_type, tenant_id, tenant):
        headers = dict({
            "Authorization": "{0} {1}".format(token_type, access_token),
            "X-Apigw-Tenant-Id": "{0}".format(tenant_id),
            #"X-Apigw-Tenant-Name"  tenant.Name;
            #"X-Apigw-Tenant-Locator": tenant.Locator;
        })
        Log.write("Resolving tenant '{}' ".format(tenant))
        return requests.get(settings.acc_url + '/api/account_server/v2/tenants/' + tenant + '?embed_path=true',
                                      headers=headers, timeout=30)


    def policy_patch(access_token, token_type, tenant_id, root_policy_id, body):
        headers = dict({
            "Authorization": "{0} {1}".format(token_type, access_token),
            "X-Apigw-Tenant-Id": "{0}".format(tenant_id),
            #"X-Apigw-Tenant-Name"  tenant.Name;
            #"X-Apigw-Tenant-Locator": tenant.Locator;
        })
        Log.write("Patching policy '{}' ".format(root_policy_id))
        return requests.patch(settings.bm_url + '/api/policy_management/v2/policies/' + root_policy_id,
                                      headers=headers, data = json.dumps(body), timeout=30)
    # get policy settings:
    settings = read_config()
    access_token, token_type = get_token(settings)
    was_patched = False
    was_skipped = False

    selection = _create_grpm_policy_selection(settings, access_token, token_type, [plan_id], 1)
    selection_id = selection.json().get('id')  #['items'][0]
    if not selection_id:
      Log.write('failed to find policy {}'.format(plan_id))
      return

    policy_filters = { 'policySelectionId': selection_id, 'fullComposite' : 'true', 'includeSettings' :'true' } 
    policies = _get_grpm_single_policy_with_filter(settings, access_token, token_type, plan_id, policy_filters)
    policies = policies.json()

    def _get_by_key(dict, keys):
      for k in keys:
        if isinstance(dict, list):
          dict = dict[k]
        else:
          dict = dict.get(k)
        if not dict:
          return None
      return dict

    def _set_by_key(dict, keys, new_value):
      current = 0
      for k in keys:
        if isinstance(dict, list):
          dict = dict[k]
        else:
          if current + 1 == len(keys):
            dict[k] = new_value
            return
          dict = dict.get(k)
        if not dict:
          return None
        current += 1

    was_patched = False
    was_skipped = False
    backup_policy_id = ''
    #for p in policies['items']: # for _get_grpm_policies_with_filter
    #  p = p.get('policy')

    is_msp = True

    for p in policies['policy']:
      if not p:
        continue
      #p = p[0] #  for _get_grpm_policies_with_filter
      p_type = p.get('type')
      if not p_type:
        continue
      cred_keys = []
      creds = []
      if p_type.find('policy.backup') == 0:
        if p.get('tenantId'):
           if len(p.get('tenantId')) == 36:
             is_msp = False
        backup_policy_id = p.get('id')
        legacy_plan = p.get('settings').get('plan')
        legacy_plan = json.loads(legacy_plan)

        passwordKey = ['legacyPlan','__vl','Options','__vl','BackupOptions','__vl','ArchiveProtection','__vl','PasswordUrl','__vl','UserName']
        archivePassword = _get_by_key(legacy_plan, passwordKey)
        if archivePassword:
          creds.append(archivePassword)
          cred_keys.append(passwordKey) 
       
        stage_root_key = ['legacyPlan','__vl','Route','__vl','Stages','__vl']
        stages  = _get_by_key(legacy_plan, stage_root_key)
        if stages:
          stage_index = 0
          for s in stages:
             stage_key = ['__vl','LocationCredentials','__vl','UserName']
             loc_password = _get_by_key(s, stage_key)
             if loc_password:
               stage_full_key = stage_root_key + [stage_index] + stage_key
               creds.append(loc_password)
               cred_keys.append(stage_full_key) 
             stage_index += 1

        # get and replace creds pointed by cred_keys[]
        if not cred_keys:
          # policy '...' has no credentials, skipping
          Log.write("policy '{}' has no credentials, skipping ".format(p['id']))
          continue

        plan_tenant = _get_by_key(legacy_plan, ['legacyPlan','__vl','Tenant', '__vl', 'ID','__vl'])
        if not is_msp:
          plan_tenant = _get_by_key(legacy_plan, ['legacyPlan', '__vl','Tenant', 'ID'])
          plan_tenant_locator = _get_by_key(legacy_plan, ['legacyPlan', '__vl','Tenant', 'Locator'])
          if plan_tenant is None:
            Log.write("can't get tenant id from plan, trying other way")
            plan_tenant = _get_by_key(legacy_plan, ['legacyPlan','__vl','Tenant', '__vl', 'ID','__vl'])
            plan_tenant_locator = _get_by_key(legacy_plan, ['legacyPlan','__vl','Tenant', '__vl', 'Locator'])
            if plan_tenant is None:
              Log.write("can't get tenant id from plan, plan will not be patched")
              return 2
          if plan_tenant_locator is None:
              Log.write("can't get tenant locator from plan, plan will not be patched")
              return 2


        def _compare_tenants(cred_tenant, plan_tenant):
            # returns True if tenant combination is allowed, that is, common parent is euc or partner, or folder within partner
            # iterate up the hierarhy until partner is encountered, or euc if no partner there
            def find_parents(tenant):
                partner = tenant
                euc = tenant
                while tenant:
                    tenant_resolved_tmp = resolve_tenant(access_token, token_type, 1, tenant)
                    tenant_resolved = tenant_resolved_tmp.json()
                    id = tenant_resolved.get('id')
                    kind = tenant_resolved.get('kind')
                    if not kind:
                       return False
                    if kind == 'partner':
                        partner = id
                        break
                    if kind == 'customer':
                       euc = id
                    print(tenant_resolved['kind'])
                    print(tenant)
                    tenant = tenant_resolved.get('parent_id')
                return (euc, partner)
            cred_euc, cred_partner = find_parents(cred_tenant)
            plan_euc, plan_partner = find_parents(plan_tenant)
            return plan_euc == cred_euc or cred_partner == plan_partner


        credentials_tenants = []
        should_patch = False
        for cred_key_in_plan in cred_keys:
          cred_id = _get_by_key(legacy_plan, cred_key_in_plan)
          if cred_id[:2] == '\u0002@':
              cred_id = cred_id[2:]
              cred_id = cred_id.replace(':', '@')
          full_credential = get_credential(access_token, token_type, 1, cred_id)
          print(full_credential)
          full_cred_json = full_credential.json()
          cred_tenant = full_cred_json.get('tenant')
          print('checking  cred  {}'.format(cred_tenant))
          tenant_resolved_tmp = resolve_tenant(access_token, token_type, 1, cred_tenant)
          tenant_resolved = tenant_resolved_tmp.json()

          # fake run for stands - always patch the plan
          if plan_tenant == cred_tenant:
              continue # tenants are equal, nothing to do

          if is_msp:
            valid = _compare_tenants(cred_tenant, plan_tenant)
            #print('cred and plan tenants compared: valid:{}'.format(valid))
            if not valid:
              Log.write('Policy will not be fixed because creds owned by unrelated owners: {} and {}'.format(plan_tenant, cred_tenant))
              was_skipped = True
              break # no more to check, plan is bad, nothing to fix

          Log.write('plan {} will be fixed'.format(plan_id))

          full_cred_json['tenant'] = plan_tenant
          full_cred_json.pop('id') # will generate new id
          #create new credlink
          cred_creation_result = create_credential(access_token, token_type, plan_tenant, full_cred_json)
          print(cred_creation_result)
          new_cred_link = cred_creation_result.json()['id']
          Log.write('generated new credlink {}'.format(new_cred_link))
          _set_by_key(legacy_plan, cred_key_in_plan, new_cred_link)
          should_patch = True 


        if should_patch:
          p.get('settings')['plan'] = legacy_plan
          patch_req_body = {"subject": {"policy": [{'id': backup_policy_id, 'settings': p.get('settings') }],
           "ensure_applications": False, "skip_ensure_total_protect": True} }

          if not is_msp:
            patch_req_body = {"subject": {"policy": [{'ID': backup_policy_id, 'Settings': p.get('settings') }] },
             "ensureApplications": False, "skipEnsureTotalProtect": True,  "disableApplicationsOnConflict": False}

          request_tenant = 1 if is_msp else plan_tenant
          request_plan_id = plan_id if is_msp else backup_policy_id
          policy_patch_result = policy_patch(access_token, token_type, request_tenant, request_plan_id, patch_req_body)

          was_patched = True
          if policy_patch_result.status_code >= 300:
            Log.write('policy patch failed {}: {}'.format(policy_patch_result.status_code, policy_patch_result.text))
            

    if was_patched:
        return 1
    if was_skipped:
        return 2
    return 0

def _get_or_default(obj, name, default):
    if isinstance(obj, dict):
        return obj.get(name, default)
    elif isinstance(obj, list) and name.isdigit():
        idx = int(name)
        return default if idx < 0 or idx >= len(obj) else obj[idx]
    else:
        return default

def repair_credentials():
    #    CONFIG_PATH = "/usr/lib/Acronis/ZmqGw/net.config"
    #    settings = read_config(CONFIG_PATH)
    settings = read_config()

    access_token, token_type = get_token(settings)
    repair_credentials_internal(args.plan_id)
    patched_counter = 0
    skipped_counter = 0
    after_cursor = None

    if args.plan_id[0] == '@':
      print('reading policy ids from file {}'.format(args.plan_id[1:]))
      with open(args.plan_id[1:]) as f:
        while True:
          try:
            line = f.readline()
            if not line:
               break
            id = line.strip()
            Log.write('Processing policy id={}'.format(id))
            result = repair_credentials_internal(id)
            if result == 2:
                skipped_counter += 1
            elif result == 1:
                patched_counter += 1
          except Exception as ex:
              Log.write('failed to process a policy id={}'.format(id))
              Log.write(format_backtrace(ex))
      
    if args.plan_id == 'all':
      print('will fix all policies')
      if args.initial_cursor:
         after_cursor = args.initial_cursor
      processed_count = 0
      while True:
          policy_filters = { 'policySelectionId': 'selection_id', 'fullComposite' : 'true', 'includeSettings' :'true', 'limit' : '400'}
          if after_cursor:
            policy_filters['after'] = after_cursor
          policies = _get_grpm_policies_with_filter(settings, access_token, token_type, policy_filters)
          policies = policies.json()
          for p in policies['items']:
             id = None
             try:
                 if p['policy'][0]['type'] == 'policy.protection.total':
                     id = p['policy'][0]['id']
                     Log.write('Processing policy id={}'.format(id))
                     processed_count += 1
                     result = repair_credentials_internal(id)
                     if result == 2:
                         skipped_counter += 1
                     elif result == 1:
                         patched_counter += 1
             except Exception as ex:
                 Log.write('failed to process a policy id={}'.format(id))
                 Log.write(format_backtrace(ex))

          Log.write('processed count : {}'.format(processed_count))
          curs = _get_or_default(policies, 'paging', {})
          curs = _get_or_default(curs, 'cursors', {})
          curs = _get_or_default(curs, 'after', {})
          after_cursor = curs
          if not after_cursor:
             break

    Log.write('patched policies: {}, skipped because tenants are unrelated: {} '.format(patched_counter, skipped_counter))

def main():
    parser = acrobind.CommandLineParser()
    parser.append_processor(acrobind.OutputArgumentsProcessor())
    parser.add_argument('--connection', nargs=3, metavar=('HOST', 'USERNAME', 'PASSWORD'))

    #resource specific options
    parser.add_argument('-mi', '--check-machine-id', dest='machine_id', help='Show info about machine by its ID and related instances.\
    Use \'-f\' option to enable fix mode. Use \'-ru\' for resetting \'MachineIsProcessed\' property. \
    Use \'-ra\' to list running activities. Use \'-br\' for dropping connection. \
    Example: \'acropsh -m amsctl -mi A8B415CD-3259-4E71-A38B-DE136FBCF6CE\'.', required=False)
    parser.add_argument('-mn', '--check-machine-name', dest='machine_name', help='List all machines that match provided name.\
    Example: \'acropsh -m amsctl -mn MyMachine\'.', required=False)
    parser.add_argument('-ru', '--reset-update', dest='reset_update', action='store_true', help='Reset \'MachineIsProcessed\' property for MachineManagement::Machine.\
    Can be used with \'-mi\' option only. Example: \'acropsh -m amsctl -mi A8B415CD-3259-4E71-A38B-DE136FBCF6CE -ru\'.', required=False)
    parser.add_argument('-ra', '--running-activities', dest='running_activities', action='store_true', help='List all running activities for tenant or machine if it is ONLINE.\
    Can be used with \'-mi\' option only. Example: \'acropsh -m amsctl -mi A8B415CD-3259-4E71-A38B-DE136FBCF6CE -ra\'.', required=False)
    parser.add_argument('-br', '--break-connection', dest='break_connection', action='store_true', help='Drop connection with agent if it is connected.\
    Can be used with \'-mi\' option only. Example: \'acropsh -m amsctl -mi A8B415CD-3259-4E71-A38B-DE136FBCF6CE -br\'.', required=False)
    parser.add_argument('-in', '--check-instance-name', dest='instance_name', help='List all instances that match provided name.', required=False)
    parser.add_argument('-ii', '--check-instance-id', dest='instance_id', help='Show info about instance by its ID ans applied protections.', required=False)
    parser.add_argument('-dom', '--drop-orphaned-machines', dest='drop_orphaned_machines', help='Remove machines without tenant.\
    Can be used with \'-ti\' option.', action='store_true', required=False)
    parser.add_argument('-doi', '--drop-orphaned-instances', dest='drop_orphaned_instances', help='Remove instances without host.\
    Can be used with \'-ti\' option.', action='store_true', required=False)

    #tenant specific options
    parser.add_argument('-ti', '--check-tenant-id', dest='tenant_id', help='Show info about specified tenant and its related objects.\
    Use \'-qr\' for recalculating quotas usage. Use \'-ms\' for showing statistics object for this tenant. Use \'-ra\' for running activities. Example: \'acropsh -m amsctl -ti 13456\'.', required=False)
    parser.add_argument('-tn', '--check-tenant-name', dest='tenant_name', help='List tenants that match specified name.', required=False)
    parser.add_argument('-qr', '--quotas-reconcile', dest='quotas_reconcile', action='store_true', help='Recalculate quotas for account.\
    Can be used with \'-ti\' only. Use \'all\' for reconcile quotas on all accounts. Example: \'acropsh -m amsctl -ti 13456 -qr\'.', required=False)
    parser.add_argument('-ms', '--machine-statistics', dest='machine_statistics', action='store_true', help='Show MachineManagement::Statistics for account.\
    Can be used with \'-ti\' only. Example: \'acropsh -m amsctl -ti 13456 -ms\'.', required=False)
    parser.add_argument('--check-multitenancy', dest='check_multitenancy', action='store_true', help='Check if tenant has virtual instances that belong to hosts of different tenant')
    parser.add_argument('-u', '--update', dest='update', action='store_true', help='Update section \'.Tenant\' in all dml objects by specified tenant')
    #plans specific options
    parser.add_argument('-pn', '--plan-name', dest='plan_name', help='List all protection plans that match specified name.', required=False)
    parser.add_argument('-pi', '--plan-id', dest='plan_id', help='Show info about protection plan and related item protections.\
    Use \'-r\' for protection plan redeployment.', required=False)
    parser.add_argument('-r', '--redeploy', dest='redeploy', action='store_true', help='Force protection plan redeployment.\
    Can be used only with \'-pi\' option. Example: \'acropsh -m amsctl -pi E9A35C00-388F-4522-AD07-981139D6F9A3 -r\'.', required=False)
    parser.add_argument('--check-plan-list', dest='check_plan_list', help='List all protection plans that match specified name.', required=False)

    # Option for both tenants and plans
    parser.add_argument('-fcp', '--fix-centralized-protection', dest='fix_centralized_protection', action='store_true', help='Create missing Gtob::Dto::CentralizedProtection object.\
    Can be used only with \'-pi\' and \'-ti\' options. Example: \'acropsh -m amsctl -pi E9A35C00-388F-4522-AD07-981139D6F9A3 -fcp\'.', required=False)
    parser.add_argument('-o', '--change-owner', dest='change_owner', help='Change owner of Gtob::Dto::CentralizedProtection object.\
    Can be used only with \'-pi\' option. Example: \'acropsh -m amsctl -pi E9A35C00-388F-4522-AD07-981139D6F9A3 -o 102665\'.', required=False)

    #common options
    parser.add_argument('-f', '--fix', dest='fix', action='store_true', help='Enable fix mode. Can be used with other options.', required=False)
    parser.add_argument('-d', '--delete', dest='delete', action='store_true', help='Enable delete mode (interactive). Can be used with other options.', required=False)
    parser.add_argument('-e', '--extra', dest='extra', action='store_true', help='Prints extra data. Can be used with other options.', required=False)

    #auto update
    parser.add_argument('-bu', '--build-url', dest='build_url', help='List auto update using build url', required=False)
    parser.add_argument('-ui', '--update-id', dest='update_id', help='List auto update using id', required=False)
    parser.add_argument('-lu', '--list-updates', dest='list_updates', action='store_true', help='List all auto updates', required=False)

    #misc options
    parser.add_argument('-p1', '--parameter1', dest='parameter1', help='Custom parameter that can be used with other options.', required=False)
    parser.add_argument('-p2', '--parameter2', dest='parameter2', help='Custom parameter that can be used with other options.', required=False)
    parser.add_argument('--fix-instances', dest='fix_instances', help='Check/Fix instances statuses for specified tenant or \'all\'. \
    It checks all recent activities for last 24 hours or time period specifid using \'p1\' and \'p2\' options.\
    Use \'--fix-instances all\' for all tenants.\
    Use \'-f\' for fixing. Use\'p1\' for specifing the amount of days and \'p2\' for hours.\
    Example: \'--fix-instances all -f -p1 2 -p2 13\'', required=False)
    parser.add_argument('--check-sync', dest='check_sync', help='Check/Fix sync statuses for machines of specified tenant or \'all\'. \
    It checks most recent activitiy on AMS side and most recent activity on MMS side. If theirs \'FinishTime\' isn\'t equal and \'-f\' specified \
    connection to MMS will be dropped and it will cause re-sync. Use \'p1\' with any value to drop connection to old agent (<=6.0).', required=False)
    parser.add_argument('--check-deprecated-vms', dest='check_deprecated_vms', action='store_true', help='Checks existence of deprecated virtual instances (aka Red hat KVM and etc).\
    Use \'-f\' to force deletion of deprecated instances.', required=False)
    parser.add_argument('-sm', '--sync-monitor', dest='sync_monitor', help='Show sync statistics. Show summary if \'summary\' is specified. Show all channels if \'all\' specified.\
    Show specific machine channels if machine ID is specified. Example: \'watch acropsh -m amsctl -sm E9A35C00-388F-4522-AD07-981139D6F9A3\'.', required=False)
    parser.add_argument('-ps', '--perf-stat', dest='perf_stat', action='store_true', help='Show aggregated DML performance statistics using data from \'SERVICE_HOME_DIR/perf_stat\' folder.', required=False)
    parser.add_argument('-ds', '--dml-stat', dest='dml_stat', action='store_true', help='Show aggregated DML performance statistics using data from \'SERVICE_HOME_DIR/perf_stat\' folder.', required=False)
    parser.add_argument('-od', '--obsolete-data', dest='obsolete_data', action='store_true', help='Analyze objects consistency. May take a lot of time if run for \'all\'.\
    Use \'-p1\' for checking objects of specific tenant . Use \'p2\' for specifing action: \'ip\' - only check item protections, \'protection\' or \'all\'.\
    Example: \'acropsh amsctl.py -od  -p1 92271\'.', required=False)
    parser.add_argument('-ai', '--agent-info', dest='agent_info', action='store_true', help='Collect agents statistics and outputs it as JSON.', required=False)
    parser.add_argument('-usi', '--user-info', dest='user_info', action='store_true', help='Collect info about users and outputs it as JSON.', required=False)
    parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', help='Disable output to stdout except result.', required=False)
    parser.add_argument('-c', '--count', dest='count', help='Count objects. Specify \'machine-statistics\' for machine statistics or \'backup-statistics\' for backup statistics, \
    otherwise tries to count objects assumes input as \'Is\' value.\
    Example: \'acropsh amsctl.py -c Gtob::Dto::ItemProtection\'.', required=False)
    parser.add_argument('-s', '--status', dest='check_status', action='store_true', help='Check AMS status.', required=False)
    parser.add_argument('-bn', '--backup-notifications', dest='backup_notifications', action='store_true', help='Show information about backup notifications', required=False)
    parser.add_argument('-do', '--delete-object', dest='delete_object', help='Delete object by ID', required=False)
    parser.add_argument('--fix-all-centralized-protections', dest='fix_all_centralized_protections', action='store_true', help='Create missing Gtob::Dto::CentralizedProtection objects \
    for all plans', required=False)
    parser.add_argument('--report-all-instances', dest='report_all_instances', action='store_true', help='Output information about all instances')
    parser.add_argument('--report-all-machines', dest='report_all_machines', action='store_true', help='Report offline machines')

    #8.0+
    parser.add_argument('-cr', '--cr', dest='get_creds', help='get credentials', action='store_true', required=False)
    parser.add_argument('-crep', '--crep', dest='repair_creds', help='repair credentials for plan. Requires -pi', action='store_true', required=False)
    parser.add_argument('-crl', '--cred_list', dest='cred_list', help='cred list for use with -cr', required=False)
    parser.add_argument('-cursor', '--cursor', dest='initial_cursor', help='initial cursor value for policies selection', required=False)

    config = None
    try:
        config = parser.parse_arguments()
    except acrort.Exception as exception:
        error = exception.to_error()
        ret = error.to_exit_code()
        if ret == acrort.common.EXCEPTION_AWARE_RETURN_CODE:
            error.throw()
        return ret

    global args
    args = config['args']

    try:
        global Log
        if args.quiet:
            Log = acrobind.NullOutput()
        else:
            Log = acrobind.Output(config, end='\n')

        if args.perf_stat:
            perf_stat()
            return

        if args.dml_stat:
            dml_stat()
            return

        if args.backup_notifications:
            check_notifications()
            return

        if args.repair_creds:
            repair_credentials()
            return
  

        if args.connection:
            hostname = args.connection[0]
            username = args.connection[1]
            password = args.connection[2]
            Log.write('Connecting to AMS \'{0}@{1}\' ...'.format(username, hostname), end='')
            connection_args = ('ams', hostname, (username, password))
        else:
            Log.write('Connecting to AMS locally...', end='')
            connection_args = ('ams', )

        if args.check_status:
            check_status(connection_args)
            return

        connection = acrort.connectivity.Connection(*connection_args, client_session_data={"identity_disabled": True})
        Log.write('done')

        if args.count:
            counter_mode(connection)
            return
        if args.list_updates or args.build_url or args.update_id:
            analyze_updates(connection)
            return
        if args.user_info:
            collect_users_info(connection)
            return
        if args.agent_info:
            collect_agents_statistics(connection)
            return
        if args.obsolete_data:
            obsolete_data(connection)
            return
        if args.report_all_machines:
            report_all_machines(connection)
            return
        if args.quotas_reconcile:
            quotas_reconcile(connection)
            return
        if args.drop_orphaned_machines:
            drop_orphaned_machines(connection)
            return
        if args.drop_orphaned_instances:
            drop_orphaned_instances(connection)
            return
        if args.tenant_id:
            check_tenant(connection)
            return
        if args.tenant_name:
            list_tenants(connection)
            return
        if args.check_plan_list:
            check_plan_list(connection)
            return
        if args.plan_name or args.plan_id:
            describe_plans(connection)
            return
        if args.machine_name:
            list_machines(connection)
            return
        if args.instance_name:
            list_instances(connection)
            return
        if args.fix_instances:
            fix_instances(connection)
            return
        if args.check_sync:
            check_sync(connection)
            return
        if args.sync_monitor:
            sync_monitor(connection)
            return
        if args.check_deprecated_vms:
            check_deprecated_vms(connection)
            return
        if args.delete_object:
            delete_object(connection, args)
            return
        if args.fix_all_centralized_protections:
            fix_all_centralized_protections(connection)
            return
        if args.report_all_instances:
            report_all_instances(connection)
            return
        if args.report_all_machines:
            report_all_machines(connection)
            return
        if args.instance_id:
            check_instance(connection)
        if args.machine_id:
            check_machine(connection)

    except Exception as e:
        error_log(format_backtrace(e))


if __name__ == '__main__':
    exit(acrobind.interruptable_safe_execute(main))

