package com.n2bb.sysmonui.availability;

import java.util.*;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.net.MalformedURLException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import com.n2bb.util.N2bbSettings;
import com.n2bb.util.N2bbException;
import com.n2bb.web.util.ListPage;

/**
 * Facade for communication with availibility server.
 *
 * @author kmatsuoka
 * @version $Id: AvailabilityFacade.java,v 1.1 2006/08/30 18:34:04 kmehta Exp $
 */
public class AvailabilityFacade {
    private static final Log log = LogFactory.getLog(N2bbSettings.N2BB_LOG);

    private static AvailabilityFacade instance;

    private AvailabilityFacade() {
        // no-op
    }

    /** Gets the singleton instance of this class. */
    public static synchronized AvailabilityFacade getInstance() {
        if (instance == null) {
            instance = new AvailabilityFacade();
        }
        return instance;
    }

    /** Gets a page of server info */
    public ListPage getServerPage(int pageNumber) throws N2bbException {
        return new ListPage(getServerList(), 1, 1);
    }

    private List getServerList() throws N2bbException {
        List servers = new ArrayList();
        Document statusXML = getStatusXMLDocument();
        Element root = statusXML.getDocumentElement();
        NodeList videos = root.getElementsByTagName("Server");
        for (int i = 0; i < videos.getLength(); ++i) {
            Element server = (Element) videos.item(i);
            servers.add(
                    new ServerBean(
                            server.getAttribute("name"),
                            server.getAttribute("curHost"),
                            Integer.parseInt(server.getAttribute("opState")),
                            Integer.parseInt(server.getAttribute("reqState")),
                            server.getAttribute("startOid"),
                            server.getAttribute("endOid")
                    )
            );
        }
        Collections.sort(servers, new Comparator() {
            public int compare(Object o1, Object o2) {
                return ((ServerBean) o1).getName().compareTo(((ServerBean) o2).getName());
            }
        });
        return servers;
    }

    /** Requests a change in server state */
    public void requestServerState(String name, int newReqState) throws N2bbException {
        try {
            String urlString = "http://availmgr:8001/get?method=setRequestedState" +
                    "&checkName=" + URLEncoder.encode(name, "UTF-8") +
                    "&requestedState=" + newReqState;
            // TODO: handle exceptions
            // this kicks of an asynchronous method on the HA server, so
            // no result is expected on success
            getURLContent(urlString);
        }
        catch (UnsupportedEncodingException e) {  // no reason to expect this to happen
            log.error("", e);
            throw new N2bbException("error.unexpected", e.getMessage());
        }
    }

    /** Gets server info. */
    public ServerBean getServer(String name) throws N2bbException {
        return findServer(getServerList(), name);
    }

    private ServerBean findServer(List servers, String name) {
        for (Iterator iterator = servers.iterator(); iterator.hasNext();) {
            ServerBean serverBean = (ServerBean) iterator.next();
            if (serverBean.getName().equals(name)) {
                return serverBean;
            }
        }
        return null;
    }

    /** Moves server to a new host */
    public void moveServer(String name, String newHost) throws N2bbException {
        try {
            String urlString = "http://availmgr:8001/get?method=moveServer" +
                    "&checkName=" + URLEncoder.encode(name, "UTF-8") +
                    "&hostName=" + URLEncoder.encode(newHost, "UTF-8");
            // TODO: handle exceptions
            // this kicks of an asynchronous method on the HA server, so
            // no result is expected on success
            getURLContent(urlString);
        }
        catch (UnsupportedEncodingException e) {  // no reason to expect this to happen
            log.error("", e);
            throw new N2bbException("error.unexpected", e.getMessage());
        }
    }

    /** Gets list of hosts available for moving a server to. */
    public List getAvailableHosts(ServerBean server) throws N2bbException {
        List hosts = new ArrayList(getHosts()); // to support removal
        for (Iterator iterator = hosts.iterator(); iterator.hasNext();) {
            HostBean hostBean = (HostBean) iterator.next();
            if (hostBean.getName().equals(server.getHostName())) {
                iterator.remove();
            }
        }
        Collections.sort(hosts, new Comparator() {
            public int compare(Object o1, Object o2) {
                return ((HostBean) o1).getName().compareTo(((HostBean) o2).getName());
            }
        });
        return hosts;
    }

    /** Gets host list. */
    public List getHosts() throws N2bbException {
        List hosts = getHostList();
        return hosts;
    }

    private List getHostList() throws N2bbException {
        List hosts = new ArrayList();
        Document statusXML = getStatusXMLDocument();
        Element root = statusXML.getDocumentElement();
        NodeList videos = root.getElementsByTagName("Host");
        for (int i = 0; i < videos.getLength(); ++i) {
            Element server = (Element) videos.item(i);
            hosts.add(
                    new HostBean(
                            server.getAttribute("name"),
                            server.getAttribute("startOid"),
                            server.getAttribute("endOid"),
                            Integer.parseInt(server.getAttribute("opState"))
                    )
            );
        }
        return hosts;
    }

    /**
     * Gets HA status XML document.
     */
    private Document getStatusXMLDocument() throws N2bbException {
        try {
            return getXMLDocument(getURLContent("http://availmgr:8001/get?method=getStatusUI"));
        }
        catch (ParserConfigurationException e) {
            log.error("", e);
            throw new N2bbException("sysmonui.error.status.xml");
        }
        catch (SAXException e) {
            log.error("", e);
            throw new N2bbException("sysmonui.error.status.xml");
        }
        catch (IOException e) {
            log.error("", e);
            throw new N2bbException("sysmonui.error.status.io");
        }
    }

    /**
     * Gets contents of URL as a string.
     */
    private String getURLContent(String urlString) {
        String response = "";
        try {
            URL url = new URL(urlString);
            Object content = url.getContent();
            if (content instanceof InputStream) {
                BufferedReader reader =
                    new BufferedReader(
                        new InputStreamReader((InputStream) content));
                String s;
                while ((s = reader.readLine()) != null) {
                    response += s;
                }
                response = response.trim();
                log.debug("response = " + response);
            } else {
                log.error(
                    "Got unknown URL content type: "
                        + content.getClass());
            }
        }
        catch (Exception e) {
            log.error("", e);
        }
        return response;
    }

    /** Gets XML document from input stream */
    private static Document getXMLDocument(InputStream xml)
            throws ParserConfigurationException, SAXException, IOException {
        return getDocumentBuilder().parse(xml);
    }

    /** Gets XML document from string */
    private static Document getXMLDocument(String xmlString)
            throws ParserConfigurationException, SAXException, IOException {
        return getXMLDocument(new ByteArrayInputStream(xmlString.getBytes("UTF-8")));
    }

    /** Gets default document builder */
    private static DocumentBuilder getDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setValidating(false);
        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
        return documentBuilder;
    }

}
