/** @file
 
 Copyright (c) 2006, Intel Corporation
 All rights reserved. This program and the accompanying materials
 are licensed and made available under the terms and conditions of the BSD License
 which accompanies this distribution.  The full text of the license may be found at
 http://opensource.org/licenses/bsd-license.php

 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 **/
package org.tianocore.build.fpd;

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.tianocore.build.global.GlobalData;
import org.tianocore.build.global.SurfaceAreaQuery;
import org.tianocore.build.id.FpdModuleIdentification;
import org.tianocore.build.id.ModuleIdentification;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
  class PlatformBuildFileGenerator is used to generate ${PLATFORM}_build.xml file. 

  @since GenBuild 1.0
**/
public class PlatformBuildFileGenerator {

    ///
    /// Mapping from modules identification to out put file name
    ///
    private Map<FpdModuleIdentification, String> outfiles;
    
    ///
    /// Mapping from FV name to its modules
    ///
    private Map<String, Set<FpdModuleIdentification>> fvs = new HashMap<String, Set<FpdModuleIdentification>>();


    private boolean isUnified = true;
    
    private SurfaceAreaQuery saq = null;
    
    private File platformBuildFile = null;
    
    private Map<String, String> aprioriType = null;
    
    private Project project;
    
    private String info = "DO NOT EDIT \n" 
        + "This file is auto-generated by the build utility\n" 
        + "\n" 
        + "Abstract:\n"
        + "Auto-generated ANT build file for building EFI Modules and Platforms\n";

    public PlatformBuildFileGenerator(Project project, Map<FpdModuleIdentification, String> outfiles, Map<String, Set<FpdModuleIdentification>> fvs, boolean isUnified, SurfaceAreaQuery saq, String platformBuildFile, Map<String, String> aprioriType){
        this.project = project;
        this.outfiles = outfiles;
        this.isUnified = isUnified;
        this.fvs = fvs;
        this.saq = saq;
        this.platformBuildFile = new File(platformBuildFile);
        this.aprioriType = aprioriType;
    }
    
    /**
      Generate build.out.xml file.
     
      @throws BuildException
                  build.out.xml XML document create error
    **/
    public void genBuildFile() throws BuildException {
        DocumentBuilderFactory domfac = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder dombuilder = domfac.newDocumentBuilder();
            Document document = dombuilder.newDocument();
            Comment rootComment = document.createComment(info);
            //
            // create root element and its attributes
            //
            Element root = document.createElement("project");
            root.setAttribute("name", project.getProperty("PLATFORM"));
            root.setAttribute("default", "all");
            root.setAttribute("basedir", ".");
            
            //
            // element for External ANT tasks
            //
            root.appendChild(document.createComment("Apply external ANT tasks"));
            Element ele = document.createElement("taskdef");
            ele.setAttribute("resource", "GenBuild.tasks");
            root.appendChild(ele);

            ele = document.createElement("taskdef");
            ele.setAttribute("resource", "frameworktasks.tasks");
            root.appendChild(ele);

            ele = document.createElement("taskdef");
            ele.setAttribute("resource", "net/sf/antcontrib/antlib.xml");
            root.appendChild(ele);
            
            ele = document.createElement("property");
            ele.setAttribute("environment", "env");
            root.appendChild(ele);
            
            //
            // Default Target
            //
            root.appendChild(document.createComment("Default target"));
            ele = document.createElement("target");
            ele.setAttribute("name", "all");
            ele.setAttribute("depends", "prebuild, modules, fvs, postbuild");
            root.appendChild(ele);
            
            //
            // Modules and Fvs Target
            //
            applyModules(document, root);
            
            applyFvs(document, root);

            //
            // Clean Target
            //
            applyClean(document, root);
            
            //
            // Deep Clean Target
            //
            applyCleanall(document, root);
            
            //
            // User Extension pre build
            //
            applyUserExtensionsPreBuild(document, root);
            
            //
            // User Extension Post build
            //
            applyUserExtensionsPostBuild(document, root);
            applyUserExtensions(document, root);

            document.appendChild(rootComment);
            document.appendChild(root);
            //
            // Prepare the DOM document for writing
            //
            Source source = new DOMSource(document);
            //
            // generate all directory path
            //
            (new File(platformBuildFile.getParent())).mkdirs();
            Result result = new StreamResult(platformBuildFile);
            //
            // Write the DOM document to the file
            //
            Transformer xformer = TransformerFactory.newInstance().newTransformer();
            xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            xformer.setOutputProperty(OutputKeys.INDENT, "yes");
            xformer.transform(source, result);
        } catch (Exception ex) {
            throw new BuildException("Generating platform build file [" + platformBuildFile.getPath() + "_build.xml] failed. \n" + ex.getMessage());
        }
    }
    
    /**
      1. Get All valid Fv Image Names in sequence
      2. For each FV, get modules by sequences
      3. Get other modules
      
      @param document XML document
      @param root Node
    **/
    private void applyModules(Document document, Node root) {
        root.appendChild(document.createComment("Modules target"));
        Element ele = document.createElement("target");
        ele.setAttribute("name", "modules");

        //
        // Try to build apriori if necessary
        //
        //
        // For every Target and ToolChain
        //
        String[] targetList = GlobalData.getToolChainInfo().getTargets();
        for (int i = 0; i < targetList.length; i++){
            String[] toolchainList = GlobalData.getToolChainInfo().getTagnames();
            for(int j = 0; j < toolchainList.length; j++){
                //
                // Prepare FV_DIR
                //
                String ffsCommonDir = project.getProperty("BUILD_DIR") + File.separatorChar 
                                + targetList[i] + "_" 
                                + toolchainList[j];
                File fvDir = new File(ffsCommonDir + File.separatorChar + "FV");
                Element fvEle = document.createElement("var");
                fvEle.setAttribute("name", "FV_DIR");
                fvEle.setAttribute("value", fvDir.getPath().replaceAll("(\\\\)", "/"));
                ele.appendChild(fvEle);
                
                Iterator<String> aprIter = aprioriType.keySet().iterator();
                while (aprIter.hasNext()) {
                    String fvName = aprIter.next();
                    Element moduleEle = document.createElement("Build_Apriori");
                    moduleEle.setAttribute("FILENAME", fvName);
                    moduleEle.setAttribute("GUID", aprioriType.get(fvName));
                    ele.appendChild(moduleEle);
                }
            }
        }
        
        //
        // Get all valid FV name
        //
        String[] validFv = saq.getFpdValidImageNames();
        
        //
        // For each valid FV, get all modules in sequence
        //
        for (int i = 0; i < validFv.length; i++) {
            if (fvs.containsKey(validFv[i])) {
                Set<FpdModuleIdentification> set = fvs.get(validFv[i]);
                Iterator<FpdModuleIdentification> iter = set.iterator();
                while (iter.hasNext()) {
                    FpdModuleIdentification fpdModuleId = iter.next();
                    applySingleModule(document, ele, fpdModuleId);
                }
            }
        }
        
        //
        // Get all other modules
        //
        Iterator<String> fvsNameIter = fvs.keySet().iterator();
        
        while (fvsNameIter.hasNext()) {
            String fvName = fvsNameIter.next();
            if (!isContain(validFv, fvName)) {
                Set<FpdModuleIdentification> set = fvs.get(fvName);
                Iterator iter = set.iterator();
                while (iter.hasNext()) {
                    FpdModuleIdentification fpdModuleId = (FpdModuleIdentification) iter.next();
                    applySingleModule(document, ele, fpdModuleId);
                }
            }
        }

        root.appendChild(ele);
    }
    
    private void applySingleModule(Document document, Node root, FpdModuleIdentification fpdModuleId) {
        ModuleIdentification moduleId = fpdModuleId.getModule();
        Element moduleEle = document.createElement("GenBuild");
        moduleEle.setAttribute("type", "build");
        //
        // Inherit Properties.
        //{"ARCH", "PACKAGE", "PACKAGE_GUID", "PACKAGE_VERSION", "MODULE_DIR"}
        //
        
        //
        // ARCH
        //
        Element property = document.createElement("property");
        property.setAttribute("name", "ARCH");
        property.setAttribute("value", fpdModuleId.getArch());
        moduleEle.appendChild(property);

        //
        // MODULE_GUID
        //
        property = document.createElement("property");
        property.setAttribute("name", "MODULE_GUID");
        property.setAttribute("value", moduleId.getGuid());
        moduleEle.appendChild(property);
        
        //
        // MODULE_VERSION
        //
        property = document.createElement("property");
        property.setAttribute("name", "MODULE_VERSION");
        property.setAttribute("value", moduleId.getVersion());
        moduleEle.appendChild(property);
        
        //
        // PACKAGE_GUID
        //
        property = document.createElement("property");
        property.setAttribute("name", "PACKAGE_GUID");
        property.setAttribute("value", moduleId.getPackage().getGuid());
        moduleEle.appendChild(property);
        
        //
        // PACKAGE_VERSION
        //
        property = document.createElement("property");
        property.setAttribute("name", "PACKAGE_VERSION");
        property.setAttribute("value", moduleId.getPackage().getVersion());
        moduleEle.appendChild(property);
        
        root.appendChild(moduleEle);
    }
    
    private boolean isContain(String[] list, String item) {
        for (int i = 0; i < list.length; i++) {
            if (list[i].equalsIgnoreCase(item)) {
                return true;
            }
        }
        return false;
    }
    
    private void applyFvs(Document document, Node root) {
        //
        // FVS Target
        //
        root.appendChild(document.createComment("FVs target"));
        Element ele = document.createElement("target");
        ele.setAttribute("name", "fvs");

        //
        // For every Target and ToolChain
        //
        String[] targetList = GlobalData.getToolChainInfo().getTargets();
        for (int i = 0; i < targetList.length; i++){
            String[] toolchainList = GlobalData.getToolChainInfo().getTagnames();
            for(int j = 0; j < toolchainList.length; j++){
                String fvOutputDir = project.getProperty("BUILD_DIR") + File.separatorChar 
                                        + targetList[i] + "_" 
                                        + toolchainList[j] + File.separatorChar + "FV";
                String[] validFv = saq.getFpdValidImageNames();
                for (int k = 0; k < validFv.length; k++) {
                    String inputFile = fvOutputDir + "" + File.separatorChar + validFv[k].toUpperCase() + ".inf";
                    Element fvEle = document.createElement("genfvimage");
                    fvEle.setAttribute("infFile", inputFile);
                    fvEle.setAttribute("outputDir", fvOutputDir);
                    ele.appendChild(fvEle);
                }
            }
        }
        root.appendChild(ele);
    }
    
    private void applyClean(Document document, Node root) {
        //
        // Clean Target
        //
        root.appendChild(document.createComment("Clean target"));
        Element ele = document.createElement("target");
        ele.setAttribute("name", "clean");

        if (isUnified) {
            Element cleanEle = document.createElement("delete");
            cleanEle.setAttribute("includeemptydirs", "true");
            Element filesetEle = document.createElement("fileset");
            filesetEle.setAttribute("dir", project.getProperty("BUILD_DIR"));
            filesetEle.setAttribute("includes", "**\\OUTPUT\\**");
            cleanEle.appendChild(filesetEle);
            ele.appendChild(cleanEle);
        } else {
            Set set = outfiles.keySet();
            Iterator iter = set.iterator();
            while (iter.hasNext()) {
                FpdModuleIdentification fpdModuleId = (FpdModuleIdentification) iter.next();
                ModuleIdentification moduleId = fpdModuleId.getModule();

                Element ifEle = document.createElement("if");
                Element availableEle = document.createElement("available");
                availableEle.setAttribute("file", moduleId.getMsaFile().getParent() + File.separatorChar
                                                  + "build.xml");
                ifEle.appendChild(availableEle);
                Element elseEle = document.createElement("then");

                Element moduleEle = document.createElement("ant");
                moduleEle.setAttribute("antfile", moduleId.getMsaFile().getParent() + File.separatorChar
                                                  + "build.xml");
                moduleEle.setAttribute("target", "clean");
                //
                // Inherit Properties.
                //{"ARCH", "PACKAGE", "PACKAGE_GUID", "PACKAGE_VERSION", "MODULE_DIR"}
                //
                
                //
                // ARCH
                //
                Element property = document.createElement("property");
                property.setAttribute("name", "ARCH");
                property.setAttribute("value", fpdModuleId.getArch());
                moduleEle.appendChild(property);

                //
                // PACKAGE
                //
                property = document.createElement("property");
                property.setAttribute("name", "PACKAGE");
                property.setAttribute("value", moduleId.getPackage().getName());
                moduleEle.appendChild(property);
                
                //
                // PACKAGE_GUID
                //
                property = document.createElement("property");
                property.setAttribute("name", "PACKAGE_GUID");
                property.setAttribute("value", moduleId.getPackage().getGuid());
                moduleEle.appendChild(property);
                
                //
                // PACKAGE_VERSION
                //
                property = document.createElement("property");
                property.setAttribute("name", "PACKAGE_VERSION");
                property.setAttribute("value", moduleId.getPackage().getVersion());
                moduleEle.appendChild(property);
                
                //
                // MODULE_DIR
                //
                property = document.createElement("property");
                property.setAttribute("name", "MODULE_DIR");
                property.setAttribute("value", moduleId.getMsaFile().getParent());
                moduleEle.appendChild(property);
                elseEle.appendChild(moduleEle);
                ifEle.appendChild(elseEle);
                ele.appendChild(ifEle);
            }
        }
        root.appendChild(ele);
    }
    
    private void applyCleanall(Document document, Node root) {
        //
        // Deep Clean Target
        //
        root.appendChild(document.createComment("Target: cleanall"));
        Element ele = document.createElement("target");
        ele.setAttribute("name", "cleanall");

        if (isUnified) {
            String[] targetList = GlobalData.getToolChainInfo().getTargets();
            for (int i = 0; i < targetList.length; ++i) {
                String[] toolchainList = GlobalData.getToolChainInfo().getTagnames();
                for(int j = 0; j < toolchainList.length; j++) {
                    Element cleanAllEle = document.createElement("delete");
                    cleanAllEle.setAttribute("dir", project.getProperty("BUILD_DIR") + File.separatorChar + targetList[i] + "_" + toolchainList[j]);
                    ele.appendChild(cleanAllEle);
                }
            }
        } else {
            Set set = outfiles.keySet();
            Iterator iter = set.iterator();
            while (iter.hasNext()) {
                FpdModuleIdentification fpdModuleId = (FpdModuleIdentification) iter.next();
                ModuleIdentification moduleId = fpdModuleId.getModule();

                Element ifEle = document.createElement("if");
                Element availableEle = document.createElement("available");
                availableEle.setAttribute("file", moduleId.getMsaFile().getParent() + File.separatorChar
                                                  + "build.xml");
                ifEle.appendChild(availableEle);
                Element elseEle = document.createElement("then");

                Element moduleEle = document.createElement("ant");
                moduleEle.setAttribute("antfile", moduleId.getMsaFile().getParent() + File.separatorChar
                                                  + "build.xml");
                moduleEle.setAttribute("target", "cleanall");
                //
                // Inherit Properties.
                //{"ARCH", "PACKAGE", "PACKAGE_GUID", "PACKAGE_VERSION", "MODULE_DIR"}
                //
                
                //
                // ARCH
                //
                Element property = document.createElement("property");
                property.setAttribute("name", "ARCH");
                property.setAttribute("value", fpdModuleId.getArch());
                moduleEle.appendChild(property);

                //
                // PACKAGE
                //
                property = document.createElement("property");
                property.setAttribute("name", "PACKAGE");
                property.setAttribute("value", moduleId.getPackage().getName());
                moduleEle.appendChild(property);
                
                //
                // PACKAGE_GUID
                //
                property = document.createElement("property");
                property.setAttribute("name", "PACKAGE_GUID");
                property.setAttribute("value", moduleId.getPackage().getGuid());
                moduleEle.appendChild(property);
                
                //
                // PACKAGE_VERSION
                //
                property = document.createElement("property");
                property.setAttribute("name", "PACKAGE_VERSION");
                property.setAttribute("value", moduleId.getPackage().getVersion());
                moduleEle.appendChild(property);
                
                //
                // MODULE_DIR
                //
                property = document.createElement("property");
                property.setAttribute("name", "MODULE_DIR");
                property.setAttribute("value", moduleId.getMsaFile().getParent());
                moduleEle.appendChild(property);
                elseEle.appendChild(moduleEle);
                ifEle.appendChild(elseEle);
                ele.appendChild(ifEle);
            }
        }
        root.appendChild(ele);
    }
    
    private void applyUserExtensionsPreBuild(Document document, Node root) {
        //
        // User Extensions
        //
        root.appendChild(document.createComment("Pre-Build Processing"));
        Element ele = document.createElement("target");
        ele.setAttribute("name", "prebuild");
        
        Node node = saq.getFpdUserExtensionPreBuild();
        if (node != null) {
            //
            // For every Target and ToolChain
            //
            String[] targetList = GlobalData.getToolChainInfo().getTargets();
            for (int i = 0; i < targetList.length; i++){
                String[] toolchainList = GlobalData.getToolChainInfo().getTagnames();
                for(int j = 0; j < toolchainList.length; j++){
                    //
                    // Prepare FV_DIR
                    //
                    String ffsCommonDir = project.getProperty("BUILD_DIR") + File.separatorChar 
                                    + targetList[i] + "_" 
                                    + toolchainList[j];
                    File fvDir = new File(ffsCommonDir + File.separatorChar + "FV");
                    Element fvEle = document.createElement("var");
                    fvEle.setAttribute("name", "FV_DIR");
                    fvEle.setAttribute("value", fvDir.getPath().replaceAll("(\\\\)", "/"));
                    ele.appendChild(fvEle);
                    
                    Element targetDirEle = document.createElement("var");
                    targetDirEle.setAttribute("name", "TARGET_DIR");
                    targetDirEle.setAttribute("value", ffsCommonDir.replaceAll("(\\\\)", "/"));
                    ele.appendChild(targetDirEle);
                    
                    NodeList childNodes = node.getChildNodes();
                    for (int k = 0; k < childNodes.getLength(); k++) {
                        Node childItem = childNodes.item(k);
                        if (childItem.getNodeType() == Node.ELEMENT_NODE) {
                            ele.appendChild(recursiveNode(childItem, document));
                        }
                    }
                }
            }
        }
        
        root.appendChild(ele);
    }
    
    private void applyUserExtensionsPostBuild(Document document, Node root) {
        //
        // User Extensions
        //
        root.appendChild(document.createComment("Post-Build Processing"));
        Element ele = document.createElement("target");
        ele.setAttribute("name", "postbuild");
        
        Node node = saq.getFpdUserExtensionPostBuild();
        if (node != null) {
            //
            // For every Target and ToolChain
            //
            String[] targetList = GlobalData.getToolChainInfo().getTargets();
            for (int i = 0; i < targetList.length; i++){
                String[] toolchainList = GlobalData.getToolChainInfo().getTagnames();
                for(int j = 0; j < toolchainList.length; j++){
                    //
                    // Prepare FV_DIR
                    //
                    String ffsCommonDir = project.getProperty("BUILD_DIR") + File.separatorChar 
                                    + targetList[i] + "_" 
                                    + toolchainList[j];
                    File fvDir = new File(ffsCommonDir + File.separatorChar + "FV");
                    Element fvEle = document.createElement("var");
                    fvEle.setAttribute("name", "FV_DIR");
                    fvEle.setAttribute("value", fvDir.getPath().replaceAll("(\\\\)", "/"));
                    ele.appendChild(fvEle);
                    
                    Element targetDirEle = document.createElement("var");
                    targetDirEle.setAttribute("name", "TARGET_DIR");
                    targetDirEle.setAttribute("value", ffsCommonDir.replaceAll("(\\\\)", "/"));
                    ele.appendChild(targetDirEle);
                    
                    NodeList childNodes = node.getChildNodes();
                    for (int k = 0; k < childNodes.getLength(); k++) {
                        Node childItem = childNodes.item(k);
                        if (childItem.getNodeType() == Node.ELEMENT_NODE) {
                            ele.appendChild(recursiveNode(childItem, document));
                        }
                    }
                
                }
            }
        }
        
        root.appendChild(ele);
    }

    private void applyUserExtensions(Document document, Node root) {
        Node[] nodeList = saq.getFpdUserExtensions();
        for (int nodeIndex = 0; nodeIndex < nodeList.length; ++nodeIndex) {
            Node node = nodeList[nodeIndex];
            //
            // User Extensions
            //
            root.appendChild(document.createComment("User Defined Target"));
            Element ele = document.createElement("target");
            ele.setAttribute("name", node.getAttributes().getNamedItem("Identifier").getNodeValue());

            if (node != null) {
                //
                // For every Target and ToolChain
                //
                String[] targetList = GlobalData.getToolChainInfo().getTargets();
                for (int i = 0; i < targetList.length; i++){
                    String[] toolchainList = GlobalData.getToolChainInfo().getTagnames();
                    for(int j = 0; j < toolchainList.length; j++){
                        //
                        // Prepare FV_DIR
                        //
                        String ffsCommonDir = project.getProperty("BUILD_DIR") + File.separatorChar 
                                        + targetList[i] + "_" 
                                        + toolchainList[j];
                        File fvDir = new File(ffsCommonDir + File.separatorChar + "FV");
                        Element fvEle = document.createElement("var");
                        fvEle.setAttribute("name", "FV_DIR");
                        fvEle.setAttribute("value", fvDir.getPath().replaceAll("(\\\\)", "/"));
                        ele.appendChild(fvEle);

                        Element targetDirEle = document.createElement("var");
                        targetDirEle.setAttribute("name", "TARGET_DIR");
                        targetDirEle.setAttribute("value", ffsCommonDir.replaceAll("(\\\\)", "/"));
                        ele.appendChild(targetDirEle);

                        NodeList childNodes = node.getChildNodes();
                        for (int k = 0; k < childNodes.getLength(); k++) {
                            Node childItem = childNodes.item(k);
                            if (childItem.getNodeType() == Node.ELEMENT_NODE) {
                                ele.appendChild(recursiveNode(childItem, document));
                            }
                        }

                    }
                }
            }

            root.appendChild(ele);
        }
    }

    private Element recursiveNode(Node node, Document document) {
        Element root = document.createElement(node.getNodeName());
        NamedNodeMap attr = node.getAttributes();
        for (int i = 0; i < attr.getLength(); i++) {
            Node attrItem = attr.item(i);
            root.setAttribute(attrItem.getNodeName(), attrItem.getNodeValue());
        }
        NodeList childNodes = node.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node childItem = childNodes.item(i);
            if (childItem.getNodeType() == Node.ELEMENT_NODE) {
                root.appendChild(recursiveNode(childItem, document));
            }
            else if (childItem.getNodeType() == Node.TEXT_NODE){
                if (!childItem.getNodeValue().trim().equalsIgnoreCase("")) {
                    root.setTextContent(childItem.getNodeValue());
                }
            }
        }
        return root;
    }
}
