/**
 * @author    STF 424_ITS_Test_Platform
 * @version    $URL: $
 *             $Id: $
 */
package org.etsi.its.uppertester.hitachi;

import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.etsi.common.ByteHelper;
import org.etsi.its.codec.TestAdapterMessageTypeEnum;
import org.etsi.its.codec.adapter.UpperTesterCodecImpl;
import org.etsi.its.network.IUdpImpl;
import org.etsi.its.network.UdpFactory;
import org.etsi.its.network.hitachi.V2xModulesEnum;
import org.etsi.its.uppertester.IBtpUpperTester;
import org.etsi.its.uppertester.IGnUpperTester;

/**
 * Hitachi implementation of GeoNetworking upper tester
 */
public class NwtUpperTester implements IGnUpperTester, IBtpUpperTester {

    /**
     * Module version
     */
    public static final String Version = "1.0.0.1";

    /**
     * Logger instance
     */
    private final static Logger _logger = Logger.getLogger("org.etsi.its");

    /**
     * Reference to the TA codec
     */
    private UpperTesterCodecImpl _codec;

    /**
     * Regex pattern for module settings decoding operation
     * For Java regex test, see http://www.cis.upenn.edu/~matuszek/General/RegexTester/regex-tester.html
     */
    private Pattern _pattern;

    /**
     * List of UDP modules
     */
    private Map<V2xModulesEnum, IUdpImpl> _udps;
    
    /**
     * Constructor
     */
    public NwtUpperTester() {
        _logger.entering("NwtUpperTester", "Constructor", String.format("version: %s", Version));
        _codec = new UpperTesterCodecImpl();
        _pattern = Pattern.compile("(\\w+)\\:(\\S+)\\:(\\d+)\\:(\\d+)");
        
        _udps = new HashMap<V2xModulesEnum, IUdpImpl>();
    }
    
    @Override
    public boolean changePosition() {
        _logger.entering("NwtUpperTester", "changePosition");
        
        if (_udps.containsKey(V2xModulesEnum.PositionProducerModule)) {
            try { // FIXME How to set numerical values?
                UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("ForceParameter Longitude Integer 100080000".getBytes());
                
                _logger.exiting("NwtUpperTester", "changePosition");
                return true;
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            } catch (SocketException e1) {
                e1.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        _logger.exiting("NwtUpperTester", "changePosition", "false");
        return false;
    }

    @Override
    public boolean checkPacket(byte[] packet) {
        _logger.entering("NwtUpperTester", "checkPacket");

        try {
            // Send PDU
            _logger.info("Sending packet");
            byte[] buffer = packet;
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GeoNetworkingPacket, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtCheck, buffer, 0);
            _udps.get(V2xModulesEnum.NwtaTrigger).send(buffer);
            _logger.info("Wait for response");
            // Wait for response
            byte[] response = _udps.get(V2xModulesEnum.NwtaTrigger).receive();
            if ((response[0] == TestAdapterMessageTypeEnum.UtResult.convert()) 
                    && (response[1] == 0x00) 
                    && (response[2] == 0x01)  
                    && (response[3] == 0x01)) {
                return true;
            } // else, return false
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;

    }

    @Override
    public boolean generateGeoAnycast(short shape, int latitude, int longitude, short distanceA, short distanceB, short angle, short lifetime, byte[] payload) {
        _logger.entering("NwtUpperTester", this + ".generateGeoAnycast");

        try {
            byte[] bufferShape = ByteHelper.intToByteArray(shape, 2);
            bufferShape = _codec.Encode(TestAdapterMessageTypeEnum.shape, bufferShape, 0);
            byte[] bufferLatitude = ByteHelper.intToByteArray(latitude, 4);
            bufferLatitude = _codec.Encode(TestAdapterMessageTypeEnum.geoAreaPosLatitude, bufferLatitude, 0);
            byte[] bufferLongitude = ByteHelper.intToByteArray(longitude, 4);
            bufferLongitude = _codec.Encode(TestAdapterMessageTypeEnum.geoAreaPosLongitude, bufferLongitude, 0);
            byte[] bufferDistanceA = ByteHelper.intToByteArray(distanceA, 2);
            bufferDistanceA = _codec.Encode(TestAdapterMessageTypeEnum.distanceA, bufferDistanceA, 0);
            byte[] bufferDistanceB = ByteHelper.intToByteArray(distanceB, 2);
            bufferDistanceB = _codec.Encode(TestAdapterMessageTypeEnum.distanceB, bufferDistanceB, 0);
            byte[] bufferAngle = ByteHelper.intToByteArray(angle, 2);
            bufferAngle = _codec.Encode(TestAdapterMessageTypeEnum.angle, bufferAngle, 0);
            byte[] bufferLifetime = ByteHelper.intToByteArray(lifetime, 2);
            bufferLifetime = _codec.Encode(TestAdapterMessageTypeEnum.lifetime, bufferLifetime, 0);
            
            byte[] bufferPayload = _codec.Encode(TestAdapterMessageTypeEnum.payload, payload, 0);
            byte[] buffer = ByteHelper.concat( 
                    bufferShape, 
                    bufferLatitude, 
                    bufferLongitude, 
                    bufferDistanceA, 
                    bufferDistanceB, 
                    bufferAngle,
                    bufferLifetime,
                    bufferPayload);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateGeoAnycastMessage, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtGNEvent, buffer, 0);
            _udps.get(V2xModulesEnum.NwtaTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }    
        
        return false;
    }

    @Override
    public boolean generateGeoBroadcast(short shape, int latitude, int longitude, short distanceA, short distanceB, short angle, short lifetime, byte[] payload) {
        _logger.entering("NwtUpperTester", this + ".generateGeoBroadcast");

        try {
            byte[] bufferShape = ByteHelper.intToByteArray(shape, 2);
            bufferShape = _codec.Encode(TestAdapterMessageTypeEnum.shape, bufferShape, 0);
            byte[] bufferLatitude = ByteHelper.intToByteArray(latitude, 4);
            bufferLatitude = _codec.Encode(TestAdapterMessageTypeEnum.geoAreaPosLatitude, bufferLatitude, 0);
            byte[] bufferLongitude = ByteHelper.intToByteArray(longitude, 4);
            bufferLongitude = _codec.Encode(TestAdapterMessageTypeEnum.geoAreaPosLongitude, bufferLongitude, 0);
            byte[] bufferDistanceA = ByteHelper.intToByteArray(distanceA, 2);
            bufferDistanceA = _codec.Encode(TestAdapterMessageTypeEnum.distanceA, bufferDistanceA, 0);
            byte[] bufferDistanceB = ByteHelper.intToByteArray(distanceB, 2);
            bufferDistanceB = _codec.Encode(TestAdapterMessageTypeEnum.distanceB, bufferDistanceB, 0);
            byte[] bufferAngle = ByteHelper.intToByteArray(angle, 2);
            bufferAngle = _codec.Encode(TestAdapterMessageTypeEnum.angle, bufferAngle, 0);
            byte[] bufferLifetime = ByteHelper.intToByteArray(lifetime, 2);
            bufferLifetime = _codec.Encode(TestAdapterMessageTypeEnum.lifetime, bufferLifetime, 0);
            
            byte[] bufferPayload = _codec.Encode(TestAdapterMessageTypeEnum.payload, payload, 0);
            byte[] buffer = ByteHelper.concat(                    
                    bufferShape, 
                    bufferLatitude, 
                    bufferLongitude, 
                    bufferDistanceA, 
                    bufferDistanceB, 
                    bufferAngle,
                    bufferLifetime,
                    bufferPayload);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateGeoBroadcastMessage, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtGNEvent, buffer, 0);
            _udps.get(V2xModulesEnum.NwtaTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }     
        
        return false;
    }

    @Override
    public boolean generateGeoUnicast(byte[] gnAddress, short lifetime, byte[] payload) {
        _logger.entering("NwtUpperTester", this + ".generateGeoUnicast");

        try {
            byte[] bufferGnAddress = _codec.Encode(TestAdapterMessageTypeEnum.gnAddress, gnAddress, 0);
            byte[] bufferLifetime = ByteHelper.intToByteArray(lifetime, 2);
            bufferLifetime = _codec.Encode(TestAdapterMessageTypeEnum.lifetime, bufferLifetime, 0);
            
            byte[] bufferPayload = _codec.Encode(TestAdapterMessageTypeEnum.payload, payload, 0);
            byte[] buffer = ByteHelper.concat(                    
                    bufferGnAddress, 
                    bufferLifetime,
                    bufferPayload);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateGeoUnicastMessage, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtGNEvent, buffer, 0);
            _udps.get(V2xModulesEnum.NwtaTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }     

        _logger.entering("NwtUpperTester", "generateGeoUnicast");

        return false;
    }

    @Override
    public boolean generateShb(byte[] payload) {
        _logger.entering("NwtUpperTester", this + ".generateShb");

        try {
            byte[] bufferPayload = _codec.Encode(TestAdapterMessageTypeEnum.payload, payload, 0);
            byte[] buffer = bufferPayload;
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateSHBMessage, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtGNEvent, buffer, 0);
            _udps.get(V2xModulesEnum.NwtaTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }    

        return false;
    }

    @Override
    public boolean generateTsb(byte[] payload, short nbHops) {
        _logger.entering("NwtUpperTester", this + ".generateTsb");

        try {
            byte[] bufferNbHops = ByteHelper.intToByteArray(nbHops, 2);
            bufferNbHops = _codec.Encode(TestAdapterMessageTypeEnum.nbHops, bufferNbHops, 0);
            byte[] bufferPayload = _codec.Encode(TestAdapterMessageTypeEnum.payload, payload, 0);
            byte[] buffer = ByteHelper.concat(bufferPayload, bufferNbHops);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateTSBMessage, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtGNEvent, buffer, 0);
            _udps.get(V2xModulesEnum.NwtaTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }        
        
        return false;
    }

    @Override
    public boolean initialize(final String settings) {
        _logger.entering("NwtUpperTester", this + ".initialize", (settings == null) ? "(null)" : settings);
        
        // Sanity check
        if ((settings == null) || settings.isEmpty()) {
            _logger.severe("NwtUpperTester.initialize: Wrong parameters");
            return false;
        }

        try {
            String[] modules = settings.split(";");
            if ((modules == null) ||(modules.length == 0)) {
                _logger.severe("NwtUpperTester.initialize: Malformed parameters");
                return false;
            }
            
            String positionProducerAttributes = null;
            for (String module : modules) {
                Matcher matcher = _pattern.matcher(module);
                if (matcher.find()) {
                    V2xModulesEnum moduleId = V2xModulesEnum.valueOf(matcher.group(1));
                    if (moduleId == V2xModulesEnum.PositionProducerModule) {
                        // Extract parameters
                        if (module.indexOf(',') != -1) {
                            positionProducerAttributes = module.substring(module.indexOf(',') + 1);
                        }
                    }
                    _udps.put(
                            moduleId, 
                            UdpFactory.getInstance().getUdpImpl(moduleId));
                    _udps.get(moduleId).initialize(matcher.group(2), Integer.parseInt(matcher.group(3)), Integer.parseInt(matcher.group(4)));
                } else {
                    _logger.severe("NwtUpperTester.initialize: Malformed parameters (2)");
                    return false;
                }
            } // End of 'for' statement
            if (_udps.isEmpty()) {
                _logger.severe("NwtUpperTester.initialize: Malformed parameters (3)");
                return false;
            }
            
            if (_udps.containsKey(V2xModulesEnum.PositionProducerModule)) {
                try {
                    if (positionProducerAttributes != null) {
                        String[] attributes = positionProducerAttributes.split(",");
                        for (String attribute : attributes) {
                            UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send(attribute.trim().getBytes());
                        } // End of 'for' statement
                    } else { // Use hard coded values
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("Reset".getBytes());
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("SetPositioningAlg NoSpeed".getBytes());
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("SetParameter Latitude Integer 440000000".getBytes());
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("SetParameter Longitude Integer 100020000".getBytes());
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("SetParameter Speed Short 0".getBytes());
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("SetParameter Heading Short 0".getBytes());
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("SetParameter Altitude Short 20".getBytes());
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("SetParameter Accuracy Byte 1".getBytes());
	                    UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("Start".getBytes());
                    }
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                } catch (SocketException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            
            byte[] buffer = new byte[] {};
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtGNInitialize, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtInitialize, buffer, 0);
            for (V2xModulesEnum key : _udps.keySet()) {
                if (key == V2xModulesEnum.PositionProducerModule) {
                    continue;
                }
                _udps.get(key).send(buffer);
                _udps.get(key).receive();
            } // End of 'for' statement
            
            _logger.exiting("NwtUpperTester", "initialize");
            return true;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean uninitialize() {
        _logger.entering("NwtUpperTester", "uninitialize");
        
        if (_udps.containsKey(V2xModulesEnum.PositionProducerModule)) {
            try {
                UdpFactory.getInstance().getUdpImpl(V2xModulesEnum.PositionProducerModule).send("Stop".getBytes());
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            } catch (SocketException e1) {
                e1.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        for (V2xModulesEnum key : _udps.keySet()) {
            _udps.get(key).uninitialize();
        }
        
        _udps.clear();
        
        return true;
    }

    @Override
    public boolean checkPayload(byte[] payload) {
        _logger.entering("NwtUpperTester", "checkPayload");

        return false;
    }

    @Override
    public boolean generateBtpA(int destinationPort, int sourcePort) {
        _logger.entering("NwtUpperTester", "generateBtpA");

        return false;
    }

    @Override
    public boolean generateBtpB(int destinationPort, int destinationPortInfo) {
        _logger.entering("NwtUpperTester", "generateBtpB");

        return false;
    }

}
