/*
 * 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.royale.test.ant.report;

import java.io.File;
import java.io.FileOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.util.DateUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.apache.royale.test.ant.LoggingUtil;

public class Report
{
    private static final String FAILURE = "failure";
    private static final String ERROR = "error";
    private static final String IGNORE = "ignore";

    private static final String TEST_SUITE = "testsuite";
    private static final String NAME_ATTRIBUTE_LABEL = "name";
    private static final String FAILURE_ATTRIBUTE_LABEL = "failures";
    private static final String ERROR_ATTRIBUTE_LABEL = "errors";
    private static final String IGNORE_ATTRIBUTE_LABEL = "skipped";
    private static final String TIME_ATTRIBUTE_LABEL = "time";
    private static final String TESTS_ATTRIBUTE_LABEL = "tests";
    private static final String HOSTNAME_ATTRIBUTE_LABEL = "hostname";
    private static final String TIMESTAMP_ATTRIBUTE_LABEL = "timestamp";

    private static final String FILENAME_PREFIX = "TEST-";
    private static final String FILENAME_EXTENSION = ".xml";

    // Exception messages
    private static final String FAILED_TEST = "RoyaleUnit test {0} in suite {1} failed.";
    private static final String ERRORED_TEST = "RoyaleUnit test {0} in suite {1} had errors.";
    private static final String IGNORED_TEST = "RoyaleUnit test {0} in suite {1} was ignored.";
    private static final String TEST_INFO = "Suite: {0}\nTests run: {1}, Failures: {2}, Errors: {3}, Skipped: {4}, Time elapsed: {5} sec";
    private static final String ERROR_SAVING_REPORT = "Error saving report.";

    // XML attribute labels
    private static final String CLASSNAME_ATTRIBUTE = "classname";
    private static final String NAME_ATTRIBUTE = "name";
    private static final String STATUS_ATTRIBUTE = "status";
    private static final String TIME_ATTRIBUTE = "time";

    protected Suite suite;
    private Document document;
    private List<String> recordedRuns;

    public Report(Suite suite)
    {
        this.recordedRuns = new ArrayList<String>();
        this.suite = suite;

        // Create a new XML document
        document = DocumentHelper.createDocument();

        // Add the test suite attributes to the document
        document
                .addElement(TEST_SUITE)
                .addAttribute(NAME_ATTRIBUTE_LABEL, suite.getName())
                .addAttribute(TESTS_ATTRIBUTE_LABEL,
                        String.valueOf(suite.getTests()))
                .addAttribute(FAILURE_ATTRIBUTE_LABEL,
                        String.valueOf(suite.getFailures()))
                .addAttribute(ERROR_ATTRIBUTE_LABEL,
                        String.valueOf(suite.getErrors()))
                .addAttribute(IGNORE_ATTRIBUTE_LABEL,
                        String.valueOf(suite.getSkips()))
                .addAttribute(TIME_ATTRIBUTE_LABEL, String.valueOf(suite.getTime()));
    }

    /**
     * Adds the test to the suite report given an XML test document
     */
    public void addTest(Document test)
    {
        Element root = test.getRootElement();
        
        // Add to the number of tests in this suite if not seen and not null
        String testMethod = root.attributeValue(NAME_ATTRIBUTE);
        if(!recordedRuns.contains(testMethod) && !testMethod.equals("null"))
        {
            recordedRuns.add(testMethod);
            suite.addTest();
        }
        
        //add test time to total time
        long time = Long.parseLong(root.attributeValue(TIME_ATTRIBUTE));
        suite.addTime(time);
        root.attribute(TIME_ATTRIBUTE).setText(formatTime(time));

        //If the test method name is null, then make it the classname
        if(root.attributeValue(NAME_ATTRIBUTE).equals("null"))
        {
            root.attribute(NAME_ATTRIBUTE).setText(root.attributeValue(CLASSNAME_ATTRIBUTE));
        }
        
        // Add the test to the report document
        document.getRootElement().add(root);

        // Check for special status adjustments to make to suite
        checkForStatus(test);
        
        //remove status attribute since it's only used by the report
        root.remove(root.attribute(STATUS_ATTRIBUTE));
    }
    
    private String formatTime(long time)
    {
        return String.format("%.3f", new Double(time / 1000.0000));
    }

    /**
     * Updates counts for failed, error, and ignore on suite as well as logs what
     * failed if told to use logging.
     * 
     * @param test
     *           Test XML document
     */
    private void checkForStatus(Document test)
    {
        // Get the root element and pull the test name and status
        final Element root = test.getRootElement();
        final String name = root.attributeValue(NAME_ATTRIBUTE);
        final String status = root.attributeValue(STATUS_ATTRIBUTE);

        String format = null;
        if (status.equals(FAILURE))
        {
            format = FAILED_TEST;
            suite.addFailure();
        } 
        else if (status.equals(ERROR))
        {
            format = ERRORED_TEST;
            suite.addError();
        } 
        else if (status.equals(IGNORE))
        {
            format = IGNORED_TEST;
            suite.addSkip();
        }

        // Creates the fail message for use with verbose
        if (format != null)
        {
            final String message = MessageFormat.format(format, new Object[]
            { name, suite });
            LoggingUtil.log(message);
        }
    }

    /**
     * Determines if any failures (errors or failures) have occurred in this
     * report.
     */
    public boolean hasFailures()
    {
        return (suite.getErrors() > 0 || suite.getFailures() > 0);
    }

    /**
     * Write the report XML document out to file
     * 
     * @param reportDir
     *           Directory to hold report file.
     */
    public void save(File reportDir) throws BuildException
    {
        try
        {
            // Open the file matching the parameter suite
            final File file = new File(reportDir, FILENAME_PREFIX + suite + FILENAME_EXTENSION);

            // Retrieve the root element and adjust the failures and test attributes
            Element root = document.getRootElement();
            root.addAttribute(FAILURE_ATTRIBUTE_LABEL, String.valueOf(suite.getFailures()));
            root.addAttribute(ERROR_ATTRIBUTE_LABEL, String.valueOf(suite.getErrors()));
            root.addAttribute(TESTS_ATTRIBUTE_LABEL, String.valueOf(suite.getTests()));
            root.addAttribute(IGNORE_ATTRIBUTE_LABEL, String.valueOf(suite.getSkips()));
            root.addAttribute(TIME_ATTRIBUTE_LABEL, String.valueOf(formatTime(suite.getTime())));
            root.addAttribute(HOSTNAME_ATTRIBUTE_LABEL, getHostname());
            
            final String timestamp = DateUtils.format(new Date(), DateUtils.ISO8601_DATETIME_PATTERN);
            root.addAttribute(TIMESTAMP_ATTRIBUTE_LABEL, timestamp);

            // Write the updated suite
            final OutputFormat format = OutputFormat.createPrettyPrint();
            final XMLWriter writer = new XMLWriter(new FileOutputStream(file), format);
            writer.write(document);
            writer.close();
        }
        catch (Exception e)
        {
            throw new BuildException(ERROR_SAVING_REPORT, e);
        }
    }

    private String getHostname()
    {
        try
        {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e)
        {
            return "localhost";
        }
    }

    public String getSummary()
    {
        String summary = "";

        try
        {
            summary = MessageFormat.format(TEST_INFO, new Object[]
            { new String(suite.getName()), new Integer(suite.getTests()),
                    new Integer(suite.getFailures()),
                    new Integer(suite.getErrors()), new Integer(suite.getSkips()),
                    formatTime(suite.getTime()) });
        } catch (Exception e)
        {
            // ignore
        }

        return summary;
    }
}
