/*
 * $Header: /home/cvspublic/jakarta-slide/testsuite/testsuite/junit/src/org/apache/slide/testsuite/testtools/tprocessor/XMLResponseBodyAssert.java,v 1.25 2003/09/09 07:05:46 juergen Exp $
 * $Revision: 1.25 $
 * $Date: 2003/09/09 07:05:46 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Slide", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.slide.testsuite.testtools.tprocessor;

//java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.apache.slide.testsuite.testtools.tutil.XConf;
import org.apache.webdav.lib.methods.XMLResponseMethodBase;
import org.jdom.Element;



public abstract class XMLResponseBodyAssert extends ResponseBodyAssert {

    // debugging purpose only
    private boolean extendedPrintlnRequest = false;


    protected XMLResponseMethodBase   method;
    protected Element                 expectedXmlTree;

    /** constructer
     * @param WebdavMethodBase Element
     */
    public XMLResponseBodyAssert(XMLResponseMethodBase method, Element expectedXmlTree, XConf xconf, XMLOutput xmlresult, List expectedResponseCodes){

        super(xconf, method.getName(), xmlresult, expectedResponseCodes);
        this.method = method;
        this.expectedXmlTree = expectedXmlTree;
        this.expectedXmlTree = expectedXmlTree;

    }



//
//
    public org.jdom.Document responseDocument(){
        org.jdom.Document jdom = null;
        try{
            org.w3c.dom.Document dDoc = method.getResponseDocument();
            if (dDoc == null) return null;
            jdom = (new org.jdom.input.DOMBuilder().build(dDoc));
        } catch(Exception e){
            e.printStackTrace();
        }
        return jdom;
    }


    /**
     * checks if Expected Response and Response are same
     */
    public boolean assertResponseBody(){

        // check if both expected and received bodies are not available
        if ((expectedXmlTree == null) &&
                ((responseDocument() == null) || responseDocument().getRootElement() == null) )
            return true;


        if (extendedPrintlnRequest) {
            // some debugging output
            // START
            System.out.println("#########################");
            System.out.println("####### method = " + method.getName() +
                                   " response received = " + method.getStatusCode() +
                                   " expected = " + listToString(expectedResponseCodes));
            System.out.println("####### expected ########");
            if (expectedXmlTree != null)
                System.out.println(TProcessors.getElementString(expectedXmlTree));
            else
                System.out.println("####### NOT PRESENT");
            System.out.println("####### received ########");
            if (responseDocument() != null && responseDocument().getRootElement() != null)
                System.out.println(TProcessors.getElementString(responseDocument().getRootElement()));
            else
                System.out.println("####### NOT PRESENT");
            System.out.println("#########################");
            System.out.println();
            // STOP
            // some debugging output
        }



        // check if either expected and received bodies are not available and
        // the expected response code list does not contain more than one response code
        if ( (expectedXmlTree == null ||
                  responseDocument() == null ||
                  responseDocument().getRootElement()== null)) {

            if (expectedResponseCodes.size() <= 1) {
                xmlresult.writeElementStart("XMLValueError");
                xmlresult.writeElement("expectedValue", expectedXmlTree==null?"not present":"present");
                xmlresult.writeElement("receivedValue", responseDocument()==null?"not present":"present");
                xmlresult.writeElementEnd("XMLValueError");
                return false;
            }

            return true;
        }

        // both expected and received bodies are now available
        return compareTrees(expectedXmlTree, responseDocument().getRootElement());
    }





    public boolean compareTrees(Element element1, Element element2) {
        return compareTrees("", element1, element2);
    }



    private boolean compareTrees(String pathName, Element element1, Element element2) {
        boolean result = true;


        //System.out.println("########## comparing now " + element1 + "  with  " + element2);


        // check the name of the element
        if (!element1.getName().equals(element2.getName())){
            xmlresult.writeElementStart("XMLNameError");
            xmlresult.writeElement("path", pathName);
            xmlresult.writeElement("expectedName", element1.getName());
            xmlresult.writeElement("receivedName", element2.getName());
            xmlresult.writeElementEnd("XMLNameError");
            result = false;
        }

        String elementName = element1.getName();

        // check the value of the element
        if (!compareValues(pathName, elementName, element1.getText(), element2.getText())) {
            result = false;
        }


        // now check the children
        List children1 = sort(element1.getChildren(), getPathName(pathName, elementName));
        List children2 = sort(element2.getChildren(), getPathName(pathName, elementName));



        // check the count of children
        if (children1.size() != children2.size()){
            if (!element1.getTextTrim().equals("*")) {
                if (reportDifferentSizeError(children1, children2, pathName, elementName)) {
                    result = false;
                }
            }
            List children1Result = repairElementList(children1, children2, getPathName(pathName, elementName));
            children2 = repairElementList(children2, children1, getPathName(pathName, elementName));
            children1 = children1Result;
        }

        // check if repair was sucessfull
        if (children1.size() != children2.size()){
            return false;
        }

        //        if (children1.size() > 3) {
//
        //            Iterator iter = children1.iterator();
        //            while (iter.hasNext())
        //            {
        //                Element element = (Element)iter.next();
        //                System.out.println(" element1: " + getDisplayName(element, elementName));
        //            }
        //                System.out.println();
        //            Iterator iter2 = children2.iterator();
        //            while (iter2.hasNext())
        //            {
        //                Element element = (Element)iter2.next();
        //                System.out.println(" element2: " + getDisplayName(element, elementName));
        //            }
        //        }


        // finally check all children too via recursion
        Iterator iter1 = children1.iterator();
        Iterator iter2 = children2.iterator();
        while (iter1.hasNext()){
            try {
                if (!compareTrees(getPathName(pathName, element1), (Element)iter1.next(), (Element)iter2.next()))
                    result = false;
            } catch (Exception e) { e.printStackTrace();
                System.out.println("###################");
                System.out.println("################### list1 = " + getChildNames(children1, getPathName(pathName, elementName)));
                System.out.println("################### list2 = " + getChildNames(children2, getPathName(pathName, elementName)));
                System.out.println("###################");
            }
        }

        return result;
    }

    private boolean reportDifferentSizeError(List expected, List received, String path, String name) {
        boolean result = false;
        String pathName = getPathName(path, name);
        List expectedAndNotReceived = getChildNames(expected, pathName);
        List receivedAndNotExpected = getChildNames(received, pathName);
        expectedAndNotReceived.removeAll(getChildNames(received, pathName));
        receivedAndNotExpected.removeAll(getChildNames(expected, pathName));

        List illegalElements = new ArrayList(expectedAndNotReceived);
        illegalElements.addAll(receivedAndNotExpected);
        illegalElements = removeLegalNamesFromList(pathName, illegalElements);

        if (illegalElements.size() > 0 ||
            repairElementList(expected, received, pathName).size() !=
            repairElementList(received, expected, pathName).size()) {

            xmlresult.writeElementStart("XMLSizeError");
            xmlresult.writeElement("path", pathName);
            xmlresult.writeElement("expectedSize", expected.size());
            xmlresult.writeElement("receivedSize", received.size());
            xmlresult.writeElement("expectedElements", getChildNames(expected, pathName).toString());
            xmlresult.writeElement("receivedElements", getChildNames(received, pathName).toString());
            expectedAndNotReceived = removeLegalNamesFromList(pathName, expectedAndNotReceived);
            if (expectedAndNotReceived.size() > 0){
                xmlresult.writeElement("expectedAndNotReceived",  expectedAndNotReceived.toString());
            }
            receivedAndNotExpected = removeLegalNamesFromList(pathName, receivedAndNotExpected);
            if (receivedAndNotExpected.size() > 0){
                xmlresult.writeElement("receivedAndNotExpected", receivedAndNotExpected.toString());
            }
            xmlresult.writeElementEnd("XMLSizeError");
            result = true;
        }
        return result;
    }


    private List repairElementList(List list1, List list2, String pathName) {
        List result = list1;


        List elementNamesToBeRemoved = getChildNames(list1, pathName);
        elementNamesToBeRemoved.removeAll(getChildNames(list2, pathName));
        result = elementsMinusDisplaynames(result, elementNamesToBeRemoved, pathName);

//System.out.println("############# list1     " + list1);
//System.out.println("############# list2     " + list2);
//System.out.println("############# Set1      " + getChildNames(list1, pathName));
//System.out.println("############# Set2      " + getChildNames(list2, pathName));
//System.out.println("############# to remove " + elementNamesToBeRemoved);
//List res = getChildNames(list1, pathName);
//res.removeAll(elementNamesToBeRemoved);
//System.out.println("############# expected  " + res);
//System.out.println("############# result    " + result);


        List onlyLegalNames = removeLegalNamesFromList(pathName, getChildNames(result, pathName));
        result = elementsRetainDisplaynames(result, onlyLegalNames, pathName);
        result = sort(result, pathName);

//System.out.println("#### repaired list = " + getChildNames(result, pathName));

        return result;

    }




    private boolean existsInConfig(String path, String name) {
        return this.configuration.contains(getPathName(path, name)) || this.configuration.contains("//"+name);
    }

    private List removeLegalNamesFromList(String path, List names) {
        List result = new ArrayList(names);
        Iterator iter = names.iterator();
        while (iter.hasNext()){
            String name = (String)iter.next();
            if (existsInConfig(path, name)){
                result.remove(name);;
            }
        }
        return result;
    }


    private Integer getSingleResponseCode(List codes) {
        Integer result = null;
        if (codes.size() == 1) {
            Iterator iter = codes.iterator();
            result = (Integer)iter.next();
        }
        return result;
    }


    public boolean compareValues(String path, String name, String expectedValue, String receivedValue) {
        boolean result = true;

        // check now if this name is excluded from checking
        if (existsInConfig(path, name)) {
            return true;
        }

        if (expectedValue.trim().equals("") && expectedValue.trim().equals(receivedValue.trim())) return true;

        // hard coded for now, should be configurable in the config file:
        //    allow multiple response codes to be checked against
        if (getPathName(path, name).equals("/multistatus/response/status")) {
            List    expectedResponseCodes = TProcessors.responseStatus(expectedValue);
            Integer receivedResponseCode  = getSingleResponseCode(TProcessors.responseStatus(receivedValue));
            //          System.out.println("#### " + getPathName(path, name));
            //          System.out.println("#### " + value1 + " --> " + expectedResponseCodes);
            //          System.out.println("#### " + value2 + " --> " + receivedResponseCode);
            if (receivedResponseCode != null &&
                expectedResponseCodes.contains(receivedResponseCode)) return true;
        }


        // hard coded for now, should be configurable in the config file:
        //    allow multiple response codes to be checked against
        if (getPathName(path, name).equals("/multistatus/response/propstat/status")) {
            List    expectedResponseCodes = TProcessors.responseStatus(expectedValue);
            Integer receivedResponseCode  = getSingleResponseCode(TProcessors.responseStatus(receivedValue));
            //          System.out.println("#### " + getPathName(path, name));
            //          System.out.println("#### " + value1 + " --> " + expectedResponseCodes);
            //          System.out.println("#### " + value2 + " --> " + receivedResponseCode);
            if (receivedResponseCode != null &&
                expectedResponseCodes.contains(receivedResponseCode)) return true;
        }

        // now perform the check and print out the error message
        if (!expectedValue.trim().equals("*") &&
            !doCompareValues(getPathName(path, name), expectedValue, receivedValue) ){
            xmlresult.writeElementStart("XMLValueError");
            xmlresult.writeElement("path", getPathName(path, name));
            xmlresult.writeElement("expectedValue", expectedValue);
            xmlresult.writeElement("receivedValue", receivedValue);
            xmlresult.writeElementEnd("XMLValueError");
            return false;
        }
        return result;
    }


    public boolean doCompareValues(String path, String received, String expected){
        return expected.equals(received);
    }


    private List getChildNames(Element e, String pathName) {
        return getChildNames(e.getChildren(), pathName);
    }

    private List getChildNames(List l, String pathName) {
        List result = new ArrayList();
        Iterator iter = l.iterator();
        while (iter.hasNext()){
            result.add(getDisplayName((Element)iter.next(), pathName));
        }
        return result;
    }


    private String getPathName(String path, String element) {
        return path + "/" + element;
    }


    private String getPathName(String path, Element element) {
        return getPathName(path, element.getName());
    }


    private List sort(List l, String pathName) {
        List result = new ArrayList(l);
        Collections.sort(result, new ElementComparator(pathName));
        return result;
    }


    public boolean sortMultistatusResponseHrefElements() {
        return true;
    }



    private String getDisplayName(Element e, String pathName) {
        String result = null;
        String fullPathName = getPathName(pathName, e.getName());
        if (fullPathName.equals("/multistatus/response/href") && sortMultistatusResponseHrefElements() ) {
            result = getPathName("/multistatus/response/href", getHrefNormalised(e.getTextTrim()));
        } else if (fullPathName.equals("/multistatus/response") && e.getChild("href", e.getNamespace()) != null && sortMultistatusResponseHrefElements() ) {
            result = getHrefNormalised(e.getChild("href", e.getNamespace()).getTextTrim());
        } else if (fullPathName.equals("/multistatus/response/propstat/prop/workspace-checkout-set/href")) {
            result = getHrefNormalised(e.getTextTrim());
        } else if (fullPathName.equals("/multistatus/response/propstat") && e.getChild("status",e.getNamespace()) != null  ) {
            result = getPathName("/multistatus/response/propstat/status", e.getChildText("status",e.getNamespace()));
        } else if (fullPathName.equals("/multistatus/response/propstat/prop/supported-report-set/supported-report") && e.getChild("report",e.getNamespace()) != null  && e.getChildren().size() == 1) {
            result = getPathName("/multistatus/response/propstat/prop/supported-report-set/supported-report/report",
                                     ((Element)e.getChild("report",e.getNamespace()).getChildren().get(0)).getName());
        } else if (fullPathName.equals("/multistatus/response/propstat/prop/supported-live-property-set/supported-live-property") && e.getChild("prop",e.getNamespace()) != null  && e.getChildren().size() == 1) {
            result = getPathName("/multistatus/response/propstat/prop/supported-live-property-set/supported-live-property/prop",
                                     ((Element)e.getChild("prop",e.getNamespace()).getChildren().get(0)).getName());
        } else if (fullPathName.equals("/multistatus/response/propstat/prop/supported-method-set/supported-method") && e.getAttribute("name") != null) {
            result = getPathName(fullPathName, e.getAttributeValue("name"));
        } else {
            result = e.getName();
        }
//System.out.println("##### get display name " + fullPathName + "  --> " + result);
        return result;
    }


    private String getHrefNormalised(String pathName) {
        String result = pathName;
        if (result.endsWith("/")){
            result = result.substring(0,result.length()-1);
        }
        return result;
    }



    private List elementsMinusDisplaynames(List elementList, List displayNames, String pathName) {
        List result = new ArrayList(elementList);
        Iterator iter = elementList.iterator();
        while (iter.hasNext()){
            Element e = (Element)iter.next();
            if (displayNames.contains(getDisplayName(e, pathName))) {
                result.remove(e);
            }
        }
        return result;
    }




    private List elementsRetainDisplaynames(List elementList, List displayNames, String pathName) {
        List result = new ArrayList();
        Iterator iter = elementList.iterator();
        while (iter.hasNext()){
            Element e = (Element)iter.next();
            if (displayNames.contains(getDisplayName(e, pathName))) {
                result.add(e);
            }
        }
        return result;
    }


    private String listToString(List list) {
        String result = null;
        Iterator iter = list.iterator();
        while (iter.hasNext()){
            Object o = iter.next();
            String s = null;
            if (o instanceof String) {
                s = (String)o;
            }
            else if(o instanceof Integer) {
                s = ((Integer)o).toString();
            }
            else {
                s = o.toString();
            }
            if (result != null) {
                result += ", " + s;
            }
            else {
                result = s;
            }
        }
        return result;
    }




    /**************************************************************/
    /* Inner class to help to create a suitable comparator        */
    /**************************************************************/
    public class ElementComparator implements Comparator {
        String pathName;
        public ElementComparator(String pathName) {
            this.pathName = pathName;
        }
        public int compare(Object o1, Object o2){
            String s1 = getDisplayName(((Element)o1), pathName);
            String s2 = getDisplayName(((Element)o2), pathName);
            return (s1.compareTo(s2));
        }
    }





}






