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

import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;

import com.nokia.helium.freemarker.WikiMethod;

/**
 * Reads the current ant project and a fileset and generates a xml file with a
 * summary of targets, macros and properties.
 */
public class Database {
    /** The default scope filter if no scope filter is defined. */
    public static final String DEFAULT_SCOPE = "public";

    private Project rootProject;
    private Map<String, AntFile> antfilesMap;
    private Map<String, PackageMeta> packagesMap;
    private String scopeFilter;

    private HashMap<String, String> namespaceMap = new HashMap<String, String>();
    private HashMap<String, List<String>> globalSignalList = new HashMap<String, List<String>>();
    private Document signaldoc;

    public Database(Project project) throws IOException {
        this(project, DEFAULT_SCOPE);
    }

    @SuppressWarnings("unchecked")
    public Database(Project project, String scopeFilter) throws IOException {
        this.rootProject = project;
        this.scopeFilter = scopeFilter;
        antfilesMap = new HashMap<String, AntFile>();
        packagesMap = new HashMap<String, PackageMeta>();
        namespaceMap.put("hlm", "http://www.nokia.com/helium");

        if (project != null) {
            Map<String, Target> targets = project.getTargets();
            Iterator<Target> targetsIter = targets.values().iterator();

            while (targetsIter.hasNext()) {
                Target target = targetsIter.next();
                String antFilePath = new File(target.getLocation().getFileName()).getCanonicalPath();

                if (!antfilesMap.containsKey(antFilePath)) {
                    addAntFile(antFilePath);
                }
            }
        }

        Collection<AntFile> antFiles = getAntFiles();
        Iterator<AntFile> antFilesIter = antFiles.iterator();
        while (antFilesIter.hasNext()) {
            AntFile antFile = (AntFile) antFilesIter.next();
            readSignals(antFile.getFile().getCanonicalPath());
        }
    }

    private void log(String string, int level) {
        if (rootProject != null) {
            rootProject.log(string, level);
        }
    }

    public void addAntFilePaths(List<String> antFilePaths) throws IOException {
        for (String antFilePath : antFilePaths) {
            addAntFile(antFilePath);
        }
    }

    private void addAntFile(String antFilePath) throws IOException {
        if (!antfilesMap.containsKey(antFilePath)) {
            log("Adding project to database: " + antFilePath, Project.MSG_DEBUG);
            AntFile antfile = new AntFile(this, antFilePath, scopeFilter);
            antfile.setProject(rootProject);
            antfilesMap.put(antFilePath, antfile);

            // See if project is part of a package
            checkPackageMembership(antfile);
            readSignals(antFilePath);

            // See if any antlibs are defined
            List<AntFile> antlibFiles = antfile.getAntlibs();
            for (AntFile antFile2 : antlibFiles) {
                antfilesMap.put(antFile2.getFile().getCanonicalPath(), antFile2);
                checkPackageMembership(antFile2);
            }
        }
    }

    private void checkPackageMembership(AntFile antfile) throws IOException {
        RootAntObjectMeta rootObjectMeta = antfile.getRootObjectMeta();
        String packageStr = rootObjectMeta.getPackage();
        if (!packagesMap.containsKey(packageStr)) {
            PackageMeta packageMeta = new PackageMeta(packageStr);
            packageMeta.setRuntimeProject(rootProject);
            packagesMap.put(packageStr, packageMeta);
        }
        PackageMeta packageMeta = packagesMap.get(packageStr);
        packageMeta.addObject(rootObjectMeta);
    }

    @SuppressWarnings("unchecked")
    private void readSignals(String antFile) throws IOException {
        SAXReader xmlReader = new SAXReader();
        Document antDoc;
        try {
            antDoc = xmlReader.read(new File(antFile));
        }
        catch (DocumentException e) {
            throw new IOException(e.getMessage());
        }

        XPath xpath = DocumentHelper.createXPath("//hlm:signalListenerConfig");
        xpath.setNamespaceURIs(namespaceMap);
        List<Node> signalNodes = xpath.selectNodes(antDoc);
        for (Iterator<Node> iterator = signalNodes.iterator(); iterator.hasNext();) {
            signaldoc = antDoc;
            Element propertyNode = (Element) iterator.next();
            String signalid = propertyNode.attributeValue("id");
            String signaltarget = propertyNode.attributeValue("target");
            List<String> existinglist = globalSignalList.get(signaltarget); 
            String failbuild = findSignalFailMode(signalid, signaldoc);
            if (existinglist == null) {
                existinglist = new ArrayList<String>();
            }
            existinglist.add(signalid + "," + failbuild);
            globalSignalList.put(signaltarget, existinglist);
        }
    }

    public List<String> getSignals(String target) {
        return globalSignalList.get(target);
    }

    @SuppressWarnings("unchecked")
    private String findSignalFailMode(String signalid, Document antDoc) {
        XPath xpath2 = DocumentHelper.createXPath("//hlm:signalListenerConfig[@id='" + signalid
                + "']/signalNotifierInput/signalInput");
        xpath2.setNamespaceURIs(namespaceMap);
        List signalNodes3 = xpath2.selectNodes(antDoc);

        for (Iterator iterator3 = signalNodes3.iterator(); iterator3.hasNext();) {
            Element propertyNode3 = (Element) iterator3.next();
            String signalinputid = propertyNode3.attributeValue("refid");

            XPath xpath3 = DocumentHelper.createXPath("//hlm:signalInput[@id='" + signalinputid + "']");
            xpath3.setNamespaceURIs(namespaceMap);
            List signalNodes4 = xpath3.selectNodes(antDoc);
            for (Iterator iterator4 = signalNodes4.iterator(); iterator4.hasNext();) {
                Element propertyNode4 = (Element) iterator4.next();
                return propertyNode4.attributeValue("failbuild");
            }
        }
        return null;
    }

    public void setScopeFilter(String scopeFilter) {
        if (!(scopeFilter.equals("public") || scopeFilter.equals("protected") || scopeFilter.equals("private"))) {
            throw new IllegalArgumentException("Invalid scope value");
        }
        this.scopeFilter = scopeFilter;
    }

    public void toXML(Writer out) throws IOException {
        // Setup configuration
        Configuration cfg = new Configuration();
        TemplateLoader[] loaders = null;
        ClassTemplateLoader ctl = new ClassTemplateLoader(getClass(), "");
        File testingDir = new File("src/com/nokia/helium/ant/data/taskdefs");
        if (testingDir.exists()) {
            FileTemplateLoader ftl1 = new FileTemplateLoader(testingDir);
            loaders = new TemplateLoader[] { ftl1, ctl };
        }
        else {
            loaders = new TemplateLoader[] { ctl };
        }
        MultiTemplateLoader mtl = new MultiTemplateLoader(loaders);
        cfg.setTemplateLoader(mtl);
        cfg.setObjectWrapper(new DefaultObjectWrapper());

        // Create the data model
        List<ProjectMeta> projects = new ArrayList<ProjectMeta>();
        List<AntlibMeta> antlibs = new ArrayList<AntlibMeta>();
        for (AntFile antfile : antfilesMap.values()) {
            antfile.setScope(scopeFilter);
            RootAntObjectMeta rootObject = antfile.getRootObjectMeta();
            if (rootObject instanceof ProjectMeta) {
                projects.add((ProjectMeta) rootObject);
            }
            else {
                antlibs.add((AntlibMeta) rootObject);
            }
        }
        // antfiles.addAll(antfilesMap.values());
        Map<String, Object> root = new HashMap<String, Object>();
        root.put("projects", projects);
        root.put("antlibs", antlibs);
        root.put("packages", getPackages());

        // Add a custom wiki formatting method
        WikiMethod wikiMethod = new WikiMethod();
        root.put("wiki", wikiMethod);

        // Process template
        Template template = cfg.getTemplate("database.xml.ftl");
        try {
            template.process(root, out);
        }
        catch (TemplateException e) {
            e.printStackTrace();
            throw new IOException(e.getMessage());
        }
        out.flush();
    }

    public Collection<AntFile> getAntFiles() {
        return antfilesMap.values();
    }

    public List<PropertyMeta> getProperties() throws IOException {
        List<PropertyMeta> propertiesList = new ArrayList<PropertyMeta>();
        for (AntFile antfile : antfilesMap.values()) {
            RootAntObjectMeta rootMeta = antfile.getRootObjectMeta();
            if (rootMeta instanceof ProjectMeta) {
                propertiesList.addAll(((ProjectMeta) rootMeta).getProperties());
            }
        }
        return propertiesList;
    }

    public List<PackageMeta> getPackages() throws IOException {
        List<PackageMeta> packages = new ArrayList<PackageMeta>();
        for (PackageMeta packageMeta : packagesMap.values()) {
            packages.add(packageMeta);
        }
        return packages;
    }
}
