package org.etsi.its.adapter.layers;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

import org.etsi.common.ByteHelper;
import org.etsi.its.adapter.IManagementLayers;
import org.etsi.its.adapter.UdpMultiplexer;

public class UdpIpCommsigniaLayer extends UdpIpLayer {
    
    private static String UdpAddress = "80.98.62.165"; //"10.200.1.101"; // FIXME Use a generic way to retrieve UDP settings
    
    private static int UdpRecvPort = 18501; // FIXME Use a generic way to retrieve UDP settings
    
    private static int UdpSendPort = 18502; // FIXME Use a generic way to retrieve UDP settings
    
    private Map<String, Short> clientsToFrameTypes = new HashMap<String, Short>();
    
    public UdpIpCommsigniaLayer(IManagementLayers management, Stack<String> lowerStack) {
        super(management, lowerStack);
        UdpMultiplexer.getInstance().UdpAddress = UdpAddress;
        UdpMultiplexer.getInstance().UdpRecvPort = UdpRecvPort;
        UdpMultiplexer.getInstance().UdpSendPort = UdpSendPort;
    }
    
    /* (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);
            // Workaround for Commsignia CAM send module
            Map<String, Object> lowerInfo = new HashMap<String, Object>();
            lowerInfo.put(UdpMultiplexer.getInstance().UDP_PORT_KEY, new Integer(UdpRecvPort).toString()); 
            super.send(new byte[] { (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA }, lowerInfo);
        } else {
            super.register(upperLayer);
        }
        clientsToFrameTypes.put(this.toString(), upperLayerFrameType);
    }
    
    /* (non-Javadoc)
     * @see org.etsi.its.adapter.layers.Layer#unregister(org.etsi.its.adapter.layers.Layer)
     */
    @Override
    public void unregister(Layer upperLayer) {
        super.unregister(upperLayer);
        clientsToFrameTypes.remove(this.toString(), upperLayerFrameType);
    }
    
    /* (non-Javadoc)
     * @see org.etsi.its.adapter.layers.Layer#send(byte[], java.util.Map)
     */
    @Override
    public boolean send(byte[] message, Map<String, Object> params) {
        //TERFactory.getInstance().logDebug("UdpIpLayer.send: " + ByteHelper.byteArrayToString(message));
        
        byte[] dst = (byte[])params.get(LINK_LAYER_DESTINATION);
        if(dst == null) {
            dst = MAC_BROADCAST;
        }
        
        byte[] buffer = ByteHelper.concat(
            new byte[] { (byte) 0xe0 }, 
            localMacAddress, 
            dst,
            ByteHelper.intToByteArray(message.length, Short.SIZE / Byte.SIZE) ,
            message
        );
        
        return super.send(buffer, params);
    }
    
    public void receive(byte[] message, Map<String, Object> lowerInfo) {
        //TERFactory.getInstance().logDebug(">>> UdpIpLayer.run: Receive packet from " + packet.getSocketAddress() + "/" + packet.getPort());
        
        if (message.length < 36) {
            // Skip it
            return;
        }
        
        ByteBuffer byteBuffer = ByteBuffer.wrap(message);
        //TERFactory.getInstance().logDebug("UdpIpLayer.run: Receive packet from " + ByteHelper.byteArrayToString(byteBuffer.array()));
        
        // Skip IEEE 802.11L Layer: 88 00 00 00 00
        byteBuffer.position(4);
        
        // Extract Dst
        byte[] dst = new byte[6];
        byteBuffer.get(dst, 0, dst.length);
        lowerInfo.put(EthernetLayer.LINK_LAYER_DESTINATION, dst);
        
        // Skip Src
        byteBuffer.position(byteBuffer.position() + 6);
        
        // Skip LLC header
        byteBuffer.position(byteBuffer.position() + 16);
        
        // Extract FrameType info
        byte[] rawFrameType = new byte[2];
        byteBuffer.get(rawFrameType, 0, rawFrameType.length);
        short frameType = ByteHelper.byteArrayToInt(rawFrameType).shortValue();
        
        if (clientsToFrameTypes.containsKey(this.toString())) {
            if(frameType == clientsToFrameTypes.get(this.toString())) {
                if(Arrays.equals(dst, MAC_BROADCAST) || Arrays.equals(dst, localMacAddress)) {
                    if(registeredUpperLayer != null) {
                        // Extract Data
                        byte[] data = new byte[byteBuffer.remaining() - 4]; // Remove CRC added by the device
                        byteBuffer.get(data, 0, byteBuffer.remaining() - 4);
                        
                        registeredUpperLayer.receive(data, lowerInfo);
                    }
                }
            }
        }
    }
    
}
