/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.xwiki.test.webstandards.framework;

import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xwiki.model.EntityType;
import org.xwiki.model.internal.reference.DefaultStringEntityReferenceResolver;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReferenceResolver;
import org.xwiki.validator.Validator;

import com.xpn.xwiki.plugin.packaging.Package;

public class AbstractValidationTest extends TestCase
{
    protected HttpClient client;

    protected Target target;

    protected String credentials;

    private static final EntityReferenceResolver<String> RESOLVER = new DefaultStringEntityReferenceResolver();

    public AbstractValidationTest(String name, Target target, HttpClient client, String credentials)
    {
        super(name);

        this.target = target;
        this.client = client;
        this.credentials = credentials;
    }

    protected GetMethod createGetMethod() throws UnsupportedEncodingException
    {
        GetMethod getMethod = null;

        if (this.target instanceof DocumentReferenceTarget) {
            DocumentReferenceTarget documentReferenceTarget = (DocumentReferenceTarget) this.target;
            getMethod =
                new GetMethod("http://127.0.0.1:8080/xwiki/bin/view/"
                    + URLEncoder.encode(documentReferenceTarget.getDocumentReference().getLastSpaceReference()
                        .getName(), "UTF-8") + "/"
                    + URLEncoder.encode(documentReferenceTarget.getDocumentReference().getName(), "UTF-8"));
        } else if (this.target instanceof URLPathTarget) {
            String urlPath = ((URLPathTarget) this.target).getUrlPath();

            if (urlPath.startsWith("http://")) {
                getMethod = new GetMethod(urlPath);
            } else {
                getMethod = new GetMethod("http://127.0.0.1:8080" + urlPath);
            }
        }

        return getMethod;
    }

    protected GetMethod getResponse() throws Exception
    {
        GetMethod method = createGetMethod();

        method.setFollowRedirects(true);

        if (this.credentials != null) {
            method.setDoAuthentication(true);
            method.addRequestHeader("Authorization", "Basic "
                + new String(Base64.encodeBase64(this.credentials.getBytes())));
        }

        // Execute the method.
        try {
            int statusCode = this.client.executeMethod(method);

            assertEquals("Method failed: " + method.getStatusLine(), HttpStatus.SC_OK, statusCode);

            // Read the response body.
            return method;
        } catch (Exception e) {
            method.releaseConnection();

            throw e;
        }
    }

    protected byte[] getResponseBody() throws Exception
    {
        GetMethod method = getResponse();

        try {
            // Read the response body.
            return method.getResponseBody();
        } finally {
            method.releaseConnection();
        }
    }

    public static Test suite(Class< ? extends AbstractValidationTest> validationTest, Validator validator)
        throws Exception
    {
        TestSuite suite = new TestSuite();

        HttpClient adminClient = new HttpClient();
        Credentials defaultcreds = new UsernamePasswordCredentials("Admin", "admin");
        adminClient.getState().setCredentials(AuthScope.ANY, defaultcreds);

        addXarFiles(validationTest, validator, suite, adminClient);
        addURLsForAdmin(validationTest, validator, suite, adminClient);

        HttpClient guestClient = new HttpClient();

        addURLsForGuest(validationTest, validator, suite, guestClient);

        return suite;
    }

    protected static void addURLsForAdmin(Class< ? extends AbstractValidationTest> validationTest, Validator validator,
        TestSuite suite, HttpClient client) throws Exception
    {
        addURLs("urlsToTestAsAdmin", validationTest, validator, suite, client, "Admin:admin");
    }

    protected static void addURLsForGuest(Class< ? extends AbstractValidationTest> validationTest, Validator validator,
        TestSuite suite, HttpClient client) throws Exception
    {
        addURLs("urlsToTestAsGuest", validationTest, validator, suite, client, null);
    }

    protected static void addURLs(String property, Class< ? extends AbstractValidationTest> validationTest,
        Validator validator, TestSuite suite, HttpClient client, String credentials) throws Exception
    {
        String urlsToTest = System.getProperty(property);

        if (urlsToTest != null) {
            for (String url : urlsToTest.split("\\s")) {
                if (StringUtils.isNotEmpty(url)) {
                    suite.addTest(validationTest.getConstructor(Target.class, HttpClient.class, Validator.class,
                        String.class).newInstance(new URLPathTarget(url), client, validator, credentials));
                }
            }
        }
    }

    protected static void addXarFiles(Class< ? extends AbstractValidationTest> validationTest, Validator validator,
        TestSuite suite, HttpClient client) throws Exception
    {
        String path = System.getProperty("localRepository") + "/" + System.getProperty("pathToXWikiXar");
        String patternFilter = System.getProperty("documentsToTest");

        for (DocumentReference documentReference : readXarContents(path, patternFilter)) {
            suite.addTest(validationTest.getConstructor(Target.class, HttpClient.class, Validator.class, String.class)
                .newInstance(new DocumentReferenceTarget(documentReference), client, validator, "Admin:admin"));
        }
    }

    protected static List<DocumentReference> readXarContents(String fileName, String patternFilter) throws Exception
    {
        FileInputStream fileIS = new FileInputStream(fileName);
        ZipInputStream zipIS = new ZipInputStream(fileIS);

        ZipEntry entry;
        Document tocDoc = null;
        while ((entry = zipIS.getNextEntry()) != null) {
            if (entry.getName().compareTo(Package.DefaultPackageFileName) == 0) {
                SAXReader reader = new SAXReader();
                tocDoc = reader.read(zipIS);
                break;
            }
        }

        if (tocDoc == null) {
            return Collections.emptyList();
        }

        List<DocumentReference> result = new ArrayList<DocumentReference>();

        Pattern pattern = patternFilter == null ? null : Pattern.compile(patternFilter);

        Element filesElement = tocDoc.getRootElement().element("files");
        List<Element> fileElementList = filesElement.elements("file");
        for (Element el : fileElementList) {
            String docFullName = el.getStringValue();

            if (pattern == null || pattern.matcher(docFullName).matches()) {
                result.add(new DocumentReference(RESOLVER.resolve("xwiki:" + docFullName, EntityType.DOCUMENT)));
            }
        }

        return result;
    }
}

@@ -3820,16 +3820,64 @@
 s(true);
+%0A        method.getParams().setSoTimeout(10000);
 %0A%0A      

Add a timeout to validity tests