/*
/*
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 * 
 * http://izpack.org/
 * http://izpack.codehaus.org/
 * 
 * Copyright 2002 Marcus Wolschon
 * Copyright 2002 Jan Blok
 * Copyright 2004 Klaus Bartz
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.izforge.izpack.ant;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;

import java.util.Enumeration;
import java.util.Vector;

/**
 * A nested element holder for the installation configuration document content.
 * The installation document must be passed in using a CDATA element.
 *
 * @author Scott Stark
 * @version $Revision: 2163 $
 */
public class ConfigHolder
{
    /**
     * The parent element project
     */
    private Project project;

    /**
     * The config element body text with ${x} property references replaced
     */
    private String installText;

    /**
     * Taken from the ant org.apache.tools.ant.PropertyHelper and '$' replaced
     * with '@' to deal with @{x} style property references.
     * <p/>
     * Parses a string containing @{xxx} style property references
     * into two lists. The first list is a collection of text fragments, while
     * the other is a set of string property names. null entries in the
     * first list indicate a property reference from the second list.
     * <p/>
     * It can be overridden with a more efficient or customized version.
     *
     * @param value        Text to parse. Must not be null.
     * @param fragments    List to add text fragments to. Must not be null.
     * @param propertyRefs List to add property names to. Must not be null.
     * @throws BuildException if the string contains an opening @{ without a
     *                        closing }
     */
    static void parseCompileProperties(String value, Vector<String> fragments, Vector<String> propertyRefs)
            throws BuildException
    {
        int prev = 0;
        int pos;
        // search for the next instance of $ from the 'prev' position
        while ((pos = value.indexOf("@", prev)) >= 0)
        {

            // if there was any text before this, add it as a fragment
            // TODO, this check could be modified to go if pos>prev;
            // seems like this current version could stick empty strings
            // into the list
            if (pos > 0)
            {
                fragments.addElement(value.substring(prev, pos));
            }
            // if we are at the end of the string, we tack on a $
            // then move past it
            if (pos == (value.length() - 1))
            {
                fragments.addElement("@");
                prev = pos + 1;
            }
            else if (value.charAt(pos + 1) != '{')
            {
                // peek ahead to see if the next char is a property or not
                // not a property: insert the char as a literal
                /*
                 * fragments.addElement(value.substring(pos + 1, pos + 2)); prev = pos + 2;
                 */
                if (value.charAt(pos + 1) == '@')
                {
                    // backwards compatibility two $ map to one mode
                    fragments.addElement("@");
                    prev = pos + 2;
                }
                else
                {
                    // new behaviour: $X maps to $X for all values of X!='$'
                    fragments.addElement(value.substring(pos, pos + 2));
                    prev = pos + 2;
                }

            }
            else
            {
                // property found, extract its name or bail on a typo
                int endName = value.indexOf('}', pos);
                if (endName < 0)
                {
                    throw new BuildException("Syntax error in property: " + value);
                }
                String propertyName = value.substring(pos + 2, endName);
                fragments.addElement(null);
                propertyRefs.addElement(propertyName);
                prev = endName + 1;
            }
        }
        // no more @ signs found
        // if there is any tail to the file, append it
        if (prev < value.length())
        {
            fragments.addElement(value.substring(prev));
        }
    }

    ConfigHolder(Project project)
    {
        this.project = project;
    }

    /**
     * Called by ant to set the config element content. The content is scanned
     * for @{x} style property references and replaced with the x project
     * property.
     *
     * @param rawText - the raw config element body text.
     */
    public void addText(String rawText)
    {
        // Locate the @{x} references
        Vector<String> fragments = new Vector<String>();
        Vector<String> propertyRefs = new Vector<String>();
        parseCompileProperties(rawText, fragments, propertyRefs);

        // Replace the references with the project property value
        StringBuffer sb = new StringBuffer();
        Enumeration<String> i = fragments.elements();
        Enumeration<String> j = propertyRefs.elements();

        while (i.hasMoreElements())
        {
            String fragment = i.nextElement();
            if (fragment == null)
            {
                String propertyName = j.nextElement();
                Object replacement = null;

                // try to get it from the project
                if (replacement == null)
                {
                    replacement = project.getProperty(propertyName);
                }

                if (replacement == null)
                {
                    project.log("Property @{" + propertyName + "} has not been set",
                            Project.MSG_VERBOSE);
                }
                if (replacement != null)
                {
                    fragment = replacement.toString();
                }
                else
                {
                    fragment = "@{" + propertyName + "}";
                }
            }
            sb.append(fragment);
        }

        installText = sb.toString();
    }

    /**
     * Get the config element body text with @{x} property references replaced
     *
     * @return the processed config element body text.
     */
    public String getText()
    {
        return installText;
    }

}
