/*
 * 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.faces.demo.poll;

import com.icesoft.net.messaging.MessageServiceClient;
import com.icesoft.net.messaging.jms.JMSAdapter;
import com.icesoft.util.ServerUtility;
import com.icesoft.util.ThreadFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import javax.naming.Context;
import javax.servlet.ServletContext;

import org.icefaces.application.PortableRenderer;
import org.icefaces.application.PushMessage;
import org.icefaces.application.PushRenderer;
import org.icepush.Configuration;
import org.icepush.ConfigurationException;
import org.icepush.servlet.ServletContextConfiguration;

@ManagedBean(eager = true, name = "pollCollection")
@ApplicationScoped
public class PollCollection {
    private static final Logger LOGGER = Logger.getLogger(PollCollection.class.getName());

    private final Map<String, Poll> pollMap = new HashMap<String, Poll>();

    private boolean isJMSEnvironment;
    {
        try {
            // throws ClassNotFoundException
            Class.forName("javax.jms.JMSException");
            isJMSEnvironment = true;
        } catch (final ClassNotFoundException exception) {
            isJMSEnvironment = false;
        }
    }

    private PortableRenderer portableRenderer;
    private String pushMode;

    private ScheduledThreadPoolExecutor executor;
    private PollMessageService pollMessageService;
    private long maxTimeNoActivity;

    private Timer timer;
    private TimerTask timerTask =
        new TimerTask() {
            public void run() {
                boolean _modified = false;
                long _currentTime = System.currentTimeMillis();
                Iterator<Map.Entry<String, Poll>> _pollEntries = pollMap.entrySet().iterator();
                while (_pollEntries.hasNext()) {
                    if ((_currentTime - _pollEntries.next().getValue().getLastActivityTime()) > maxTimeNoActivity) {
                        _pollEntries.remove();
                        if (!_modified) {
                            _modified = true;
                        }
                    }
                }
                if (_modified) {
                    if (pushMode.equalsIgnoreCase(Constants.PushMode.PUSH) ||
                        pushMode.equalsIgnoreCase(Constants.PushMode.CLOUD_PUSH)) {

                        portableRenderer.render(Poll.POLL_GROUP_NAME);
                    }
                }
            }
        };

    public PollCollection() {
        // Do nothing.
    }

    public void addPoll(final Poll poll) {
        addPoll(poll, true);
    }

    public Poll createPoll(final String statement, final String detail) {
        return new Poll(statement, detail, this, portableRenderer, pollMessageService);
    }

    public static PollCollection getInstance() {
        return (PollCollection)FacesUtils.getManagedBean("pollCollection");
    }

    public List<Poll> getPolls() {
        List<Poll> _pollList = new ArrayList<Poll>();
        String _realm = ParticipantView.getInstance().getRealm();
        Collection<Poll> _pollCollection = pollMap.values();
        for (Poll poll : _pollCollection) {
            String _pollId = poll.getId();
            if ((_realm == null && _pollId.indexOf(":") == -1) ||
                (_realm != null && _pollId.startsWith(_realm) && _pollId.indexOf(":") == _realm.length())) {

                _pollList.add(poll);
            }
        }
        Collections.sort(
            _pollList,
            new Comparator<Poll>() {
                public int compare(final Poll poll1, final Poll poll2) {
                    return poll1.getStatement().compareTo(poll2.getStatement());
                }
            });
        return Collections.unmodifiableList(_pollList);
    }

    public String getPushMode() {
        return pushMode;
    }

    @PostConstruct
    public void setUp() {
        pushMode = ApplicationConfiguration.getInstance().getPushMode();
        if (pushMode.equalsIgnoreCase(Constants.PushMode.PUSH) ||
            pushMode.equalsIgnoreCase(Constants.PushMode.CLOUD_PUSH)) {

            portableRenderer = PushRenderer.getPortableRenderer();
        }
        maxTimeNoActivity = ApplicationConfiguration.getInstance().getMaxTimeNoActivity() * 60 * 1000;
        if (isJMSEnvironment && ApplicationConfiguration.getInstance().isMessagingEnabled()) {
            ServletContext _servletContext = FacesUtils.getServletContext();
            if (ServerUtility.isWebLogic(_servletContext)) {
                waitUntillRunning(_servletContext);
            } else {
                setUpMessageService(_servletContext);
            }
        }
        timer = new Timer();
        timer.scheduleAtFixedRate(timerTask, 0, 60000);
    }

    @PreDestroy
    public void tearDown() {
        timerTask.cancel();
        timer.cancel();
        timer = null;
        if (isJMSEnvironment) {
            tearDownMessageService();
        }
    }

    void addPoll(final Poll poll, final boolean broadcast) {
        pollMap.put(poll.getId(), poll);
        if (pushMode.equalsIgnoreCase(Constants.PushMode.PUSH)) {
            portableRenderer.render(Poll.POLL_GROUP_NAME);
        } else if (pushMode.equalsIgnoreCase(Constants.PushMode.CLOUD_PUSH)) {
            portableRenderer.render(
                Poll.POLL_GROUP_NAME, new PushMessage("Poll", "New poll added: " + poll.getStatement()));
        }
        if (broadcast) {
            if (pollMessageService != null) {
                pollMessageService.publishNow(
                    poll.getId() + "\r\n" + poll.getStatement() + "\r\n" + poll.getDetail(),
                    PollMessageService.ADD_POLL_MESSAGE_TYPE);
            }
        }
    }

    Poll createPoll(final String id, final String statement, final String detail) {
        return new Poll(id, statement, detail, this, portableRenderer, pollMessageService);
    }

    List<Poll> getAllPolls() {
        List<Poll> _pollList = new ArrayList<Poll>(pollMap.values());
        Collections.sort(
            _pollList,
            new Comparator<Poll>() {
                public int compare(final Poll poll1, final Poll poll2) {
                    return poll1.getStatement().compareTo(poll2.getStatement());
                }
            });
        return Collections.unmodifiableList(_pollList);
    }

    Poll getPoll(final String id) {
        return pollMap.get(id);
    }

    private void setUpMessageService(final ServletContext servletContext) {
        ThreadFactory _threadFactory = new ThreadFactory();
        _threadFactory.setPrefix("ICEpoll Thread");
        executor = new ScheduledThreadPoolExecutor(10, _threadFactory);
        pollMessageService =
            new PollMessageService(
                new MessageServiceClient("ICEpoll MSC", new JMSAdapter(servletContext), servletContext),
                executor,
                new ServletContextConfiguration("com.icesoft.icepush", servletContext),
                true,
                this);
        if (pollMessageService.setUpNow()) {
            pollMessageService.start();
            pollMessageService.publish(
                "",
                PollMessageService.GET_POLLS_MESSAGE_TYPE);
        } else {
            pollMessageService = null;
        }
    }

    private void tearDownMessageService() {
        if (pollMessageService != null) {
            pollMessageService.stop();
            pollMessageService.tearDownNow();
            pollMessageService.close();
        }
        if (executor != null) {
            executor.shutdownNow();
        }
    }

    private void waitUntillRunning(final ServletContext servletContext) {
        new Thread(
            new Runnable() {
                public void run() {
                    Configuration _weblogicConfiguration = new ServletContextConfiguration("weblogic", servletContext);
                    try {
                        // throws ConfigurationException, MalformedURLException
                        JMXServiceURL _serviceURL =
                            new JMXServiceURL(
                                "t3",
                                _weblogicConfiguration.getAttribute("adminHost"),
                                _weblogicConfiguration.getAttributeAsInteger("adminPort"),
                                "/jndi/weblogic.management.mbeanservers.domainruntime");
                        Hashtable _environment = new Hashtable();
                        // throws ConfigurationException
                        _environment.put(
                            Context.SECURITY_PRINCIPAL, _weblogicConfiguration.getAttribute("adminUserName"));
                        // throws ConfigurationException
                        _environment.put(
                            Context.SECURITY_CREDENTIALS, _weblogicConfiguration.getAttribute("adminPassword"));
                        _environment.put(
                            JMXConnectorFactory.PROTOCOL_PROVIDER_PACKAGES, "weblogic.management.remote");
                        // throws IOException
                        JMXConnector connector = JMXConnectorFactory.connect(_serviceURL, _environment);
                        // throws IOException
                        MBeanServerConnection connection = connector.getMBeanServerConnection();
                        boolean _running = false;
                        while (!_running) {
                            // throws MalformedObjectNameException
                            ObjectName[] _serverRuntimes =
                                (ObjectName[])connection.getAttribute(
                                    new ObjectName(
                                        "com.bea:Name=DomainRuntimeService," +
                                            "Type=weblogic.management.mbeanservers.domainruntime.DomainRuntimeServiceMBean"),
                                    "ServerRuntimes");
                            for (final ObjectName _serverRuntime : _serverRuntimes) {
                                // throws
                                //     MBeanException, AttributeNotFoundException, InstanceNotFoundException,
                                //     ReflectionException, IOException
                                String _listenAddress = (String)connection.getAttribute(_serverRuntime, "ListenAddress");
                                //     MBeanException, AttributeNotFoundException, InstanceNotFoundException,
                                //     ReflectionException, IOException
                                String _state = (String)connection.getAttribute(_serverRuntime, "State");
                                // throws UnknownHostException
                                String _localAddress = InetAddress.getLocalHost().getHostAddress();
                                if (_localAddress.equals(_listenAddress.substring(_listenAddress.indexOf('/') + 1))) {
                                    if (_state.equals("RUNNING")) {
                                        _running = true;
                                        break;
                                    }
                                }
                            }
                            if (!_running) {
                                try {
                                    Thread.sleep(1000);
                                } catch (final InterruptedException exception) {
                                    // Do nothing.
                                }
                            }
                        }
                        // throws IOException
                        connector.close();
                        try {
                            Thread.sleep(3000);
                        } catch (final InterruptedException exception) {
                            // Do nothing.
                        }
                        setUpMessageService(servletContext);
                    } catch (final ConfigurationException exception) {
                        LOGGER.log(
                            Level.WARNING,
                            "Please note that if running in a WebLogic Server cluster the following context parameters " +
                                "must correctly be configured in this application's web.xml:\r\n\r\n" +
                            "- weblogic.adminHost\r\n" +
                            "- weblogic.adminPort\r\n" +
                            "- weblogic.adminUserName\r\n" +
                            "- weblogic.adminPassword\r\n");
                    } catch (final MalformedURLException exception) {
                        LOGGER.log(
                            Level.SEVERE,
                            "An error occurred while trying to connect to the Domain Runtime MBean Server",
                            exception);
                    } catch (final IOException exception) {
                        LOGGER.log(
                            Level.SEVERE,
                            "An I/O error occurred while trying to connect to the Domain Runtime MBean Server",
                            exception);
                    } catch (final MalformedObjectNameException exception) {
                        LOGGER.log(
                            Level.SEVERE,
                            "An error occurred while trying to connect to the Domain Runtime MBean Server",
                            exception);
                    } catch (final MBeanException exception) {
                        LOGGER.log(
                            Level.SEVERE,
                            "An error occurred while trying to connect to the Domain Runtime MBean Server",
                            exception);
                    } catch (final AttributeNotFoundException exception) {
                        LOGGER.log(
                            Level.SEVERE,
                            "An error occurred while trying to connect to the Domain Runtime MBean Server",
                            exception);
                    } catch (final InstanceNotFoundException exception) {
                        LOGGER.log(
                            Level.SEVERE,
                            "An error occurred while trying to connect to the Domain Runtime MBean Server",
                            exception);
                    } catch (final ReflectionException exception) {
                        LOGGER.log(
                            Level.SEVERE,
                            "An error occurred while trying to connect to the Domain Runtime MBean Server",
                            exception);
                    }
                }
            }).start();
    }
}
