package org.etsi.its.adapter.layers;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.etsi.adapter.TERFactory;
import org.etsi.common.ByteHelper;
import org.etsi.its.adapter.IManagementLayers;
import org.etsi.ttcn.tci.CharstringValue;

public class UdpIpLayer extends Layer {

    private static final String SETTINGS_PATTERN = "(\\S+)\\:(\\d+)\\:(\\d+)"; // FIXME The second port shall be optional if the same port shall be used for server and client
    
    private Pattern settingsPattern = Pattern.compile(SETTINGS_PATTERN);
    
    public UdpIpLayer(IManagementLayers management, Stack<String> lowerStack) {
        super(management, lowerStack);
    }

    /* (non-Javadoc)
     * @see org.etsi.its.adapter.layers.Layer#send(byte[], java.util.Map)
     */
    @Override
    public boolean send(byte[] message, Map<String, Object> params) {
        DatagramPacket packet = new DatagramPacket(message, message.length, iutAddress, iutClientPort);
        try {
            iutSocket.send(packet);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }
    
    /* (non-Javadoc)
     * @see org.etsi.its.adapter.layers.Layer#register(org.etsi.its.adapter.layers.Layer)
     */
    @Override
    public void register(Layer upperLayer) {
        if(registeredUpperLayer == null) {
            super.register(upperLayer);
            try {
                // UDP connection parameters
                String settings = ((CharstringValue)TERFactory.getInstance().getTaParameter("LowerTesterSettings")).getString();
                Matcher matcher = settingsPattern.matcher(settings);
                if (matcher.find()) {
                    iutAddress = InetAddress.getByName(matcher.group(1));
                    iutClientPort = Integer.parseInt(matcher.group(2));
                    if (matcher.group(3) != null) {
                    	iutServerPort = Integer.parseInt(matcher.group(3));
                    } else {
                        iutServerPort = iutClientPort;
                    }
                } else {
                    System.err.println("No pattern matching");
                }
                // UDP server socket for communication with UT
                iutSocket = new DatagramSocket(iutServerPort); 
                iutThread = new UdpThread(iutSocket);
                iutThread.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    /* (non-Javadoc)
     * @see org.etsi.its.adapter.layers.Layer#unregister(org.etsi.its.adapter.layers.Layer)
     */
    @Override
    public void unregister(Layer upperLayer) {
        iutSocket.close();
        iutThread.interrupt();
        try {
            iutThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        super.unregister(upperLayer);
    }
    
    private DatagramSocket iutSocket;
    private InetAddress iutAddress;
    private int iutClientPort;
    private int iutServerPort;
    private Thread iutThread;
  
    private class UdpThread extends Thread {

        private DatagramSocket taSocket;
        private boolean running = true;
        
        public UdpThread(DatagramSocket taSocket) throws IOException {
            this.taSocket = taSocket;
        }

        @Override
        public void run() {
            
            while(running) {
                try {
                    byte[] buf = new byte[4096];
                    Map<String, Object> lowerInfo = new HashMap<String, Object>();
                    
                    // receive packet
                    DatagramPacket packet = new DatagramPacket(buf, buf.length);
                    taSocket.receive(packet);
                    lowerInfo.put(Layer.RECEPTION_TIMESTAMP, System.currentTimeMillis());
                    receive(ByteHelper.extract(packet.getData(), packet.getOffset(), packet.getLength()), lowerInfo);
                } catch (IOException e) {
                    running = false;
                }
            }
        }
    }
}
