/*
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jivesoftware.openfire.pep;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.locks.Lock;

import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.pubsub.CollectionNode;
import org.jivesoftware.openfire.pubsub.Node;
import org.jivesoftware.openfire.pubsub.PubSubEngine;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;

/**
 * Manages the creation, persistence and removal of {@link PEPService}
 * instances.
 * 
 * @author Guus der Kinderen, guus.der.kinderen@gmail.com
 * 
 */
public class PEPServiceManager {

	public static final Logger Log = LoggerFactory
			.getLogger(PEPServiceManager.class);

	private final static String GET_PEP_SERVICE = "SELECT DISTINCT serviceID FROM ofPubsubNode WHERE serviceID=?";

	/**
	 * Cache of PEP services. Table, Key: bare JID (String); Value: PEPService
	 */
	private final Cache<String, PEPService> pepServices = CacheFactory
			.createLocalCache("PEPServiceManager");

	private PubSubEngine pubSubEngine = null;

	/**
	 * Retrieves a PEP service -- attempting first from memory, then from the
	 * database.
	 * 
	 * @param jid
	 *            the bare JID of the user that owns the PEP service.
	 * @return the requested PEP service if found or null if not found.
	 */
	public PEPService getPEPService(String jid) {
		PEPService pepService = null;

		final Lock lock = CacheFactory.getLock(jid, pepServices);
		try {
			lock.lock();
			if (pepServices.containsKey(jid)) {
				// lookup in cache
				pepService = pepServices.get(jid);
			} else {
				// lookup in database.
				pepService = loadPEPServiceFromDB(jid);
				
				// always add to the cache, even if it doesn't exist. This will
				// prevent future database lookups.
				pepServices.put(jid, pepService);
			}
		} finally {
			lock.unlock();
		}

		return pepService;
	}

	public PEPService create(JID owner) {
		// Return an error if the packet is from an anonymous, unregistered user
		// or remote user
		if (!XMPPServer.getInstance().isLocal(owner)
				|| !UserManager.getInstance().isRegisteredUser(owner.getNode())) {
			throw new IllegalArgumentException(
					"Request must be initiated by a local, registered user, but is not: "
							+ owner);
		}

		PEPService pepService = null;
		final String bareJID = owner.toBareJID();
		final Lock lock = CacheFactory.getLock(owner, pepServices);
		try {
			lock.lock();

			pepService = pepServices.get(bareJID);
			if (pepService == null) {
				pepService = new PEPService(XMPPServer.getInstance(), bareJID);
				pepServices.put(bareJID, pepService);

				if (Log.isDebugEnabled()) {
					Log.debug("PEPService created for : " + bareJID);
				}
			}
		} finally {
			lock.unlock();
		}

		return pepService;
	}

	/**
	 * Loads a PEP service from the database, if it exists.
	 * 
	 * @param jid
	 *            the JID of the owner of the PEP service.
	 * @return the loaded PEP service, or null if not found.
	 */
	private PEPService loadPEPServiceFromDB(String jid) {
		PEPService pepService = null;

		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			con = DbConnectionManager.getConnection();
			// Get all PEP services
			pstmt = con.prepareStatement(GET_PEP_SERVICE);
			pstmt.setString(1, jid);
			rs = pstmt.executeQuery();
			// Restore old PEPServices
			while (rs.next()) {
				String serviceID = rs.getString(1);

				// Create a new PEPService
				pepService = new PEPService(XMPPServer.getInstance(), serviceID);
				pepServices.put(serviceID, pepService);
				pubSubEngine.start(pepService);

				if (Log.isDebugEnabled()) {
					Log.debug("PEP: Restored service for " + serviceID
							+ " from the database.");
				}
			}
		} catch (SQLException sqle) {
			Log.error(sqle.getMessage(), sqle);
		} finally {
			DbConnectionManager.closeConnection(rs, pstmt, con);
		}

		return pepService;
	}

	/**
	 * Deletes the {@link PEPService} belonging to the specified owner.
	 * 
	 * @param owner
	 *            The JID of the owner of the service to be deleted.
	 */
	public void remove(JID owner) {
		PEPService service = null;

		final Lock lock = CacheFactory.getLock(owner, pepServices);
		try {
			lock.lock();
			service = pepServices.remove(owner.toBareJID());
		} finally {
			lock.unlock();
		}

		if (service == null) {
			return;
		}

		// Delete the user's PEP nodes from memory and the database.
		CollectionNode rootNode = service.getRootCollectionNode();
		for (final Node node : service.getNodes()) {
			if (rootNode.isChildNode(node)) {
				node.delete();
			}
		}
		rootNode.delete();
	}

	public void start(PEPService pepService) {
		pubSubEngine.start(pepService);
	}

	public void start() {
		pubSubEngine = new PubSubEngine(XMPPServer.getInstance()
				.getPacketRouter());
	}

	public void stop() {

		for (PEPService service : pepServices.values()) {
			pubSubEngine.shutdown(service);
		}

		pubSubEngine = null;
	}

	public void process(PEPService service, IQ iq) {
		pubSubEngine.process(service, iq);
	}
	
	public boolean hasCachedService(JID owner) {
		return pepServices.get(owner.toBareJID()) != null;
	}
	
	// mimics Shutdown, without killing the timer.
	public void unload(PEPService service) {	
		pubSubEngine.shutdown(service);
	}
}
