/*
 * ICESOFT COMMERCIAL SOURCE CODE LICENSE V 1.1
 *
 * The contents of this file are subject to the ICEsoft Commercial Source
 * Code License Agreement V1.1 (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.icesoft.com/license/commercial-source-v1.1.html
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License.
 *
 * Copyright 2009-2015 ICEsoft Technologies Canada, Corp. All Rights Reserved.
 */
package com.icesoft.push;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContext;

import org.icepush.AbstractPushGroupManager;
import org.icepush.BlockingConnectionServer;
import org.icepush.ExtendedPushGroupManager;
import org.icepush.LocalPushGroupManager;
import org.icepush.NotificationBroadcaster;
import org.icepush.NotifyBackURI;
import org.icepush.PushConfiguration;
import org.icepush.PushGroupManager;
import org.icepush.PushID;
import com.icesoft.push.messaging.CoreMessageService;

@ExtendedPushGroupManager
public class DynamicPushGroupManager
extends AbstractPushGroupManager
implements CoreMessageService.EPSListener, PushGroupManager {
    private static final Logger LOGGER = Logger.getLogger(DynamicPushGroupManager.class.getName());

    private static enum Manager {
        LOCAL,
        REMOTE
    };

    private final ReentrantLock lock = new ReentrantLock();
    private final Set<NotificationBroadcaster.Receiver> receiverSet = new HashSet<NotificationBroadcaster.Receiver>();

    private final ServletContext servletContext;

    private Manager requestedManager;
    private Manager currentManager;

    private PushGroupManager localPushGroupManager;
    private PushGroupManager remotePushGroupManager;
    private PushGroupManager currentPushGroupManager;

    public DynamicPushGroupManager(final ServletContext servletContext)
    throws Exception {
        this.servletContext = servletContext;
        requestedManager = Manager.LOCAL;
        remotePushGroupManager = new RemotePushGroupManager(this.servletContext, this);
    }

    public void addBlockingConnectionServer(final BlockingConnectionServer server) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.addBlockingConnectionServer(server);
        } finally {
            lock.unlock();
        }
    }

    public void addMember(final String groupName, final String pushID) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.addMember(groupName, pushID);
        } finally {
            lock.unlock();
        }
    }

    public void addNotificationReceiver(final NotificationBroadcaster.Receiver receiver) {
        lock.lock();
        try {
            checkManager();
            receiverSet.add(receiver);
            currentPushGroupManager.addNotificationReceiver(receiver);
        } finally {
            lock.unlock();
        }
    }

    public void backOff(final String browserID, final long delay) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.backOff(browserID, delay);
        } finally {
            lock.unlock();
        }
    }

    public void cancelConfirmationTimeout(final Set<String> pushIDSet) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.cancelConfirmationTimeout(pushIDSet);
        } finally {
            lock.unlock();
        }
    }

    public void cancelExpiryTimeout(final Set<String> pushIDSet) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.cancelExpiryTimeout(pushIDSet);
        } finally {
            lock.unlock();
        }
    }

    public void clearPendingNotification(final String pushID) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.clearPendingNotification(pushID);
        } finally {
            lock.unlock();
        }
    }

    public void clearPendingNotifications(final Set<String> pushIDSet) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.clearPendingNotifications(pushIDSet);
        } finally {
            lock.unlock();
        }
    }

    public void deleteNotificationReceiver(final NotificationBroadcaster.Receiver receiver) {
        lock.lock();
        try {
            checkManager();
            receiverSet.remove(receiver);
            currentPushGroupManager.deleteNotificationReceiver(receiver);
        } finally {
            lock.unlock();
        }
    }

    public void detected(final CoreMessageService.EPSEvent event) {
        lock.lock();
        try {
            requestedManager = Manager.REMOTE;
        } finally {
            lock.unlock();
        }
    }

    public Map<String, String[]> getGroupMap() {
        lock.lock();
        try {
            checkManager();
            return currentPushGroupManager.getGroupMap();
        } finally {
            lock.unlock();
        }
    }

    public Set<String> getPendingNotificationSet() {
        lock.lock();
        try {
            checkManager();
            return currentPushGroupManager.getPendingNotificationSet();
        } finally {
            lock.unlock();
        }
    }

    public PushID getPushID(final String pushIDString) {
        lock.lock();
        try {
            checkManager();
            return currentPushGroupManager.getPushID(pushIDString);
        } finally {
            lock.unlock();
        }
    }

    public void park(final String pushId, final NotifyBackURI notifyBackURI) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.park(pushId, notifyBackURI);
        } finally {
            lock.unlock();
        }
    }

    public void pruneParkedIDs(final NotifyBackURI notifyBackURI, final Set<String> listenedPushIDSet)  {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.pruneParkedIDs(notifyBackURI, listenedPushIDSet);
        } finally {
            lock.unlock();
        }
    }

    public void push(final String groupName) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.push(groupName);
        } finally {
            lock.unlock();
        }
    }

    public void push(final String groupName, final PushConfiguration config) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.push(groupName, config);
        } finally {
            lock.unlock();
        }
    }

    public void recordListen(final Set<String> pushIDSet, final long sequenceNumber) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.recordListen(pushIDSet, sequenceNumber);
        } finally {
            lock.unlock();
        }
    }

    public void removeBlockingConnectionServer(final BlockingConnectionServer server) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.removeBlockingConnectionServer(server);
        } finally {
            lock.unlock();
        }
    }

    public void removeMember(final String groupName, final String pushId) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.removeMember(groupName, pushId);
        } finally {
            lock.unlock();
        }
    }

    public boolean setNotifyBackURI(
        final Set<String> pushIDSet, final NotifyBackURI notifyBackURI, final boolean broadcast) {

        lock.lock();
        try {
            checkManager();
            return currentPushGroupManager.setNotifyBackURI(pushIDSet, notifyBackURI, broadcast);
        } finally {
            lock.unlock();
        }
    }

    public NotifyBackURI getNotifyBackURI(String pushID) {
        lock.lock();
        try {
            checkManager();
            return currentPushGroupManager.getNotifyBackURI(pushID);
        } finally {
            lock.unlock();
        }
    }

    public void shutdown() {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.shutdown();
        } finally {
            lock.unlock();
        }
    }

    public void startConfirmationTimeout(final Set<String> pushIDSet) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.startConfirmationTimeout(pushIDSet);
        } finally {
            lock.unlock();
        }
    }

    public void startExpiryTimeout(final Set<String> pushIDSet, final NotifyBackURI notifyBackURI) {
        lock.lock();
        try {
            checkManager();
            currentPushGroupManager.startExpiryTimeout(pushIDSet, notifyBackURI);
        } finally {
            lock.unlock();
        }
    }

    private void checkManager() {
        lock.lock();
        try {
            if (currentManager != requestedManager) {
                switch (requestedManager) {
                    case LOCAL :
                        for (NotificationBroadcaster.Receiver receiver : receiverSet) {
                            localPushGroupManager.addNotificationReceiver(receiver);
                        }
                        if (localPushGroupManager == null) {
                            localPushGroupManager = new LocalPushGroupManager(servletContext);
                        }
                        currentPushGroupManager = localPushGroupManager;
                        currentManager = Manager.LOCAL;
                        break;
                    case REMOTE :
                        for (NotificationBroadcaster.Receiver receiver : receiverSet) {
                            localPushGroupManager.deleteNotificationReceiver(receiver);
                        }
                        currentPushGroupManager = remotePushGroupManager;
                        currentManager = Manager.REMOTE;
                        break;
                }
            }
        } finally {
            lock.unlock();
        }
    }
}
