package org.apache.torque.generator.template.velocity;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.File;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.StringUtils;
import org.apache.torque.generator.GeneratorException;
import org.apache.torque.generator.control.ControllerState;
import org.apache.torque.generator.source.SourceElement;
import org.apache.torque.generator.source.SourceException;
import org.apache.torque.generator.variable.Variable;

/**
 * This class acts as an interface to the Torque generator from the
 * templates. It lets the user access Torque generator properties from the
 * templates, and allows to execute certain action from within the templates.
 */
public class TorqueGenVelocity
{
    /**
     * The state of the controller in which this generator interface is used.
     */
    private ControllerState controllerState;

    /**
     * The outlet in which context this class is used.
     */
    private VelocityOutlet outlet;

    /**
     * A counter which can be used in velocity templates.
     */
    private static AtomicInteger counter = new AtomicInteger(1);

    /**
     * Constructs a generator interface within the given controllerState.
     *
     * @param outlet the outlet in which this generator interface will be used,
     *        not null.
     * @param controllerState the controller context.
     *
     * @throws NullPointerException if outlet or controllerState are null.
     */
    public TorqueGenVelocity(
            VelocityOutlet outlet,
            ControllerState controllerState)
    {
        if (controllerState == null)
        {
            throw new NullPointerException("controllerState may not be null");
        }
        if (outlet == null)
        {
            throw new NullPointerException("outlet may not be null");
        }
        this.controllerState = controllerState;
        this.outlet = outlet;
    }

    /**
     * Processes the mergepoint with the given name.
     *
     * @param mergepointName the name of the mergepoint.
     * @return the output generated by the mergepoint.
     * @throws GeneratorException if the mergepoint could not be processed
     *         completely.
     */
    public String mergepoint(String mergepointName)
            throws GeneratorException
    {
        return outlet.mergepoint(mergepointName, controllerState);
    }

    /**
     * Returns the current controller state.
     *
     * @return The current controller state, never null.
     */
    public ControllerState getControllerState()
    {
        return controllerState;
    }

    /**
     * Returns the current model object. This method is shorthand for
     * <code>getControllerState().getModel()</code>
     *
     * @return the current source element, never null.
     */
    public Object getModel()
    {
        return controllerState.getModel();
    }

    /**
     * Returns the current source element, if it is a SourceElement.
     * This method is shorthand for
     * <code>(SourceElement)getControllerState().getModel()</code>
     *
     * @return the current source element, never null.
     *
     * @throws SourceException if the model is no SourceElement
     */
    public SourceElement getSourceElement() throws SourceException
    {
        Object model = controllerState.getModel();
        if (model instanceof SourceElement)
        {
            return (SourceElement) model;
        }
        throw new SourceException("model is no SourceElement but is of class "
                + model.getClass().getName());
    }

    /**
     * Returns all children of the current source element.
     * This method is shorthand for
     * <code>getSourceElement().getChildren()</code>
     *
     * @return the children of the current source element, never null.
     *
     * @throws SourceException if the model is no SourceElement
     */
    public List<SourceElement> getChildren() throws SourceException
    {
        return getSourceElement().getChildren();
    }

    /**
     * Returns the children of the current source element with a certain name.
     * This method is shorthand for
     * <code>getSourceElement().getChildren(name)</code>
     *
     * @param name the name of the children elements to select.
     *
     * @return the children of the current source element with the name name,
     *         never null.
     *
     * @throws SourceException if the model is no SourceElement
     */
    public List<SourceElement> getChildren(String name) throws SourceException
    {
        return getSourceElement().getChildren(name);
    }

    /**
     * Returns the first child of the current source element
     * with the given name.
     * This method is shorthand for
     * <code>getSourceElement().getChild(name)</code>
     *
     * @param name the name of the child element to select.
     *
     * @return the first child with the given name, or null if no such child
     *         exists.
     *
     * @throws SourceException if the model is no SourceElement
     */
    public SourceElement getChild(String name) throws SourceException
    {
        return getSourceElement().getChild(name);
    }

    /**
     * Returns the parent of the current source element.
     * <code>getSourceElement().getParent()</code>
     *
     * @return the parent of the current source element, or null if the current
     *         source element has no parent.
     *
     * @throws SourceException if the model is no SourceElement
     */
    public SourceElement getParent() throws SourceException
    {
        return getSourceElement().getParent();
    }

    /**
     * Returns the option with the given key. The key can either be a name
     * prefixed with a namespace, or a name without namespace, in which case
     * the namespace of the currently active outlet is used.
     *
     * In the case that the option is not set in this namespace, the parent
     * namespaces are searched recursively.  If the option is not set in any
     * of the parent namespaces, null is returned.
     *
     * @param key the key for the option to retrieve.
     * @return the option for the given key.
     */
    public Object option(String key)
    {
        Object result = controllerState.getOption(key);

        return result;
    }

    /**
     * Returns the option with the given key as boolean value.
     * The key can either be a name prefixed with a namespace,
     * or a name without namespace, in which case the namespace of the
     * currently active outlet is used.
     *
     * In the case that the option is not set in this namespace, the parent
     * namespaces are searched recursively.  If the option is not set in any
     * of the parent namespaces, false is returned.
     *
     * @param key the key for the option to retrieve.
     * @return the option for the given key, converted to a boolean
     */
    public boolean booleanOption(String key)
    {
        boolean result = controllerState.getBooleanOption(key);

        return result;
    }

    /**
     * Returns the option with the given key as int value.
     * The key can either be a name prefixed with a namespace,
     * or a name without namespace, in which case the namespace of the
     * currently active outlet is used.
     *
     * In the case that the option is not set in this namespace, the parent
     * namespaces are searched recursively.  If the option is not set in any
     * of the parent namespaces or empty, 0 is returned.
     *
     * @param key the key for the option to retrieve.
     * @return the option for the given key, converted to a boolean
     */
    public int intOption(String key)
    {
        Object optionValue = controllerState.getOption(key);
        if (optionValue == null)
        {
            return 0;
        }
        String optionString = optionValue.toString();
        if (StringUtils.isBlank(optionString))
        {
            return 0;
        }

        return Integer.parseInt(optionString);
    }

    /**
     * Returns the variable with the given key. The key can either be a name
     * prefixed with a namespace, or a name without namespace, in which case
     * the namespace of the currently active outlet is used.
     *
     * In the case that the variable is not set in this namespace, the parent
     * namespaces are searched recursively.  If the variable is not set in any
     * of the parent namespaces, null is returned.
     *
     * @param key the key for the variable to retrieve.
     * @return the variable for the given key, or null if the variable is not
     *         set or explicitly set to null.
     */
    public Object getVariable(String key)
    {
        return outlet.getVariable(key, controllerState);
    }

    /**
     * Sets a variable. The key can be given with or without namespace;
     * in the latter case, the variable is set in the namespace of the
     * currently active outlet.
     * The Scope of the variable is this outlet and its children.
     *
     * @param key the name of the variable, not null
     * @param value the value of the variable, may be null.
     *
     * @throws NullPointerException if key or scope is null.
     * @throws IllegalArgumentException if the key is no valid QualifiedName.
     */
    public void setVariable(String key, Object value)
    {
        outlet.setVariable(key, value, controllerState);
    }

    /**
     * Sets a variable. The key can be given with or without namespace;
     * in the latter case, the variable is set in the namespace of the
     * currently active outlet.
     *
     * @param key the name of the variable, not null.
     * @param value the value of the variable, may be null.
     * @param scope the scope of the variable, not null.
     *
     * @throws NullPointerException if key or scope is null.
     * @throws IllegalArgumentException if the key is no valid QualifiedName.
     */
    public void setVariable(String key, Object value, String scope)
    {
        Variable.Scope scopeValue = Variable.Scope.valueOf(scope);
        outlet.setVariable(key, value, scopeValue, controllerState);
    }

    /**
     * Returns the currently processed source file.
     *
     * @return the source file which is currently processed.
     */
    public File getSourceFile()
    {
        return controllerState.getSourceFile();
    }

    /**
     * Returns the current date.
     *
     * @return the current date, not null.
     */
    public Date now()
    {
        return new Date();
    }

    /**
     * Returns a counter value which is increased each time this function is
     * accessed. Start value is 1.
     * If <code>resetCounter</code> is not called, the returned value is unique
     * over the generation process.
     *
     * @return the counter value.
     */
    public static int getCounter()
    {
        return counter.incrementAndGet();
    }

    /**
     * Resets the counter accessible though <code>getCounter()</code> back to 1.
     */
    public static  void resetCounter()
    {
        counter.set(1);
    }
}
