/**
 *  This class is used to centralise test adapter configuration and execution parameters 
 *  All settings are component specific (multiton)
 *  
 *  @author     ETSI / STF424
 *  @version    $URL$
 *              $Id$
 *
 */

package org.etsi.its.adapter;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.etsi.adapter.TERFactory;
import org.etsi.common.ByteHelper;
import org.etsi.common.ITuple;
import org.etsi.common.Tuple;
import org.etsi.its.adapter.ports.GnPort;
import org.etsi.ttcn.tci.CharstringValue;

/**
 *  This class is used to centralise test adapter configuration and execution parameters 
 *  All settings are component specific (multiton)
 */
public class Management implements IManagementTA, IManagementLayers {

    /**
     * Instances of Management
     */
	private static final ConcurrentMap<String, Management> instances = new ConcurrentHashMap<String, Management>();
	
	/**
	 * GeoNetworking beaconning interval
	 */
	private static final int GN_BEACON_INTERVAL = Integer.decode(((CharstringValue)TERFactory.getInstance().getTaParameter("TsBeaconInterval")).getString());

    /**
     * Maximum time for getting Long position vector (in seconds)
     */
    private static final int GET_LPV_TIMEOUT = 10;

    /**
     * Interval for polling the location table during GetLpv (in ms)
     */
    private static final long GET_LPV_POLL_INTERVAL = 1000;
    
    /**
     * Test system latitude 
     */
    private static final int latitude = Integer.decode(((CharstringValue)TERFactory.getInstance().getTaParameter("TsLatitude")).getString());
    
    /**
     * Test system longitude
     */
    private static final int longitude = Integer.decode(((CharstringValue)TERFactory.getInstance().getTaParameter("TsLongitude")).getString());
	
	/**
	 * Link-layer address of Component
	 */
	private byte[] linkLayerAddress = null;

	/**
	 * Registered GN Port
	 */
	private GnPort gnPort = null;
	
	/**
     * Private constructor (Multiton pattern)
     */
	private Management() {
		//empty
	    
	    // FIXME: For debug only:
	    byte[] mid = new byte[] {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 
	                             (byte)0x00, (byte)0x00};
	    byte[] lpv = new byte[] {(byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, 
	                             (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
                                 (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
	                             (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
	                             (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
	                             (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00};
	    gnUpdateLocTable(mid, 0, lpv);
	}

	/**
     * Gets the Management instance associated to a component  
     * @param   key Component name
     * @return  Management instance
     */
	public static Management getInstance(String key) {
		if (instances.get(key) == null){
			// Lazily create instance and try to add it to the map
			Management instance = new Management();
		    instances.putIfAbsent(key, instance);
		}
		return instances.get(key);
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementTA#startBeaconing(byte[])
	 */
	@Override
	public void startBeaconing(byte[] beaconHeader) {
		this.beaconHeader = beaconHeader;
		if(gnPort != null) {
		    gnPort.startBeaconning();
		}
	}
	
	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementTA#stopBeaconing()
	 */
	@Override
	public void stopBeaconing() {
		this.beaconHeader = null;
		if(gnPort != null) {
            gnPort.stopBeaconning();
        }
	}
	
	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementTA#startEnqueueingBeacons(byte[])
	 */
	@Override
	public void startEnqueueingBeacons(byte[] beaconHeader) {
		this.enqueueBeacon = beaconHeader;
	}
	
	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementTA#stopEnqueueingBeacons()
	 */
	@Override
	public void stopEnqueueingBeacons() {
		this.enqueueBeacon = null;
	}
	
	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementTA#startMultipleBeaconing(byte[], int)
	 */
	@Override
	public void startMultipleBeaconing(byte[] beaconHeader, int nbNeighbours) {
		/* TODO: Multiple beacons */
	}
	
	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementTA#stopMultipleBeaconing()
	 */
	@Override
	public void stopMultipleBeaconing() {
		/* TODO: Multiple beacons */
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementTA#getLongPositionVector(byte[])
	 */
	@Override
	public byte[] getLongPositionVector(byte[] targetGnAddress) {
		byte[] mid = ByteHelper.extract(targetGnAddress, 2, 6);
		long key = ByteHelper.byteArrayToLong(mid);
		for(int i = 0; i < GET_LPV_TIMEOUT; ++i) {
    		if (locTable.containsKey(key)) {
    			ITuple<Long, byte[]> entry = locTable.get(key);
    			return entry.getB();	
    		}
    		try {
                Thread.sleep(GET_LPV_POLL_INTERVAL);
            } catch (InterruptedException e) {
                // Do nothing, we do not care
            }
		}
		return null;
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementLayers#getGnBeacon()
	 */
	@Override
	public byte[] getGnBeacon() {
		return beaconHeader;
	}
	
	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementLayers#getGnBeaconInterval()
	 */
	@Override
	public int getGnBeaconInterval() {
		return GN_BEACON_INTERVAL;
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementLayers#getGnEnqueueBeacon()
	 */
	@Override
	public byte[] getGnEnqueueBeacon() {
		return enqueueBeacon;
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementLayers#gnUpdateLocTable(byte[], long, byte[])
	 */
	@Override
	public void gnUpdateLocTable(byte[] mid, long timestamp, byte[] lpv) {
		// Java does not provide unsigned int
		timestamp &= 0xffffffffL;
		long key = ByteHelper.byteArrayToLong(mid);
		ITuple<Long, byte[]> entry = locTable.get(key);		
		if(entry == null || entry.getA() < timestamp) {
		    //ByteHelper.dump("Adding Loc Entry for: ", mid);
			locTable.put(key, new Tuple<Long, byte[]>(timestamp, lpv));
		}		
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementLayers#getLinkLayerAddress()
	 */
	@Override
	public byte[] getLinkLayerAddress() {
		return linkLayerAddress;
	}
	
	/* (non-Javadoc)
     * @see org.etsi.its.adapter.IManagementLayers#getLinkLayerAddress()
     */
    @Override
    public void setLinkLayerAddress(byte[] linkLayerAddress) {
        this.linkLayerAddress = linkLayerAddress;
    }

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementLayers#getLatitude()
	 */
	@Override
	public byte[] getLatitude() {
		return ByteHelper.intToByteArray(latitude, 4);
	}

	/* (non-Javadoc)
	 * @see org.etsi.its.adapter.IManagementLayers#getLongitude()
	 */
	@Override
	public byte[] getLongitude() {
		return ByteHelper.intToByteArray(longitude, 4);
	}
	
	/**
	 * Set to null in order to prevent Test Adapter from sending beacons
	 * Otherwise, it shall be set to a valid encoded beacon header to be send periodically by Test Adapter
	 * @see startEnqueueingBeacons
	 * @see stopEnqueueingBeacons
	 */
	private byte[] beaconHeader = null;
		
	/**
	 * Set to null if received Beacon messages have to be discarded by test adapter (= not enqueued)
	 * Otherwise, it shall be set to the to an encoded beacon header value acting as a filter for enqueueing received beacons
     * @see startBeaconing
     * @see stopBeaconing    
	 */
	private byte[] enqueueBeacon = null;
	
	/**
	 * Table used to store neighbours (= SUT) position vectors
	 * @see gnUpdateLocTable
	 * @see getLongPositionVector
	 */
	private static Map<Long, ITuple<Long, byte[]>> locTable = new HashMap<Long, ITuple<Long, byte[]>>();

    /* (non-Javadoc)
     * @see org.etsi.its.adapter.IManagementLayers#registerBeaconThread(java.lang.Thread)
     */
    @Override
    public void registerGnPort(GnPort gnPort) {
        this.gnPort = gnPort;
        
        // Ensure that management settings are reset
        beaconHeader = null;
        enqueueBeacon = null;
    }

}
