package org.summer.dp.cms.shiro;

import java.io.Serializable;
import java.util.Collection;
import java.util.UUID;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hazelcast.config.Config;
import com.hazelcast.config.GroupConfig;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.MulticastConfig;
import com.hazelcast.config.NetworkConfig;
import com.hazelcast.config.SSLConfig;
import com.hazelcast.config.TcpIpConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;

/**
 * Data Access Object for Shiro {@link Session} persistence in Hazelcast.
 */
public class HazelcastSessionDao extends AbstractSessionDAO {

    private static final Logger log = LoggerFactory
            .getLogger(HazelcastSessionDao.class);
    private static final String HC_MAP = "sessions";
    private static final String HC_GROUP_NAME = "hc";
    private static final String HC_GROUP_PASSWORD = "oursessionssecret";
    private static final int HC_PORT = 5701;
    private static final String HC_MULTICAST_GROUP = "224.2.2.3";
    private static final int HC_MULTICAST_PORT = 54327;
    private String hcInstanceName = UUID.randomUUID().toString();
    private IMap<Serializable, Session> map;

    public HazelcastSessionDao() {
        log.info("Initializing Hazelcast Shiro session persistence..");

        // configure Hazelcast instance
        final Config cfg = new Config();
        cfg.setInstanceName(hcInstanceName);
        // group configuration
        cfg.setGroupConfig(new GroupConfig(HC_GROUP_NAME, HC_GROUP_PASSWORD));
        // network configuration initialization
        final NetworkConfig netCfg = new NetworkConfig();
        netCfg.setPortAutoIncrement(true);
        netCfg.setPort(HC_PORT);
        // multicast
        final MulticastConfig mcCfg = new MulticastConfig();
        mcCfg.setEnabled(false);
        mcCfg.setMulticastGroup(HC_MULTICAST_GROUP);
        mcCfg.setMulticastPort(HC_MULTICAST_PORT);
        // tcp
        final TcpIpConfig tcpCfg = new TcpIpConfig();
        tcpCfg.addMember("127.0.0.1");
        tcpCfg.setEnabled(false);
        // network join configuration
        final JoinConfig joinCfg = new JoinConfig();
        joinCfg.setMulticastConfig(mcCfg);
        joinCfg.setTcpIpConfig(tcpCfg);
        netCfg.setJoin(joinCfg);
        // ssl
        netCfg.setSSLConfig(new SSLConfig().setEnabled(false));

        // get map
        map = Hazelcast.newHazelcastInstance(cfg).getMap(HC_MAP);
        log.info("Hazelcast Shiro session persistence initialized.");
    }

    @Override
    protected Serializable doCreate(Session session) {
        final Serializable sessionId = generateSessionId(session);
        log.debug("Creating a new session identified by[{}]", sessionId);
        assignSessionId(session, sessionId);
        map.put(session.getId(), session);

        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        log.debug("Reading a session identified by[{}]", sessionId);
        return map.get(sessionId);
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        log.debug("Updating a session identified by[{}]", session.getId());
        map.replace(session.getId(), session);
    }

    @Override
    public void delete(Session session) {
        log.debug("Deleting a session identified by[{}]", session.getId());
        map.remove(session.getId());
    }

    @Override
    public Collection<Session> getActiveSessions() {
        return map.values();
    }

    /**
     * Retrieves a collection of sessions related to a user.
     * <p>
     * @param email the authentication identifier to look sessions for.
     * @return a collection of sessions.
     */
    public Collection<Session> getSessionsForAuthenticationEntity(
            final String userName) {
        log.debug("Looking up for sessions related to [{}]", userName);
        final SessionAttributePredicate<String> predicate
                = new SessionAttributePredicate<>("email", userName);
        return map.values(predicate);
    }

    /**
     * Destroys currently allocated instance.
     */
    public void destroy() {
        log.info("Shutting down Hazelcast instance [{}]..", hcInstanceName);
        final HazelcastInstance instance = Hazelcast.getHazelcastInstanceByName(
                hcInstanceName);
        if (instance != null) {
            instance.shutdown();
        }
    }

}