package org.etsi.its.uppertester.nec;

import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.etsi.common.ByteHelper;
import org.etsi.its.uppertester.IBtpUpperTester;
import org.etsi.its.uppertester.IGnUpperTester;

import org.etsi.its.network.UdpImpl;


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

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

    /**
     * Logger instance
     */
    private final static Logger _logger = Logger.getLogger("org.etsi.its");
    
    // PTs
    private static final short PT_GUC = 2;
    private static final short PT_GAC = 3;
    private static final short PT_GBC = 4;
    private static final short PT_TSB = 5;
    
    // PSTs
    private static final short PST_UNSPECIFIED = 0;
    private static final short PST_SHB = 0;
    private static final short PST_TSB = 1;
    
    // Default values
    private static final short DEFAULT_HL = 8;
    private static final short DEFAULT_FLAGS = 0;
    private static final short DEFAULT_TC = 0;
    private static final int DEFAULT_REP_INTERVAL = 0;
    private static final int DEFAULT_LT = 10000;
    
    private static final short DEFAULT_DPORT = 5000;
    private static final short DEFAULT_DPORT_INFO = 0;
    
    /**
     * Regex pattern for module settings decoding operation
     */
    private Pattern _pattern;

    /**
     * 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
        utSendPort = null;
        
        
    }
    
    @Override
    public boolean changePosition() {
        return false;
    }

    @Override
    public boolean checkPacket(byte[] expectedPacket) {
        
        ListIterator<byte[]> it = receivedNetPackets.listIterator();
        
        while (it.hasNext()) {
            byte[] current = it.next();
            if(comparePacket(expectedPacket, current)) {
                System.out.println("NwtaTrigger.checkPacket: Matched, removing");
                receivedNetPackets.remove(current);
                return true;
            }
        }

        return false;  
    }
    
    private boolean comparePacket(byte[] expectedPacket, byte[] receivedPacket) {
        
        // compare only payloads
        try {
            byte[] receivedPayload;
            if((receivedPacket[0] == PT_GBC) || (receivedPacket[0] == PT_GAC)) {
                receivedPayload = ByteHelper.extract(receivedPacket, 56, receivedPacket.length - 56);
            }
            else {
                receivedPayload = ByteHelper.extract(receivedPacket, 40, receivedPacket.length - 40);
            }
            byte[] expectedPayload = ByteHelper.extract(expectedPacket, expectedPacket.length - receivedPayload.length, receivedPayload.length);
            
            ByteHelper.dump("Expected payload: ", expectedPayload);
            
            if(Arrays.equals(expectedPayload, receivedPayload)) {
                return true;
            }
        }
        catch(Exception e) {
            e.printStackTrace();
            return false;
        }
        
        return false;        
    }

    @Override
    public boolean generateGeoAnycast(short shape, int latitude, int longitude, short distanceA, short distanceB, short angle, short lifetime, byte[] payload) {
        byte[] com = generateCommonParams(
                PT_GAC, 
                shape, 
                DEFAULT_HL, 
                DEFAULT_FLAGS, 
                (short)payload.length, 
                DEFAULT_TC,
                DEFAULT_REP_INTERVAL,
                lifetime);
        byte[] bLatitude = ByteHelper.intToByteArray(latitude, 4);
        byte[] bLongitude = ByteHelper.intToByteArray(longitude, 4);
        byte[] bDistanceA = ByteHelper.intToByteArray(distanceA, 2);
        byte[] bDistanceB = ByteHelper.intToByteArray(distanceB, 2);
        byte[] bAngle = ByteHelper.intToByteArray(angle, 2);
        byte[] bReserved = ByteHelper.intToByteArray(0, 2);        
        byte[] dport = ByteHelper.intToByteArray(DEFAULT_DPORT, 2);
        byte[] dportInfo = ByteHelper.intToByteArray(DEFAULT_DPORT_INFO, 2);
        
        try {
            utSendPort.send(ByteHelper.concat(com, bLatitude, bLongitude, bDistanceA, bDistanceB, bAngle, bReserved, dport, dportInfo, payload));
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public boolean generateGeoBroadcast(short shape, int latitude, int longitude, short distanceA, short distanceB, short angle, short lifetime, byte[] payload) {
        byte[] com = generateCommonParams(
                PT_GBC, 
                shape, 
                DEFAULT_HL, 
                DEFAULT_FLAGS, 
                (short)payload.length, 
                DEFAULT_TC,
                DEFAULT_REP_INTERVAL,
                lifetime);
        byte[] bLatitude = ByteHelper.intToByteArray(latitude, 4);
        byte[] bLongitude = ByteHelper.intToByteArray(longitude, 4);
        byte[] bDistanceA = ByteHelper.intToByteArray(distanceA, 2);
        byte[] bDistanceB = ByteHelper.intToByteArray(distanceB, 2);
        byte[] bAngle = ByteHelper.intToByteArray(angle, 2);
        byte[] bReserved = ByteHelper.intToByteArray(0, 2);        
        byte[] dport = ByteHelper.intToByteArray(DEFAULT_DPORT, 2);
        byte[] dportInfo = ByteHelper.intToByteArray(DEFAULT_DPORT_INFO, 2);
        
        try {
            utSendPort.send(ByteHelper.concat(com, bLatitude, bLongitude, bDistanceA, bDistanceB, bAngle, bReserved, dport, dportInfo, payload));
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public boolean generateGeoUnicast(byte[] gnAddress, short lifetime, byte[] payload) {
        byte[] com = generateCommonParams(
                PT_GUC, 
                PST_UNSPECIFIED, 
                DEFAULT_HL, 
                DEFAULT_FLAGS, 
                (short)payload.length, 
                DEFAULT_TC,
                DEFAULT_REP_INTERVAL,
                lifetime);
        byte[] dport = ByteHelper.intToByteArray(DEFAULT_DPORT, 2);
        byte[] dportInfo = ByteHelper.intToByteArray(DEFAULT_DPORT_INFO, 2);
        
        try {
            utSendPort.send(ByteHelper.concat(com, ByteHelper.changeEndianness(gnAddress), dport, dportInfo, payload));
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public boolean generateShb(byte[] payload) {
        byte[] com = generateCommonParams(
                PT_TSB, 
                PST_SHB, 
                DEFAULT_HL, 
                DEFAULT_FLAGS, 
                (short)payload.length, 
                DEFAULT_TC,
                DEFAULT_REP_INTERVAL,
                DEFAULT_LT);
        byte[] dport = ByteHelper.intToByteArray(DEFAULT_DPORT, 2);
        byte[] dportInfo = ByteHelper.intToByteArray(DEFAULT_DPORT_INFO, 2);
        
        try {
            ByteHelper.dump("Generate SHB", ByteHelper.concat(com, dport, dportInfo, payload));
            utSendPort.send(ByteHelper.concat(com, dport, dportInfo, payload));
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public boolean generateTsb(byte[] payload, short nbHops) {
        byte[] com = generateCommonParams(
                PT_TSB, 
                PST_TSB, 
                DEFAULT_HL, 
                DEFAULT_FLAGS, 
                (short)payload.length, 
                DEFAULT_TC,
                DEFAULT_REP_INTERVAL,
                DEFAULT_LT);
        byte[] dport = ByteHelper.intToByteArray(DEFAULT_DPORT, 2);
        byte[] dportInfo = ByteHelper.intToByteArray(DEFAULT_DPORT_INFO, 2);
        
        try {
            utSendPort.send(ByteHelper.concat(com, dport, dportInfo, payload));
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private byte[] generateCommonParams(short pt, short pst, short hl, short flags, short pl, short tc, int repInterval, int lt) {
        byte[] bPt = ByteHelper.intToByteArray(pt, 1);
        byte[] bPst = ByteHelper.intToByteArray(pst, 1);
        byte[] bHl = ByteHelper.intToByteArray(hl, 1);
        byte[] bFlags = ByteHelper.intToByteArray(flags, 1);
        byte[] bPl = ByteHelper.intToByteArray(pl, 2);
        byte[] bTc = ByteHelper.intToByteArray(tc, 1);
        byte[] bReserved = ByteHelper.intToByteArray(0, 1);
        byte[] bRepInterval = ByteHelper.intToByteArray(repInterval, 4);
        byte[] bLt = ByteHelper.intToByteArray(lt / 1000, 4);
        return ByteHelper.concat(bPt, bPst, bHl, bFlags, bPl, bTc, bReserved, bRepInterval, bLt);
    }
    
    /* (non-Javadoc)
     * @see org.etsi.its.uppertester.IUpperTester#initialize(java.lang.String)
     */
    @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 {
            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;
            }
            
            running = true;
            receptionThread = new Thread(this);
            receptionThread.start();
            
            _logger.exiting("NwtUpperTester", "initialize");
            return true;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return false;
    }

    /* (non-Javadoc)
     * @see org.etsi.its.uppertester.IUpperTester#uninitialize()
     */
    @Override
    public boolean uninitialize() {
        _logger.entering("NwtUpperTester", "uninitialize");
        running = false;
        
        if (utSendPort != null) {
            utSendPort.uninitialize();
            utSendPort = null;
        }
        
        try {
            receptionThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        return true;
    }

    @Override
    public boolean checkPayload(byte[] payload) {
        return false;
    }

    @Override
    public boolean generateBtpA(int destinationPort, int sourcePort) {
        return false;
    }

    @Override
    public boolean generateBtpB(int destinationPort, int destinationPortInfo) {
        return false;
    }

    private UdpImpl utSendPort;
    private Thread receptionThread;
    private boolean running;
    
    /**
     * Stack the NetPacket received from lower layer
     */
    private List<byte[]> receivedNetPackets = new ArrayList<byte[]>();

    @Override
    public void run() {
        while(running) {
            byte[] msgInd;
            try {
                msgInd = utSendPort.receive();
                receivedNetPackets.add(msgInd);
            } catch (java.net.SocketException e) {
                // ignore
            } catch (IOException e) {
                e.printStackTrace();
            }           
        }        
    }
}
