/*
 * Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of the License "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description:  
 *
 */
package com.nokia.helium.ant.coverage.listener;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;

import org.apache.ant.antunit.listener.BaseAntUnitListener;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildListener;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.Target;

import com.nokia.helium.ant.coverage.AntMacros;
import com.nokia.helium.ant.coverage.AntScriptDefs;
import com.nokia.helium.ant.coverage.AntTargets;

import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * Helium antunit listener. This listener will collect the information of test
 * targets run and feeds the information into ParseTestFiles to get the tested
 * targets, macros and scriptdefs information.
 */

public class HlmAntUnitListener extends BaseAntUnitListener {
    private static final String NEW_LINE = System.getProperty("line.separator");
    private OutputStream out;
    private AntTargets antTargets;
    private AntMacros antMacro;
    private AntScriptDefs antScriptDef;
    private PrintWriter wri;
    private StringWriter inner;
    private File outputFile;

    /**
     * Default constructor.
     */
    public HlmAntUnitListener() {
        super(new BaseAntUnitListener.SendLogTo(SendLogTo.ANT_LOG), "txt");
        antTargets = new AntTargets();
        antMacro = new AntMacros();
        antScriptDef = new AntScriptDefs();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.ant.antunit.AntUnitListener#endTest(java.lang.String)
     */
    @Override
    public void endTest(String target) {
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.apache.ant.antunit.AntUnitListener#endTestSuite(org.apache.tools.
     * ant.Project, java.lang.String)
     */
    @Override
    public void endTestSuite(Project testProject, String buildFile) {
        try {
            StringWriter ftlWriter = new StringWriter();
            Configuration cfg = new Configuration();
            Template template = null;
            cfg.setTemplateLoader(new ClassTemplateLoader(this.getClass(), ""));
            template = cfg.getTemplate("ant_coverage_report.txt.ftl");
            Map<String, Object> data = new Hashtable<String, Object>();
            data.put("target_percentage", (int)this.getTargetCoverage());
            data.put("target_testcases", antTargets.getExecutedCount());
            data.put("total_targets", antTargets.getCount());

            data.put("macros_percentage", (int)this.getMacroCoverage());
            data.put("macros_testcases", antMacro.getExecutedCount());
            data.put("total_macros", antMacro.getCount());

            data.put("scriptdefs_percentage", (int)this.getScriptDefCoverage());
            data.put("scriptdefs_testcases", antScriptDef.getExecutedCount());
            data.put("total_scriptdefs", antScriptDef.getCount());
            template.process(data, ftlWriter);

            if (getOutputFile() != null) {
                File outputFile = getOutputFile();
                if (!outputFile.getParentFile().exists()) {
                    outputFile.getParentFile().mkdirs();
                }
                OutputStreamWriter output = new OutputStreamWriter(
                        new FileOutputStream(getOutputFile()));
                output.append(ftlWriter.getBuffer().toString());
                output.close();
                output = new OutputStreamWriter(new FileOutputStream(outputFile
                        .getParentFile().toString()
                        + File.separator + "target.plot.property"));
                output.append("YVALUE=" + (int)this.getTargetCoverage());
                output.close();
                output = new OutputStreamWriter(new FileOutputStream(outputFile
                        .getParentFile().toString()
                        + File.separator + "macro.plot.property"));
                output.append("YVALUE=" + (int)this.getMacroCoverage());
                output.close();
                output = new OutputStreamWriter(new FileOutputStream(outputFile
                        .getParentFile().toString()
                        + File.separator + "scriptdef.plot.property"));
                output.append("YVALUE="
                        + (int)this.getScriptDefCoverage());
                output.close();
            } else {
                StringBuffer sb = new StringBuffer(NEW_LINE);
                sb.append(ftlWriter.getBuffer().toString());
                if (out != null) {
                    out.write(sb.toString().getBytes());
                    wri.close();
                    out.write(inner.toString().getBytes());
                    out.flush();
                }
            }
        } catch (IOException ioe) {
            throw new BuildException(ioe.getMessage(), ioe);
        } catch (TemplateException ftle) {
            throw new BuildException(ftle.getMessage(), ftle);
        } finally {
            close(out);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.apache.ant.antunit.listener.BaseAntUnitListener#startTestSuite(org
     * .apache.tools.ant.Project, java.lang.String)
     */
    public void startTestSuite(Project testProject, String buildFile) {
        inner = new StringWriter();
        wri = new PrintWriter(inner);
        out = getOut(buildFile);
        addTargets(testProject);
        addMacros(testProject);
        addScriptDefs(testProject);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.apache.ant.antunit.listener.BaseAntUnitListener#startTest(java.lang
     * .String)
     */
    public void startTest(String target) {

    }

    /**
     * @param outputFile
     *            the outputFile to set
     */
    public void setOutputFile(File outputFile) {
        this.outputFile = outputFile;
    }

    /**
     * Returns the output file path.
     * 
     * @return
     */
    public File getOutputFile() {
        return outputFile;
    }

    /**
     * To add Ant targets.
     * 
     * @param project
     */
    @SuppressWarnings("unchecked")
    private void addTargets(Project project) {
        Hashtable<String, Target> projectTargets = project.getTargets();
        Enumeration<String> e = projectTargets.keys();
        while (e.hasMoreElements()) {
            String key = e.nextElement();
            System.out.println(key + ": " + projectTargets.get(key));
            if (!isAntUnitTestTarget(key) && key.length() > 0) {
                antTargets.add((Target) projectTargets.get(key));
            }
        }
    }

    /**
     * To add Ant macros.
     * 
     * @param project
     */
    @SuppressWarnings("unchecked")
    private void addMacros(Project project) {
        Hashtable<String, Class> projectMacros = project.getTaskDefinitions();
        Enumeration<String> e = projectMacros.keys();
        while (e.hasMoreElements()) {
            String key = e.nextElement();
            Class newTask = projectMacros.get(key);
            if (newTask.getName().equals(
                    "org.apache.tools.ant.taskdefs.MacroInstance")) {
                String macroName = ProjectHelper
                        .extractNameFromComponentName(key);
                if (macroName != null && !macroName.contains("assert")) {
                    antMacro.add(macroName);
                }
            }
        }
    }

    /**
     * To add Ant scriptdefs.
     * 
     * @param project
     */
    @SuppressWarnings("unchecked")
    private void addScriptDefs(Project project) {
        Hashtable<String, Class> projectMacros = project.getTaskDefinitions();
        Enumeration<String> e = projectMacros.keys();
        while (e.hasMoreElements()) {
            String key = e.nextElement();
            Class newTask = projectMacros.get(key);
            if (newTask
                    .getName()
                    .equals(
                            "org.apache.tools.ant.taskdefs.optional.script.ScriptDefBase")) {
                String scriptdefName = ProjectHelper
                        .extractNameFromComponentName(key);
                if (scriptdefName != null) {
                    antScriptDef.add(scriptdefName);
                }
            }
        }
    }

    /**
     * To get the Target covered percentage.
     * 
     * @param measure
     * @return
     */
    private float getTargetCoverage() {
        if (antTargets.getCount() == 0) {
            return 100;
        } else {
            return ((float)antTargets.getExecutedCount() / (float)antTargets
                    .getCount()) * 100;
        }
    }

    /**
     * To get Macros covered percentage.
     * 
     * @param measure
     * @return
     */
    private float getMacroCoverage() {
        if (antMacro.getCount() == 0) {
            return 100;
        } else {
            return ((float)antMacro.getExecutedCount() / (float)antMacro.getCount()) * 100;
        }
    }

    /**
     * To get ScriptDefs covered percentage.
     * 
     * @param measure
     * @return
     */
    private float getScriptDefCoverage() {
        if (antScriptDef.getCount() == 0) {
            return 100;
        } else {
            return ((float)antScriptDef.getExecutedCount() / (float)antScriptDef
                    .getCount()) * 100;
        }
    }

    /**
     * Listener for collecting the targets/tasks information. This listener will
     * collect from the targets which actually run by test projects.
     * 
     */
    private class AntListener implements BuildListener {

        @Override
        public void buildFinished(BuildEvent event) {
        }

        @Override
        public void buildStarted(BuildEvent event) {
        }

        @Override
        public void messageLogged(BuildEvent event) {
        }

        @Override
        public void targetFinished(BuildEvent event) {
        }

        @Override
        public void targetStarted(BuildEvent event) {
            String targetName = event.getTarget().getName();
            if (!isAntUnitTestTarget(targetName) && targetName.length() > 0) {
                antTargets.markAsExecuted(targetName);
            }

        }

        @Override
        public void taskFinished(BuildEvent event) {

        }

        @Override
        public void taskStarted(BuildEvent event) {
            // macro and scriptdef are creating UnknownElement type of tasks
            if (event.getTask().getTaskType() != null) {
                String taskName = ProjectHelper
                        .extractNameFromComponentName(event.getTask()
                                .getTaskType());
                if (taskName != null) {
                    antMacro.markAsExecuted(taskName);
                    antScriptDef.markAsExecuted(taskName);
                }
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.apache.ant.antunit.listener.BaseAntUnitListener#setCurrentTestProject
     * (org.apache.tools.ant.Project)
     */
    @Override
    public void setCurrentTestProject(Project p) {
        p.addBuildListener(new AntListener());
    }
    
    /**
     * Check is the target is a special name for AntUnit.
     * @param targetName
     * @return
     */
    public static boolean isAntUnitTestTarget(String targetName) {
        if (targetName == null) {
            return false;
        }
        if ((targetName.startsWith("test") && !targetName.equals("test"))
            || targetName.equals("setUp") || targetName.equals("tearDown")) {
            return true;
        }
        return false;
    }
}
