package manager;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.UnreadableException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Scanner;

import org.apache.commons.io.FilenameUtils;

import structures.ExpertData;
import structures.ExpertStatus;
import structures.Location;

import communication.ExpertMessage;
import communication.Message;
import communication.MessageType;
import communication.Order;

public class OrderManagerAgent extends Agent {

	private static final long serialVersionUID = 1L;
	private static final long REFRESH_TIME = 1 * 5 * 1000;
	ArrayList<Message> unhandledOrders;
	ArrayList<ExpertData> experts;

	@Override
	protected void setup() {
		super.setup();

		System.out.println("Setting up OrderManagerAgent");

		unhandledOrders = new ArrayList<Message>();
		experts = new ArrayList<>();

		unhandledOrders = new ArrayList<>();

		registerInYP();

		// odbieranie wiadomości:
		addBehaviour(new CyclicBehaviour() {

			@Override
			public void action() {
				ACLMessage msg = receive();
				if (msg != null) {
					try {
						Message msgMessage = (Message) msg.getContentObject();
						System.out.println("Message received!");

						// wiadomośc ze zgłoszeniem:
						if (msgMessage.getType() == MessageType.ORDER) {
							System.out.println("ORDER MESSAGE");
							String category = findCategory(msgMessage);
							ExpertData expert = findExpert(category, msgMessage);
							if (expert != null)
								sendMessage(msgMessage, expert.getAid());
							else
								unhandledOrders.add(msgMessage);
							return;
						}

						// wiadomośc z lokacją serwisanata:
						if (msgMessage.getType() == MessageType.LOCATION) {
							System.out.println("LOCATION MESSAGE");
							updateExpert(msgMessage);
							return;
						}

						// wiadomośc rejestracyjna eksperta:
						if (msgMessage.getType() == MessageType.REGISTRATION) {
							ExpertMessage em = (ExpertMessage) msgMessage;
							System.out.println("REGISTRATION MESSAGE");
							DataOperations.printExpert(em.getExpertData());

							updateExpert(msgMessage);
							addExpert(em.getExpertData());

							return;
						}

						// wiadomośc rejestracyjna eksperta:
						if (msgMessage.getType() == MessageType.EXPERT_MESSAGE) {
							ExpertMessage em = (ExpertMessage) msgMessage;
							System.out.println("EXPERT MESSAGE");
							DataOperations.printExpert(em.getExpertData());
							updateExpert(msgMessage);

							return;
						}

						// wiadomośc derejestracyjna:
						if (msgMessage.getType() == MessageType.DEREGISTRATION) {
							System.out.println("DEREGISTRATION MESSAGE");
							ExpertMessage em = (ExpertMessage) msgMessage;
							DataOperations.printExpert(em.getExpertData());

							removeExpert(em.getExpertData());
							return;
						}

					} catch (UnreadableException e) {
						e.printStackTrace();
					} catch (FileNotFoundException e) {
						System.out.println("Reading file error.");
						e.printStackTrace();
					}

				} else
					block();
			}

		});

		// obsługa nieobsłużonych zleceń - ponowne szukanie specjalistow:
		addBehaviour(new TickerBehaviour(this, REFRESH_TIME) {

			@Override
			protected void onTick() {
				
				DateFormat df = new SimpleDateFormat("dd/MM/yy HH:mm:ss");
				Date dateobj = new Date();
				
				System.out.println("---------"+df.format(dateobj)+"----------");
				System.out.println("EXPERT LIST:");
				for (ExpertData expert : experts) {
					DataOperations.printExpert(expert);
				}
				System.out.println("-------------------------------");

				if (unhandledOrders.isEmpty()) {
					System.out.println("ZERO UNHANDLED ORDERS");
					return;
				}

				System.out
						.println("SEARCHING EXPERTS FOR UNHANDLED ORDERS - "
								+ unhandledOrders.size());

				ArrayList<Message> toDelete = new ArrayList<>();
				for (Message order : unhandledOrders) {
					
					String category = null;
					try {
						category = findCategory(order);
					} catch (FileNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("#category - "+category);
					ExpertData expert = findExpert(category, order);
					if (expert != null) {
						sendMessage(order, expert.getAid());
						toDelete.add(order);
					}
				}

				for (Message message : toDelete) {
					unhandledOrders.remove(message);
				}

				System.out.println("\n");
			}
		});
	}

	protected void removeExpert(ExpertData expert) {
		for (ExpertData ex : experts) {
			if (ex.getName().equals(expert.getName())) {
				experts.remove(ex);
				return;
			}
		}
	}
	
	@Override
	protected void takeDown() {
		deregisterFromYP();
		super.takeDown();
	}

	protected void addExpert(ExpertData expert) {
		for (ExpertData ex : experts) {
			if (ex.getName().equals(expert.getName()))
				return;
		}
		experts.add(expert);
	}

	protected ExpertData findExpert(String category, Message msgMessage) {
		// szuka dostępnych
		ArrayList<ExpertData> expertsFound = new ArrayList<>();

		for (ExpertData expert : experts) {
			if (expert.getStatus() == ExpertStatus.AVAILABLE
					&& (expert.getCategory().equals(category) || category == null ))
				expertsFound.add(expert);
		}

		// jezeli nie ma dostępnych to szuka na przerwie
		if (experts.isEmpty())
			for (ExpertData expert : experts) {
				if (expert.getStatus() == ExpertStatus.BREAK
						&& (expert.getCategory().equals(category) || category == null ))
					expertsFound.add(expert);
			}

		// jezeli nie ma dostępnych to szuka zajętych
		if (experts.isEmpty())
			for (ExpertData expert : experts) {
				if (expert.getStatus() == ExpertStatus.IN_PROGRESS
						&& (expert.getCategory().equals(category) || category == null ))
					expertsFound.add(expert);
			}

		// jezeli nie ma nikogo zwraca null i dodaje wiadomośc do kolejki do
		// pozniejszego sprawdzenia:
		if (experts.isEmpty()) {
			System.out.println("Zero experts on the list.");
			return null;
		}

		// szuka najblizszego - liczy odległości euklidesowe
		double winningDistance = Double.MAX_VALUE;
		ExpertData winningExpert = null;

		for (ExpertData expert : expertsFound) {
			double distance = MathOperations.calcDistance(msgMessage
					.getLocation().getLatitude(), msgMessage.getLocation()
					.getLongitude(), expert.getLocation().getLatitude(), expert
					.getLocation().getLongitude());
			if (distance < winningDistance) {
				winningDistance = distance;
				winningExpert = expert;
			}
		}

		return winningExpert;

	}

	protected void updateExpert(Message msgMessage) {
		ExpertMessage em = (ExpertMessage) msgMessage;
		ExpertData data = em.getExpertData();

		int j = -1;

		for (int i = 0; i < experts.size(); i++) {

			if (experts.get(i).getAid().equals(data.getAid())) {
				j = i;
				System.out.println("location updated");
			}
		}

		if (j != -1) {
			experts.remove(j);
		}
		experts.add(data);

		DataOperations.printExpert(data);
	}

	protected String findCategory(Message msgMessage)
			throws FileNotFoundException {

		Order o = (Order) msgMessage;

		File folder = new File("categories");
		File[] listOfFiles = folder.listFiles();

		String actualCategory, winningCategory = null;
		int actualPoints = 0, winningPoints = 0;

		String text = o.getDescription();
		if (text == null)
			return "none";
		String[] words = text.split("\\s+");
		List<String> wordsList = Arrays.asList(words);

		for (File file : listOfFiles) {

			Scanner scanner = new Scanner(file);

			actualCategory = FilenameUtils.removeExtension(file.getName());
			actualPoints = 0;

			while (scanner.hasNext()) {
				int wage = scanner.nextInt();
				String word = scanner.next();

				for (String string : wordsList) {
					if (string.equals(word)) {

						switch (wage) {
						case 1:
							actualPoints += 1;
							break;
						case 2:
							actualPoints += 2;
							break;
						case 3:
							return actualCategory;
						}

						continue;

					}
				}
			}

			if (actualPoints > winningPoints) {
				winningCategory = actualCategory;
				winningPoints = actualPoints;
			}

			scanner.close();
		}

		System.out.println("Category - " + winningCategory);

		return winningCategory;

	}

	private void sendMessage(Message message, AID user) {
		ACLMessage msg = new ACLMessage(ACLMessage.INFORM);

		System.out.println(user.getLocalName());

		msg.addReceiver(user);
		try {
			msg.setContentObject(message);
		} catch (IOException e) {
			e.printStackTrace();
		}
		send(msg);
	}

	private void registerInYP() {
		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(getAID());
		ServiceDescription sd = new ServiceDescription();
		sd.setName("Manager");
		sd.setType("Manager");

		dfd.addServices(sd);

		try {
			DFService.register(this, dfd);
		} catch (FIPAException e) {
			e.printStackTrace();
		}
	}
	
	private void deregisterFromYP() {
		try {
			DFService.deregister(this);
		} catch (FIPAException e) {
			e.printStackTrace();
		}
		
	}


}
