/* Copyright (c) 2013 Alexander Sherstnev
 * Copyright (c) 2011 Peter Troshin
 *  
 *  JAva Bioinformatics Analysis Web Services (JABAWS) @version: 2.0     
 * 
 *  This library is free software; you can redistribute it and/or modify it under the terms of the
 *  Apache License version 2 as published by the Apache Software Foundation
 * 
 *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Apache 
 *  License for more details.
 * 
 *  A copy of the license is in apache_license.txt. It is also available here:
 * @see: http://www.apache.org/licenses/LICENSE-2.0.txt
 * 
 * Any republication or derived work distributed in source code form
 * must include this copyright and license notice.
 */

package compbio.stat.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.net.*;
import java.text.SimpleDateFormat;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.Query;
import javax.management.ReflectionException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import compbio.engine.conf.PropertyHelperManager;
import compbio.stat.servlet.util.RefreshIterator;
import compbio.stat.servlet.util.Scheduler;
import compbio.stat.servlet.util.SchedulerTask;
import compbio.util.PropertyHelper;
import compbio.util.Util;
import org.apache.log4j.Logger;

import compbio.ws.client.Services;
import compbio.ws.client.WSTester;

/**
 * Use cases:
 * <dl>
 * <li>Test web services and display results on the web page</li>
 * </dl>
 *
 * @author pvtroshin
 */

public class ServiceStatus extends HttpServlet {

    private static final Logger log = Logger.getLogger(ServiceStatus.class);

    private static PropertyHelper ph = PropertyHelperManager.getPropertyHelper();

    private final Scheduler scheduler = new Scheduler();

    private List<String> getEndPoints() throws MalformedObjectNameException, NullPointerException, UnknownHostException,
            AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        Set<ObjectName> objs = mbs.queryNames(new ObjectName("*:type=Connector,*"),
                Query.match(Query.attr("protocol"), Query.value("HTTP/1.1")));
        List<String> endPoints = new ArrayList<String>();
        for (Iterator<ObjectName> i = objs.iterator(); i.hasNext(); ) {
            ObjectName obj = i.next();
            String scheme = mbs.getAttribute(obj, "scheme").toString();
            String port = obj.getKeyProperty("port");
            String hostname = InetAddress.getLocalHost().getHostName();
            endPoints.add(scheme + "://" + hostname + ":" + port);
        }
        return endPoints;
    }

    private static int getIntProperty(String propValue) {
        int value = 0;
        if (!Util.isEmpty(propValue)) {
            propValue = propValue.trim();
            value = Integer.parseInt(propValue);
        }
        return value;
    }

    private static int refreshServiceStatusFrequency() {
        return getIntProperty(ph.getProperty("local.service.status.refresh.frequency"));
    }

    static int refreshUsageStatsFrequency() {
        return getIntProperty(ph.getProperty("local.usage.stats.refresh.frequency"));
    }

    @Override
    public void init() {
        // startup the scheduler with current time
        int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        int min = Calendar.getInstance().get(Calendar.MINUTE);
        int sec = Calendar.getInstance().get(Calendar.SECOND);
        // refresh sleep time (in between requests) - minutes
        final int refresh_freq = refreshServiceStatusFrequency();

        scheduler.schedule(new SchedulerTask() {
            public void run() {
                refreshCache();
            }
            private void refreshCache() {
                hitEndpointForRefresh();
                log.info("Refreshing the In Memory Cache...");
                log.info(String.format("Refreshing again in %d minutes", refresh_freq));
            }
        }, new RefreshIterator(hour, min, sec, refresh_freq));
    }

    public void clearContextCache() {
        // try remove the current values from the context
        try {
            this.getServletConfig().getServletContext().removeAttribute("serviceStatusResults");
            this.getServletConfig().getServletContext().removeAttribute("serviceStatusTimestamp");
            this.getServletConfig().getServletContext().removeAttribute("serviceStatusStart");
            this.getServletConfig().getServletContext().removeAttribute("serviceStatusEnd");
            this.getServletConfig().getServletContext().removeAttribute("serviceStatusAllGood");
            log.info("In Memory Cache Cleared!");
        } catch (Exception e) {
            log.warn("In Memory Cache Not Cleared. Perhaps not available yet!");
        }
    }

    private void hitEndpointForRefresh() {
        String endpointURL;
        endpointURL = (String) this.getServletConfig().getServletContext().getAttribute("serviceStatusURL");
        if (endpointURL != null){
            try {
                URL url = new URL(endpointURL);
                System.out.println(endpointURL);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestProperty("User-Agent", "JABAWS Service Status Refreshing the Context Cache");
                int status = connection.getResponseCode();
                log.info(String.format("Hitting %s with an internal user-agent! status code: %s",
                        endpointURL, status));
            } catch (IOException e) {
                log.warn("Something wrong when hitting " + endpointURL);
                log.warn(e);
            }
        }
    }

    public ArrayList<ServiceTestResult> testServiceStatus(HttpServletRequest req) throws ServletException, IOException {

        ArrayList<ServiceTestResult> testResults = new ArrayList<ServiceTestResult>();

        // run the service status checkup
        List<String> eps = new ArrayList<String>();
        try {
            eps = getEndPoints();
        } catch (MalformedObjectNameException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (AttributeNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstanceNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NullPointerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (MBeanException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ReflectionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (1 != eps.size()) {
            log.info(eps.size() + "EndPoints found");
            //System.out.println("      " + eps.size() + " EndPoints found");
            for (String endpoint : eps) {
                log.info(eps.size() + "EndPoint is " + endpoint);
                //System.out.println("   EndPoint is " + endpoint);
            }
        }

        String serverPath = new String();
        for (String endpoint : eps) {
            serverPath = endpoint + req.getContextPath();
        }

        for (Services service : Services.values()) {
            StringWriter testres = new StringWriter();
            PrintWriter writer = new PrintWriter(testres, true);
            WSTester tester = new WSTester(serverPath, writer);
            ServiceTestResult result = new ServiceTestResult(service);
            try {
                result.failed = tester.checkService(service);
            } catch (Exception e) {
                log.info(e, e.getCause());
                String mess = "Fails to connect to the web service: " + service + ". Reason: ";
                writer.println(mess + e.getLocalizedMessage() + "\nDetails: ");
                e.printStackTrace(writer);
            } finally {
                writer.close();
            }
            result.details = testres.toString();
            testResults.add(result);
        }
        return testResults;
    }

    public String overallStatusGood(ArrayList<ServiceTestResult> testResults) {
        // assumes all good but returns as soon as some service is down
        String success = "true";
        for (int i = 0; i < testResults.size(); i++) {
            if (!testResults.get(i).failed) {
                success = "false";
                break;
            }
        }
        return success;
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        /**
         * // PROBLEM: the code tries to test not WS endpoints on an internal
         * Tomcat, but on an external // endpoints with wrong info on the
         * proxing StringBuffer jabawspath = req.getRequestURL(); jabawspath =
         * jabawspath.delete(jabawspath.lastIndexOf("/"), jabawspath.length());
         * String serverPath = jabawspath.toString();
         */

        ArrayList<ServiceTestResult> testResults = new ArrayList<ServiceTestResult>();
        String timeStamp = new String();
        long startTime;
        long endTime;
        String refresh_freq;
        String goodStatus;

        // save the main caller URL in the context
        String url = new String();
        url = req.getRequestURL().toString();
        System.out.println("UR: " + url);
        this.getServletConfig().getServletContext().setAttribute("serviceStatusURL", url);

        // check user-agent: this is a trick to get the cache refreshed periodically
        String useragent = new String();
        useragent = req.getHeader("user-agent");
        // check if there are values available in the context
        if (this.getServletConfig().getServletContext().getAttribute("serviceStatusResults") == null ||
                useragent.equals("JABAWS Service Status Refreshing the Context Cache")) {

            // test the services and timeit
            startTime = System.nanoTime();
            testResults = testServiceStatus(req);
            endTime = System.nanoTime();
            // try clear the previous values if any
            clearContextCache();
            // add the current values to the context
            this.getServletConfig().getServletContext().setAttribute("serviceStatusResults", testResults);
            timeStamp = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(
                    Calendar.getInstance().getTime());
            this.getServletConfig().getServletContext().setAttribute("serviceStatusTimestamp", timeStamp);
            this.getServletConfig().getServletContext().setAttribute("serviceStatusStart", startTime);
            this.getServletConfig().getServletContext().setAttribute("serviceStatusEnd", endTime);
            refresh_freq = String.valueOf(refreshServiceStatusFrequency());
            this.getServletConfig().getServletContext().setAttribute("serviceStatusRefreshFreq", refresh_freq);
            goodStatus = overallStatusGood(testResults);
            this.getServletConfig().getServletContext().setAttribute("serviceStatusAllGood", goodStatus);

        } else {
            // get last results available
            testResults = (ArrayList) this.getServletConfig().getServletContext().getAttribute("serviceStatusResults");
            timeStamp = (String) this.getServletConfig().getServletContext().getAttribute("serviceStatusTimestamp");
            startTime = (Long) this.getServletConfig().getServletContext().getAttribute("serviceStatusStart");
            endTime = (Long) this.getServletConfig().getServletContext().getAttribute("serviceStatusEnd");
            refresh_freq = (String) this.getServletConfig().getServletContext().getAttribute("serviceStatusRefreshFreq");
        }

        req.setAttribute("results", testResults);
        req.setAttribute("timestamp", timeStamp);
        req.setAttribute("timeexec", (endTime - startTime) / 1000000000);
        req.setAttribute("refreshfreq", refresh_freq);
        RequestDispatcher rd = req.getRequestDispatcher("statpages/ServicesStatus.jsp");
        rd.forward(req, resp);
    }

}
