package MV3500Cohort2018JulySeptember.projects.FriscoFurr;

import java.net.*;
import java.io.*;
import java.util.*;
import edu.nps.moves.dis.*; // OpenDIS version 4
import java.io.IOException;
import edu.nps.moves.disutil.PduFactory;
import edu.nps.moves.disutil.DisTime;

public class FDCSendRecieve {

	/**
	 * Default multicast group address we send on.
	 */
	public static final String DEFAULT_MULTICAST_ADDRESS = "239.1.2.3";

	/**
	 * Default multicast port used, matches Wireshark DIS capture default
	 */
	public static final int DEFAULT_MULTICAST_PORT = 3000;

	private int port;
	InetAddress multicastAddress;

    /** socket value of shared interest */
	public static final int MULTICAST_PORT = 3000;
    /** socket value of shared interest */
	public static final String MULTICAST_GROUP = "239.1.2.3";
	private static final boolean USE_FAST_ESPDU = false;
	private long[] sentBuffer = new long[100];
	private static List sentBufferList;
	DisTime disTime = DisTime.getInstance();

	public FDCSendRecieve(int port, String multicast) {
		this.sentBufferList = new ArrayList<>();
		try {
			this.port = port;
			multicastAddress = InetAddress.getByName(multicast);
			if (!multicastAddress.isMulticastAddress()) {
				System.out.println("Not a multicast address: " + multicast);
			}
		} catch (UnknownHostException e) {
			System.out.println("Unable to open socket: " + e);
		}
	}

	/**
	 * This would be the sending Run method.  Takes in several PDUs and for each one has a switch statement ready for it. 
	 * @param pdupass
	 * @throws UnknownHostException unable to reach host address
	 * @throws IOException input-output error
	 */
	public void run(Pdu... pdupass) throws UnknownHostException, IOException {

		List<Pdu> generatedPdus = new ArrayList<>();
		Pdu aPdu = null;
		System.out.println("\nFDC Sender started...");
		// Send the PDUs we created
		InetAddress localMulticastAddress = InetAddress.getByName(DEFAULT_MULTICAST_ADDRESS);
		MulticastSocket socket = new MulticastSocket(DEFAULT_MULTICAST_PORT);
		socket.joinGroup(localMulticastAddress);

		for (Pdu i : pdupass) {
			Pdu pdu = i;
			if (sentBufferList.contains(pdu.getTimestamp())) {
				break;
			}

			short currentPduType = pdu.getPduType();
			System.out.println("in sender, recived PDU type: " + currentPduType);

			switch (currentPduType) // using enumeration values from edu.nps.moves.disenum.*
			{

				case 1: //ENTITY_STATE:
					aPdu = pdu;
					break;

				case 2: //FIRE
					aPdu = pdu;
					break;
//
				case 15: //AcknowledgePdu
					aPdu = pdu;
					break;

				case 17:
					aPdu = pdu;
					break;

				case 14:
					aPdu = pdu;
					break;

				default:
					System.out.print("PDU of type " + pdu + " not supported, created or sent ");
					System.out.println();
			}
			if (aPdu != null) {
				generatedPdus.add(aPdu);
				System.out.println("APDU container count " + generatedPdus.size());
			}

		}
		for (int idx = 0; idx < generatedPdus.size(); idx++) {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			DataOutputStream dos = new DataOutputStream(baos);
			byte[] buffer;

			aPdu = generatedPdus.get(idx);
			aPdu.marshal(dos);

			buffer = baos.toByteArray();
			DatagramPacket packet = new DatagramPacket(buffer, buffer.length, localMulticastAddress, DEFAULT_MULTICAST_PORT);
			socket.send(packet);
			sentBufferList.add(aPdu.getTimestamp());
			System.out.println("Sent PDU of type " + aPdu.getClass().getName()+ "\n");
		}
	}

	/**
	 * Main function takes no specific arguments, but is the recieving portion of the code.  Once it hears a specific type of PDU it creates another and sends it to the Run function 
	 * called sender created on line 130 (2nd statement in the main function)
	 * @param args command-line arguments
	 * @throws IOException input-output error
	 * @throws InterruptedException interruption
	 */
	public static void main(String[] args) throws IOException, InterruptedException {
		DisTime disTime = DisTime.getInstance();

		FDCSendRecieve sender = new FDCSendRecieve(DEFAULT_MULTICAST_PORT, DEFAULT_MULTICAST_ADDRESS); //initalize the sender

		EntityStatePdu FDCespdu = new EntityStatePdu();
		Marking marking = new Marking();
		marking.setCharactersString("FDC");
		FDCespdu.setMarking(marking);
		FDCespdu.setExerciseID((short) 1);
		EntityID FDCID = FDCespdu.getEntityID();
		FDCID.setSite(1);  
		FDCID.setApplication(1);
		FDCID.setEntity(1); 
		EntityType entityType = FDCespdu.getEntityType();
		//
		//Need to update the info below to match the unit type IAW SISO-REF-010-2015 V.21
		//  https://www.sisostds.org/DesktopModules/Bring2mind/DMX/Download.aspx?Command=Core_Download&EntryId=42916&PortalId=0&TabId=105 
		//
		entityType.setEntityKind((short) 1);      // Platform (vs lifeform, munition, sensor, etc.)  TGT=1, OBS = 1
		entityType.setCountry(225);              // USA TGT = 222 (Russia), OBS = 225
		entityType.setDomain((short) 1);          // Land (vs air, surface, subsurface, space) both TGT and OBS are 1
		entityType.setCategory((short) 3);        // FDC  TGT = 1, Tank  OBS=40 OP
		entityType.setSubcategory((short) 12);     // M1068 TGT = 2, T72 tank  NONE FOR OP
		entityType.setSpec((short) 1);            // M1068 w/ SICUP Tent  NONE FOR TGT OR OP
		Vector3Double location = new Vector3Double();
		location.setX(0.0);  //TGT = 150   OBS = 75
		location.setY(0.0);  //TGT = 150   OBS = 75
		location.setZ(10.0); //TGT = 20    OBS = 50
		FDCespdu.setEntityLocation(location);
		int timestamp = disTime.getDisAbsoluteTimestamp();
		FDCespdu.setTimestamp(timestamp);

		sender.run(FDCespdu);  //sends inital here I am and who I am

		//Set up other players to look for:
		EntityID OBSEntityID = new EntityID(); 
		OBSEntityID.setEntity(2);
		OBSEntityID.setApplication(1);
		OBSEntityID.setSite(1);

		EntityID TGTEntityID = new EntityID(); 
		TGTEntityID.setEntity(3);
		TGTEntityID.setApplication(1);
		TGTEntityID.setSite(1);

		/*  BELOW IS THE RECIEVE CODE  //
		//                             //
		//                             //
		 */                             //
		PduFactory factory;
		MulticastSocket socket = null;
		InetAddress address = null;
		DatagramPacket packet;
		short currentPduType = 0;  //will use the curentPduType as the check for sending other packets.

		try {
			System.out.println("FDC is alive and ready to recieve fire missions...\n\n");
			socket = new MulticastSocket(MULTICAST_PORT);
			address = InetAddress.getByName(MULTICAST_GROUP);
			socket.joinGroup(address);

			factory = new PduFactory();

			while (true) // Loop infinitely, receiving datagrams
			{
				byte buffer[] = new byte[1500]; // typical MTU size

				packet = new DatagramPacket(buffer, buffer.length); // reset

				socket.receive(packet);

				String marking2 = new String();
				Pdu pdu = factory.createPdu(packet.getData());
				currentPduType = pdu.getPduType();
				if (currentPduType == 14) //stop/freeze PDU
				{
					System.out.println("recieved Stop/Freeze packet...");
					return;
				}
//					pdu.setExerciseID((short)5);
				if (pdu != null) {
					if (sentBufferList.contains(pdu.getTimestamp())) {
						pdu = null;
					}
					if (pdu != null) {

						String currentPduTypeName = pdu.getClass().getName();

						if (currentPduType == 1) {
							EntityStatePdu pdu2 = (EntityStatePdu) pdu;
							marking2 = pdu2.getMarking().getCharactersString();
						}
						StringBuilder message = new StringBuilder();
						message.append("received DIS PDU: ");
						message.append("pduType ");
						if (currentPduType < 10) {
							message.append(" ");
						}
						message.append(currentPduType).append(" ").append(currentPduTypeName);
						message.append(" ");
						message.append(marking2);
						System.out.println(message.toString());
						//Reference for PDU Types: 
						// http://faculty.nps.edu/brutzman/vrtp/mil/navy/nps/disEnumerations/JdbeHtmlFiles/pdu/8.htm 
						//

						if (currentPduType == 1) //EntityState
						{
							EntityStatePdu entityPDU = (EntityStatePdu) pdu;
							EntityType PduEntityType = entityPDU.getEntityType();
							if (PduEntityType.getCountry() == 225) {
								Thread.sleep((long) 200);
								timestamp = disTime.getDisAbsoluteTimestamp();
								FDCespdu.setTimestamp(timestamp);
								System.out.println("Talking to the Observer, sending a radio check ");
								sender.run(FDCespdu);
							}
						}
						if (currentPduType == 16) //Action request
						{
							// Action response is sending a Null PDU, not sure why... so we will use the acknowledge pdu instead
							AcknowledgePdu ack = new AcknowledgePdu();
							ack.setExerciseID((short) 1);
							ack.setRequestID((long) 1);
							timestamp = disTime.getDisAbsoluteTimestamp();
							ack.setTimestamp(timestamp);

							//Creating a fire PDU
							FirePdu fire = new FirePdu();
							fire.setExerciseID((short) 1);
							fire.setFireMissionIndex(1000);
							fire.setRangeToTarget((float) Math.sqrt(Math.pow(150, 2) + Math.pow(150, 2)));  //would pass in target info, but here we know location of tgt is (150,150) and FDC (0,0)
							fire.setFiringEntityID(FDCID);
							fire.setTargetEntityID(TGTEntityID);
							timestamp = disTime.getDisAbsoluteTimestamp();
							fire.setTimestamp(timestamp);
							sender.run(ack, fire);
						}

						if (currentPduType == 22) //Comment PDU
						{
							AcknowledgePdu ack = new AcknowledgePdu();
							ack.setExerciseID((short) 1);
							ack.setRequestID((long) 1);
							timestamp = disTime.getDisAbsoluteTimestamp();
							ack.setTimestamp(timestamp);
							
							//and the freeze PDU being created to stop everything. 
							StopFreezePdu stop = new StopFreezePdu();
							stop.setExerciseID((short) 1);
							stop.setRequestID((long) 1);
							sender.run(ack, stop);
						}

					} else {
						System.out.println("received packet but pdu is null and originated from FDC. Still standing by...");
					}
				}
			}
		} catch (IOException e) {
			System.out.println("Problem with FDCSendRecieve, see exception trace:");
			System.out.println(e);
		} finally {
			System.out.println("FDC SendReceive complete. - OUT!");
		}

	}

}