/*****************************************************************
JADE - Java Agent DEvelopment Framework is a framework to develop 
multi-agent systems in compliance with the FIPA specifications.
Copyright (C) 2000 CSELT S.p.A. 

GNU Lesser General Public License

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation, 
version 2.1 of the License. 

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA  02111-1307, USA.
 *****************************************************************/

package jade.core.messaging;

//#J2ME_EXCLUDE_FILE


import java.io.IOException;

import jade.core.AID;
import jade.core.Profile;


import jade.util.leap.List;
import jade.util.leap.LinkedList;
import jade.util.leap.Map;
import jade.util.leap.HashMap;
import jade.util.leap.Iterator;


/**
 * This class supports the ACL persistent delivery service, managing
 * actual ACL messages storage, scheduled message delivery and other
 * utility tasks related to the service.
 *
 * @author  Giovanni Rimassa - FRAMeTech s.r.l.
 * @author  Nicolas Lhuillier - Motorola
 *
 */
class PersistentDeliveryManager {

	public static synchronized PersistentDeliveryManager instance(Profile p, MessageManager.Channel ch) {
		if(theInstance == null) {
			theInstance = new PersistentDeliveryManager();
			theInstance.initialize(p, ch);
		}

		return theInstance;
	}

	// How often to check for expired deliveries
	private static final long DEFAULT_SENDFAILUREPERIOD = 60*1000; // One minute
	
	private static final String FILE_STORAGE_SHORTCUT = "file";

	// Default storage class
	private static final String DEFAULT_STORAGE = "jade.core.messaging.PersistentDeliveryManager$DummyStorage";
	private static final String FILE_STORAGE = "jade.core.messaging.FileMessageStorage";

	private static class DeliveryItem {

		public DeliveryItem(GenericMessage msg, AID id, MessageManager.Channel ch, String sid) {
			toDeliver = msg;
			receiver = id;
			channel = ch;
			storeName = sid;
		}

		public GenericMessage getMessage() {
			return toDeliver;
		}

		public AID getReceiver() {
			return receiver;
		}

		public MessageManager.Channel getChannel() {
			return channel;
		}

		public String getStoreName() {
			return storeName;
		}

		private GenericMessage toDeliver;
		private AID receiver;
		private MessageManager.Channel channel;
		private String storeName;


	} // End of DeliveryItem class



	private class ExpirationChecker implements Runnable {

		public ExpirationChecker(long t) {
			period = t;
			myThread = new Thread(this, "Persistent Delivery Service -- Expiration Checker Thread");
		}

		public void run() {
			while(active) {
				try {
					Thread.sleep(period);
					synchronized(pendingMessages) {
						// Try to send all stored messages...
						// If the receiver still not exists and the due date has elapsed
						// the sender will get back a FAILURE
						Object[] keys = pendingMessages.keySet().toArray();
						for(int i = 0; i < keys.length; i++) {
							flushMessages((AID) keys[i]);
						}
					}
				}
				catch (InterruptedException ie) {
					// Just do nothing
				}
			}
		}

		public void start() {
			active = true;
			myThread.start();
		}

		public void stop() {
			active = false;
			myThread.interrupt();
		}

		private boolean active = false;
		private long period;
		private Thread myThread;

	} // End of ExpirationChecker class


	public static class DummyStorage implements MessageStorage {

		public void init(Profile p) {
			// Do nothing
		}

		public String store(GenericMessage msg, AID receiver) throws IOException {
			// Do nothing
			return null;
		}

		public void delete(String storeName, AID receiver) throws IOException {
			// Do nothing
		}

		public void loadAll(LoadListener il) throws IOException {
			// Do nothing
		}

	} // End of DummyStorage class


	public void initialize(Profile p, MessageManager.Channel ch) {

		users = 0;
		myMessageManager = MessageManager.instance(p);
		deliveryChannel = ch;

		try {
			// Choose the persistent storage method
			String storageClass = p.getParameter(PersistentDeliveryService.PERSISTENT_DELIVERY_STORAGEMETHOD,DEFAULT_STORAGE);
			if (FILE_STORAGE_SHORTCUT.equalsIgnoreCase(storageClass)) {
				storageClass = FILE_STORAGE;
			}
			storage = (MessageStorage)Class.forName(storageClass).newInstance();
			storage.init(p);

			// Load all data persisted from previous sessions
			storage.loadAll(new MessageStorage.LoadListener() {
				public void loadStarted(String storeName) {
					System.out.println("--> Load BEGIN <--");
				}

				public void itemLoaded(String storeName, GenericMessage msg, AID receiver) {

					// Put the item into the pending messages table
					synchronized(pendingMessages) {
						List msgs = (List)pendingMessages.get(receiver);
						if(msgs == null) {
							msgs = new LinkedList();
							pendingMessages.put(receiver, msgs);
						}

						DeliveryItem item = new DeliveryItem(msg, receiver, deliveryChannel, storeName);
						msgs.add(item);
					}

					System.out.println("Message for <" + receiver.getLocalName() + ">");
				}

				public void loadEnded(String storeName) {
					System.out.println("--> Load END <--");
				}

			});
		}
		catch(IOException ioe) {
			ioe.printStackTrace();
		}
		catch(Exception e) {
			e.printStackTrace();
		}

		sendFailurePeriod = DEFAULT_SENDFAILUREPERIOD;
		String s = p.getParameter(PersistentDeliveryService.PERSISTENT_DELIVERY_SENDFAILUREPERIOD, null);
		if(s != null) {
			try {
				sendFailurePeriod = Long.parseLong(s);
			}
			catch(NumberFormatException nfe) {
				// Do nothing: the default value will be used...
			}
		}
	}

	public void storeMessage(String storeName, GenericMessage msg, AID receiver) throws IOException {

		// Store the ACL message and its receiver for later re-delivery...
		synchronized(pendingMessages) {
			List msgs = (List)pendingMessages.get(receiver);
			if(msgs == null) {
				msgs = new LinkedList();
				pendingMessages.put(receiver, msgs);
			}

			String tmpName = storage.store(msg, receiver);
			msgs.add(new DeliveryItem(msg, receiver, deliveryChannel, tmpName));
		}

	}

	public int flushMessages(AID receiver) {

		// Send messages for this agent, if any...
		int cnt = 0;
		List l = null;
		synchronized(pendingMessages) {
			l = (List)pendingMessages.remove(receiver);
		}

		if(l != null) {
			Iterator it = l.iterator();
			while(it.hasNext()) {
				DeliveryItem item = (DeliveryItem)it.next();
				retry(item);
				cnt++;
			}
		}
		return cnt;
	}

	public synchronized void start() {

		if(users == 0) {
			failureSender = new ExpirationChecker(sendFailurePeriod);
			failureSender.start();
		}

		users++;

	}

	public synchronized void stop() {
		users--;

		if(users == 0) {
			failureSender.stop();
		}
	}


	// A shared instance to have a single thread pool
	private static PersistentDeliveryManager theInstance; // FIXME: Maybe a table, indexed by a profile subset, would be better?


	private PersistentDeliveryManager() {
	}

	private void retry(DeliveryItem item) {
		// Remove the message from the storage
		try {
			storage.delete(item.getStoreName(), item.getReceiver());
		}
		catch(IOException ioe) {
			ioe.printStackTrace();
		}
		// Deliver it
		myMessageManager.deliver(item.getMessage(), item.getReceiver(), item.getChannel());
	}


	// The component managing asynchronous message delivery and retries
	private MessageManager myMessageManager;

	// The actual channel over which messages will be sent
	private MessageManager.Channel deliveryChannel;

	// How often pending messages due date will be checked (the
	// message will be sent out if expired)
	private long sendFailurePeriod;

	// How many containers are sharing this active component
	private long users;

	// The table of undelivered messages to send
	private Map pendingMessages = new HashMap();

	// The active object that periodically checks the due date of ACL
	// messages and sends them after it expired
	private ExpirationChecker failureSender;

	// The component performing the actual storage and retrieval from
	// a persistent support
	private MessageStorage storage;
}
