package pt.utl.ist.scripts.tests.loadTests;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.contrib.ssl.EasySSLProtocolSocketFactory;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.protocol.DefaultProtocolSocketFactory;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.lang.StringUtils;

import pt.utl.ist.fenix.tools.util.FileUtils;
import pt.utl.ist.fenix.tools.util.StringAppender;

public class FenixHttpClient {

    public class Response {
        public String response;
        public Collection<String> layoutLinks = new ArrayList<String>();
        public Collection<String> portalLinks = new ArrayList<String>();
        public Collection<String> actionLinks = new ArrayList<String>();
        final ResponseValidator responseValidator;

        private Response(String string, final ResponseValidator responseValidator, final String username) {
            response = string;
            this.responseValidator = responseValidator;
            for (int i = response.indexOf("href="); i != -1 && i < response.length(); i = response.indexOf("href=", i)) {
                final char quote = response.charAt(i + 5);
                final int endOfLink = response.indexOf(quote, i + 6);
                String link = response.substring(i + 6, endOfLink).replaceAll("&amp;", "&").replace(" ", "%20");
                if (link.startsWith("enrollStudentInShifts.do")) {
                    link = StringAppender.append(applicationPath, "/student/", link);
                }
                if (link.startsWith("studentShiftEnrollmentManager.do")) {
                    link = StringAppender.append(applicationPath, "/student/", link);
                }
                if (!alreadyProcessedRequests.contains(link)) {
                    alreadyProcessedRequests.add(link);
                    if (link.endsWith("logoff.do")) {
                        // Don't logoff
                    } else if (link.endsWith(".css") || link.endsWith(".gif") || link.endsWith(".jpg") || link.endsWith(".js")) {
                        layoutLinks.add(link);
                        folowLink(link, null, username);
                    } else if (link.indexOf("dotIstPortal.do") != -1) {
                        portalLinks.add(link);
                        // folowLink(link, responseValidator, username);
                    } else if (link.startsWith("mailto:") || link.startsWith("http://")) {
                        // nothing to do with mails
                    } else if (link.indexOf("publico/") != -1 || link.indexOf("person/organizationalStructure") != -1) {
                        // folowLink(link, responseValidator, username);
                        // actionLinks.add(link);
                        // don't process public pages
                    } else if (link.indexOf(".do") != -1 || link.indexOf(".faces") != -1) {
                        // folowLink(link, responseValidator, username);
                        actionLinks.add(link);
                    } else {
                        // not sure what this is... do nothing.
                    }
                }
                i = endOfLink;
            }
        }

        public boolean isValidResponse(final String path) {
            if (responseValidator != null) {
                if (responseValidator.isValid(path, this)) {
                    numberSuccessfullRequests++;
                    numberSuccessfullRequestsTotal++;
                    return true;
                } else {
                    numberUnSuccessfullRequests++;
                    numberUnSuccessfullRequestsTotal++;
                    return false;
                }
            }
            numberSuccessfullRequests++;
            numberSuccessfullRequestsTotal++;
            return true;
        }

        private void folowLink(final String link, ResponseValidator responseValidator, final String username) throws Error {
            final PostMethod getMethod = createGetMethod(link);
            try {
                queryHost(getMethod, responseValidator, username);
            } catch (Exception ex) {
                throw new Error(ex);
            }
        }

        public void printLinks() {
            printLinks("Layout", layoutLinks);
            printLinks("Portal", portalLinks);
            printLinks("Actions", actionLinks);
        }

        private void printLinks(final String title, final Collection<String> links) {
            System.out.println(title);
            for (final String link : links) {
                System.out.print("\t");
                System.out.println(link);
            }
        }
    }

    public static abstract class ResponseValidator {
        abstract public boolean isValid(final String path, final Response response);
    }

    private final HttpClient httpClient;

    private final String applicationPath;

    private final Set<String> alreadyProcessedRequests = new HashSet<String>();

    private long serverResponseTime = 0;
    static long serverResponseTimeTotal = 0;

    private int numberRequests = 0;
    static int numberRequestsTotal = 0;

    private int numberSuccessfullRequests = 0;
    static int numberSuccessfullRequestsTotal = 0;

    private int numberUnSuccessfullRequests = 0;
    static int numberUnSuccessfullRequestsTotal = 0;

    private String password;

    public FenixHttpClient(final String host, final int port, final String applicationPath, String password) {
        httpClient = new HttpClient();
        final Protocol protocol;
        if (port == 80 || port == 8080) {
            protocol = new Protocol("http", new DefaultProtocolSocketFactory(), port);
        } else if (port == 443 || port == 8443) {
            protocol = new Protocol("https", new EasySSLProtocolSocketFactory(), port);
        } else {
            throw new Error("Unknown protocol for port: " + port);
        }

        httpClient.getHostConfiguration().setHost(host, port, protocol);
        httpClient.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
        httpClient.setConnectionTimeout(30000000);
        httpClient.setStrictMode(true);
        this.applicationPath = applicationPath == null ? "" : applicationPath;
        this.password = password;
    }

    private String constructUrl(String localAppPath, boolean addAppPrefix) {
        final StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(httpClient.getHostConfiguration().getHostURL());
        final int port = httpClient.getHostConfiguration().getPort();
        if (port != 80 && port != 443) {
            stringBuilder.append(':');
            stringBuilder.append(port);
        }
        if (addAppPrefix) {
            stringBuilder.append(applicationPath);
        }
        stringBuilder.append(localAppPath);
        return stringBuilder.toString();
    }

    public PostMethod createPostMethod(final String localAppPath) {
        final PostMethod postMethod = new PostMethod(constructUrl(localAppPath, true));
        return postMethod;
    }

    public PostMethod createGetMethod(final String path) {
        final PostMethod postMethod = new PostMethod(path);
        int questionMark = path.indexOf('?');
        if (questionMark >= 0) {
            for (int i = questionMark + 1; i != -1 && i < path.length();) {
                final int equals = path.indexOf('=', i);
                final int seperator = path.indexOf("&amp;", i);
                final int endOfValue = seperator == -1 ? path.length() : Math.min(seperator, path.length());
                final String parameterName = path.substring(i, equals);
                final String parameterValue = path.substring(equals + 1, endOfValue);
                postMethod.addParameter(parameterName, parameterValue);
                final int x = seperator == -1 ? path.length() : endOfValue + 5;
                i = seperator == -1 ? path.length() : endOfValue + 5;
            }
        }
        return postMethod;
    }

    private ByteArrayOutputStream performQueryHost(final HttpMethod httpMethod) throws HttpException, IOException {
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        final long start = System.currentTimeMillis();
        httpClient.executeMethod(httpMethod);
        FileUtils.copy(httpMethod.getResponseBodyAsStream(), outputStream);
        final long end = System.currentTimeMillis();
        final long time = end - start;
        serverResponseTime += time;
        serverResponseTimeTotal += time;
        numberRequests++;
        numberRequestsTotal++;
        return outputStream;
    }

    public Response queryHost(final HttpMethod postMethod, final ResponseValidator responseValidator, final String username)
            throws HttpException, IOException {
        final Response response = new Response(performQueryHost(postMethod).toString(), responseValidator, username);
        final String path = postMethod.getPath();
        final boolean isvalid = response.isValidResponse(path);
        persistResponse(response.response, username, numberRequests, path, isvalid);
        // System.out.println("Querying: " + postMethod.getPath() + " Passed: "
        // + isvalid);
        // if (postMethod.getPath().endsWith(".do")) {
        // System.out.println(response.response.substring(0, Math.max(100,
        // response.response.length())));
        // }
        return response;
    }

    private static final String outputDir = "/tmp/loadTests/" + Long.toString(System.currentTimeMillis());
    static {
        final File file = new File(outputDir);
        file.mkdirs();
    }

    private void persistResponse(final String response, final String username, final int numberRequest, final String path,
            final boolean isvalid) throws IOException {
        final String filename;
        if (path.endsWith(".css") || path.endsWith(".gif") || path.endsWith(".jpg") || path.endsWith(".js")) {
            final String dirname = outputDir + StringUtils.substringBeforeLast(path, "/");
            final File dir = new File(dirname);
            dir.mkdirs();
            filename = StringAppender.append(outputDir, "/", path);
        } else {
            final String simplePath = StringUtils.substringBeforeLast(path.replace('/', '_'), ".");
            final String isEmpty = response.length() == 0 ? "empty_" : "";
            final String isInvalid = !isvalid ? "invalid_" : "";
            filename =
                    StringAppender.append(outputDir, "/", isEmpty, isInvalid, username, "_", getFourDigitString(numberRequest),
                            "_", simplePath, ".html");
        }
        final FileOutputStream fileOutputStream = new FileOutputStream(filename);
        fileOutputStream.write(response.getBytes());
        fileOutputStream.close();
    }

    private String getFourDigitString(final int numberRequest) {
        if (numberRequest < 10) {
            return "0000" + numberRequest;
        }
        if (numberRequest < 100) {
            return "000" + numberRequest;
        }
        if (numberRequest < 1000) {
            return "00" + numberRequest;
        }
        if (numberRequest < 10000) {
            return "0" + numberRequest;
        }
        return Integer.toString(numberRequest);
    }

    public Response login(final String username, final ResponseValidator responseValidator) throws HttpException, IOException {
        final PostMethod postMethod = createPostMethod("/login.do");
        System.out.println("Username: " + username + " Password: " + password);
        postMethod.addParameter("username", username);
        postMethod.addParameter("password", password);
        return queryHost(postMethod, responseValidator, username);

        // final PostMethod getMethod = createGetMethod("/siteMap.do");
        // try {
        // return queryHost(getMethod, responseValidator);
        // } catch (Exception ex) {
        // throw new Error(ex);
        // }

    }

    public void report() {
        System.out.println("Number of Requests  :::   Total: " + numberRequests + "     Clicks:::   Successfull: "
                + numberSuccessfullRequests + "    Failures: " + numberUnSuccessfullRequests);
        System.out.println("Server response time:::   Total: " + serverResponseTime + "ms    Avg: "
                + (serverResponseTime / numberRequests) + "ms");
    }

}
