package net.gcdc.geonetworking;

import java.nio.ByteBuffer;
import java.util.Arrays;

public class BtpPacket {

    private final Optional<Short>              sourcePort;
    private final short                        destinationPort;
    private final Optional<Short>              destinationPortInfo;
    private final byte[]                       payload;  // Final reference, but mutable content!
    private final Destination                  destination;
    private final Optional<TrafficClass>       trafficClass;
    private final Optional<LongPositionVector> senderPosition;

    private byte[] gnPayload = null;

    private BtpPacket(
            Optional<Short>              sourcePort,
            short                        destinationPort,
            Optional<Short>              destinationPortInfo,
            byte[]                       payload,
            Destination                  destination,
            Optional<TrafficClass>       trafficClass,
            Optional<LongPositionVector> senderPosition
        ) {
        this.sourcePort          = sourcePort;
        this.destinationPort     = destinationPort;
        this.destinationPortInfo = destinationPortInfo;
        this.payload             = payload.clone();  // Defensive. Just in case.
        this.destination         = destination;
        this.trafficClass        = trafficClass;
        this.senderPosition      = senderPosition;
    }

    public static BtpPacket singleHop(byte[] payload, short destinationPort) {
        Optional<Short>              emptySourcePort          = Optional.empty();
        Optional<Short>              emptyDestinationPortInfo = Optional.empty();
        Optional<TrafficClass>       emptyTrafficClass        = Optional.empty();
        Optional<LongPositionVector> emptySenderPosition      = Optional.empty();

        return new BtpPacket(
                emptySourcePort,
                destinationPort,
                emptyDestinationPortInfo,
                payload,
                Destination.singleHop(),
                emptyTrafficClass,
                emptySenderPosition
                );
    }

    public static BtpPacket singleHop(byte[] payload, short destinationPort, double maxLifetimeSec) {
        Optional<Short>              emptySourcePort          = Optional.empty();
        Optional<Short>              emptyDestinationPortInfo = Optional.empty();
        Optional<TrafficClass>       emptyTrafficClass        = Optional.empty();
        Optional<LongPositionVector> emptySenderPosition      = Optional.empty();

        return new BtpPacket(
                emptySourcePort,
                destinationPort,
                emptyDestinationPortInfo,
                payload,
                Destination.singleHop().withMaxLifetimeSeconds(maxLifetimeSec),
                emptyTrafficClass,
                emptySenderPosition
                );
    }
    /** Dummy BTP-A packet for Plugtest. */
    public static BtpPacket singleHopEmptyA(short destinationPort, short sourcePort) {
        Optional<Short>              emptyDestinationPortInfo = Optional.empty();
        Optional<TrafficClass>       emptyTrafficClass        = Optional.empty();
        Optional<LongPositionVector> emptySenderPosition      = Optional.empty();

        return new BtpPacket(
                Optional.of(sourcePort),
                destinationPort,
                emptyDestinationPortInfo,
                new byte[] {},
                Destination.singleHop(),
                emptyTrafficClass,
                emptySenderPosition
                );
    }

    /** Dummy BTP-B packet for Plugtest. */
    public static BtpPacket singleHopEmptyB(short destinationPort, short DestinationPortInfo) {
        Optional<Short>              emptySourcePort          = Optional.empty();
        Optional<TrafficClass>       emptyTrafficClass        = Optional.empty();
        Optional<LongPositionVector> emptySenderPosition      = Optional.empty();

        return new BtpPacket(
                emptySourcePort,
                destinationPort,
                Optional.of(DestinationPortInfo),
                new byte[] {},
                Destination.singleHop(),
                emptyTrafficClass,
                emptySenderPosition
                );
    }

    public static BtpPacket customDestination(byte[] payload, short destinationPort,
            Destination destination) {
        Optional<Short>              emptySourcePort          = Optional.empty();
        Optional<Short>              emptyDestinationPortInfo = Optional.empty();
        Optional<TrafficClass>       emptyTrafficClass        = Optional.empty();
        Optional<LongPositionVector> emptySenderPosition      = Optional.empty();

        return new BtpPacket(
                emptySourcePort,
                destinationPort,
                emptyDestinationPortInfo,
                payload,
                destination,
                emptyTrafficClass,
                emptySenderPosition
                );
    }

    public Optional<Short>              sourcePort()           { return sourcePort;          }
    public short                        destinationPort()      { return destinationPort;     }
    public Optional<Short>              destinationPortInfo () { return destinationPortInfo; }
    public byte[]                       payload ()             { return payload.clone();     }  // Extra defensive here. Just in case.
    public Destination                  destination ()         { return destination;         }
    public Optional<TrafficClass>       trafficClass ()        { return trafficClass;        }
    public Optional<LongPositionVector> senderPosition ()      { return senderPosition;      }

    public byte[] asBytes() {
        if (gnPayload == null) {
            ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH + payload.length);
            gnPayload = putHeaderTo(buffer).put(payload).array();
        }
        return gnPayload;
    }

    public static final int HEADER_LENGTH = 4;

    private ByteBuffer putHeaderTo(ByteBuffer buffer) {
        return sourcePort.isPresent() ?
                buffer.putShort(sourcePort.get()).putShort(destinationPort) :
                buffer.putShort(destinationPort).putShort(destinationPortInfo.orElse((short)0x00));
    }

    public static BtpPacket fromGeonetData(GeonetData data) {
        ByteBuffer buffer = ByteBuffer.wrap(data.payload);
        Optional<Short>         sourcePort;
        short                   destinationPort;
        Optional<Short>         destinationPortInfo;
        switch (data.protocol) {
            case BTP_A:
            case BTP_B: {  // Carries the source and the destination port.
                boolean isA = data.protocol == UpperProtocolType.BTP_A;
                Optional<Short> emptyPort = Optional.empty();
                short port1 = buffer.getShort();
                short port2 = buffer.getShort();
                sourcePort          = isA ? Optional.of(port1)  :         emptyPort;
                destinationPort     = isA ?             port2   :             port1;
                destinationPortInfo = isA ?           emptyPort : Optional.of(port2);
                byte[] btpPayload = Arrays.copyOfRange(data.payload, HEADER_LENGTH,
                        data.payload.length);
                return new BtpPacket(
                        sourcePort,
                        destinationPort,
                        destinationPortInfo,
                        btpPayload,
                        data.destination,
                        data.trafficClass,
                        data.sender
                        );
            }
            case ANY:
            case IPv6:
            default:
                throw new IllegalArgumentException("Unsupported BTP protocol: " + data.protocol);
        }
    }
}

@@ -6134,82 +6134,14 @@
 urn 
-sourcePort.isPresent() ?%0A                buffer.putShort(sourcePort.get())
+buffer
 .put
@@ -6166,11 +6166,14 @@
 ort)
- :%0A
+%0A     
     
@@ -6184,22 +6184,16 @@
         
-buffer
 .putShor
@@ -6198,33 +6198,25 @@
 ort(
-destinationPort).putShort
+sourcePort.orElse
 (des
@@ -6252,16 +6252,17 @@
 t)0x00))
+)
 ;%0A    %7D%0A
@@ -6815,19 +6815,23 @@
     
-short p
+destinationP
 ort
-1
  = b
@@ -6957,17 +6957,17 @@
 .of(port
-1
+2
 )  :    
@@ -6986,93 +6986,8 @@
 rt;%0A
-                destinationPort     = isA ?             port2   :             port1;%0A
     

Fix BTP ports order in BTP_A packet