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

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.types.DataType;
import org.dom4j.Comment;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.Visitor;
import org.dom4j.VisitorSupport;
import org.dom4j.io.SAXReader;

import com.nokia.helium.ant.data.PropertyCommentMeta;
import com.nokia.helium.ant.data.PropertyMeta;
import com.nokia.helium.core.ant.PostBuildAction;

/**
 * Class to store the status of the signal of a particular target.
 */
public class BuildStatusDef extends DataType implements PostBuildAction {
    private static final String DEPRECATED = "deprecated:";
    private HashSet<String> output = new HashSet<String>();

    @Override
    public void executeOnPostBuild(Project project, String[] targetNames) {
        // Run after targets execute so dynamic target names are resolved
        for (int i = 0; i < targetNames.length; i++) {
            String[] array = { targetNames[i] };
            Target target = findTarget(targetNames[i], getProject(), array);
            targetCallsHeliumTarget(target, getProject());
        }
        checkTargetsProperties(getProject());
        checkDeprecatedProperties(getProject());

        if (!output.isEmpty()) {
            log("*** Configuration report ***", Project.MSG_INFO);
            for (String outputStr : output) {
                log(outputStr, Project.MSG_INFO);
            }
        }
    }

    /**
     * @param desiredTarget
     *            Target name to search
     * @param project
     *            Object of the project
     * @param targetNames
     *            Array of target names
     * 
     */
    @SuppressWarnings("unchecked")
    public Target findTarget(String desiredTarget, Project project,
            String[] targetNames) {
        Hashtable<String, Target> targets;
        Vector<Target> sorted;

        // get all targets of the current project
        targets = project.getTargets();

        // sort all targets of the current project
        sorted = project.topoSort(targetNames[0], targets);

        // Find the desiredTarget Target object
        for (Target target : sorted) {
            if (target.getName().equals(desiredTarget)) {
                return target;
            }
        }
        throw new BuildException("Could not find target matching "
                + desiredTarget + "\n");
    }

    /**
     * If a target defined outside helium are calling a private Helium target
     * then print warning
     * 
     */
    @SuppressWarnings("unchecked")
    public void targetCallsHeliumTarget(Target target, Project project) {
        String location = target.getLocation().getFileName();

        try {
            String heliumpath = new File(project.getProperty("helium.dir"))
                    .getCanonicalPath();
            String targetpath = new File(location).getCanonicalPath();

            if (!targetpath.contains(heliumpath)) {
                ArrayList<String> antcallTargets = new ArrayList<String>();
                Visitor visitorTarget = new AntTargetVisitor(antcallTargets,
                        project);

                Element element = findTargetElement(target, project);
                if (element != null) {
                    element.accept(visitorTarget);
                }
                for (String depTargetString : antcallTargets) {
                    String[] array = { depTargetString };
                    try {
                        Target depTarget = findTarget(depTargetString, project,
                                array);
                        targetCallsHeliumTarget(depTarget, project);
                    } catch (BuildException x) {
                        // We are Ignoring the errors as no need to fail the
                        // build.
                        log(
                                "Exception occured while target defined outside helium are calling a private Helium target "
                                        + x.toString(), Project.MSG_DEBUG);
                        x = null;
                    }
                }

                for (Enumeration<String> depsEnum = target.getDependencies(); depsEnum
                        .hasMoreElements();) {
                    String depTargetString = depsEnum.nextElement();
                    String[] array = { depTargetString };
                    try {
                        Target depTarget = findTarget(depTargetString, project,
                                array);
                        targetCallsHeliumTarget(depTarget, project);
                    } catch (BuildException x) {
                        // We are Ignoring the errors as no need to fail the
                        // build.
                        log(
                                "Exception occured while target defined outside helium are calling a private Helium target "
                                        + x.toString(), Project.MSG_DEBUG);
                        x = null;
                    }
                }
            } else {
                checkIfTargetPrivate(target, project);
            }

        } catch (IOException e) {
            // We are Ignoring the errors as no need to fail the build.
            log(
                    "IOException occured while target defined outside helium are calling a private Helium target "
                            + e.getMessage(), Project.MSG_DEBUG);
            e.printStackTrace();
        }
    }

    private class AntTargetVisitor extends VisitorSupport {
        private List<String> targetList;
        private Project project;

        public AntTargetVisitor(List<String> targetList, Project project) {
            this.targetList = targetList;
            this.project = project;
        }

        public void visit(Element node) {
            String name = node.getName();
            if (name.equals("antcall") || name.equals("runtarget")) {
                String text = node.attributeValue("target");
                extractTarget(text);
            }
        }

        private void extractTarget(String text) {
            String iText = project.replaceProperties(text);
            targetList.add(iText);
        }

    }

    /**
     * Find the xml Element for the target
     * 
     */
    @SuppressWarnings("unchecked")
    public Element findTargetElement(Target target, Project project) {
        SAXReader xmlReader = new SAXReader();

        Document antDoc = null;

        String location = target.getLocation().getFileName();

        try {
            File file = new File(location);
            antDoc = xmlReader.read(file);
        } catch (DocumentException e) {
            // We are Ignoring the errors as no need to fail the build.
            log("Not able read the XML file. " + e.getMessage(),
                    Project.MSG_WARN);
        }

        String projectName = antDoc.valueOf("/project/@name");
        for (Iterator<Element> iterator = antDoc.selectNodes("//target")
                .iterator(); iterator.hasNext();) {
            Element element = iterator.next();

            String targetName = element.attributeValue("name");
            if (targetName.equals(target.getName())
                    || (projectName + "." + targetName)
                            .equals(target.getName())) {
                return element;
            }
        }
        return null;
    }

    /**
     * If target has comment that says it is private them print warning
     * 
     */
    @SuppressWarnings("unchecked")
    public void checkIfTargetPrivate(Target target, Project project) {
        Element targetElement = findTargetElement(target, project);
        if (targetElement != null) {
            List<Node> children = targetElement
                    .selectNodes("preceding-sibling::node()");
            if (children.size() > 0) {
                // Scan past the text nodes, which are most likely whitespace
                int index = children.size() - 1;
                Node child = children.get(index);
                while (index > 0 && child.getNodeType() == Node.TEXT_NODE) {
                    index--;
                    child = (Node) children.get(index);
                }

                // Check if there is a comment node
                String commentText = null;
                if (child.getNodeType() == Node.COMMENT_NODE) {
                    Comment macroComment = (Comment) child;
                    commentText = macroComment.getStringValue().trim();
                    // log(macroName + " comment: " + commentText,
                    // Project.MSG_DEBUG);
                }

                if (commentText != null) {
                    if (commentText.contains("Private:")) {
                        output
                                .add("Warning: "
                                        + target.getName()
                                        + " is private and should only be called by helium");
                    }
                    if (commentText.contains("<deprecated>")) {
                        output.add("Warning: " + target.getName() + "\n"
                                + commentText);
                    }
                }
            }
        }
    }

    /**
     * To check, is the private properties are overridden by customers.
     * 
     * @param project
     */
    public void checkTargetsProperties(Project project) {
        try {
            String heliumpath = new File(project.getProperty("helium.dir"))
                    .getCanonicalPath();
            com.nokia.helium.ant.data.Database db = new com.nokia.helium.ant.data.Database(
                    project, "private");
            ArrayList<String> customerProps = getCustomerProperties(project);

            for (PropertyMeta propertyMeta : db.getProperties()) {
                if (propertyMeta.getType().equals("boolean"))
                {
                    String value = project.getProperty(propertyMeta.getName());
                    if (value != null && !value.equals("true") && !value.equals("false"))
                    {
                        output.add("Warning: " + propertyMeta.getName() + " property is boolean type and not set to true or false, value is " + value);
                    }
                }
            }
            for (PropertyCommentMeta propertyMeta : db.getCommentProperties()) {
                if (propertyMeta.getType().equals("boolean"))
                {
                    String value = project.getProperty(propertyMeta.getName());
                    if (value != null && !value.equals("true") && !value.equals("false"))
                    {
                        output.add("Warning: " + propertyMeta.getName() + " property is boolean type and not set to true or false, value is " + value);
                    }
                }
            }
            for (PropertyMeta propertyMeta : db.getProperties()) {
                if (propertyMeta.getLocation().contains(heliumpath)
                        && propertyMeta.getScope().equals("private")
                        && customerProps.contains(propertyMeta.getName())) {
                    output.add("Warning: " + propertyMeta.getName()
                            + " property has been overridden");
                }
            }

            for (PropertyCommentMeta propertyCommentMeta : db.getCommentProperties()) {
                if (propertyCommentMeta.getLocation().contains(heliumpath)
                        && propertyCommentMeta.getScope().equals("private")
                        && customerProps
                                .contains(propertyCommentMeta.getName())) {
                    output.add("Warning: " + propertyCommentMeta.getName()
                            + " property has been overridden");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * To display the warnings for deprecated properties.
     * 
     * @param project
     */
    public void checkDeprecatedProperties(Project project) {
        try {
            String heliumpath = new File(project.getProperty("helium.dir"))
                    .getCanonicalPath();
            com.nokia.helium.ant.data.Database db = new com.nokia.helium.ant.data.Database(
                    project, "private");
            ArrayList<String> customerProps = getCustomerProperties(project);

            for (PropertyMeta propertyMeta : db.getProperties()) {
                if (propertyMeta.getLocation().contains(heliumpath)
                        && (!propertyMeta.getDeprecated().equals(""))
                        && customerProps.contains(propertyMeta.getName())) {
                    output.add("Warning: "
                            + propertyMeta.getName()
                            + " property has been deprecated "
                            + propertyMeta.getDeprecated()
                            + "."
                            + propertyMeta.getSummary().substring(
                                    propertyMeta.getSummary().lastIndexOf(
                                            DEPRECATED)
                                            + DEPRECATED.length()));
                }
            }

            for (PropertyCommentMeta propertyCommentMeta : db
                    .getCommentProperties()) {
                if (propertyCommentMeta.getLocation().contains(heliumpath)
                        && (!propertyCommentMeta.getDeprecated().equals(""))
                        && customerProps
                                .contains(propertyCommentMeta.getName())) {
                    output.add("Warning: "
                            + propertyCommentMeta.getName()
                            + " property has been deprecated "
                            + propertyCommentMeta.getDeprecated()
                            + "."
                            + propertyCommentMeta.getSummary().substring(
                                    propertyCommentMeta.getSummary()
                                            .lastIndexOf(DEPRECATED)
                                            + DEPRECATED.length()));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings("unchecked")
    public ArrayList<String> getCustomerProperties(Project project) {
        ArrayList<String> props = new ArrayList<String>();
        Database db = new Database(null, null, null);
        try {
            String heliumpath = new File(project.getProperty("helium.dir"))
                    .getCanonicalPath();

            for (Object object : db.getAntFiles(project)) {
                String antFile = (String) object;
                antFile = new File(antFile).getCanonicalPath();

                if (!antFile.contains(heliumpath)) {
                    SAXReader xmlReader = new SAXReader();
                    Document antDoc = xmlReader.read(new File(antFile));

                    List<Element> propertyNodes = antDoc
                            .selectNodes("//property | //param");
                    for (Element propertyNode : propertyNodes) {
                        props.add(propertyNode.attributeValue("name"));
                    }
                }
            }
        } catch (IOException e) {
            // We are Ignoring the errors as no need to fail the build.
            log("IOException: Not able read the Customer Properties "
                    + e.getMessage(), Project.MSG_WARN);
        } catch (DocumentException e) {
            // We are Ignoring the errors as no need to fail the build.
            log("DocumentException: Not able read the Customer Properties "
                    + e.getMessage(), Project.MSG_WARN);
        }

        return props;
    }

}