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

import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import ru.bitel.bgbilling.apps.inet.access.sa.ServiceActivator;
import ru.bitel.bgbilling.apps.inet.access.sa.ServiceActivatorEvent;
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.dyn.device.terminal.AbstractTerminalServiceActivator;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Utils;

public class ManadServiceActivator
    extends AbstractTerminalServiceActivator
    implements ServiceActivator
{
	private static Logger logger = LogManager.getLogger();
	
	private InetDevice inetDevice;
	
	protected static final Pattern paramMultiPattern = Pattern.compile( "\\$paramMulti\\((.*)\\)" );
	
	private Socket socket;
	PrintWriter out;
	@Override
    public Object init( Setup setup, int moduleId, InetDevice device, InetDeviceType deviceType, ParameterMap config )
        throws Exception
    {
	    super.init( setup, moduleId, device, deviceType, config );
		this.inetDevice = device;
	    return null;
    }	
	
	@Override
    public Object connect()
        throws Exception
    {
		List<InetSocketAddress> hosts = inetDevice.getHosts();
		
		InetSocketAddress socketAddress = hosts.get( 0 );
		String host = socketAddress.getAddress().getHostAddress();
		int port = socketAddress.getPort();
 		
		socket = new Socket( host, port );
		out = new PrintWriter( socket.getOutputStream(), true );
		
		logger.info( "Connected" );
		
		return super.connect();
    }

	@Override
    public Object disconnect()
        throws Exception
    {
		super.disconnect();
		out.close();
		socket.close();
		
		logger.info( "Disconnected" );
		return null;
    }

	@Override
    protected void executeCommand( String command )
        throws Exception
    { 
		logger.info( "execute: " + command );
		out.println( command );
    }

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

		if ( this.workingOptions != null )
		{
			options = new HashSet<>( options );
			options.retainAll( this.workingOptions );
		}
		
		List<InetServ> childrens = serv.getChildren();
		List<InetServ> servs = new ArrayList<>();
		//если потомков у сервиса нет, то мы только его обрабатываем 
		if ( childrens == null || childrens.size() == 0 )
		{
			servs.add( serv );
		}// если есть дети , то обратывваем только детей 
		else
		{
			servs.addAll( childrens );
		}
				
		String fullCommand = generateRule( commands, serv, servs, e, connection, options );		
		fullCommand = fullCommand.replaceAll( "\\\\t", "\t" );
		fullCommand = fullCommand.replaceAll( "\n", "|" );
		logger.debug( "fullCommand=" + fullCommand );
		
		if ( Utils.notBlankString( fullCommand ) )
		{
			executeCommand( fullCommand.trim() );
		}
	
		return null;
	}
	
	
	public final String generateRule( String[] commands, InetServ mainServ, List<InetServ> servs, ServiceActivatorEvent e, InetConnection connection, Set<Integer> options )
	{
		String commandStr = "";
		for ( String command : commands )
		{
			commandStr +=  command + "\n";
		}
		
		//Если указан макрос multiParam, учитываем его
		commandStr = getParamMulti( commandStr );
		
		StringBuffer resultBuf = null; 
		resultBuf = new StringBuffer ();
		String loopPattern = "(<LOOP>.*?</LOOP>)?(.*?)<LOOP>(.*?)</LOOP>";
		Pattern pattern = Pattern.compile( loopPattern, Pattern.DOTALL );
		Matcher m = pattern.matcher( commandStr );
		boolean find = false;

		while( m.find() )
		{
			find = true;
			
			String block = m.group( 3 ).trim();
			block = processBlock( block, servs, e, connection, options ).toString();
			
			String beforeLoop =  m.group( 2 );
			beforeLoop = this.macrosFormat.format( beforeLoop, e, mainServ, connection, options );
			
			resultBuf.append( beforeLoop.trim() + "\n" );
			resultBuf.append( block.trim() );				
		}
		
		if (find)
		{
			//хвост(ищем жадным алгоритмом) или если вообще нет ни одного цикла 
			loopPattern = "(?:<LOOP>(?:.*)</LOOP>)(.*)\\z";
			
			pattern = Pattern.compile( loopPattern, Pattern.DOTALL );
			m = pattern.matcher( commandStr );		
			
			if ( m.find() )
			{
				String afterLoop =  m.group( 1 ).trim();
				afterLoop = this.macrosFormat.format( afterLoop, e, mainServ, connection, options );
				resultBuf.append( afterLoop.trim() );
			}
		}
		else
		{	
			commandStr = this.macrosFormat.format( commandStr, e, mainServ, connection, options );
			
			resultBuf.append( commandStr ); 		
		}	
		
		return resultBuf.toString();
	}
	
	private String getParamMulti( String commands )
	{
		Matcher matcher = paramMultiPattern.matcher( commands );
		if ( matcher.find() )
		{
			String prefix =  matcher.group( 1 );
			
			String param = this.config.get( prefix, "" );
			// команды заведены отдельными параметрами
			if ( Utils.isBlankString( param ) )						
			{
				param = "";
				for ( ParameterMap params : this.config.subIndexed( prefix + "." ).values() )
				{
					String command = params.get( "", null );
					if ( Utils.notBlankString( command ) )
					{								
						param += command +  "\n";
					}
				}
			}
			return param;					
		}
		return commands;
	}

	private StringBuffer processBlock( String ruleText, List<InetServ> servs, ServiceActivatorEvent e, InetConnection connection, Set<Integer> options )
	{
		StringBuffer result = new StringBuffer();
		
		List<PatternItem> items = new ArrayList<>( 10 );
		Map<String, Integer> letterMaxNumbers = new HashMap<>();
		
		Pattern pattern = Pattern.compile( "\\{([A-Z]+)(\\d+)\\}" );
		Matcher m = pattern.matcher( ruleText );
		
		while( m.find() )
		{
			String letter = m.group( 1 );
			int number = Utils.parseInt( m.group( 2 ), 0 );
			
			PatternItem item = new PatternItem();
			item.number = number;
			item.letter = letter;
			
			items.add( item );
			
			Integer maxNumber = letterMaxNumbers.get( letter );
			if ( maxNumber == null || maxNumber < number )
			{
				letterMaxNumbers.put( letter, number );
			}
		}
				
		final int size = servs.size();
		
		for ( int i = 0; i < size; i++ )
		{
			//String address = IPUtils.convertLongIpToString( Utils.parseLong( addreses[i], 0 ) );
			String addressRule = new String( ruleText );
			InetServ serv = servs.get( i );
			
			for ( PatternItem item : items )
			{
				int number = i*(letterMaxNumbers.get( item.letter ) + 1) + item.number;
				addressRule = addressRule.replaceAll( 
						"\\{" + item.letter + item.number + "\\}",
						"{" + item.letter + number + "}" );
			}

			String str = addressRule;
			
			str = this.macrosFormat.format( str, e, serv, connection, options );
			result.append( str + "\n"  );
		}
		return result;
	}	
	
	private static class PatternItem
	{
		public String letter;
		public int number;
	}
}