/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.vxquery.xtest;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.vxquery.exceptions.SystemException;

public class TestCaseResult {
    private static final int DISPLAY_LEN = 1000;
    private static Pattern XML_RES_PREFIX = Pattern.compile("<\\?[xX][mM][lL][^\\?]*\\?>");

    TestCase testCase;

    Throwable error;

    String result;

    long time;

    String report;

    State state;

    public TestCaseResult(TestCase testCase) {
        this.testCase = testCase;
    }

    public boolean error() {
        return error != null;
    }

    public boolean userError() {
        return error instanceof SystemException;
    }

    public void compare() {
        String eErr = testCase.getExpectedError();
        report = "No result file found";
        state = State.NO_RESULT_FILE;
        if (eErr != null) {
            if (userError()) {
                String aErr = String.valueOf(((SystemException) error).getCode());
                if (eErr.equals(aErr)) {
                    report = "Result matches expected error: " + eErr;
                    state = State.EXPECTED_ERROR_GOT_SAME_ERROR;
                } else {
                    report = "Expected error: " + eErr + ", got error: " + aErr;
                    state = State.EXPECTED_ERROR_GOT_DIFFERENT_ERROR;
                }
            } else if (error()) {
                report = "Expected error: " + eErr + ", got failure: " + error;
                state = State.EXPECTED_ERROR_GOT_FAILURE;
            } else {
                report = "Expected error: " + eErr + ", got result";
                state = State.EXPECTED_ERROR_GOT_RESULT;
            }
        } else {
            if (userError()) {
                String aErr = ((SystemException) error).getCode().toString();
                report = "Expected result, Got error: " + aErr;
                state = State.EXPECTED_RESULT_GOT_ERROR;
            } else if (error()) {
                report = "Expected result, Got failure: " + error;
                state = State.EXPECTED_RESULT_GOT_FAILURE;
            } else {
                File[] resFiles = testCase.getExpectedResultFiles();
                for (int i = 0; i < resFiles.length; ++i) {
                    File resFile = resFiles[i];
                    String expResult = slurpFile(resFile);
                    if (expResult == null) {
                        report = "No result file found";
                        state = State.NO_RESULT_FILE;
                    } else {
                        expResult = expResult.trim();
                        if (result != null) {
                            Matcher m = XML_RES_PREFIX.matcher(result);
                            if (m.find() && m.start() == 0) {
                                result = result.substring(m.end());
                            }
                            result = result.trim();
                            Pair<Boolean, String> cmp = textCompare(expResult, result);
                            report = cmp.getRight();
                            state = cmp.getLeft() ? State.EXPECTED_RESULT_GOT_SAME_RESULT
                                    : State.EXPECTED_RESULT_GOT_DIFFERENT_RESULT;
                            if (cmp.getLeft()) {
                                break;
                            }
                        }
                    }
                }
            }
        }
    }

    private Pair<Boolean, String> textCompare(String expRes, String actRes) {
        boolean cmp = expRes.equals(actRes);
        if (testCase.getConfig().options.verbose) {
            System.err.println("Comparing:");
            System.err.println("Expected: ---" + expRes + "---");
            System.err.println("Actual  : ---" + actRes + "---");
            System.err.println("Result  : " + cmp);
        }
        if (cmp) {
            return Pair.<Boolean, String> of(Boolean.TRUE, "Got expected result");
        } else {
            return Pair.<Boolean, String> of(Boolean.FALSE, "Expected: " + truncate(expRes) + " Got: "
                    + truncate(actRes));
        }
    }

    private String truncate(String str) {
        if (str == null) {
            return "&lt;NULL&gt;";
        }
        if (str.length() > DISPLAY_LEN) {
            str = str.substring(0, DISPLAY_LEN);
            str += "...";
        }
        return xmlEncode(str);
    }

    private String xmlEncode(String str) {
        StringBuilder buffer = new StringBuilder();
        int len = str.length();
        for (int i = 0; i < len; ++i) {
            char ch = str.charAt(i);
            switch (ch) {
                case '<':
                    buffer.append("&lt;");
                    break;

                case '>':
                    buffer.append("&gt;");
                    break;

                case '"':
                    buffer.append("&quot;");
                    break;

                case '\'':
                    buffer.append("&apos;");
                    break;

                case '&':
                    buffer.append("&amp;");
                    break;

                default:
                    buffer.append(ch);
            }
        }
        return buffer.toString();
    }

    private String slurpFile(File f) {
        StringWriter out = new StringWriter();
        try {
            FileReader in = new FileReader(f);
            try {
                char[] buffer = new char[8192];
                int c;
                while ((c = in.read(buffer)) >= 0) {
                    out.write(buffer, 0, c);
                }
            } finally {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        } catch (IOException e) {
            return null;
        }
        return out.toString();
    }

    public enum State {
        EXPECTED_RESULT_GOT_SAME_RESULT(GREEN),
        EXPECTED_ERROR_GOT_SAME_ERROR(GREEN),
        EXPECTED_RESULT_GOT_DIFFERENT_RESULT(ORANGE),
        EXPECTED_RESULT_GOT_ERROR(ORANGE),
        EXPECTED_ERROR_GOT_DIFFERENT_ERROR(ORANGE),
        EXPECTED_ERROR_GOT_RESULT(ORANGE),
        EXPECTED_RESULT_GOT_FAILURE(RED),
        EXPECTED_ERROR_GOT_FAILURE(RED),
        NO_RESULT_FILE(RED);

        private String color;

        private State(String color) {
            this.color = color;
        }

        public String getColor() {
            return color;
        }
    }

    private static final String RED = "#FF9900";
    private static final String ORANGE = "#FFCC00";
    private static final String GREEN = "#99CC00";
}