/**
 * JaDOrT: JASMINe Deployment Orchestration Tool
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Copyright (C) 2008-2009 France Telecom R&D
 * Contact: jasmine@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * 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 GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: ModProxyBalancer.java 5638 2009-11-24 17:24:39Z alitokmen $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.action.modProxyBalancer;

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.codec.binary.Base64;

/**
 * The mod_proxy_balancer manager.
 * 
 * @author Malek Chahine
 * @author Remy Bresson
 * @author S. Ali Tokmen
 */
public class ModProxyBalancer {

    private String login;

    private String password;

    private String url;

    // keep this public for tests
    public List<Balancer> balancers;

    // keep this public for tests
    public String nonce;

    /**
     * Constructor.
     * 
     * @param url : mod_proxy_balancer manager url
     * @param login : login of mod_proxy_balancer manager web application
     * @param password : password of mod_proxy_balancer manager web application
     */
    public ModProxyBalancer(final String url, final String login, final String password) {
        this.url = url;
        this.login = login;
        this.password = password;
    }

    /**
     * Constructor.
     * 
     * @param url : mod_proxy_balancer manager url
     */
    public ModProxyBalancer(final String url) {
        this(url, null, null);
    }

    protected void updateBalancerInfo() throws Exception {
        String response = this.openConnection();
        this.parseAndUpdateModProxyBalancer(response);
    }

    /**
     * parse the html response and update the modProxyBalacncer Info
     * 
     * @param response : the html response to be parsed
     */
    // keep this public for tests
    public void parseAndUpdateModProxyBalancer(String response) {
        List<Balancer> balancers = new ArrayList<Balancer>();

        String balancerName = null;
        List<Worker> workers = null;

        while (true) {
            // the <h3> tags separate balancer names,
            // each balancer member is in a <tr> tag
            int h3Start = response.indexOf("<h3>");
            int tdStart = response.indexOf("<td><a ");

            if (h3Start > -1 && (tdStart < 0 || h3Start < tdStart)) {
                if (balancerName != null && workers != null) {
                    balancers.add(new Balancer(balancerName, workers));
                }

                response = response.substring(h3Start + 4);
                int balancerURLStart = response.indexOf("balancer://");
                int h3End = response.indexOf("</h3>");
                String s = response.substring(balancerURLStart + 11, h3End);

                if (s.endsWith("</a>")) {
                    // Old mod_proxy_balancer format
                    balancerName = s.substring(0, s.lastIndexOf('<'));
                } else {
                    balancerName = s;
                }
                workers = new ArrayList<Worker>();

                response = response.substring(h3End + 4);
            } else if (tdStart > -1 && (h3Start < 0 || tdStart < h3Start)) {
                if (balancerName == null) {
                    throw new IllegalStateException("Worker's row (<tr> element) is before the balancer's title (<h3> element)");
                }

                response = response.substring(tdStart + 7);
                int tagEnd = response.indexOf("\">");
                int aEnd = response.indexOf("</a></td>");

                int nonceIndex = response.indexOf("&nonce=");
                if (nonceIndex > 0) {
                    this.nonce = response.substring(nonceIndex + 7, tagEnd);
                } else {
                    this.nonce = null;
                }
                String workerName = response.substring(tagEnd + 2, aEnd);
                response = response.substring(aEnd);

                for (int i = 0; i < 4; i++) {
                    response = response.substring(response.indexOf("</td><td>") + 9);
                }
                String status = response.substring(0, response.indexOf("</td>"));
                try {
                    // New field called "Set" added in rev. 427172. See:
                    // http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/proxy/mod_proxy_balancer.c?view=diff&r1=427171&r2=427172
                    Integer.parseInt(status);
                    response = response.substring(response.indexOf("</td><td>") + 9);
                    status = response.substring(0, response.indexOf("</td>"));
                } catch (NumberFormatException ignored) {
                    // This is Apache before revision 427172 (2.2.3 and older)
                }
                workers.add(new Worker(workerName, !status.startsWith("Dis")));

                response = response.substring(response.indexOf("</td></tr>") + 8);
            } else {
                break;
            }
        }

        if (balancerName != null && workers != null) {
            balancers.add(new Balancer(balancerName, workers));
        }

        this.balancers = balancers;
    }

    /**
     * open the http connection and send the request to ModProxyBalancer url
     * 
     * @return the html content of the requested page
     */
    protected String openConnection() throws Exception {
        return this.openConnection(null);
    }

    /**
     * open the http connection and send the request to ModProxyBalancer url
     * 
     * @param param : parameters that will be passed to the url
     * @return the html content of the requested page
     * @throws Exception
     */
    protected String openConnection(final String param) throws Exception {
        HttpURLConnection con = null;
        String response = null;

        if (param != null) {
            con = (HttpURLConnection) new URL(this.url + "?" + param).openConnection();
        } else {
            con = (HttpURLConnection) new URL(this.url).openConnection();
        }
        con.setRequestMethod("GET");

        if (this.login != null) {
            Base64 enc = new Base64();
            String authData = this.login + ":" + this.password;
            byte[] base64AuthData = enc.encode(authData.getBytes());
            con.setRequestProperty("Authorization", "Basic " + new String(base64AuthData, "UTF-8"));
        }

        int responseCode = con.getResponseCode();

        if (responseCode == HttpURLConnection.HTTP_OK) {
            InputStream is = con.getInputStream();
            byte[] b = new byte[is.available()];
            int nbOctetsLus = is.read(b);
            response = new String(b, 0, nbOctetsLus);
        } else {
            throw new Exception("Connection error: " + con.getResponseMessage());
        }

        return response;
    }

    public String getLogin() {
        return this.login;
    }

    public void setLogin(final String login) {
        this.login = login;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(final String password) {
        this.password = password;
    }

    public String getUrl() {
        return this.url;
    }

    public void setUrl(final String url) {
        this.url = url;
    }

    public List<Balancer> getBalancers() throws Exception {
        this.updateBalancerInfo();
        return this.balancers;
    }

    /**
     * Activate a worker
     * 
     * @param balancerName : the name of the target balancer
     * @param workerUrl : the url of the target worker that will be activated
     * @throws Exception
     */
    public void activateWorker(final String balancerName, final String workerUrl) throws Exception {
        this.updateBalancerInfo();

        StringBuffer param = new StringBuffer();
        param.append("dw=Enable");
        param.append("&w=");
        param.append(URLEncoder.encode(workerUrl, "UTF-8"));
        param.append("&b=");
        param.append(URLEncoder.encode(balancerName, "UTF-8"));
        if (this.nonce != null) {
            param.append("&nonce=");
            param.append(this.nonce);
        }

        String response = this.openConnection(param.toString());
        this.parseAndUpdateModProxyBalancer(response);
    }

    /**
     * Deactivate a worker
     * 
     * @param balancerName : the name of the target balancer
     * @param workerUrl : the url of the target worker that will be deactivated
     * @throws Exception
     */
    public void deactivateWorker(final String balancerName, final String workerUrl) throws Exception {
        this.updateBalancerInfo();

        StringBuffer param = new StringBuffer();
        param.append("dw=Disable");
        param.append("&w=");
        param.append(URLEncoder.encode(workerUrl, "UTF-8"));
        param.append("&b=");
        param.append(URLEncoder.encode(balancerName, "UTF-8"));
        if (this.nonce != null) {
            param.append("&nonce=");
            param.append(this.nonce);
        }

        String response = this.openConnection(param.toString());
        this.parseAndUpdateModProxyBalancer(response);
    }
}
