/**
 *  Upper Tester port implementation. This port is used to trigger IUT's upper interface
 *  
 *  @author     ETSI / STF424
 *  @version    $URL$
 *              $Id$
 *
 */
package org.etsi.its.adapter.ports;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

/** This class implements behaviour for Upper Tester port
 * The Upper tester entity in the SUT enables triggering Protocol functionalities by simulating primitives from 
 * application or LDM entities
 * It is required to trigger the Protocol layer in the SUT to send Protocol specific messages, which are 
 * resulting from upper layer primitives
 */
public class UpperTesterPort extends AdapterPort implements IPort, IObservable {

    private static final String SETTINGS_PATTERN = "(\\S+)\\:(\\d+)";

    /**
     * Constructor
     * @param   portName            Name of the port
     * @param   componentName       Name of the component owning this port instance
     * @param   localPortNumber     Local port number for the UDP listener
     * @param   remotePortNumber    UDP port listener of remote UT application
     */
    public UpperTesterPort(final String portName, final String componentName) {
        super(portName, componentName);

        // UDP connection parameters
        String settings = ((CharstringValue)TERFactory.getInstance().getTaParameter("UpperTesterSettings")).getString();
        Matcher matcher = settingsPattern.matcher(settings);
        if (matcher.find()) {
            try {
                utPeerAddress = InetAddress.getByName(matcher.group(1));
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            }
            utPeerPort = Integer.parseInt(matcher.group(2));
        } else {
            
        }   
        running = true;

        // UDP socket for communication with UT
        try {
            utSocket = new DatagramSocket();               
            utThread = new UdpThread(utSocket);
            utThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public boolean send(final byte[] message) {
/* FIXME: For debug only. Uncomment if no UT
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // Do nothing, we do not care
        }
        
        setChanged();
        byte[] rsp;
        switch (message[0]) {
            case 0x00:
                rsp = new byte[]{(byte)0x01, (byte)0x01};
                break;
            default:
                rsp = new byte[]{(byte)0x41, (byte)0x01};
                break;
        }
        notifyObservers(new PortEvent(rsp, getPortName(), getComponentName()));
        if(true)
            return true;
*/
        DatagramPacket packet = new DatagramPacket(message, message.length, utPeerAddress, utPeerPort);
        try {
            utSocket.send(packet);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } 
        return true;
    }

    @Override
    public void dispose() {
        if(running) {           
            running = false;
            if(utThread != null) {
                try {
                    utSocket.close();
                    utThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private DatagramSocket utSocket;
    private Thread utThread;
    private InetAddress utPeerAddress = null;
    private int utPeerPort = 0;
    private Pattern settingsPattern = Pattern.compile(SETTINGS_PATTERN);
    
    /**
     * Indicates whether the port is still active. Setting this field to false will cause
     * the UDP communication with Upper Tester to be stopped
     */
    private volatile boolean running;
    
    private class UdpThread extends Thread {

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

        @Override
        public void run() {
            
            while(running) {
                try {
                    byte[] buf = new byte[4096];
                    
                    // receive packet
                    DatagramPacket packet = new DatagramPacket(buf, buf.length);
                    taSocket.receive(packet);

                    setChanged();
                    notifyObservers(new PortEvent(ByteHelper.extract(packet.getData(), packet.getOffset(), packet.getLength()), getPortName(), getComponentName()));
                } catch (IOException e) {
                    running = false;
                }
            }
            taSocket.close();
        }        
    }
    
} 
