/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License).  You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Copyright (c) Ericsson AB, 2004-2007. All rights reserved.
 */
package com.ericsson.ssa.sip;

import com.ericsson.ssa.config.ConvergedContext;
import com.ericsson.ssa.config.ConvergedContextImpl;
import com.ericsson.ssa.dd.SipApplicationListeners;
import com.ericsson.ssa.sip.PathNode.Type;
import com.ericsson.ssa.sip.SipApplicationSessionImpl;
import com.ericsson.ssa.sip.SipFactoryImpl;
import com.ericsson.ssa.sip.SipSessionsUtilImpl;
import com.ericsson.ssa.sip.timer.ServletTimerImpl;

import com.sun.enterprise.ee.web.sessmgmt.EEPersistenceTypeResolver;
import com.sun.enterprise.web.ServerConfigLookup;

import org.apache.catalina.Container;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.util.LifecycleSupport;

import java.io.Serializable;

import java.util.*;
import java.util.concurrent.*;

import javax.servlet.sip.Address;
import javax.servlet.sip.SipApplicationSessionEvent;
import javax.servlet.sip.SipApplicationSessionListener;
import javax.servlet.sip.TimerListener;


/**
 * Base implementation of the SipSessionManager interface.
 *
 * @author jluehe
 */
public class SipSessionManagerBase implements SipSessionManager, Lifecycle {
    // Instance-wide mapping of converged context names to SipSessionManagers
    private static final ConcurrentHashMap<String, SipSessionManager> sipSessionManagers =
        new ConcurrentHashMap<String, SipSessionManager>();

    // The name of the instance on which this SipSessionManager resides
    private String instanceName;
    protected SipFactoryImpl sipFactory = null;

    // Has this SipSessionManager been started?
    protected boolean started;

    // Has this SipSessionManager been initialized?
    protected boolean initialized;
    protected int sessionTimeout;

    // The associated converged context    
    protected ConvergedContextImpl convergedContext;

    // The lifecycle event support.
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);

    /*
     * Active cache of SipApplicationSessions.
     *
     * 2-arg (<initialCapacity, loadFactor>) constructor not available on
     * Java 5, specify DEFAULT_CONCURRENCY_LEVEL as 3rd argument
     */
    protected Map<String, SipApplicationSessionImpl> applicationSessions = new ConcurrentHashMap<String, SipApplicationSessionImpl>(100,
            0.75f, 16);

    // Active cache of SipSessions
    protected Map<String, SipSessionDialogImpl> sipSessions = new ConcurrentHashMap<String, SipSessionDialogImpl>();

    // Active cache of ServletTimers
    protected Map<String, ServletTimerImpl> servletTimers = new ConcurrentHashMap<String, ServletTimerImpl>();
    protected boolean isRepairDuringFailure = true;

    // The application id
    protected String applicationId = null;

    // The cluster id
    protected String clusterId = null;

    /**
     * Constructor.
     */
    public SipSessionManagerBase() {
        ServerConfigLookup lookup = new ServerConfigLookup();
        instanceName = lookup.getServerName();
    }

    /**
     * Registers the given SipSessionManager under the name of its
     * converged context.
     *
     * @param manager The SipSessionManager to register
     */
    public static void register(SipSessionManager manager) {
        sipSessionManagers.put(manager.getApplicationId(), manager);
    }

    /**
     * Unregisters the given SipSessionManager.
     *
     * @param manager The SipSessionManager to unregister
     */
    public static void unregister(SipSessionManager manager) {
        sipSessionManagers.remove(manager.getApplicationId());
    }

    /**
     * Gets the SipSessionManager that matches the given appId.
     *
     * @param appId The appId to match
     *
     * @return The mapped SipSessionManager, or null if no mapping for
     * the given appId exists
     */
    public static SipSessionManager get(String appId) {
        return sipSessionManagers.get(appId);
    }

    /**
     * Associates the given ConvergedContext with this SipSessionManager.
     *
     * @param context The ConvergedContext with which to associate this
     * SipSessionManager
     */
    public void setContext(ConvergedContext context) {
        convergedContext = (ConvergedContextImpl) context;
    }

    /**
     * Gets the ConvergedContext with which this SipSessionManager has been
     * associated.
     *
     * @return The ConvergedContext with which this SipSessionManager has
     * been associated.
     */
    public ConvergedContext getContext() {
        return convergedContext;
    }

    /**
     * Creates a new SipApplicationSession, and adds it to this session
     * manager's active cache.
     *
     * @param sipApplicationListeners The SipApplicationListeners whose
     * SipApplicationSessionListeners need to be notified of the session
     * creation
     *
     * @return The new SipApplicationSession
     */
    public SipApplicationSessionImpl createSipApplicationSession(
        SipApplicationListeners sipApplicationListeners) {
        return createSipApplicationSession(null, sipApplicationListeners);
    }

    /**
     * Creates a new SipApplicationSession with the given id, and adds it to
     * this session manager's active cache.
     *
     * @param id The id to assign to the new SipApplicationSession
     * @param sipApplicationListeners The SipApplicationListeners whose
     * SipApplicationSessionListeners need to be notified of the session
     * creation
     *
     * @return The new SipApplicationSession
     */
    public SipApplicationSessionImpl createSipApplicationSession(String id,
        SipApplicationListeners sipApplicationListeners) {
        SipApplicationSessionImpl sess = null;

        if (id != null) {
            sess = createNewSipApplicationSession(id);
        } else {
            sess = createNewSipApplicationSession(UUID.randomUUID().toString());
        }

        if (convergedContext != null) {
            sess.setHttpSessionManager(convergedContext.getManager());
        }

        applicationSessions.put(sess.getId(), sess);
        initSipApplicationSession(sess, sipApplicationListeners);

        return sess;
    }

    /**
     * Gets the SipApplicationSession with the given id.
     *
     * @return The SipApplicationSession with the given id, or null if not
     * found
     */
    public SipApplicationSessionImpl findSipApplicationSession(String id) {
        return applicationSessions.get(id);
    }

    /**
     * Removes the given SipApplicationSession from this session manager's
     * active cache.
     *
     * @param sas The SipApplicationSession to be removed
     */
    public void removeSipApplicationSession(SipApplicationSessionImpl sas) {
        applicationSessions.remove(sas.getId());
        SipSessionsUtilImpl ssu = convergedContext.getSipSessionsUtil();
        if (ssu != null) {
            ssu.removeSessionsMapping(sas);
        }
    }

    /**
     * Adds the given SipApplicationSession to this session manager's
     * active cache.
     *
     * @param sas The SipApplicationSession to add
     */
    public void addSipApplicationSession(SipApplicationSessionImpl sas) {
        applicationSessions.put(sas.getId(), sas);
    }

    /**
     * Creates a new SipSession and adds it to the given SipApplicationSession
     * and this session manager's active cache.
     *
     * @param set
     * @param to
     * @param appSession
     * @param handler
     *
     * @return The new SipSession
     */
    public SipSessionDialogImpl createSipSession(DialogSet set, Address to,
        SipApplicationSessionImpl appSession, String handler) {
        return createSipSession(set, to, appSession, handler, null);
    }

    /**
     * Creates a new SipSession and adds it to the given SipApplicationSession
     * and this session manager's active cache.
     *
     * @param set
     * @param to
     * @param appSession
     * @param handler
     * @type type
     *
     * @return The new SipSession
     */
    public SipSessionDialogImpl createSipSession(DialogSet set, Address to,
        SipApplicationSessionImpl appSession, String handler, Type type) {
        SipSessionDialogImpl sess = null;

        if (type != null) {
            sess = createNewSipSession(set, to, appSession, handler, type);
        } else {
            sess = createNewSipSession(set, to, appSession, handler,
                    Type.Undefined);
        }

        sipSessions.put(sess.getId(), sess);

        return sess;
    }

    /**
     * Gets the SipSession with the given id.
     *
     * @return The SipSession with the given id, or null if not found
     */
    public SipSessionDialogImpl findSipSession(String id) {
        return sipSessions.get(id);
    }

    /**
     * Removes the given SipSession from this session manager's active cache.
     *
     * The SipSession is also removed from its SipApplicationSession parent,
     * if it had been attached to any.
     *
     * @param sipSession The SipSession to remove
     */
    public void removeSipSession(SipSessionDialogImpl sipSession) {
        SipApplicationSessionImpl sas = sipSession.getApplicationSessionImpl();

        if (sas != null) {
            sas.removeSession(sipSession);
        }

        sipSessions.remove(sipSession.getId());
    }

    /**
     * Adds the given SipSession to this session manager's active cache.
     *
     * @param sipSession The SipSession to add
     */
    public void addSipSession(SipSessionDialogImpl sipSession) {
        sipSessions.put(sipSession.getId(), sipSession);
    }

    /**
     * Creates a new ServletTimer.
     *
     * @return The new ServletTimer
     */
    public ServletTimerImpl createServletTimer(SipApplicationSessionImpl sas,
        Serializable info, long delay, TimerListener listener) {
        ServletTimerImpl timer = createNewServletTimer(sas, info, delay,
                                                       listener);
        servletTimers.put(timer.getId(), timer);

        return timer;
    }

    /**
     * Creates a new ServletTimer.
     *
     * @return The new ServletTimer
     */
    public ServletTimerImpl createServletTimer(SipApplicationSessionImpl sas,
        Serializable info, long delay, boolean fixedDelay, long period,
        TimerListener listener) {
        ServletTimerImpl timer = createNewServletTimer(sas, info, delay,
                fixedDelay, period, listener);
        servletTimers.put(timer.getId(), timer);

        return timer;
    }

    /**
     * Gets the ServletTimer with the given id.
     *
     * @return The ServletTimer with the given id, or null if not found
     */
    public ServletTimerImpl findServletTimer(String id) {
        return servletTimers.get(id);
    }

    /**
     * Removes the given ServletTimer.
     *
     * @param timer The ServletTimer to be removed
     */
    public void removeServletTimer(ServletTimerImpl timer) {
        servletTimers.remove(timer.getId());
    }

    /**
     * Adds the given ServletTimer to this session manager's
     * active cache.
     *
     * @param timer The ServletTimer to add
     */
    public void addServletTimer(ServletTimerImpl timer) {
        servletTimers.put(timer.getId(), timer);
    }

    /**
     * Sets the session timeout.
     *
     * @param timeout The session timeout
     */
    public void setSessionTimeout(int timeout) {
        sessionTimeout = timeout;
    }

    /**
     * Gets the session timeout.
     *
     * @return The session timeout
     */
    public int getSessionTimeout() {
        return sessionTimeout;
    }

    /**
     * Sets the repairDuringFailure property of this session manager.
     *
     * @param isRepairDuringFailure The value of the repairDuringFailure
     * property
     */
    public void setRepairDuringFailure(boolean isRepairDuringFailure) {
        this.isRepairDuringFailure = isRepairDuringFailure;
    }

    /**
     * @return True if this session manager should participate in a repair
     * during failure, and false otherwise
     */
    public boolean isRepairDuringFailure() {
        return isRepairDuringFailure;
    }

    /**
     * Starts this SipSessionManager.
     *
     * This method must not be called outside the lifecycle of the
     * associated converged context.
     */
    public void start() throws LifecycleException {
        if (!initialized) {
            init();
        }

        if (started) {
            return;
        }

        started = true;

        sipFactory = SipFactoryImpl.getInstance();
        SipSessionManagerBase.register(this);
    }

    /**
     * Stops this SipSessionManager.
     *
     * This method must not be called outside the lifecycle of the
     * associated converged context.
     */
    public void stop() throws LifecycleException {
        if (!started) {
            throw new LifecycleException("Not started");
        }

        started = false;

        SipSessionManagerBase.unregister(this);

        // TBD Clean up sessions, etc.
    }

    /**
     * Adds a lifecycle event listener to this SipSessionManager.
     *
     * @param listener The lifecycle event listener to add
     */
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

    /**
     * Gets the lifecycle event listeners of this SipSessionManager.
     *
     * @return Array (possibly of zero length) containing the lifecycle
     * event listeners of this SipSessionManager
     */
    public LifecycleListener[] findLifecycleListeners() {
        return lifecycle.findLifecycleListeners();
    }

    /**
     * Removes the give lifecycle event listener from this
     * SipSessionManager.
     *
     * @param listener The lifecycle event listener to remove
     */
    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }

    /**
     * Gets the name of the instance.on which this session manager resides.
     *
     * @return The name of the instance on which this session manager
     * resides
     */
    public String getInstanceName() {
        return instanceName;
    }

    /**
     * Creates a new SipSession.
     *
     * @param set
     * @param to
     * @param appSession
     * @param handler
     * @type type
     *
     * @return The new SipSession
     */
    protected SipSessionDialogImpl createNewSipSession(DialogSet set,
        Address to, SipApplicationSessionImpl appSession, String handler,
        Type type) {
        return new SipSessionDialogImpl(this, set, to, appSession, handler, type);
    }

    /**
     * Creates a new SipApplicationSession.
     *
     * @param id The id of the new SipApplicationSession
     *
     * @return The new SipApplicationSession
     */
    protected SipApplicationSessionImpl createNewSipApplicationSession(String id) {
        return new SipApplicationSessionImpl(this, id);
    }


    /**
     * Creates a new ServletTimer.
     *
     * @return The new ServletTimer
     */
    protected ServletTimerImpl createNewServletTimer(
            SipApplicationSessionImpl sas, Serializable info, long delay,
            TimerListener listener) {
        return new ServletTimerImpl(sas, info, delay, listener);
    }


    /**
     * Creates a new ServletTimer.
     *
     * @return The new ServletTimer
     */
    protected ServletTimerImpl createNewServletTimer(
            SipApplicationSessionImpl sas, Serializable info, long delay,
            boolean fixedDelay, long period, TimerListener listener) {
        return new ServletTimerImpl(sas, info, delay, fixedDelay, period,
                                    listener);
    }


    /**
     * Gets the persistence type of this session manager.
     *
     * @return The persistence type of this session manager.
     */
    public String getPersistenceType() {
        return EEPersistenceTypeResolver.MEMORY_TYPE;
    }
  
    public String getApplicationId() {
        if (applicationId != null) {
            return applicationId;
        }
        Container container = getContext();
        StringBuilder sb = new StringBuilder(50);
        //prepend "SIP:" to distinguish from non-sip manager
        sb.append("SIP:");
        String clusterId = getClusterId();
        if (clusterId != null) {
            sb.append(getClusterId());
        }
        ArrayList<String> list = new ArrayList<String>();
        while (container != null) {
            if (container.getName() != null) {
                list.add(":" + container.getName());
            }
            container = container.getParent();
        }
        for(int i=(list.size()-1); i>-1; i--) {
            String nextString = (String) list.get(i);
            sb.append(nextString);
        }
        applicationId = sb.toString();
        return applicationId;
    }
  
    /**
     * @return The cluster id as defined in domain.xml
     */ 
    protected String getClusterId() {
        if(clusterId == null)
            clusterId = getClusterIdFromConfig();
        return clusterId;
    }     
    
    /**
     * @return The cluster id as defined in domain.xml
     */  
    private String getClusterIdFromConfig() {
        ServerConfigLookup lookup = new ServerConfigLookup();
        return lookup.getClusterIdFromConfig();
    }    

    /**
     * Initializes this SipSessionManager.
     */
    private void init() {
        if (initialized) {
            return;
        }

        initialized = true;
    }

    private void initSipApplicationSession(SipApplicationSessionImpl sas,
        SipApplicationListeners sipApplicationListeners) {
        sas.setName(getContext().getName());

        if (sas.getSipApplicationListeners() != null) {
            sas.setDefaultSessionTimeout(sessionTimeout);

            ArrayList<SipApplicationSessionListener> listeners = sas.getSipApplicationListeners()
                                                                   .getSipApplicationSessionListeners();

            for (Iterator<SipApplicationSessionListener> iter = listeners.iterator();
                    iter.hasNext();) {
                SipApplicationSessionListener list = iter.next();
                list.sessionCreated(new SipApplicationSessionEvent(sas));
            }
        }

        sas.initAppSessionTimer();
    }
}
