package org.etsi.its.uppertester.itri;

import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
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.UdpImpl;
import org.etsi.its.uppertester.IBtpUpperTester;
import org.etsi.its.uppertester.IGnUpperTester;

/**
 * ITRI implementation of GeoNetworking upper tester
 *
 * Please refer to TR 1xx xxx V<0.0.3> (<2011-08>) document for Upper tester messages encoding description
 */
public class NwtUpperTester implements IGnUpperTester, IBtpUpperTester {
    
    /**
     * Module version
     */
    public static final String Version = "1.0.0.0";

    /**
     * Logger instance
     */
    private final static Logger _logger = Logger.getLogger("org.etsi.its");
    
    /**
     * Regex pattern for module settings decoding operation
     */
    private Pattern _pattern;
    
    /**
     * Reference to the TA codec
     */
    private UpperTesterCodecImpl _codec;
    
    private UdpImpl _utSendPort;
    
    /**
     * Constructor
     */
    public NwtUpperTester() {
        _logger.entering("NwtUpperTester", "Constructor", String.format("version: %s", Version));
        _pattern = Pattern.compile("(\\w+)\\:(\\S+)\\:(\\d+)\\:(\\d+)"); // For Java regex test, see http://www.cis.upenn.edu/~matuszek/General/RegexTester/regex-tester.html
        _codec = new UpperTesterCodecImpl();
        _utSendPort = null;
    }
    
    @Override
    public boolean changePosition() {
        _logger.entering("NwtUpperTester", "changePosition");
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(0, 4);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.changePosition, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean checkPacket(byte[] packet) {
        _logger.entering("NwtUpperTester", "checkPacket");
        
        try {
            byte[] buffer = ByteHelper.concat(
                new byte[] { (byte) 0xff }, 
                ByteHelper.intToByteArray(packet.length, 2), 
                packet);
            // Send PDU
            _logger.info("Sending packet");
            _utSendPort.send(buffer);
            _logger.info("Wait for response");
            // Wait for response
            byte[] response = _utSendPort.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", "generateGeoAnycast");
        
        try {
            byte[] buffer = ByteHelper.concat(
                    new byte[] { (byte)shape }, 
                    ByteHelper.intToByteArray(lifetime, 2), 
                    ByteHelper.intToByteArray(latitude, 4), 
                    ByteHelper.intToByteArray(longitude, 4), 
                    ByteHelper.intToByteArray(distanceA, 2), 
                    ByteHelper.intToByteArray(distanceB, 2), 
                    ByteHelper.intToByteArray(angle, 2), 
                    ByteHelper.intToByteArray(payload.length, 2));
            if (payload.length != 0) buffer = ByteHelper.concat(buffer, payload);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateGeoAnycastMessage, buffer, 0);
            _utSendPort.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", "generateGeoBroadcast");
        
        try {
            byte[] buffer = ByteHelper.concat(
                    new byte[] { (byte)shape }, 
                    ByteHelper.intToByteArray(lifetime, 2), 
                    ByteHelper.intToByteArray(latitude, 4), 
                    ByteHelper.intToByteArray(longitude, 4), 
                    ByteHelper.intToByteArray(distanceA, 2), 
                    ByteHelper.intToByteArray(distanceB, 2), 
                    ByteHelper.intToByteArray(angle, 2), 
                    ByteHelper.intToByteArray(payload.length, 2));
            if (payload.length != 0) buffer = ByteHelper.concat(buffer, payload);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateGeoBroadcastMessage, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean generateGeoUnicast(byte[] gnAddress, short lifetime, byte[] payload) {
        _logger.entering("NwtUpperTester", "generateGeoUnicast", String.format("lifetime: %d - %d", lifetime, payload.length));
        
        try {
            byte[] buffer = ByteHelper.concat(
                    gnAddress, 
                    ByteHelper.intToByteArray(lifetime, 2), 
                    ByteHelper.intToByteArray(payload.length, 2));
            if (payload.length != 0) buffer = ByteHelper.concat(buffer, payload);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateGeoUnicastMessage, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean generateShb(byte[] payload) {
        _logger.entering("NwtUpperTester", "generateShb");
        
        try {
            byte[] buffer = ByteHelper.concat(
                    ByteHelper.intToByteArray(payload.length, 2));
            if (payload.length != 0) buffer = ByteHelper.concat(buffer, payload);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateSHBMessage, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean generateTsb(byte[] payload, short nbHops) {
        _logger.entering("NwtUpperTester", "generateTsb");
        
        try {
            byte[] buffer = ByteHelper.concat(
                    new byte[] { (byte)nbHops }, 
                    ByteHelper.intToByteArray(payload.length, 2));
            if (payload.length != 0) buffer = ByteHelper.concat(buffer, payload);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.GenerateTSBMessage, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean initialize(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 {
            Matcher matcher = _pattern.matcher(settings);
            if (matcher.find()) {
                _utSendPort = new UdpImpl();
                _utSendPort.initialize(matcher.group(2), Integer.parseInt(matcher.group(3)), Integer.parseInt(matcher.group(4)));
            } else {
                _logger.severe("NwtUpperTester.initialize: Malformed parameters (2)");
                return false;
            }
            
            try {
                _utSendPort.send(new byte[] { TestAdapterMessageTypeEnum.UtGNInitialize.convert() });
                _utSendPort.receive();
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            _logger.exiting("NwtUpperTester", "initialize");
            return true;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean uninitialize() {
        _logger.entering("NwtUpperTester", "uninitialize");
        
        if (_utSendPort != null) {
            _utSendPort.uninitialize();
            _utSendPort = null;
        }
        
        return true;
    }

    @Override
    public boolean checkPayload(byte[] payload) {
        throw new RuntimeException("NwtUpperTester.checkPayload: Not implemented");
    }

    @Override
    public boolean generateBtpA(int destinationPort, int sourcePort) {
        throw new RuntimeException("NwtUpperTester.generateBtpA: Not implemented");
    }

    @Override
    public boolean generateBtpB(int destinationPort, int destinationPortInfo) {
        throw new RuntimeException("NwtUpperTester.generateBtpB: Not implemented");
    }

}
