/*
 * gvNIX is an open source tool for rapid application development (RAD).
 * Copyright (C) 2010 Generalitat Valenciana
 * 
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.gvnix.dynamic.configuration.roo.addon;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynComponent;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynComponentList;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynConfiguration;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynConfigurationList;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynProperty;
import org.gvnix.dynamic.configuration.roo.addon.entity.DynPropertyList;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Manage POM.
 * 
 * @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a
 *         href="http://www.dgti.gva.es">General Directorate for Information
 *         Technologies (DGTI)</a>
 */
@Component
@Service
public class PomManagerImpl implements PomManager {

    private static final String RESOURCES_PATH = "src/main/resources";

    @Reference
    private ProjectOperations projectOperations;
    @Reference
    private FileManager fileManager;

    /**
     * {@inheritDoc}
     */
    public DynConfigurationList export(DynConfigurationList dynConfs) {

        // Pom root element
        String pom = projectOperations.getPathResolver().getIdentifier(
                LogicalPath.getInstance(Path.ROOT, ""), "pom.xml");
        Document doc = XmlUtils.readXml(fileManager.getInputStream(pom));

        // Iterate stored dynamic configurations for export to pom
        for (DynConfiguration dynConf : dynConfs) {

            Element prof = exportConfiguration(doc, dynConf);

            // Iterate components of dynamic configuration
            DynComponentList dynComps = dynConf.getComponents();
            for (DynComponent dynComp : dynComps) {

                Element props = exportComponent(doc, prof);

                // Iterate properties of dynamic configuration
                DynPropertyList dynProps = dynComp.getProperties();
                for (DynProperty dynProp : dynProps) {

                    exportProperty(doc, props, dynProp);
                }
            }

            // Update POM configuration before store vars to avoid overwrite
            fileManager.createOrUpdateTextFileIfRequired(pom,
                    XmlUtils.nodeToString(doc), true);
        }

        return dynConfs;
    }

    /**
     * Write a configurations into POM profile.
     * 
     * @param doc Pom document
     * @param dynConf
     * @return New configuration element
     */
    protected Element exportConfiguration(Document doc, DynConfiguration dynConf) {

        Element root = doc.getDocumentElement();

        // <project>
        // <profiles>
        // <profile>
        // <id>test</id>
        // <activation>
        // <activeByDefault>true</activeByDefault>
        // </activation>
        // </profile>
        // </profiles>
        // </project>

        // Profiles section: find or create if not exists
        Element profs = XmlUtils.findFirstElement("/project/profiles", root);
        if (profs == null) {

            profs = doc.createElement("profiles");
            root.appendChild(profs);
        }

        // Remove profile if already exists
        Element prof = XmlUtils.findFirstElement("profile/id[text()='"
                + dynConf.getName() + "']/..", profs);
        if (prof != null) {

            profs.removeChild(prof);
        }

        // Create a profile section for this dynamic configuration
        prof = doc.createElement("profile");
        profs.appendChild(prof);

        // Create an identifier for profile
        Element id = doc.createElement("id");
        id.setTextContent(dynConf.getName());
        prof.appendChild(id);

        // If dynamic configuration is active: profile active by default
        if (dynConf.isActive()) {

            // Create an activation section
            Element activation = doc.createElement("activation");
            prof.appendChild(activation);

            Element active = doc.createElement("activeByDefault");
            active.setTextContent("true");
            activation.appendChild(active);
        }

        return prof;
    }

    /**
     * Write a component into POM profile.
     * 
     * @param doc Pom document
     * @param prof Profile element
     * @return New component element
     */
    protected Element exportComponent(Document doc, Element prof) {

        Element root = doc.getDocumentElement();

        // <resources>
        // <resource>
        // <directory>src/main/resources</directory>
        // <filtering>true</filtering>
        // </resource>
        // </resources>

        // Build section: find or create if not exists
        Element build = XmlUtils.findFirstElement("/project/build", root);
        if (build == null) {

            build = doc.createElement("build");
            root.appendChild(build);
        }

        Element resos;

        // Resources section: find or create if not exists
        resos = XmlUtils.findFirstElement("resources", build);
        if (resos == null) {

            resos = doc.createElement("resources");
            build.appendChild(resos);
        }

        // Find resource section with directory and filter
        Element reso = XmlUtils.findFirstElement("resource/directory"
                + "[text()='" + RESOURCES_PATH
                + "']/../filtering[text()='true']/..", resos);

        // Remove resource if already exists
        if (reso != null) {

            resos.removeChild(reso);
        }

        // Create resource section with directory and filter
        reso = doc.createElement("resource");
        resos.appendChild(reso);
        Element dir = doc.createElement("directory");
        dir.setTextContent(RESOURCES_PATH);
        reso.appendChild(dir);
        Element filter = doc.createElement("filtering");
        filter.setTextContent("true");
        reso.appendChild(filter);

        // <properties>
        // </properties>

        // Properties section: find or create if not exists
        Element props = XmlUtils.findFirstElement("properties", prof);
        if (props == null) {
            props = doc.createElement("properties");
            prof.appendChild(props);
        }

        return props;
    }

    /**
     * Write a property into POM profile.
     * <p>
     * No write property if null value.
     * </p>
     * 
     * @param doc Pom document
     * @param props Properties element
     * @return New property element
     */
    protected void exportProperty(Document doc, Element props,
            DynProperty dynProp) {

        String key = dynProp.getKey().replace('/', '.').replace('[', '.')
                .replace(']', '.').replace('@', '.').replace(':', '.')
                .replace('-', '.').replace('%', '.');
        while (key.startsWith(".")) {
            key = key.substring(1, key.length());
        }
        while (key.endsWith(".")) {
            key = key.substring(0, key.length() - 1);
        }
        while (key.contains("..")) {
            key = key.replace("..", ".");
        }

        // Create property if not empty (no write property if null value)
        if (dynProp.getValue() != null) {

            // <log4j.rootLogger>INFO, stdout</log4j.rootLogger>

            // Create a property element for this dynamic property
            Element prop = doc.createElement(key);
            props.appendChild(prop);

            // Store this dynamic property value in pom and replace
            // dynamic property value with a var
            prop.setTextContent(dynProp.getValue());
        }

        dynProp.setValue("${" + key + "}");
    }
}
