package at.ac.tuwien.sbc.valesriegler.group;

import at.ac.tuwien.sbc.valesriegler.common.Util;
import at.ac.tuwien.sbc.valesriegler.group.gui.DeliveryOverviewModel;
import at.ac.tuwien.sbc.valesriegler.group.gui.GroupCreationDetailsRequest;
import at.ac.tuwien.sbc.valesriegler.group.gui.GroupOverviewModel;
import at.ac.tuwien.sbc.valesriegler.group.jms.GroupJMSNACMsgListener;
import at.ac.tuwien.sbc.valesriegler.group.jms.JMSGroupConnector;
import at.ac.tuwien.sbc.valesriegler.jms.nac.JMSNAC;
import at.ac.tuwien.sbc.valesriegler.jms.nac.actions.BenchmarkStart;
import at.ac.tuwien.sbc.valesriegler.jms.nac.actions.BenchmarkStop;
import at.ac.tuwien.sbc.valesriegler.types.DeliveryGroupData;
import at.ac.tuwien.sbc.valesriegler.types.GroupData;
import at.ac.tuwien.sbc.valesriegler.types.Order;
import at.ac.tuwien.sbc.valesriegler.types.PizzaType;
import at.ac.tuwien.sbc.valesriegler.xvsm.GroupAgentXVSM;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jms.JMSException;
import javax.swing.*;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * The Main class of the Group component.
 * <p/>
 * Start the communication and the group GUI:
 * 
 * @author Gregor Riegler <gregor DOT riegler AT gmail DOT com>
 * @author jan
 */
public class GroupAgent {
	private static final String USAGE = "This application needs exactly 1 parameter: <\"XVSM\"|\"JMS\">";

	private static final Logger log = LoggerFactory.getLogger(GroupAgent.class);
	private GroupOverviewModel groupModel;
	private DeliveryOverviewModel deliveryOverviewModel;
	private GroupAgentXVSM xvsm;
	static private JMSNAC jmsnac;
	static private GroupGUI groupGui;
	/**
	 * Contains the identifiers of the pizzerias. Whenever a new Pizzeria emerges or dies the group agent has to be
	 * notified about this somehow and the list has to be adapted as it is the model of the dropdown in the GUI for
	 * selecting for which pizzeria the customer groups are created
	 * In case of XVSM the identifier string is a port number
	 */
	private Set<String> pizzeriaIdentifiers = Collections.synchronizedSet(new HashSet<String>());

	public static GroupAgent groupAgent;

	/**
	 * Benchmarkstuff
	 */
	public static AtomicLong startTime = new AtomicLong();

	public static void main(String[] args) {
		if (args.length < 1) {
			throw new IllegalArgumentException(USAGE);
		}

		String mw = args[0];
		log.warn("Middleware: " + mw);
		log.warn("JVM: " + ManagementFactory.getRuntimeMXBean().getName());
		log.warn("THREAD: " + Thread.currentThread().getName());

		if ("JMS".equalsIgnoreCase(mw)) {
			Util.useJMS = true;
		} else if ("XVSM".equalsIgnoreCase(mw)) {
			Util.useJMS = false;
		} else {
			throw new IllegalArgumentException(USAGE);
		}

		groupAgent = new GroupAgent();
		if (!Util.runSimulation) {
			groupGui = new GroupGUI();
			SwingUtilities.invokeLater(groupGui);
		} else if (Util.useJMS) {
			JMSGroupConnector.getConnectors().put("tcp://localhost:61621?jms.prefetchPolicy.all=1",
					new JMSGroupConnector("tcp://localhost:61621?jms.prefetchPolicy.all=1"));
			JMSGroupConnector.getConnectors().put("tcp://localhost:61622?jms.prefetchPolicy.all=1",
					new JMSGroupConnector("tcp://localhost:61622?jms.prefetchPolicy.all=1"));
			try {
				List<PizzaType> pizzaTypes1 = Arrays.asList(PizzaType.CARDINALE, PizzaType.SALAMI, PizzaType.MARGHERITA,
						PizzaType.MARGHERITA);
				List<PizzaType> pizzaTypes2 = Arrays.asList(PizzaType.CARDINALE, PizzaType.SALAMI, PizzaType.MARGHERITA);
				List<PizzaType> pizzaTypes3 = Arrays.asList(PizzaType.SALAMI, PizzaType.MARGHERITA);

				final String pizzeria1 = "tcp://localhost:61621?jms.prefetchPolicy.all=1";
				final String pizzeria2 = "tcp://localhost:61622?jms.prefetchPolicy.all=1";
				List<DeliveryGroup> groups1 = GroupAgent.getInstance().createGroups(pizzaTypes1, pizzeria1, 4);
				List<DeliveryGroup> groups2 = GroupAgent.getInstance().createGroups(pizzaTypes2, pizzeria2, 3);
				List<DeliveryGroup> groups3 = GroupAgent.getInstance().createGroups(pizzaTypes3, pizzeria2, 2);

				final DeliveryOverviewModel deliveryModel = GroupAgent.getInstance().getDeliveryModel();
				deliveryModel.addItems(groups1);
				deliveryModel.addItems(groups2);
				deliveryModel.addItems(groups3);

				GroupAgent.getInstance().onDeliveryGroupsCreated(groups2);
				GroupAgent.getInstance().onDeliveryGroupsCreated(groups3);
				GroupAgent.getInstance().onDeliveryGroupsCreated(groups1);

				log.warn("Starting benchmark in 20 sec");
				Thread.sleep(20000);
                startTime.set(new Date().getTime());
				jmsnac.sendNACMsg(new BenchmarkStart());
				Thread.sleep(60000);
				jmsnac.sendNACMsg(new BenchmarkStop());
				log.warn("Benchmark Stop Signal sent.");
			} catch (InterruptedException e) {
				log.warn("EXCEPTION!", e);
			} catch (JMSException e) {
				log.warn("EXCEPTION!", e);
			}
		}
	}

	public GroupAgent() {
		deliveryOverviewModel = new DeliveryOverviewModel();
		groupModel = new GroupOverviewModel();
		if (Util.useJMS) {
			GroupJMSNACMsgListener tmp = new GroupJMSNACMsgListener();
			jmsnac = new JMSNAC(tmp);
		} else {

			xvsm = new GroupAgentXVSM();

			xvsm.listenForNewPizzerias();

			if (Util.runSimulation) {
				xvsm.listenToPizzeria("9875");
				xvsm.listenToPizzeria("9874");
				xvsm.runSimulation();
			}
		}
	}

	public List<DeliveryGroup> createGroups(List<PizzaType> pizzaTypes1, String pizzeria, int number) {
		List<DeliveryGroup> newDeliveryGroups = new ArrayList<DeliveryGroup>();
		final String fakeAddress = "address";
		final GroupCreationDetailsRequest gc = new GroupCreationDetailsRequest(number, 100, pizzaTypes1, fakeAddress,
				pizzeria);

		for (int i = 0; i < gc.numberOfGroups; i++) {
			DeliveryGroup group = new DeliveryGroup();
			final Order order = Util.createOrder(group, gc, true);
			group.getDeliveryGroupData().setOrder(order);
			group.getDeliveryGroupData().setPizzeriaId(gc.pizzeria);
			group.getDeliveryGroupData().setAddress(gc.address);
			newDeliveryGroups.add(group);
		}

		return newDeliveryGroups;
	}

	public static GroupAgent getInstance() {
		return groupAgent;
	}

	public Set<String> getPizzeriaIdentifiers() {
		return pizzeriaIdentifiers;
	}

	public GroupOverviewModel getGroupModel() {
		return groupModel;
	}

	public static GroupGUI getGroupGui() {
		return groupGui;
	}

	public static JMSNAC getJmsnac() {
		return jmsnac;
	}

	public DeliveryOverviewModel getDeliveryModel() {
		return deliveryOverviewModel;
	}

	public void onGroupsCreated(List<Group> newGroups) {
		if (!Util.useJMS) {
			List<GroupData> groupData = new ArrayList<GroupData>();

			for (Group group : newGroups) {
				groupData.add(group.getGroupData());
			}
			final String pizzeriaId = groupData.get(0).getPizzeriaId();

			int pizzeriaSpacePort = 0;
			try {
				pizzeriaSpacePort = Integer.parseInt(pizzeriaId);
			} catch (NumberFormatException e) {
				log.error("Pizzeria Identifier should be an integer in the XVSM version!");
			}
			xvsm.sendNewGroupsToSpace(groupData, pizzeriaSpacePort);

			log.info("New normal groups were sent to the pizzeria space of port {}", pizzeriaSpacePort);

			// start the space group in a new thread
			for (GroupData group : groupData) {
				new Thread(new SpaceGroup(group.getId(), pizzeriaSpacePort)).start();
			}
		}

	}

	public void onDeliveryGroupsCreated(List<DeliveryGroup> newDeliveryGroups) {
		if (!Util.useJMS) {
			List<DeliveryGroupData> groupData = new ArrayList<DeliveryGroupData>();
			for (DeliveryGroup group : newDeliveryGroups) {
				groupData.add(group.getDeliveryGroupData());
			}
			final String pizzeriaId = groupData.get(0).getPizzeriaId();

			int pizzeriaSpacePort = 0;
			try {
				pizzeriaSpacePort = Integer.parseInt(pizzeriaId);
			} catch (NumberFormatException e) {
				log.error("Pizzeria Identifier should be an integer in the XVSM version!");
			}

			xvsm.sendNewDeliveriesToSpace(groupData, pizzeriaSpacePort);

			log.info("New delivery groups were sent to the pizzeria space of port {}", pizzeriaSpacePort);

			if (!Util.runSimulation) {
				for (DeliveryGroupData deliveryGroupData : groupData) {
					new Thread(new SpaceDeliveryGroup(deliveryGroupData.getId(), pizzeriaSpacePort,
							deliveryGroupData.getAddress())).start();
				}
			}
		}
	}
}
