/**
 *  GeoNetworking port implementation (background thread)
 *  
 *  @author     ETSI / STF424
 *  @version    $URL$
 *              $Id$
 *
 */
package org.etsi.its.adapter.ports;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.etsi.adapter.TERFactory;
import org.etsi.common.ByteHelper;
import org.etsi.its.adapter.layers.EthernetLayer;
import org.etsi.its.adapter.layers.IEthernetSpecific;
import org.etsi.its.adapter.layers.Layer;
import org.etsi.its.codec.TestAdapterMessageTypeEnum;
import org.etsi.ttcn.tci.CharstringValue;

/**
 *  GeoNetworking port implementation (background thread)
 */
public class GnPort extends ProtocolPort implements Runnable, IEthernetSpecific {

	/**
	 * GeoNetworking header type for beacon messages
	 */
	private static final int HT_BEACON = 1;
	
	/**
     * Constructor
     * @param   portName        Name of the port
     * @param   componentName   Name of the component owning this port instance
     * @param   lowerStackDesc  Description of the port's lower stack in the form "Layer/Layer/Layer/..."
     * @param  linkLayerAddress    Link-layer address to be used by this port as source address (null if not applicable)
     */
	public GnPort(String portName, String componentName, String lowerStackDesc, String linkLayerAddress) {
		super(portName, componentName, lowerStackDesc, linkLayerAddress);
		running = true;
		management.registerGnPort(this);
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.ports.IEthernetSpecific#getEthernetType()
	 */
	@Override
	public short getEthernetType() {
	    
        // Retrieve EthernetType value
        Integer iutEthernetTypeValue = Integer.decode(((CharstringValue)TERFactory.getInstance().getTaParameter("IutEthernetTypeValue")).getString());
        return iutEthernetTypeValue.shortValue();
    }

	/**
	 * Starts beaconning
	 */
	public void startBeaconning() {
	    running = true;
	    if(beaconThread == null) {
	        beaconThread = new Thread(this);
	        beaconThread.start();
	    }
	}
	
	/**
     * Stops beaconning
     */
    public void stopBeaconning() {
        if(beaconThread != null) {
            running = false;
            try {
                beaconThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            beaconThread = null;
        }
    }
    
	/**
	 * Thread function for sending periodic beacons
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		byte[] beaconHeader = management.getGnBeacon();
		if(beaconHeader != null) {
	        long modulo = (long)Math.pow(2,32);     
	        Map<String, Object> params = new HashMap<String, Object>();
		    while(running) {
				// Update timestamp. Timestamp is 1s older than current time to avoid sending beacons coming from the future (time sync between nodes)
				byte[] tst = ByteHelper.intToByteArray((int)(System.currentTimeMillis() % modulo - 1000), 4);
				System.arraycopy(tst, 0, beaconHeader, 16, 4);
				send(beaconHeader, params);			
                try {
                    Thread.sleep(management.getGnBeaconInterval());
                } catch (InterruptedException e) {
                    // Do nothing, we do not care
                }
		    }
		}
	}

	/* (non-Javadoc)
     * @see org.etsi.its.adapter.ports.IPort#send(byte[])
     */
    @Override
    public boolean send(byte[] message) {
        HashMap<String, Object> params = new HashMap<String, Object>();
        
        //TODO: clean this + extract params from MsgReq    (params.put)    
        // Extract MsgReq/Ind + msgOut/msgIn field (one byte) + length field (2 bytes)
        message = ByteHelper.extract(message, 3, message.length - 3);
        // Extract msgIn/msgOut field (one byte) + length field (2 bytes)
        message = ByteHelper.extract(message, 3, message.length - 3);
        
        byte[] destMacAddress = ByteHelper.extract(message, message.length - 6, 6);
        message = ByteHelper.extract(message, 0, message.length - 6);
        params.put(EthernetLayer.LINK_LAYER_DESTINATION, destMacAddress);
        ByteHelper.dump("GnPort.send", message);
        return send(message, params);
    }
	
	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.ports.ProtocolPort#receive(byte[])
	 */
	@Override
	public void receive(byte[] message, Map<String, Object> lowerInfo) {
		byte[] commonHdr = new byte[36];
		System.arraycopy(message, 0, commonHdr, 0, 36);
		byte[] htHst = new byte[1];
		System.arraycopy(commonHdr, 1, htHst, 0, 1);
		int headerType = (int)(htHst[0] >> 4);
		
		// Update LPV table
		byte[] sepv = new byte[28];
		System.arraycopy(commonHdr, 8, sepv, 0, 28);
		byte[] gn = new byte[8];
		System.arraycopy(sepv, 0, gn, 0, 8);
		byte[] mid = new byte[6];
		System.arraycopy(gn, 2, mid, 0, 6);
		byte[] tst = new byte[4];
		System.arraycopy(sepv, 8, tst, 0, 4);
		management.gnUpdateLocTable(mid, ByteHelper.byteArrayToInt(tst), sepv);
		
		// Filter beacons
		byte [] beaconFilter = management.getGnEnqueueBeacon();
		if(headerType != HT_BEACON || beaconFilter != null) {
			if(headerType == HT_BEACON) {
				byte[] filterMid = new byte[6];
				System.arraycopy(beaconFilter, 10, filterMid, 0, 6);
				if(java.util.Arrays.equals(mid, filterMid) == false) {
					// Received beacon does not match filter
					return;
				}
			}	
			// Encode with GN indication header
			try {
				// Extract LINK_LAYER_DESTINATION
				byte[] msgInd = ByteHelper.concat(message, (byte[])lowerInfo.get(EthernetLayer.LINK_LAYER_DESTINATION));
				// Build MsgInd correctly
				msgInd = getTaCodec().Encode(TestAdapterMessageTypeEnum.GeoNetworkingInd, msgInd, (Long)lowerInfo.get(Layer.RECEPTION_TIMESTAMP));
				// And send it
				super.receive(msgInd, lowerInfo);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.ports.ProtocolPort#dispose()
	 */
	@Override
	public void dispose() {
		if(running && beaconThread != null) {
			running = false;
			try {
				beaconThread.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		super.dispose();
	}
	
	/**
	 * Indicates whether the port is still active. Setting this field to false will cause
	 * the beaconing thread to stop its execution.
	 */
	private volatile boolean running;
	
	/**
	 * Beaconing thread instance.
	 */
	private Thread beaconThread = null;
}

