package ru.bitel.bgbilling.modules.inet.dyn.device.terminal;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.regex.Pattern;

import jakarta.annotation.Resource;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import ru.bitel.bgbilling.apps.inet.access.Access;
import ru.bitel.bgbilling.apps.inet.access.sa.ServiceActivatorAdapter;
import ru.bitel.bgbilling.apps.inet.access.sa.ServiceActivatorEvent;
import ru.bitel.bgbilling.common.BGException;
import ru.bitel.bgbilling.common.BGRuntimeException;
import ru.bitel.bgbilling.common.bean.IPUtils;
import ru.bitel.bgbilling.common.model.ConfigParameter;
import ru.bitel.bgbilling.kernel.base.server.DefaultContext;
import ru.bitel.bgbilling.kernel.container.managed.ServerContext;
import ru.bitel.bgbilling.kernel.contract.api.common.util.MACParser;
import ru.bitel.bgbilling.kernel.contract.runtime.ContractRuntime;
import ru.bitel.bgbilling.kernel.contract.runtime.ContractRuntimeMap;
import ru.bitel.bgbilling.modules.inet.common.bean.InetConnection;
import ru.bitel.bgbilling.modules.inet.common.bean.InetDevice;
import ru.bitel.bgbilling.modules.inet.common.bean.InetDeviceType;
import ru.bitel.bgbilling.modules.inet.common.bean.InetServ;
import ru.bitel.bgbilling.modules.inet.common.bean.enums.InetServState;
import ru.bitel.bgbilling.modules.inet.common.service.InetServService;
import ru.bitel.bgbilling.modules.inet.server.runtime.InetInterfaceMap;
import ru.bitel.bgbilling.modules.inet.server.runtime.InetOptionRuntime;
import ru.bitel.bgbilling.modules.inet.server.runtime.InetOptionRuntimeMap;
import ru.bitel.bgbilling.modules.inet.server.runtime.device.InetDeviceRuntime;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Preferences;
import ru.bitel.common.TimeUtils;
import ru.bitel.common.Utils;
import ru.bitel.common.inet.IpAddress;
import ru.bitel.common.inet.IpNet;
import ru.bitel.common.ref.ConcurrentReferenceHashMap;
import ru.bitel.common.util.MacrosFormat;
import ru.bitel.common.util.RangedIntegerSet;
import ru.bitel.oss.kernel.entity.common.bean.EntityAttr;
import ru.bitel.oss.systems.inventory.resource.common.bean.VlanResource;
import ru.bitel.oss.systems.inventory.resource.common.service.ResourceService;
import ru.bitel.oss.systems.inventory.resource.server.ip.dynamic.IpResourceRuntime;

/**
 * Обработчик активации сервисов на основе выполнения простых команд.
 * @author amir
 *
 */
public abstract class AbstractTerminalServiceActivator
    extends ServiceActivatorAdapter
{
	private static final Logger logger = LogManager.getLogger();
	
	protected final static String SA_HOST = "sa.host";
	protected final static String SA_PORT = "sa.port";
	protected final static String SA_USERNAME = "sa.username";
	protected final static String SA_PASSWORD = "sa.password";
    protected final static String SA_SOURCE_HOST = "sa.sourceHost";
    protected final static String SA_SOURCE_PORT = "sa.sourcePort";
	
	private static final ConcurrentReferenceHashMap<Integer, Semaphore> SEMAPHORE_MAP = new ConcurrentReferenceHashMap<Integer, Semaphore>( ConcurrentReferenceHashMap.ReferenceType.SOFT, ConcurrentReferenceHashMap.ReferenceType.STRONG );
	
	protected int moduleId;

	/**
	 * Код устройства.
	 */
	protected int deviceId;

	/**
	 * Устройство.
	 */
	protected InetDevice device;

	/**
	 * Конфигурация устройства.
	 */
	protected ParameterMap config;

	protected String host;
	protected int port;

	protected String username;
	protected String password;

	protected int timeout;
	protected long readWait;

	/**
	 * Мап опций Inet.
	 */
	protected InetOptionRuntimeMap optionRuntimeMap;

	protected MacrosFormat macrosFormat;

	protected static class Commands
	{
		public final String[] enableCommands;
		public final String[] disableCommands;

		public Commands( String[] enableCommands, String[] disableCommands )
		{
			this.enableCommands = enableCommands;
			this.disableCommands = disableCommands;
		}
	}

	protected static class CommandSet
	{
		/**
		 * Команды создания сервиса на устройстве.
		 */
		protected String[] servCreateCommands;

		/**
		 * Команды удаления сервиса с устройства.
		 */
		protected String[] servCancelCommands;

		/**
		 * Команды изменения сервиса на устройстве.
		 */
		protected Commands servModifyCommands;
		
		/**
		 * Команды изменения опций сервиса на устройстве.
		 */
		protected String[] servOptionModifyCommands;
		/**
		 * Команды изменения опций сервиса на устройстве.
		 */
		protected Map<Integer, Commands> servOptionModifyCommandsMap;

		/**
		 * Команды изменения соединения на устройстве.
		 */
		protected Commands connectionModifyCommands;
		/**
		 * Команды изменения опций соединения на устройстве.
		 */
		protected Map<Integer, Commands> connectionOptionModifyCommandsMap;
		/**
		 * Команды закрытия соединения на устройстве.
		 */
		protected String[] connectionCloseCommands;

		/**
		 * Команды на начало аккаунтинга (старта соединения).
		 */
		protected String[] onAccountingStartCommands;
		/**
		 * Команды на окончание аккаунтинга (стоп соединения).
		 */
		protected String[] onAccountingStopCommands;
		
		/**
		 * Команды включения/выключения опций сервиса на устройстве(общие для всех опций ) 
		 */
		protected Commands servOptionCommonCommands;
		
        /**
         * Команды включения/выключения опций соединения на устройстве(общие для всех опций ) 
         */
		protected Commands conOptionCommonCommands;
        
        /**
         * Команды изменения опций сервиса на устройстве, которые задаются на опции предке (действуют для всех опции, которые находятся снизу).
         */
        protected Map<Integer, Commands> servOptionAncestorModifyCommandsMap;
        
        /**
         * Команды изменения опций соединения на устройстве, которые задаются на опции предке (действуют для всех опции, которые находятся снизу).
         */
        protected Map<Integer, Commands> conOptionAncestorModifyCommandsMap;     
        
        /**
         * Переключать ли опции на accounting stop/start  
         */
        protected boolean switchOptionsOnAccouting;
        
        protected boolean switchStateOnAccouting;
	}

	/**
	 * Команды, выполняемые при подключении к терминалу.
	 */
	protected String[] connectCommands;
	/**
	 * Команды, выполняемые перед отключением от терминала.
	 */
	protected String[] disconnectCommands;

	/**
	 * Команда выхода (отключечния от терминала).
	 */
	protected String exitCommand;

	/**
	 * Набор команд по умолчанию.
	 */
	protected CommandSet commandSet;

	protected Map<String, CommandSet> commandSetMap;

	protected Map<Integer, CommandSet> servTypeCommandSetMap;

	/**
	 * Фильтр опций Inet, с котороми происходит работа.
	 */
	protected Set<Integer> workingOptions;

	/**
	 * Нужно ли после смены состояния соединения сразу менять состояние в базе.
	 */
	protected boolean needConnectionStateModify;

	protected boolean skipServiceAccountingEvents;

	protected Set<Integer> accountingEventDeviceIds;
	
	protected int connectionSemaphorePermits = 0;
	
	protected Semaphore connectionSemaphore;
	
	protected boolean connectionSemaphoreAcquired;

	@Resource(name = "access")
	private Access access;

	private EntityAttr getDeviceAttribute( final int deviceId, final int entitySpecAttrId )
	{
		final InetDeviceRuntime deviceRuntime = access.deviceMap.get( deviceId );
		if ( deviceRuntime == null )
		{
			logger.warn( "Device not found with id=" + deviceId );
			return null;
		}

		final InetDevice device = deviceRuntime.inetDevice;

		return device.getEntityAttributes().get( entitySpecAttrId );
	}
	
	private String getDeviceIP( final InetDevice device )
	{
		final List<InetSocketAddress> list = device.getHosts();
		if ( list.size() > 0 )
		{
			return IpAddress.toString( list.get( 0 ).getAddress().getAddress() );
		}

		return "null";
	}
	
	private String getDeviceIP( final int deviceId )
	{
		final InetDeviceRuntime deviceRuntime = access.deviceMap.get( deviceId );
		if ( deviceRuntime == null )
		{
			logger.warn( "Device not found with id=" + deviceId );
			return "null";
		}

		final InetDevice device = deviceRuntime.inetDevice;
		return getDeviceIP( device );
	}
	
	private String getDeviceIdentifier( final int deviceId )
	{
		final InetDeviceRuntime deviceRuntime = access.deviceMap.get( deviceId );
		if ( deviceRuntime == null )
		{
			logger.warn( "Device not found with id=" + deviceId );
			return "null";
		}

		final InetDevice device = deviceRuntime.inetDevice;

		return device.getIdentifier();
	}

	private IpResourceRuntime getIpResource( final InetServ serv, final InetConnection connection )
		throws BGException
	{
		int ipResourceId;
		if ( connection != null )
		{
			ipResourceId = connection.getIpResourceId();
			if ( ipResourceId <= 0 )
			{
				ipResourceId = serv.getIpResourceId();
			}
		}
		else
		{
			ipResourceId = serv.getIpResourceId();
		}

		if ( ipResourceId > 0 )
		{
			return access.ipResourceManager.getResource( ipResourceId );
		}

		return null;
	}
	
	private void ipList( InetServ serv, StringBuilder sb )
	{
		final byte[] addressFrom = serv.getAddressFrom();
		byte[] addressTo = serv.getAddressTo();

		if ( addressFrom == null )
		{
			return;
		}

		if ( addressTo == null )
		{
			addressTo = addressFrom;
		}

		final byte[] ip = new byte[addressFrom.length];
		System.arraycopy( addressFrom, 0, ip, 0, addressFrom.length );

		for ( int i = 0; i < 255 && IpAddress.compare( ip, addressTo ) <= 0; i++ )
		{
			sb.append( IpAddress.toString( ip ) ).append( ',' );

			if ( !IpAddress.increment( ip ) )
			{
				break;
			}
		}
	}

	@Override
	public Object init( Setup setup, int moduleId, InetDevice device, InetDeviceType deviceType, ParameterMap config )
		throws Exception
	{
		super.init( setup, moduleId, device, deviceType, config );
		
		final ParameterMap deviceConfig = new Preferences( deviceType.getConfig(), "\n" )
			.inherit( new Preferences( device.getInvConfig(), "\n" ) )
			.inherit( new Preferences( device.getConfig(), "\n" ) );

		this.moduleId = moduleId;

		this.deviceId = device.getId();
		this.device = device;
		this.config = config;

		List<InetSocketAddress> hosts = device.getHosts();
		if ( hosts.size() > 0 )
		{
			InetSocketAddress socketAddress = hosts.get( 0 );
			this.host = socketAddress.getAddress().getHostAddress();
			this.port = socketAddress.getPort();
		}

		this.host = config.get( SA_HOST, this.host );
		this.port = config.getInt( SA_PORT, this.port );

		this.username = device.getUsername();
		this.password = device.getPassword();
		
		this.username =  config.get( SA_USERNAME, device.getUsername() );
        this.password = config.get( SA_PASSWORD, device.getPassword() );

		this.optionRuntimeMap = InetOptionRuntimeMap.getInstance( moduleId );

		this.macrosFormat = createMacrosFormat();

		this.timeout = this.config.getInt( "sa.command.timeout", 60000 );
		this.readWait = this.config.getInt( "sa.command.readWait", 50 );

		if ( config.getInt( "sa.config.inherit", 1 ) <= 0 )
		{
			parseCommands( deviceConfig );
		}
		else
		{
			parseCommands( config );
		}

		Set<Integer> rootOptions = Utils.toIntegerSet( config.get( "sa.inetOption.root", null ) );
		this.workingOptions = new HashSet<>();

		for ( Integer rootOption : rootOptions )
		{
			InetOptionRuntime rootOptionRuntime = InetOptionRuntimeMap.getInstance( moduleId ).get( rootOption );
			if ( rootOptionRuntime != null )
			{
				this.workingOptions.addAll( rootOptionRuntime.descendantIds );
			}
		}

		workingOptions.remove( 0 );

		if ( this.workingOptions.size() == 0 )
		{
			this.workingOptions = null;
		}

		needConnectionStateModify = config.getInt( "sa.command.connection.stateModify", 0 ) > 0;

		skipServiceAccountingEvents = config.getInt( "sa.command.connection.skipServiceAccounting", 1 ) > 0;

		accountingEventDeviceIds = Utils.toIntegerSet( config.get( "sa.command.connection.deviceIds", null ) );
		if ( accountingEventDeviceIds.size() == 0 )
		{
			accountingEventDeviceIds = null;
		}
		
		this.connectionSemaphorePermits = deviceConfig.getInt( "sa.connection.semaphorePermits", 0 );
		this.connectionSemaphore = getConnectionSemaphore();

		return null;
	}

    protected void parseCommands( ParameterMap config )
	{
		this.exitCommand = config.get( "sa.command.exit", "exit" );

		this.connectCommands = parseCommands( config, "sa.command.connect", null );
		this.disconnectCommands = parseCommands( config, "sa.command.disconnect", null );

		this.commandSet = parseCommands( config, "sa.command." );

		this.commandSetMap = new HashMap<>();
		this.servTypeCommandSetMap = new HashMap<>();

		for ( Map.Entry<String, ParameterMap> e : config.subKeyed( "sa.commandSet." ).entrySet() )
		{
			CommandSet commandSet = parseCommands( e.getValue(), "" );
			this.commandSetMap.put( e.getKey(), commandSet );

			Set<Integer> servTypeIds = Utils.toIntegerSet( e.getValue().get( "servTypeIds", "" ) );
			for ( Integer servTypeId : servTypeIds )
			{
				this.servTypeCommandSetMap.put( servTypeId, commandSet );
			}
		}
	}

	protected CommandSet parseCommands( ParameterMap config, String prefix )
	{
		CommandSet commands = new CommandSet();

		String[] servEnableCommands = parseCommands( config, prefix + "serv.enable", null );
		String[] servDisableCommands = parseCommands( config, prefix + "serv.disable", null );

		commands.servCreateCommands = parseCommands( config, prefix + "serv.create", null );
		commands.servCancelCommands = parseCommands( config, prefix + "serv.cancel", servDisableCommands );

		commands.servModifyCommands = new Commands( servEnableCommands, servDisableCommands );
		
		commands.servOptionModifyCommands = parseCommands( config, prefix + "serv.modify", null );
		
		commands.servOptionModifyCommandsMap = parseOptionCommands( config, prefix + "inetOption." );
		commands.servOptionModifyCommandsMap.putAll( parseOptionCommands( config, prefix + "serv.inetOption." ) );

		String[] connectionEnableCommands = parseCommands( config, prefix + "connection.enable", null );
		String[] connectionDisableCommands = parseCommands( config, prefix + "connection.disable", null );

		commands.connectionModifyCommands = new Commands( connectionEnableCommands, connectionDisableCommands );
		commands.connectionOptionModifyCommandsMap = parseOptionCommands( config, prefix + "connection.inetOption." );

		commands.connectionCloseCommands = parseCommands( config, prefix + "connection.close", null );

		commands.onAccountingStartCommands = parseCommands( config, prefix + "onAccountingStart", null );
		commands.onAccountingStopCommands = parseCommands( config, prefix + "onAccountingStop", null );
		
		String [] servOptionCommonEnableCommands = parseCommands( config, prefix + "common.inetOption.enable", null );
        String [] servOptionCommonDisableCommands = parseCommands( config, prefix + "common.inetOption.disable", null );
        commands.servOptionCommonCommands = new Commands( servOptionCommonEnableCommands, servOptionCommonDisableCommands );
        
        String [] conOptionCommonEnableCommands = parseCommands( config, prefix + "connection.common.inetOption.enable", null );
        String [] conOptionCommonDisableCommands = parseCommands( config, prefix + "connection.common.inetOption.disable", null );
        commands.conOptionCommonCommands = new Commands( conOptionCommonEnableCommands, conOptionCommonDisableCommands );
                        
        commands.servOptionAncestorModifyCommandsMap = readOptionAncestorModifyCommandsMap( config, prefix + "common.ancestor.inetOption." );
        
        commands.conOptionAncestorModifyCommandsMap = readOptionAncestorModifyCommandsMap( config, prefix + "connection.common.ancestor.inetOption." );
            
        commands.switchOptionsOnAccouting =  config.getBoolean ( prefix + "connection.switchOptionsOnAccouting", false );
        
        commands.switchStateOnAccouting =  config.getBoolean ( prefix + "connection.switchStateOnAccouting", false );

		return commands;
	}
	
    private Map<Integer, Commands > readOptionAncestorModifyCommandsMap( ParameterMap config, String prefix )
    {        
        Map<Integer, Commands > ancestorMap = parseOptionCommands( config, prefix );
        Map<Integer, Commands > result = new HashMap<>();
        
        
        for ( Map.Entry<Integer, Commands> entry : ancestorMap.entrySet() )
        {
            Integer optionId = entry.getKey();
            Commands commands = entry .getValue();
            InetOptionRuntime optioRuntime = optionRuntimeMap.get( optionId );
            
            for ( Integer o : optioRuntime.descendantIds )
            {
                result.put( o, commands );
            }
        }
        
        return result;        
    }
	

	protected static final Pattern semicolonPattern = Pattern.compile( "\\s*;\\s*" );

	protected String[] parseCommands( ParameterMap config, String prefix, String[] def )
	{
		List<String> list = new ArrayList<>();

		String param = config.get( prefix );
		// команды заведены через точку с запятой
		if ( Utils.notBlankString( param ) )
		{
			list.addAll( Arrays.asList( semicolonPattern.split( param ) ) );
		}
		// команды заведены отдельными параметрами 	
		else
		{
			for ( ParameterMap params : config.subIndexed( prefix + "." ).values() )
			{
				String command = params.get( "", null );
				if ( Utils.notBlankString( command ) )
				{
					list.add( command );
				}
			}
		}

		logger.debug( prefix + " commands: " + list );

		if ( list.size() == 0 )
		{
			return def;
		}

		return list.toArray( new String[list.size()] );
	}

	protected Map<Integer, Commands> parseOptionCommands( ParameterMap config, String prefix )
	{
		Map<Integer, Commands> result = new HashMap<>();

		for ( Map.Entry<Integer, ParameterMap> e : config.subIndexed( prefix ).entrySet() )
		{
			final Integer option = e.getKey();
			final ParameterMap params = e.getValue();

			String[] enableCommands = parseCommands( params, "enable", null );
			String[] disableCommands = parseCommands( params, "disable", null );

			if ( enableCommands != null || disableCommands != null )
			{
				result.put( option, new Commands( enableCommands, disableCommands ) );
			}
		}

		return result;
	}

	protected MacrosFormat createMacrosFormat()
	{
		return new MacrosFormat()
		{
			@SuppressWarnings({ "unchecked", "unused" })
			@Override
			protected Object invoke( String macros, Object[] args, Object[] globalArgs )
			{
				if ( "arg".equals( macros ) )
				{
					return globalArgs[getInt( args, 0, 0 )];
				}
				else if ( "macros".equals( macros ) )
				{
					String pattern = getString( args, 0, null );
					if ( Utils.isEmptyString( pattern ) )
					{
						return "";
					}

					return this.format( pattern, globalArgs );
				}
				else if ( "loopServ".equals( macros ) )
				{
					String macr = AbstractTerminalServiceActivator.this.config.get( getString( args, 0, "n_2356" ), null );
					if ( Utils.isEmptyString( macr ) )
					{
						return "";
					}
					
					boolean needParent = Utils.parseBoolean( getString( args, 1, "false" ) );
					boolean exec = Utils.parseBoolean( getString( args, 2, "false" ) );

					final InetServ serv = (InetServ)globalArgs[1];
					
					if ( exec )
					{
						if ( needParent )
						{
							try
							{
								executeCommand0( macr, (ServiceActivatorEvent)globalArgs[0], serv, (InetConnection)globalArgs[2], (Set<Integer>)globalArgs[3] );
							}
							catch( Exception e )
							{
								throw new BGRuntimeException( e );
							}
						}

						final List<InetServ> children = serv.getChildren();
						if ( children != null && children.size() > 0 )
						{
						    Date now = new Date();
							for ( InetServ child : children )
							{
                                if ( !TimeUtils.dateInRange( now, child.getDateFrom(), child.getDateTo() ) )
                                {
                                    continue;
                                }
							    
								try
								{
									executeCommand0( macr, (ServiceActivatorEvent)globalArgs[0], child, (InetConnection)globalArgs[2], (Set<Integer>)globalArgs[3] );
								}
								catch( Exception e )
								{
									throw new BGRuntimeException( e );
								}
							}
						}
						
						return "";
					}
					else
					{
                        String delim = getString( args, 2, "" );
                        if ( "false".equals( delim.trim() ) || "true".equals( delim.trim() ) )
                        {
                            delim = "";
                        }
					    
						StringBuilder sb = new StringBuilder();
						
						if ( needParent )
						{
							//getValue( (ServiceActivatorEvent)globalArgs[0], (InetServ)globalArgs[1], (InetConnection)globalArgs[2], (Set<Integer>)globalArgs[3], macros, args, globalArgs );
							Object[] globalArgs2 = Arrays.copyOf( globalArgs, globalArgs.length );
							globalArgs[1] = serv;

							sb.append( this.format( macr, globalArgs ) );
							sb.append( delim );
						}

						final List<InetServ> children = serv.getChildren();
						if ( children != null && children.size() > 0 )
						{
						    Date now = new Date();
							for ( InetServ child : children )
							{
                                if ( !TimeUtils.dateInRange( now, child.getDateFrom(), child.getDateTo() ) )
                                {
                                    continue;
                                }
							    
								//getValue( (ServiceActivatorEvent)globalArgs[0], (InetServ)globalArgs[1], (InetConnection)globalArgs[2], (Set<Integer>)globalArgs[3], macros, args, globalArgs );
								Object[] globalArgs2 = Arrays.copyOf( globalArgs, globalArgs.length );
								globalArgs[1] = child;

								sb.append( this.format( macr, globalArgs ) );
								sb.append( delim );
							}
						}
						
                        if ( sb.length() > 0 )
                        {
                            sb.setLength( sb.length() - delim.length() );
                        }
						
						return sb.toString();
					}
				}
				else if ( "param".equals( macros ) )
				{
					if ( args.length > 2 )
					{
						Object o = args[0];
						if ( o instanceof InetOptionRuntime )
						{
							return ((InetOptionRuntime)o).inheritedConfig.get( getString( args, 1, "param" ), getString( args, 2, "" ) );
						}
						else
						{
							return getString( args, 2, "" );
						}
					}
					else
					{
						return AbstractTerminalServiceActivator.this.config.get( getString( args, 0, "param" ), getString( args, 1, "" ) );
					}
				}
				else if ( "servParam".equals( macros ) )
				{
					final InetServ serv = (InetServ)globalArgs[1];
					final String configString = serv.getConfig();

					if ( Utils.isBlankString( configString ) )
					{
						return getString( args, 1, "" );
					}

					ParameterMap config = new Preferences( configString, "\n" );
					String key = getString( args, 0, "" );
					String result = config.get( key, null );
					if ( result == null )
					{
						result = config.get( key + ".1", null );
						if ( result == null )
						{
							result = getString( args, 1, "" );
						}
					}

					return result;
				}
				else if ( "host".equals( macros ) )
				{
					List<InetSocketAddress> addressList = AbstractTerminalServiceActivator.this.device.getHosts();

					if ( addressList.size() > 0 )
					{
						InetSocketAddress socketAddress = addressList.get( 0 );
						return socketAddress.getAddress().getHostAddress();
					}
					else
					{
						return null;
					}
				}
				else if ( "concat".equals( macros ) )
				{
					StringBuilder result = new StringBuilder();
					for ( Object arg : args )
					{
						result.append( arg );
					}

					return result.toString();
				}
				else if ( "option".equals( macros ) )
				{
					Set<Integer> options = (Set<Integer>)globalArgs[3];

					if ( args.length > 0 )
					{
						int rootOption = getInt( args, 0, 0 );
						if ( rootOption > 0 )
						{
							InetOptionRuntime rootOptionRuntime = optionRuntimeMap.get( rootOption );
							for ( Integer option : options )
							{
								if ( rootOptionRuntime.descendantIds.contains( option ) )
								{
									return optionRuntimeMap.get( option );
								}
							}

							return null;
						}
					}

					for ( Integer option : options )
					{
						return optionRuntimeMap.get( option );
					}

					return null;
				}
				else if ( "switch".equals( macros ) )
				{
					int value = getInt( args, 0, 0 );
					boolean def = args.length % 2 == 0;
					int max = def ? args.length - 1 : args.length;

					for ( int i = 1; i < max; i = i + 2 )
					{
						if ( value == getInt( args, i, 0 ) )
						{
							return args[i + 1];
						}
					}

					// default
					if ( args.length % 2 == 0 )
					{
						return args[args.length - 1];
					}

					return null;
				}
				else
				{
					try
					{
						Object result;

						Object arg = globalArgs[2];
						if ( arg instanceof InetConnection )
						{
							result = getValue( (ServiceActivatorEvent)globalArgs[0], (InetServ)globalArgs[1], (InetConnection)globalArgs[2], (Set<Integer>)globalArgs[3], macros, args, globalArgs );
						}
						else
						{
							result = getValue( (ServiceActivatorEvent)globalArgs[0], (InetServ)globalArgs[1], (InetConnection)globalArgs[2], (Set<Integer>)globalArgs[3], macros, args, globalArgs );
						}

						return result;
					}
					catch( Exception e )
					{
						logger.error( e.getMessage(), e );
					}

					return null;
				}
			}
		};
	}
	
	private Set<Integer> vlanSet;

	protected Object getValue( final ServiceActivatorEvent e, final InetServ serv, final InetConnection connection, final Set<Integer> options, final String macros,
							   final Object[] args, final Object[] globalArgs )
		throws Exception
	{
		if ( "ip".equals( macros ) )
		{
			if ( connection != null && connection.getInetAddressBytes() != null )
			{
				return IpAddress.toString( connection.getInetAddressBytes() );
			}
			else
			{
				return IpAddress.toString( serv.getAddressFrom() );
			}
		}
		else if ( "net".equals( macros ) )
		{
			return IpNet.toString( serv.getAddressFrom(), serv.getAddressTo() );
		}
		else if ( "iplist".equals( macros ) )
		{
			StringBuilder sb = new StringBuilder();

			ipList( serv, sb );

			if ( args.length > 0 && Utils.parseBoolean( String.valueOf( args[0] ) ) )
			{
				final List<InetServ> children = serv.getChildren();
				if ( children != null && children.size() > 0 )
				{
				    Date now = new Date();
					for ( InetServ child : children )
					{
                        if ( !TimeUtils.dateInRange( now, child.getDateFrom(), child.getDateTo() ) )
                        {
                            continue;
                        }
					    
						ipList( child, sb );
					}
				}
			}

			if ( sb.length() > 0 )
			{
				sb.setLength( sb.length() - 1 );
			}

			return sb.toString();
		}
		else if ( "mask".equals( macros ) || "bitmask".equals( macros ) )
		{
			return IpNet.getMask( serv.getAddressFrom(), serv.getAddressTo() );
		}
		else if ( "netmask".equals( macros ) )
		{
			int bitMask = IpNet.getMask( serv.getAddressFrom(), serv.getAddressTo() );
			long mask = (0xFFFFFFFFl << (32 - bitMask)) & 0xFFFFFFFFl;

			return IPUtils.convertLongIpToString( mask );
		}
		else if ( "netmaskWild".equals( macros ) )
		{
			int bitMask = IpNet.getMask( serv.getAddressFrom(), serv.getAddressTo() );
			long mask = (0xFFFFFFFFl << (32 - bitMask)) & 0xFFFFFFFFl;
			long maskWild = mask ^ 0xFFFFFFFFL;

			return IPUtils.convertLongIpToString( maskWild );
		}
		else if ( "vlan".equals( macros ) )
		{
			return serv.getVlan();
		}
		else if ( "iface".equals( macros ) || "port".equals( macros ) )
		{
			return serv.getInterfaceId();
		}
		else if ( "ifaceTitle".equals( macros ) )
		{
			final int interfaceId = serv.getInterfaceId();
			return InetInterfaceMap.getInstance( moduleId ).getInterfaceTitle( device.getInvDeviceId(), interfaceId );
		}
		else if ( "mac".equals( macros ) )
		{
			return MACParser.macAddressToString( serv.getMacAddressListBytes() );
		}
		else if ( "macBytes".equals( macros ) )
		{
			return Utils.bytesToString( serv.getMacAddressListBytes(), false, null );
		}
		else if ( "servTitle".equals( macros ) )
		{
			return serv.getTitle();
		}
		else if ( "identifier".equals( macros ) )
		{
			List<String> list = serv.getIdentifierList();
			if ( list != null && list.size() > 0 )
			{
				return list.get( 0 );
			}
			else
			{
				return "";
			}
		}
		else if ( "contractId".equals( macros ) )
		{
			return serv.getContractId();
		}
		else if ( "servId".equals( macros ) )
		{
			return serv.getId();
		}
		else if ( "servTypeId".equals( macros ) )
		{
			return serv.getTypeId();
		}
		else if ( "deviceId".equals( macros ) )
		{
			return this.deviceId;
		}
		else if ( "servDeviceId".equals( macros ) )
		{
			return serv.getDeviceId();
		}
		else if ( "agentDeviceId".equals( macros ) )
		{
			return connection != null ? connection.getAgentDeviceId() : serv.getId();
		}
		else if ( "connectionDeviceId".equals( macros ) )
		{
			return connection != null ? connection.getDeviceId() : serv.getDeviceId();
		}
		else if ( "ipGate".equals( macros ) )
		{
			IpResourceRuntime ipResourceRuntime = getIpResource( serv, connection );
			if ( ipResourceRuntime != null )
			{
				return ipResourceRuntime.ipResource.getRouter();
			}
		}
		else if ( "ipDns".equals( macros ) )
		{
			IpResourceRuntime ipResourceRuntime = getIpResource( serv, connection );
			if ( ipResourceRuntime != null )
			{
				return ipResourceRuntime.ipResource.getDns();
			}
		}
		else if ( "ipSubnetMask".equals( macros ) )
		{
			IpResourceRuntime ipResourceRuntime = getIpResource( serv, connection );
			if ( ipResourceRuntime != null )
			{
				return ipResourceRuntime.ipResource.getSubnetMask();
			}
		}
		else if ( "ipParam".equals( macros ) && args.length > 0 )
		{
			IpResourceRuntime ipResourceRuntime = getIpResource( serv, connection );
			if ( ipResourceRuntime != null )
			{
				return ipResourceRuntime.config.get( String.valueOf( args[0] ) );
			}
		}
		else if ( "deviceAttr".equals( macros ) )
		{
			final int deviceId;
			final int entitySpecAttrId;
			final Object def;

			switch( args.length )
			{
				case 1:
					deviceId = this.deviceId;
					entitySpecAttrId = (Integer)args[0];
					def = null;
					break;

				case 2:
					deviceId = (Integer)args[0];
					entitySpecAttrId = (Integer)args[1];
					def = null;
					break;

				case 3:
					deviceId = (Integer)args[0];
					entitySpecAttrId = (Integer)args[1];
					def = args[2];
					break;

				default:
					deviceId = this.deviceId;
					entitySpecAttrId = 0;
					def = null;
					break;
			}

			final Object result = getDeviceAttribute( deviceId, entitySpecAttrId );
			if ( result != null )
			{
				return result;
			}

			return def;
		}
		/** 
		 * может кому пригодится. помогло в EPON OLT от BDCom для добавления статических bind-onu
	     * http://forum.bitel.ru/viewtopic.php?f=44&t=10376
	     */
		/* 
	     * выдаем MAC в формате abcd.ef12.3456
	     */
		else if ( "macBytesDoted".equals( macros ) )
		{
			return Utils.bytesToString( serv.getMacAddressListBytes(), false, null ).replaceAll( "(.{4})(.{4})(.{4})", "$1.$2.$3" );
		}
		/*
		 * берем название интерфейса из тайтла до : . Например EPON0/1:2 = EPON0/1
		 */
		else if ( "ifaceTitleBeforeColon".equals( macros ) )
		{
			final int interfaceId = serv.getInterfaceId();
			return InetInterfaceMap.getInstance( moduleId ).getInterfaceTitle( device.getInvDeviceId(), interfaceId ).replaceAll( "(.*):(\\d*)", "$1" );
		}
		/*
		 * берем название интерфейса из тайтла после : . Например EPON0/1:2 = 2
		 */
		else if ( "ifaceTitleAfterColon".equals( macros ) )
		{
			final int interfaceId = serv.getInterfaceId();
			return InetInterfaceMap.getInstance( moduleId ).getInterfaceTitle( device.getInvDeviceId(), interfaceId ).replaceAll( "(.*):(\\d*)", "$2" );
		}
		else if ( "contractTitle".equals( macros ) )
		{
			final DefaultContext context = DefaultContext.get();
			final ContractRuntime contractRuntime = ContractRuntimeMap.getInstance().getContractRuntime( context.getConnectionSet(), serv.getContractId() );
			if ( contractRuntime == null )
			{
				return "null";
			}

			return contractRuntime.getContractTitle();
		}
		else if ( "translit".equals( macros ) )
		{
			if ( args.length > 0 )
			{
				return Utils.toTranslit( String.valueOf( args[0] ) );
			}

			return "null";
		}
		else if ( "dateNow".equals( macros ) )
        {
            return TimeUtils.format( new Date(),  "dd.MM.yyyy HH:mm:ss");
        }
		// {$now,date,yyyy-MM-dd}
		else if ( "now".equals( macros ) )
        {
            return new Date();
        }
		else if ( "deviceIP".equals( macros ) )
		{
			switch( args.length )
			{
				case 1:
					int deviceId = (Integer)args[0];
					return getDeviceIP( deviceId );

				default:
					return getDeviceIP( this.device );
			}
		}
		else if ( "deviceIdentifier".equals( macros ) )
		{
			switch( args.length )
			{
				case 1:
					int deviceId = (Integer)args[0];
					return getDeviceIdentifier( deviceId );

				default:
					return this.device.getIdentifier();
			}
		}
		else if ( "ipNetHostMin".equals( macros ) )
		{
			IpNet net = IpNet.newInstance( serv.getAddressFrom(), serv.getAddressTo() );
			return IpAddress.toString( net.getHostMin() );
		}
		else if ( "ipNetHostMax".equals( macros ) )
		{
			IpNet net = IpNet.newInstance( serv.getAddressFrom(), serv.getAddressTo() );
			return IpAddress.toString( net.getHostMax() );
		}
		else if ( "ipNetBroadcast".equals( macros ) )
		{
			IpNet net = IpNet.newInstance( serv.getAddressFrom(), serv.getAddressTo() );
			return IpAddress.toString( net.getBroadcast() );
		}
		else if ( "vlanList".equals( macros ) )
		{
			if ( vlanSet == null )
			{
				ServerContext context = ServerContext.get();
				InetServService servService = context.getService( InetServService.class, moduleId );

				ResourceService resourceService = context.getService( ResourceService.class, moduleId );

				Set<Integer> categoryIds = servService.vlanResourceCategoryIds( this.deviceId );

				List<int[]> ranges = new ArrayList<>();

				for ( Integer categoryId : categoryIds )
				{
					List<VlanResource> list = resourceService.vlanResourceList( categoryId );
					for ( VlanResource resource : list )
					{
						ranges.add( new int[] { resource.getVlanFrom(), resource.getVlanTo() } );
					}
				}

				RangedIntegerSet rangedIntegerSet = new RangedIntegerSet( ranges );
				this.vlanSet = rangedIntegerSet;
			}

			return Utils.toString( this.vlanSet );
		}

		return null;
	}

	protected Object executeCommands( ServiceActivatorEvent e, InetServ serv, InetConnection connection, Set<Integer> options, String[] commands )
		throws Exception
	{
		if ( commands == null )
		{
			return null;
		}

		if ( options != null && this.workingOptions != null )
		{
			options = new HashSet<>( options );
			options.retainAll( this.workingOptions );
		}

		for ( String command : commands )
		{
			executeCommand0( command, e, serv, connection, options );
		}

		return null;
	}
	
	private void executeCommand0( String command, ServiceActivatorEvent e, InetServ serv, InetConnection connection, Set<Integer> options )
		throws Exception
	{
		command = this.macrosFormat.format( command, e, serv, connection, options );
		if ( Utils.notBlankString( command ) )
		{
			command = command.trim();
			if ( command.equals( "\\n" ) )
			{
				command = "\n";
			}

			executeCommand( command );
		}
	}

	protected abstract void executeCommand( String command )
		throws Exception;

	@Override
	public Object connect()
		throws Exception
	{
		executeCommands( null, null, null, null, this.connectCommands );
		return null;
	}

	@Override
	public Object disconnect()
		throws Exception
	{
		executeCommands( null, null, null, null, this.disconnectCommands );
		return null;
	}

	protected CommandSet getCommandSet( ServiceActivatorEvent e )
	{
		if ( e == null || e.getInetServRuntime() == null )
		{
			return this.commandSet;
		}

		final int servTypeId = e.getInetServRuntime().getInetServ().getTypeId();

		final CommandSet commandSet = servTypeCommandSetMap.get( servTypeId );
		if ( commandSet != null )
		{
			return commandSet;
		}

		return this.commandSet;
	}

	@Override
	public Object serviceCreate( ServiceActivatorEvent e )
		throws Exception
	{
        logger.info( "serviceCreate" );
	    
		executeCommands( e, e.getNewInetServ(), null, e.getNewOptions(), getCommandSet( e ).servCreateCommands );

		// отключение
		if ( e.getNewState() == InetServState.STATE_DISABLE.getCode() )
		{
			return executeCommands( e, e.getNewInetServ(), null, e.getOldOptions(), getCommandSet( e ).servModifyCommands.disableCommands );
		}
		else
		{
			return serviceEnable( e );
		}
	}

	@Override
	public Object serviceCancel( ServiceActivatorEvent e )
		throws Exception
	{
        logger.info( "serviceCancel" );
	    
		CommandSet commandSet = getCommandSet( e );
	    switchOptions( e, e.getOldInetServ(), null, commandSet.servOptionModifyCommandsMap, 
	                   commandSet.servOptionCommonCommands, commandSet.servOptionAncestorModifyCommandsMap, e.getOldOptions(), null );
		return executeCommands( e, e.getOldInetServ(), null, e.getOldOptions(), getCommandSet( e ).servCancelCommands );
	}

	@Override
	public Object serviceModify( ServiceActivatorEvent e )
		throws Exception
	{
		logger.info( "serviceModify" );
		
		// смена интерфейса/VLAN/ip в родительском и дочерних интерфейсах
		if ( e.getOldInetServ() != null && e.getNewInetServ() != null )
		{

			boolean childrenChanged = false;
			if ( e.getOldInetServ().getChildren().size() == e.getNewInetServ().getChildren().size() )
			{
				for ( int i = 0; i < e.getOldInetServ().getChildren().size(); i++ )
				{
					InetServ oldChild = e.getOldInetServ().getChildren().get( i );
					InetServ newChild = e.getNewInetServ().getChildren().get( i );

					if ( serviceChanged( oldChild, newChild ) )
					{
						childrenChanged = true;
						break;
					}
				}
			}
            else
            {
                childrenChanged = true;
            }

			if ( (serviceChanged( e.getOldInetServ(), e.getNewInetServ() ))
				|| e.getOldInetServ().getChildren().size() != e.getNewInetServ().getChildren().size()
				|| childrenChanged )
			{
			    logger.info( "Children modified, forcing cancel-create" );
			    
				serviceCancel( e );
				serviceCreate( e );

				return null;
			}
		}

		// отключение
		if ( e.getNewState() == InetServState.STATE_DISABLE.getCode() )
		{
			return serviceDisable( e );
		}

		// включение
		if ( e.getOldState() == InetServState.STATE_DISABLE.getCode() )
		{
			return serviceEnable( e );
		}

		// изменение опций
		return serviceOptionsModify( e );
	}

	private boolean serviceChanged( InetServ serviceOld, InetServ serviceNew )
	{
		return (serviceOld.getInterfaceId() != serviceNew.getInterfaceId())
			   || (serviceOld.getVlan() != serviceNew.getVlan())
			   || !addressEquals( serviceOld.getAddressFrom(), serviceNew.getAddressFrom() )
			   || !addressEquals( serviceOld.getAddressTo(), serviceNew.getAddressTo() )
			   || !TimeUtils.dateEqual( serviceOld.getDateFrom(), serviceNew.getDateFrom() )
			   || !TimeUtils.dateEqual( serviceOld.getDateTo(), serviceNew.getDateTo() );
	}

	private boolean addressEquals( byte[] addrr1, byte[] addr2 )
	{
		return ((addrr1 == null && addr2 == null) || IpAddress.equals( addrr1, addr2 ));
	}

	protected Object serviceDisable( ServiceActivatorEvent e )
		throws Exception
	{
		logger.info( "serviceDisable" );
		
		CommandSet commandSet = getCommandSet( e );
		
		switchOptions( e, e.getOldInetServ(), null, commandSet.servOptionModifyCommandsMap, 
		               commandSet.servOptionCommonCommands, commandSet.servOptionAncestorModifyCommandsMap, e.getOldOptions(), null );

		executeCommands( e, e.getOldInetServ(), null, e.getOldOptions(), commandSet.servModifyCommands.disableCommands );

		return null;
	}

	protected Object serviceEnable( ServiceActivatorEvent e )
		throws Exception
	{
		logger.info( "serviceEnable" );
		
		CommandSet commandSet = getCommandSet( e );
		
		executeCommands( e, e.getNewInetServ(), null, e.getNewOptions(), commandSet.servModifyCommands.enableCommands );

		switchOptions( e, e.getNewInetServ(), null, commandSet.servOptionModifyCommandsMap, 
		               commandSet.servOptionCommonCommands, commandSet.servOptionAncestorModifyCommandsMap, null, e.getNewOptions() );

		return null;
	}

	protected Object serviceOptionsModify( ServiceActivatorEvent e )
		throws Exception
	{
		logger.info( "serviceOptionsModify" );
		
		CommandSet commandSet = getCommandSet( e );
		
		executeCommands( e, e.getNewInetServ(), null, e.getNewOptions(), commandSet.servOptionModifyCommands );
		
		Set<Integer> removeOptions = e.getOptionsToRemove();
		Set<Integer> addOptions = e.getOptionsToAdd();

		switchOptions( e, e.getNewInetServ(), null, commandSet.servOptionModifyCommandsMap, 
		               commandSet.servOptionCommonCommands, commandSet.servOptionAncestorModifyCommandsMap, removeOptions, addOptions );

		return null;
	}

	@Override
	public Object connectionModify( ServiceActivatorEvent e )
		throws Exception
	{
		logger.info( "connectionModify" );
		
		// отключение
		if ( e.getNewState() == InetServState.STATE_DISABLE.getCode() )
		{
			// устанавливаем флаг, что нужно будет поменять состояние соединения в базе
			if ( needConnectionStateModify )
			{
				e.setConnectionStateModified( true );
			}

			return connectionDisable( e );
		}

		// включение
		if ( e.getOldState() == InetServState.STATE_DISABLE.getCode() )
		{
			// устанавливаем флаг, что нужно будет поменять состояние соединения в базе
			if ( needConnectionStateModify )
			{
				e.setConnectionStateModified( true );
			}

			return connectionEnable( e );
		}

		// измененний опций
		return connectionOptionsModify( e );
	}

	protected Object connectionDisable( ServiceActivatorEvent e )
		throws Exception
	{
		CommandSet commandSet = getCommandSet( e );
		
	    switchOptions( e, e.getNewInetServ(), e.getConnection(),commandSet.connectionOptionModifyCommandsMap, 
	                   commandSet.conOptionCommonCommands, commandSet.conOptionAncestorModifyCommandsMap, e.getOldOptions(), null );

		executeCommands( e, e.getNewInetServ(), e.getConnection(), e.getOldOptions(), getCommandSet( e ).connectionModifyCommands.disableCommands );

		return null;
	}

	protected Object connectionEnable( ServiceActivatorEvent e )
		throws Exception
	{
		CommandSet commandSet = getCommandSet( e );
		
	    executeCommands( e, e.getNewInetServ(), e.getConnection(), e.getNewOptions(), commandSet.connectionModifyCommands.enableCommands );

		switchOptions( e, e.getNewInetServ(), e.getConnection(), getCommandSet( e ).connectionOptionModifyCommandsMap, 
		               commandSet.conOptionCommonCommands,  commandSet.conOptionAncestorModifyCommandsMap, null, e.getNewOptions() );

		return null;
	}

	protected Object connectionOptionsModify( ServiceActivatorEvent e )
		throws Exception
	{
		Set<Integer> removeOptions = e.getOptionsToRemove();
		Set<Integer> addOptions = e.getOptionsToAdd();
		CommandSet commandSet = getCommandSet( e );

		switchOptions( e, e.getNewInetServ(), e.getConnection(), commandSet.connectionOptionModifyCommandsMap, 
		               commandSet.conOptionCommonCommands,  commandSet.conOptionAncestorModifyCommandsMap, removeOptions, addOptions );

		return null;
	}

	protected void switchOptions( ServiceActivatorEvent e, InetServ serv, InetConnection connection, Map<Integer, Commands> optionModifyCommandsMap, 
	                              Commands commonCommands, Map<Integer, Commands> optionAncestorModifyCommandsMap, 
								  Set<Integer> optionsDisable, Set<Integer> optionsEnable )
		throws Exception
	{
		logger.info( "switchOptions" );
	    if ( optionsDisable != null )
		{
			if ( this.workingOptions != null )
			{
				optionsDisable = new HashSet<>( optionsDisable );
				optionsDisable.retainAll( this.workingOptions );
			}

			for ( Integer option : optionsDisable )
			{
				Commands commands = optionModifyCommandsMap.get( option );
				if ( commands != null && commands.disableCommands != null )
				{
					executeCommands( e, serv, connection, Collections.singleton( option ), commands.disableCommands );
				}
				
				//запускаем общие команды
				if ( commonCommands.disableCommands != null  )
				{    
				    executeCommands( e, serv, connection, Collections.singleton( option ), commonCommands.disableCommands );
				}
                
				if ( optionAncestorModifyCommandsMap != null )
				{
    				//команды заданные в опции предке 				
    				Commands servOptionAncestorCommands = optionAncestorModifyCommandsMap.get( option );
    				if ( servOptionAncestorCommands != null && servOptionAncestorCommands.disableCommands != null )
    				{
    				    executeCommands( e, serv, connection, Collections.singleton( option ), servOptionAncestorCommands.disableCommands );
    				}
				}
				
			}
			
		}

		if ( optionsEnable != null )
		{
			if ( this.workingOptions != null )
			{
				optionsEnable = new HashSet<>( optionsEnable );
				optionsEnable.retainAll( this.workingOptions );
			}

			for ( Integer option : optionsEnable )
			{
				Commands commands = optionModifyCommandsMap.get( option );
				if ( commands != null && commands.enableCommands != null )
				{
					executeCommands( e, serv, connection, Collections.singleton( option ), commands.enableCommands );
				}
				
				//запускаем общие команды
                if ( commonCommands.enableCommands != null  )
                {    
                    executeCommands( e, serv, connection, Collections.singleton( option ), commonCommands.enableCommands );
                }
                
                if ( optionAncestorModifyCommandsMap != null ) 
                {    
                    //команды заданные в опции предке               
                    Commands servOptionAncestorCommands = optionAncestorModifyCommandsMap.get( option );
                    if ( servOptionAncestorCommands != null && servOptionAncestorCommands.enableCommands != null )
                    {
                        executeCommands( e, serv, connection, Collections.singleton( option ), servOptionAncestorCommands.enableCommands );
                    }
                }                   
                
			}
		}
	}

	@Override
	public Object connectionClose( ServiceActivatorEvent e )
		throws Exception
	{
		executeCommands( e, e.getNewInetServ(), e.getConnection(), e.getNewOptions(), getCommandSet( e ).connectionCloseCommands );

		return null;
	}

	@Override
	public Object onAccountingStart( final ServiceActivatorEvent e )
		throws Exception
	{
		final InetConnection connection = e.getConnection();

		if ( skipServiceAccountingEvents && connection.getParentConnectionId() > 0L )
		{
			logger.debug( "Skip service connection" );
			return null;
		}

		if ( accountingEventDeviceIds != null && !accountingEventDeviceIds.contains( connection.getDeviceId() ) )
		{
			logger.debug( "Skip connection with deviceId=" + connection.getDeviceId() );
			return null;
		}
		
		if ( commandSet.switchStateOnAccouting )
		{
			if ( connection.getDeviceState() == InetServState.STATE_ENABLE.getCode() )
			{
				logger.debug( "Enable connection" );
				executeCommands( e, e.getNewInetServ(), e.getConnection(), e.getNewOptions(), commandSet.connectionModifyCommands.enableCommands );
			}
			else
			{
				logger.debug( "Disable connection" );
				executeCommands( e, e.getNewInetServ(), e.getConnection(), e.getOldOptions(), getCommandSet( e ).connectionModifyCommands.disableCommands );
			}
		}

		CommandSet commandSet = getCommandSet( e );

		executeCommands( e, e.getNewInetServ(), connection, e.getNewOptions(), getCommandSet( e ).onAccountingStartCommands );
		
		if ( commandSet.switchOptionsOnAccouting && connection.getDeviceState() == InetServState.STATE_ENABLE.getCode() )
		{
            switchOptions( e, e.getNewInetServ(), e.getConnection(),commandSet.connectionOptionModifyCommandsMap, 
                commandSet.conOptionCommonCommands, commandSet.conOptionAncestorModifyCommandsMap, null, e.getNewOptions() );
		}


		return null;
	}

	@Override
	public Object onAccountingStop( final ServiceActivatorEvent e )
		throws Exception
	{
		final InetConnection connection = e.getConnection();

		if ( skipServiceAccountingEvents && connection.getParentConnectionId() > 0L )
		{
			logger.debug( "Skip service connection" );
			return null;
		}

		if ( accountingEventDeviceIds != null && !accountingEventDeviceIds.contains( connection.getDeviceId() ) )
		{
			logger.debug( "Skip connection with deviceId=" + connection.getDeviceId() );
			return null;
		}
		
		CommandSet commandSet =  getCommandSet( e ); 
		
		if ( commandSet.switchOptionsOnAccouting && connection.getDeviceState() == InetServState.STATE_ENABLE.getCode() )
        {
            switchOptions( e, e.getNewInetServ(), e.getConnection(),commandSet.connectionOptionModifyCommandsMap, 
                commandSet.conOptionCommonCommands, commandSet.conOptionAncestorModifyCommandsMap, e.getOldOptions(), null );
        }


		executeCommands( e, e.getNewInetServ(), connection, e.getNewOptions(), commandSet.onAccountingStopCommands );

		return null;
	}

	private Semaphore getConnectionSemaphore()
	{
		if ( connectionSemaphorePermits <= 0 )
		{
			return null;
		}

		Semaphore result = SEMAPHORE_MAP.get( deviceId );
		if ( result != null )
		{
			return result;
		}

		final Semaphore newResult = new Semaphore( connectionSemaphorePermits, true );
		result = SEMAPHORE_MAP.putIfAbsent( deviceId, newResult );
		if ( result == null )
		{
			result = newResult;
		}

		return result;
	}
	
	@Override
	public List<ConfigParameter> configParameterList()
	{
	    List<ConfigParameter> configParameters = super.configParameterList();
	    configParameters.add( new ConfigParameter( SA_HOST, "" ) );
	    configParameters.add( new ConfigParameter( SA_PORT, "" ) );
	    configParameters.add( new ConfigParameter( SA_USERNAME, "" ) );
	    configParameters.add( new ConfigParameter( SA_PASSWORD, "" ) );
	    return configParameters;
	}
}
