/*
 * 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.
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 */
package com.ericsson.ssa.sip;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpSession;
import javax.servlet.sip.ServletTimer;

import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.jvnet.glassfish.comms.util.LogUtil;

import com.ericsson.ssa.config.ConvergedContext;
import com.ericsson.ssa.dd.SipApplicationListeners;
import com.ericsson.ssa.sip.timer.ServletTimerImpl;
import com.ericsson.ssa.sip.timer.ServletTimerImplBase;


public class SipApplicationSessionImpl extends SipApplicationSessionBase
    implements Serializable, LifeCycle {
    private static LogUtil logger = LogUtil.SIP_LOGGER;
    private static final long serialVersionUID = 1972145565288132288L;
    private transient SipSessionManager sipSessionManager;

    // ---- Persistable fields ----
    private String applicationName;
    private long creationDate = new Date().getTime();
    private Map<String, Object> applicationAttributes;
    private Set<String> persistentSipSessionIds;
    private Set<String> transientSipSessionIds;
    private Set<String> httpProtocolSessionIds;
    private Set<String> nonPersistentApplicationTimerIds;
    private Set<String> persistentApplicationTimerIds;
    private String id;
    protected String description;
    protected String appId;
    private String beKey;


    // ----- Extra param field -------
    // these are persisted as well, but maybe overridden later
    private long expirationTime;

    /**
     * Constructor.
     */
    public SipApplicationSessionImpl(SipSessionManagerBase manager, String id) {
        this.id = id;
        this.sipSessionManager = manager;
        this.appId = manager.getApplicationId();
        description = "SipApplicationSession with id " +
            manager.getApplicationId() + ":" + id;
    }

    

    @Override
    public synchronized SipSessionManager getSipSessionManager() {
       if (sipSessionManager == null) {
           sipSessionManager = SipSessionManagerBase.get(appId);

           if (sipSessionManager == null) {
               logger.warning("SipSessionManager " + appId + " not found");
           }
       }

       return sipSessionManager;
   }

    public Set<String> getPersistentSessionIds() {
        return new HashSet<String>(getPersistentSipSessionIds(false));
    }

    public Set<String> getPersistentTimerIds() {
        return new HashSet<String>(getPersistentApplicationTimerIds(false));
    }

    protected Manager getHttpSessionManager() {
       SipSessionManager manager = getSipSessionManager();
       if (manager != null) {
          ConvergedContext ctxt = manager.getContext();

          if (ctxt != null) {
              return ctxt.getManager();
          }
      }

      return null;
    }

    @Override
    protected void removeMeFromManager() {
        sipSessionManager.removeSipApplicationSession(this);
    }


    @Override
    protected String getPFieldApplicationName() {
        return applicationName;
    }

    @Override
    protected void setPFieldApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }

    @Override
    protected Object getPFieldApplicationAttribute(String key) {
        if (applicationAttributes == null) {
            return null;
        }

        return applicationAttributes.get(key);
    }

    @Override
    protected void setPFieldApplicationAttribute(String key, Object value) {
        if (applicationAttributes == null) {
            applicationAttributes = new HashMap<String, Object>();
        }

        applicationAttributes.put(key, value);
    }

    @Override
    protected Iterator<String> getPFieldApplicationAttributeNames() {
        if (applicationAttributes == null) {
            return null;
        }

        return applicationAttributes.keySet().iterator();
    }

    @Override
    protected void removePFieldApplicationAttribute(String key) {
        if (applicationAttributes != null) {
            applicationAttributes.remove(key);
        }
    }

    @Override
    protected long getPFieldCreationDate() {
        return creationDate;
    }

    @Override
    protected long getPFieldExpirationTime() {
        return expirationTime;
    }

    @Override
    protected void setPFieldExpirationTime(long anExpirationTime) {
        expirationTime = anExpirationTime;
    }

    @Override
    protected Iterable<SipSessionBase> getPFieldSipSessions() {
        final Set<String> allSsIds = new HashSet<String>(getTransientSipSessionIds(
                    false));
        allSsIds.addAll(getPersistentSipSessionIds(false));

        return new Iterable<SipSessionBase>() {
                // The iterator skips SS:es that can not be found in the SipSessionManager
                public Iterator<SipSessionBase> iterator() {
                    return new Iterator<SipSessionBase>() {
                            Iterator<String> idIterator = allSsIds.iterator();
                            private SipSessionDialogImpl nextSs;

                            public boolean hasNext() {
                                while (idIterator.hasNext()) {
                                    nextSs = getSipSessionManager()
                                                 .findSipSession(idIterator.next());

                                    if (nextSs != null) {
                                        return true;
                                    }
                                }

                                return false;
                            }

                            public SipSessionBase next() {
                                SipSessionDialogImpl ssTmp = nextSs;
                                nextSs = null;

                                if (ssTmp != null) {
                                    return ssTmp;
                                }

                                while (ssTmp != null) {
                                    // If the caller has not checked with hasNext()
                                    // this loop can cause NoSuchElementException
                                    ssTmp = getSipSessionManager()
                                                .findSipSession(idIterator.next());
                                }

                                return ssTmp;
                            }

                            public void remove() {
                                throw new UnsupportedOperationException(
                                    "Removal via iterator is not supported");
                            }
                        };
                }
            };
    }

    @Override
    protected void removePFieldSipProtocolSession(SipSessionBase session) {
        getPersistentSipSessionIds(false).remove(session.getId());
        getTransientSipSessionIds(false).remove(session.getId());
    }

    @Override
    protected void addSipProtocolSession(SipSessionBase session) {
        getTransientSipSessionIds(true).add(session.getId());
    }

    private Set<String> getTransientSipSessionIds(boolean create) {
        if (transientSipSessionIds == null) {
            if (!create) {
                return Collections.emptySet();
            } else {
                transientSipSessionIds = new HashSet<String>();
            }
        }

        return transientSipSessionIds;
    }

    private Set<String> getPersistentSipSessionIds(boolean create) {
        if (persistentSipSessionIds == null) {
            if (!create) {
                return Collections.emptySet();
            } else {
                persistentSipSessionIds = new HashSet<String>();
            }
        }

        return persistentSipSessionIds;
    }

    @Override
    protected Iterable<HttpSession> getPFieldHttpSessions() {
        return new Iterable<HttpSession>() {
                public Iterator<HttpSession> iterator() {
                    return new Iterator<HttpSession>() {
                            Iterator<String> idIterator = getHttpProtocolSessionIds(false)
                                                              .iterator();
                            private HttpSession nextSess;

                            public boolean hasNext() {
                                while (idIterator.hasNext()) {
                                    try {
                                        Session sess = getHttpSessionManager()
                                                           .findSession(idIterator.next());

                                        if (sess != null) {
                                            nextSess = sess.getSession();
                                        }
                                    } catch (IOException e) {
                                        // Ignore, skip to the next session
                                        // TODO add warning logging
                                    }

                                    if (nextSess != null) {
                                        return true;
                                    }
                                }

                                return false;
                            }

                            public HttpSession next() {
                                HttpSession sessTmp = nextSess;
                                nextSess = null;

                                if (sessTmp != null) {
                                    return sessTmp;
                                }

                                while (sessTmp != null) {
                                    // If the caller has not checked with hasNext()
                                    // this loop can cause NoSuchElementException
                                    try {
                                        Session sess = getHttpSessionManager()
                                                           .findSession(idIterator.next());

                                        if (sess != null) {
                                            nextSess = sess.getSession();
                                        }
                                    } catch (IOException e) {
                                        // Ignore, skip to next
                                        // TODO add warning logging
                                    }
                                }

                                return sessTmp;
                            }

                            public void remove() {
                                throw new UnsupportedOperationException(
                                    "Removal via iterator is not supported");
                            }
                        };
                }
            };
    }

    @Override
    protected void removePFieldHttpProtocolSession(HttpSession session) {
        try {
            getHttpSessionManager().remove(getHttpSessionManager().findSession(
                    session.getId()));
        } catch (IOException e) {
            // Ignore, the session apparently does not exist anyway
        }

        getHttpProtocolSessionIds(false).remove(session.getId());
    }

    @Override
    protected void addPFieldHttpProtocolSession(HttpSession session) {
        getHttpProtocolSessionIds(true).add(session.getId());
    }

    private Set<String> getHttpProtocolSessionIds(boolean create) {
        if (httpProtocolSessionIds == null) {
            if (!create) {
                return Collections.emptySet();
            } else {
                httpProtocolSessionIds = new HashSet<String>();
            }
        }

        return httpProtocolSessionIds;
    }

    @Override
    protected Collection<ServletTimer> getPFieldApplicationTimers() {
        Set<String> persistentTimerIds = getPersistentApplicationTimerIds(false);
        Set<String> nonPersistentTimerIds = getNonPersistentApplicationTimerIds(false);
        List<ServletTimer> timers = new ArrayList<ServletTimer>(persistentTimerIds.size() +
                nonPersistentTimerIds.size());

        for (String id : persistentTimerIds) {
            ServletTimerImpl st = getSipSessionManager().findServletTimer(id);

            if (st != null) {
                timers.add(st);
            }
        }

        for (String id : nonPersistentTimerIds) {
            ServletTimerImpl st = getSipSessionManager().findServletTimer(id);

            if (st != null) {
                timers.add(st);
            }
        }

        return timers;
    }

    /**
     * @see com.ericsson.ssa.sip.SipApplicationSessionImpl#addPFieldApplicationTimer(com.ericsson.ssa.sip.timer.ServletTimerImpl)
     */
    @Override
    protected void addPFieldApplicationTimer(ServletTimerImpl timer) {
        if (timer.isPersistent()) {
            getPersistentApplicationTimerIds(true).add(timer.getId());
        } else {
            getNonPersistentApplicationTimerIds(true).add(timer.getId());
        }
    }

    /**
     * @see com.ericsson.ssa.sip.SipApplicationSessionImpl#removePFieldApplicationTimer(com.ericsson.ssa.sip.timer.ServletTimerImpl)
     */
    @Override
    protected void removePFieldApplicationTimer(ServletTimerImpl timer) {
        getNonPersistentApplicationTimerIds(false).remove(timer.getId());
        getPersistentApplicationTimerIds(false).remove(timer.getId());
    }

    private Set<String> getPersistentApplicationTimerIds(boolean create) {
        if (persistentApplicationTimerIds == null) {
            if (!create) {
                return Collections.emptySet();
            } else {
                persistentApplicationTimerIds = new HashSet<String>();
            }
        }

        return persistentApplicationTimerIds;
    }

    private Set<String> getNonPersistentApplicationTimerIds(boolean create) {
        if (nonPersistentApplicationTimerIds == null) {
            if (!create) {
                return Collections.emptySet();
            } else {
                nonPersistentApplicationTimerIds = new HashSet<String>();
            }
        }

        return nonPersistentApplicationTimerIds;
    }

    /**
     * @see com.ericsson.ssa.sip.SipApplicationSessionImpl#getId()
     */
    @Override
    public String getId() {
        return id;
    }

    /**
     * @see com.ericsson.ssa.sip.SipApplicationSessionImpl#setPFieldId(java.lang.String)
     */
    @Override
    protected void setPFieldId(String id) {
        this.id = id;
    }

    @Override
    protected String getPFieldBeKey() {
        return beKey;
    }

    @Override
    protected void setPFieldBeKey(String beKey) {
        this.beKey = beKey;
    }

    protected void setSipSessionShouldBePersisted(
        SipSessionDialogImpl sipSession) {
        synchronized (getSasObjectLock()) {
            getTransientSipSessionIds(false).remove(sipSession.getId());
            getPersistentSipSessionIds(true).add(sipSession.getId());
            setShouldBePersisted();
        }
    }

    public void setServletTimerShouldBePersisted(ServletTimerImplBase timer) {
        synchronized (getSasObjectLock()) {
            getNonPersistentApplicationTimerIds(false).remove(timer.getId());
            getPersistentApplicationTimerIds(true).add(timer.getId());
        }
    }

    /**
     * Serializes this object.
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeUTF(getId());
        out.writeUTF(appId);
        out.writeUTF(applicationName);
        out.writeLong(creationDate);
        out.writeLong(expirationTime); // possibly overridden by extraparams
        out.writeObject(beKey);

        Set<String> sipSessionIds = getPersistentSipSessionIds(false);
        out.writeInt(sipSessionIds.size());

        for (String ssId : sipSessionIds) {
            out.writeUTF(ssId);
        }

        Set<String> httpSessionIds = getHttpProtocolSessionIds(false);
        out.writeInt(httpSessionIds.size());

        for (String hsId : httpSessionIds) {
            out.writeUTF(hsId);
        }

        Set<String> appTimerIds = getPersistentApplicationTimerIds(false);
        out.writeInt(appTimerIds.size());

        for (String atId : appTimerIds) {
            out.writeUTF(atId);
        }

        int length = (applicationAttributes == null) ? 0
                                                     : applicationAttributes.size();
        out.writeInt(length);

        if (length > 0) {
            for (Map.Entry<String, Object> entry : applicationAttributes.entrySet()) {
                out.writeUTF(entry.getKey());
                out.writeObject(entry.getValue());
            }
        }
    }

    
    
    
    public SipApplicationListeners getSipApplicationListeners()
    {
       SipSessionManager manager = getSipSessionManager();
       if (manager != null) {
          ConvergedContext ctxt = manager.getContext();

          if (ctxt != null) {
              return ctxt.getSipApplicationListeners();
          }
      }

      return null;
    }
    
    
    /**
     * Deserializes this object.
     */
    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException {
        id = in.readUTF();

        appId = in.readUTF();
        description = "SipApplicationSession with id " + appId + ":" + id;

        applicationName = in.readUTF();
        creationDate = in.readLong();
        expirationTime = in.readLong();
        beKey = (String) in.readObject();

        int size = in.readInt();

        if (size > 0) {
            persistentSipSessionIds = new HashSet<String>(size);

            for (int i = 0; i < size; i++) {
                persistentSipSessionIds.add(in.readUTF());
            }
        }

        size = in.readInt();

        if (size > 0) {
            httpProtocolSessionIds = new HashSet<String>(size);

            for (int i = 0; i < size; i++) {
                httpProtocolSessionIds.add(in.readUTF());
            }
        }

        size = in.readInt();

        if (size > 0) {
            persistentApplicationTimerIds = new HashSet<String>(size);

            for (int i = 0; i < size; i++) {
                persistentApplicationTimerIds.add(in.readUTF());
            }
        }

        size = in.readInt();

        if (size > 0) {
            applicationAttributes = new HashMap<String, Object>();

            for (int i = 0; i < size; i++) {
                String key = in.readUTF();
                Object value = in.readObject();
                applicationAttributes.put(key, value);
            }
        }
        setShouldBePersisted();
    }

    /**
     * Activates this SipApplicationSession.
     */
    public boolean activate() {
        if (!validate()) {
            return false;
        }

        // Add to active cache
        SipSessionManager mgr = getSipSessionManager();

        if (mgr != null) {
            mgr.addSipApplicationSession(this);
        }

        restartAppSessionTimer();

        notifySessionDidActivate();

        return true;
    }

    protected boolean validate() {
        if (!isValid()) {
            return false;
        }

        return true;
    }

    /**
     * Passivates this SipApplicationSession.
     */
    public void passivate() {
        notifySessionWillPassivate();

        // Remove from active cache
        SipSessionManager mgr = getSipSessionManager();

        if (mgr != null) {
            if (mgr instanceof PersistentSipSessionManagerBase) {
                ((PersistentSipSessionManagerBase) mgr).removeSipApplicationSessionFromCache(this);
            } else {
                mgr.removeSipApplicationSession(this);
            }
        }
    }

    public String toString() {
        return description;
    }
}
