package org.etsi.its.adapter.ports;

/**
 *  Upper Tester port implementation. This port is used to trigger IUT's upper interface
 *  
 *  @author     ETSI / STF424
 *  @version    $URL$
 *              $Id$
 *
 */

import java.io.ByteArrayOutputStream;
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 Configuration Tester port. This port is used to access to the IUT management port 
 * The Configuration tester entity in the SUT enables triggering Protocol functionalities by simulating primitives from 
 * from SAPs
 * It is required to trigger the Protocol layer in the SUT to send Protocol specific messages, which are 
 * resulting from management layer primitives
 */
public class ConfigTesterPort 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 ConfigTesterPort(final String portName, final String componentName) {
        super(portName, componentName);

        // UDP connection parameters
        String settings = ((CharstringValue)TERFactory.getInstance().getTaParameter("ConfigTesterSettings")).getString();
        Matcher matcher = settingsPattern.matcher(settings);
        if (matcher.find()) {
            try {
                cfPeerAddress = InetAddress.getByName(matcher.group(1));
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            }
            cfPeerPort = Integer.parseInt(matcher.group(2));
        } else {
            // FIXME
        }   
        
        // UDP socket for communication with Configuration module
        running = true;
        try {
            cfSocket = new DatagramSocket(cfPeerPort + 1);
            cfThread = new UdpThread(cfSocket);
            cfThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public boolean send(final byte[] message) {
        try {
            ByteArrayOutputStream dataToSent = new ByteArrayOutputStream();
            dataToSent.write(message);
            byte[] output = dataToSent.toByteArray();
            DatagramPacket packet = new DatagramPacket(output, output.length, cfPeerAddress, cfPeerPort);
            cfSocket.send(packet);
            
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        } 
        
        return false;
    }

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

    private DatagramSocket cfSocket;
    private Thread cfThread;
    private InetAddress cfPeerAddress = null;
    private int cfPeerPort = 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();
        }
        
    } // End of class UdpThread
    
} // End of class ConfigTesterPort 
