/*
 * Created on Nov 22, 2004
 * Copyright (C) Andrea Schweer, 2004
 *
 * This file is part of the Greenstone Alerting Service.
 * Refer to the COPYING file in the base directory of this package
 * for licensing information.
 */
package org.greenstone.gsdlas;

import java.lang.reflect.Method;
import java.net.URL;
import java.sql.SQLException;
import java.util.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.*;

import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.apache.velocity.servlet.VelocityServlet;
import org.greenstone.gsdlas.database.DatabaseException;
import org.greenstone.gsdlas.profiles.Predicate;
import org.greenstone.gsdlas.profiles.Subscription;
import org.greenstone.gsdlas.users.UserManagementException;
import org.greenstone.gsdlas.users.UserManager;
import org.greenstone.gsdlas.util.ArrayHelper;

/**
 * @author schweer
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class AlertingService extends VelocityServlet {
    
    /* (non-Javadoc)
     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
     */
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        System.out.println("reloading subs");
        ProfileStore.getInstance().restoreFromDatabase();
        try {
            Set subs = ProfileStore.getInstance().getAllSubscriptionsFor("andrea");
            System.out.println("reloaded " + subs.size() + " subs for andrea: ");
            for (Iterator iter = subs.iterator(); iter.hasNext();) {
                System.out.println(iter.next());
            }
        } catch (DatabaseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static final String[] actions = new String[] {
            "createSubscription",
            "deleteSubscription",
            "editSubscription",
            "showEvents",
            "showFeed",
            "listSubscriptions",
            "login",
            "register",
            "logout",
            "showLoginForm",
            "showRegistrationForm"
    };
    
    
    protected Template handleRequest(HttpServletRequest req,
            HttpServletResponse res, Context context) {
        
        String action = req.getParameter(Constants.ACTION_PARAM);

        Map args = req.getParameterMap();
        
        if (action != null && action.equals("receiveEvent")) {
            receiveEvent(args);
            return null;
        }
        
        args = normalise(args);
        
        if (action == null || !ArrayHelper.contains(actions, action)) {
            String title = "Unknown action";
            String message = "I don't know how to " + action;
            String details = "The only actions I know are " + ArrayHelper.toString(actions);
            return showError(context, message, details);
        }
                
        String templateString = "";
        
        try {
            Method method = AlertingService.class.getDeclaredMethod(action, new Class[] {Map.class, Context.class});
            templateString = (String) method.invoke(this, new Object[] {args, context});
        } catch (Exception e) {
            String message = "An error has occured, I couldn't do what you told me to do.";
            String details = e.getMessage() + " (" + e.getClass().getName() + "); "
            	+ e.getCause() 
            	+ " ; action is " + action;
            return showError(context, message, details);
        }
        
        Template template = null;
        try {
            template = getTemplate(templateString);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            try {
                error((HttpServletRequest)context.get(REQUEST), 
                        (HttpServletResponse)context.get(RESPONSE),
                        e);
            } catch (Exception e2) {
                // TODO Auto-generated catch block
                e2.printStackTrace();
            }
        }
        return template;
    }
    
    /**
     * @param arguments
     * @param context
     * @return the Velocity template to use
     * @throws Exception
     */
    public String createSubscription(Map arguments, Context context) throws Exception {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        if (!UserManager.getInstance().isLoggedIn(session)) {
            session.setAttribute("next_action", "createSubscription");
            return showLoginForm(arguments, context);
        }
        if (arguments.containsKey("next_page") && arguments.get("next_page").equals("finish")) {
            arguments.putAll(getPageArgsFromSession(session));
            arguments.put("username", session.getAttribute("username"));
            ProfileStore.getInstance().createSubscription(arguments);
            return listSubscriptions(arguments, context);
        } else {
            return showSubscriptionWizardPage(arguments, context, true);
        }
    }

    public String deleteSubscription(Map arguments, Context context) {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        if (!UserManager.getInstance().isLoggedIn(session)) {
            session.setAttribute("next_action", "deleteSubscription");
            return showLoginForm(arguments, context);
        }
        String subscriptionID = (String) arguments.get("subscriptionID");
        try {
            ProfileStore.getInstance().deleteSubscription(subscriptionID);
        } catch (DatabaseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return listSubscriptions(arguments, context);
    }
    
    public String editSubscription(Map arguments, Context context) throws Exception {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        if (!UserManager.getInstance().isLoggedIn(session)) {
            session.setAttribute("next_action", "editSubscription");
            return showLoginForm(arguments, context);
        }
        if (arguments.containsKey("next_page") && arguments.get("next_page").equals("finish")) {
            ProfileStore.getInstance().changeSubscription(arguments, session);
            return listSubscriptions(arguments, context);
        } else {
            return showSubscriptionWizardPage(arguments, context, false);
        }
    }
    
    public String showFeed(Map arguments, Context context) throws NumberFormatException, DatabaseException, SQLException {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        if (!UserManager.getInstance().isLoggedIn(session)) {
            session.setAttribute("next_action", "showFeed");
            return showLoginForm(arguments, context);
        }
        String subscriptionID = (String) arguments.get("subscriptionID");
        Integer subID = new Integer(subscriptionID);
        Set events = EventStore.getInstance().getEvents(subID);
        context.put("list", events);
        context.put("subscription", ProfileStore.getInstance().getSubscription(subID.intValue()));
        HttpServletResponse res = (HttpServletResponse) context.get(RESPONSE);
        res.setContentType("text/xml");
        return "feed.vm";
    }
    
    public String showEvents(Map arguments, Context context) throws NumberFormatException, DatabaseException, SQLException {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        if (!UserManager.getInstance().isLoggedIn(session)) {
            session.setAttribute("next_action", "showEvents");
            return showLoginForm(arguments, context);
        }
        String subscriptionID = (String) arguments.get("subscriptionID");
        Integer subID = new Integer(subscriptionID);
        Set events = EventStore.getInstance().getEvents(subID);
        context.put("list", events);
        context.put("subscription", ProfileStore.getInstance().getSubscription(subID.intValue()));
        return "events.vm";
    }
    
    public String showLoginForm(Map arguments, Context context) {
        return "login.vm";
    }
    
    public String showRegistrationForm(Map arguments, Context context) {
        return "register.vm";
    }

    public String listSubscriptions(Map arguments, Context context) {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        if (!UserManager.getInstance().isLoggedIn(session)) {
            session.setAttribute("next_action", "listSubscriptions");
            return showLoginForm(arguments, context);
        }
        String username = (String) session.getAttribute("username");
        context.put("title", "List of Subscriptions for " + username);
        try {
            Collection subscriptions = ProfileStore.getInstance().getAllSubscriptionsFor(username); 
            context.put("list", subscriptions);
            return "list.vm";
        } catch (DatabaseException de) {
            context.put("message", "couldn't get list of subscriptions for " + username);
            context.put("details", de.getMessage());
            return "error.vm";
        }
    }
    
    public String login(Map arguments, Context context) throws Exception {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        try {
            UserManager.getInstance().loginUser(arguments, session);
        } catch (UserManagementException e) {
            context.put("error", Boolean.TRUE);
            context.put("errormessage", e.getMessage());
            return showLoginForm(arguments, context); 
        }
        if (session.getAttribute("next_action") != null) {
            String nextAction = (String) session.getAttribute("next_action");
            Method method = AlertingService.class.getDeclaredMethod(nextAction, new Class[] {Map.class, Context.class});
            return (String) method.invoke(this, new Object[] {arguments, context});
        }
        return listSubscriptions(arguments, context);
    }
    
    public String register(Map arguments, Context context) throws Exception {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        try {
            UserManager.getInstance().createUser(arguments, session);
        } catch (UserManagementException e) {
            context.put("error", Boolean.TRUE);
            context.put("errormessage", e.getMessage());
            return showRegistrationForm(arguments, context);
        }
        return login(arguments, context);
    }
    
    public String logout(Map arguments, Context context) {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        Enumeration atts = session.getAttributeNames();
        while (atts.hasMoreElements()) {
            session.removeAttribute((String) atts.nextElement());
        }
        session.invalidate();
        return "general.vm";
    }
    
    public void receiveEvent(Map rawEvent) {
        Map event = new TreeMap();
        for (Iterator iter = rawEvent.keySet().iterator(); iter.hasNext();) {
            String key = (String) iter.next();
            if (key.equals("action")) continue; // we don't want this
            String[] value = (String[]) rawEvent.get(key);
            event.put(key, value[0]);
        }
        System.out.println("receiving event " + event);
        
        GreenstoneCommunicator gsComm = null;
        try {
            String hostID = (String) event.get(Constants.HOST_ID_FIELD);
            gsComm = new GreenstoneCommunicator(new URL(hostID));
        } catch (Exception e) {
            System.err.println("Can't communicate to Greenstone: " + e.getMessage());
            e.printStackTrace();
        }
        
        Set matchedSubscriptions = ProfileStore.getInstance().filter(event, gsComm);
        
        try {
            EventStore.getInstance().add(event, matchedSubscriptions);
            Notifier.getInstance().sendNotifications(event, matchedSubscriptions);
        } catch (Exception e) {
            System.err.println("Couldn't save events: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println(matchedSubscriptions.size() + " matching subscriptions: " + matchedSubscriptions);
    }

    /**
     * @param args
     */
    private Map normalise(Map args) {
        Map result = new TreeMap();
        for (Iterator iter = args.keySet().iterator(); iter.hasNext();) {
            String key = (String) iter.next();
            if (Predicate.isMultiValued(key) || key.equals("way")) {
                // multi-valued attributes
                String[] values = ((String[]) args.get(key));
                result.put(key, Arrays.asList(values));
            } else {
                String firstValue = ((String[])args.get(key))[0];
                result.put(key, firstValue);
            }
        }
        return result;
    }

    /**
     * @param context
     * @param message
     * @param details
     * @return
     */
    private Template showError(Context context, String message, String details) {
        context.put("title", "Error");
        context.put("message", message);
        context.put("details", details);
        try {
            return getTemplate("error.vm");
        } catch (Exception e) {
            try {
                super.error((HttpServletRequest)context.get("req"), 
                        (HttpServletResponse)context.get("res"), 
                        e);
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return null;
    }

    /**
     * @param arguments
     * @param context
     * @param create
     * @return
     * @throws Exception
     */
    private String showSubscriptionWizardPage(Map arguments, Context context, boolean create) throws Exception {
        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
        if (create) {
            context.put(Constants.ACTION_PARAM, "createSubscription");
        } else {
            // prefill from existing subscription
            context.put(Constants.ACTION_PARAM, "editSubscription");
            String subscriptionID = (String) arguments.get("subscriptionID");
            int subID = Integer.parseInt(subscriptionID);
            prefillFromSubscription(session, subID);
        }
        if (!arguments.containsKey("current_page")) {
            return "sub_type-details.vm";
        }
        
        String currentPage = (String) arguments.get("current_page");
        String direction = (String) arguments.get("next_page");
        
        if (arguments.containsKey(Constants.HOST_QUERY_FIELD)) {
            String hostQuery = (String) arguments.get(Constants.HOST_QUERY_FIELD);
            if (hostQuery != null && hostQuery.length() != 0)
                arguments.remove(Constants.HOST_ID_FIELD);
        }
        if (arguments.containsKey(Constants.COLLECTION_QUERY_FIELD)) {
            String collQuery = (String) arguments.get(Constants.COLLECTION_QUERY_FIELD);
            if (collQuery != null && collQuery.length() != 0)
                arguments.remove(Constants.COLLECTION_ID_FIELD);
        }
        
        // save page arguments
        savePageArgsToSession(currentPage, arguments, session);
        
        String nextPage = getNextPage(currentPage, direction);

        // fill prefill
        context.put("prefill", getPageArgsFromSession(nextPage, session));
        
        // fill preview
        context.put("preview", getPagePreview(nextPage, session));
        
        // get page-specific stuff
        if (nextPage.equals("host")) {
            String[] hostNames;
            try {
                GreenstoneCommunicator gsComm = new GreenstoneCommunicator();
                hostNames = gsComm.getHostNames();
            } catch (Exception e) {
                hostNames = new String[] { "localhost" };
            }
            context.put("hostnames", hostNames);
            session.setAttribute("hostnames", hostNames);
        } else if (nextPage.equals("collection")) {
            List hostNames = (List) arguments.get(Constants.HOST_ID_FIELD);
            if (hostNames == null || hostNames.isEmpty()) {
                hostNames = new Vector();
                // no host names -> use host query
                String[] hostsFromSession = (String[]) session.getAttribute("hostnames");
                if (hostsFromSession == null || hostsFromSession.length == 0) {
                    try {
                        GreenstoneCommunicator gsComm = new GreenstoneCommunicator();
                        hostsFromSession = gsComm.getHostNames();
                    } catch (Exception e) {
                        hostsFromSession = new String[] { "localhost" };
                    }
                }
                String hostQuery = (String) arguments.get(Constants.HOST_QUERY_FIELD);
                for (int i = 0; i < hostsFromSession.length; i++) {
                    if (hostsFromSession[i] != null && hostsFromSession[i].indexOf(hostQuery) >= 0) {
                        hostNames.add(hostsFromSession[i]);
                    }
                }
            }
            
            Map collNames = new TreeMap();
            for (Iterator iter = hostNames.iterator(); iter.hasNext();) {
                String host = (String) iter.next();
                Set collNamesForHost = new TreeSet();   
                try {
                    URL url = new URL("http://" + host + ":8080/soap/servlet/rpcrouter");
                    GreenstoneCommunicator gsComm = new GreenstoneCommunicator(url);
                    collNamesForHost.addAll(Arrays.asList(gsComm.getCollectionNames()));
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }	
                collNames.put(host, collNamesForHost);
            }
            context.put("collectionnames", collNames);
            context.put("hostnames", hostNames);
        }
        
        return "sub_" + nextPage + ".vm";
    }

    /**
     * @param session
     * @param subID
     */
    private void prefillFromSubscription(HttpSession session, int subID) {
        Subscription sub = ProfileStore.getInstance().getSubscription(subID);
        Map typeArgs = new HashMap();
        // TODO really fill stuff
        savePageArgsToSession("type-details", typeArgs, session);
        Map hostArgs = new HashMap();
        savePageArgsToSession("host", hostArgs, session);
        Map collArgs = new HashMap();
        savePageArgsToSession("collection", collArgs, session);
        Map notificationArgs = new HashMap();
        savePageArgsToSession("notification", notificationArgs, session);
    }

    /**
     * @param nextPage
     * @param session
     * @return
     */
    private Map getPagePreview(String nextPage, HttpSession session) {
        Map preview = new TreeMap();
        if (nextPage.equals("type-details")) {
            return preview;
        }
        preview.putAll(getPageArgsFromSession("type-details", session));
        if (nextPage.equals("host")) {
            return preview;
        }
        preview.putAll(getPageArgsFromSession("host", session));
        if (nextPage.equals("collection")) {
            return preview;
        }
        preview.putAll(getPageArgsFromSession("collection", session));
        return preview;
    }

    /**
     * @param currentPage
     * @param direction
     * @return
     * @throws Exception
     */
    private String getNextPage(String currentPage, String direction) throws Exception {
        String nextPage;
        if (currentPage.equals("host") && direction.equals("back")) {
            nextPage = "type-details";
        } else if (currentPage.equals("type-details") || (currentPage.equals("collection") && direction.equals("back"))) {
            nextPage = "host";
        } else if (currentPage.equals("host") || (currentPage.equals("notification") && direction.equals("back"))) {
            nextPage = "collection";
        } else if (currentPage.equals("collection")) {
            nextPage = "notification";
        } else {
            throw new Exception("unknown combination of currentPage=" + currentPage + " and nextPage=" + direction);
        }
        return nextPage;
    }

    /**
     * @param page
     * @param session
     * @return 
     */
    private Map getPageArgsFromSession(String page, HttpSession session) {
        Map pageArgs = (Map) session.getAttribute("page_args");
        if (pageArgs == null || !pageArgs.containsKey(page))
            return new TreeMap();
        return (Map) pageArgs.get(page);
    }

    private Map getPageArgsFromSession(HttpSession session) {
        Map result = new TreeMap();
        Map pageArgs = (Map) session.getAttribute("page_args");
        if (pageArgs != null) {
            for (Iterator iter = pageArgs.values().iterator(); iter.hasNext();) {
                Map args = (Map) iter.next();
                result.putAll(args);
            }
        }
        return result;
    }
    
    /**
     * @param page
     * @param arguments
     * @param session
     */
    private void savePageArgsToSession(String page, Map arguments, HttpSession session) {
        Map pageArgs = (Map) session.getAttribute("page_args");
        if (pageArgs == null) {
            pageArgs = new TreeMap();
        }
        pageArgs.put(page, arguments);
        session.setAttribute("page_args", pageArgs);
    }
}
