package org.apache.torque.generator.control;

/*
 * 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.ArrayList;
import java.util.List;

import org.apache.torque.generator.configuration.UnitConfiguration;
import org.apache.torque.generator.configuration.controller.OutletReference;
import org.apache.torque.generator.configuration.controller.Output;
import org.apache.torque.generator.option.Option;
import org.apache.torque.generator.option.OptionName;
import org.apache.torque.generator.option.Options;
import org.apache.torque.generator.outlet.Outlet;
import org.apache.torque.generator.qname.Namespace;
import org.apache.torque.generator.qname.QualifiedName;
import org.apache.torque.generator.source.SourceElement;
import org.apache.torque.generator.source.SourceProvider;
import org.apache.torque.generator.variable.VariableStore;

/**
 * The state of the controller.  Contains all stuff the controller needs to
 * track.
 */
public class ControllerState
{
    /**
     * The Source provider which is currently used.
     */
    private SourceProvider sourceProvider;

    /**
     * The output which currently processed .
     */
    private Output output;

    /**
     * The current stack of outlets being executed.
     */
    private final List<Outlet> outlets = new ArrayList<Outlet>();

    /**
     * The root element of the source graph.
     */
    private SourceElement rootElement;

    /**
     * The current element within the source.
     */
    private SourceElement sourceElement;

    /**
     * The generation unit which is currently processed.
     */
    private UnitConfiguration unitConfiguration;

    /**
     * The reference in the controller to the root outlet.
     * May override mergepoints in the outlets.
     */
    private OutletReference rootOutletReference;

    /**
     * The variable store.
     */
    private final VariableStore variableStore = new VariableStore();

    /**
     * The currently generated output file. May be null if
     * no file is currently generated (e.g. if the filename is currently
     * generated).
     */
    private File outputFile;

    /**
     * The name of the currently processed source file. May be null if
     * no source file is used (e.g. if the input is created by other means
     * than reading a file).
     */
    private File sourceFile;

    /**
     * The name space in which the current outlet operates.
     */
    private Namespace outletNamespace;

    /**
     * Returns the source provider which is currently in use.
     *
     * @return the current source provider.
     */
    public SourceProvider getSourceProvider()
    {
        return sourceProvider;
    }

    /**
     * Sets the source provider which is currently in use.
     *
     * @param sourceProvider the current source provider.
     */
    public void setSourceProvider(SourceProvider sourceProvider)
    {
        this.sourceProvider = sourceProvider;
    }

    /**
     * Returns the output declaration which is currently processed.
     *
     * @return the output declaration which is currently processed, may be null
     *         only if no output is processed at the moment.
     */
    public Output getOutput()
    {
        return output;
    }

    /**
     * Sets the output declaration which is currently processed.
     *
     * @param output the output which is currently processed.
     */
    void setOutput(Output output)
    {
        this.output = output;
    }

    /**
     * Returns the topmost outlet in the stack of outlets.
     *
     * @return the topmost outlet in the stack of outlets, or null
     *         if the stack is empty.
     */
    public Outlet getOutlet()
    {
        if (outlets.isEmpty())
        {
            return null;
        }
        return outlets.get(outlets.size() - 1);
    }

    /**
     * Pushes a outlet onto the stack of outlets.
     *
     * @param outlet the outlet to be added to the stack of outlets,
     *        not null.
     */
    public void pushOutlet(Outlet outlet)
    {
        if (outlet == null)
        {
            throw new NullPointerException("outlet must not be null");
        }
        this.outlets.add(outlet);
    }

    /**
     * Pops the topmost outlets from the stack of outlets.
     *
     * @return the removed outlet, not null.
     *
     * @throws IndexOutOfBoundsException if the stack is empty.
     */
    public Outlet popOutlet()
    {
        return outlets.remove(outlets.size() - 1);
    }

    /**
     * Returns the current source element. Does not return null
     * during generation.
     *
     * @return the current source element.
     */
    public SourceElement getSourceElement()
    {
        return sourceElement;
    }

    /**
     * Sets the current source element.
     *
     * @param sourceElement the new current source element, or null
     *        to remove the current source element.
     */
    public void setSourceElement(SourceElement sourceElement)
    {
        this.sourceElement = sourceElement;
    }

    /**
     * Returns the root element of the current source.
     *
     * @return The the root element of the current source;
     *         may be null only if no source is currently processed.
     */
    public SourceElement getRootElement()
    {
        return rootElement;
    }

    /**
     * Sets the root element of the current source.
     *
     * @param rootElement the the root element of the current source,
     *        or null to remove the current root element.
     */
    public void setRootElement(SourceElement rootElement)
    {
        this.rootElement = rootElement;
    }

    /**
     * Returns the reference to the current outlet.
     *
     * @return the reference to the current outlet, or null if no
     *         outlet is currently active.
     */
    public OutletReference getRootOutletReference()
    {
        return rootOutletReference;
    }

    /**
     * Sets the reference to the root outlet.
     *
     * @param rootOutletReference the reference to the root outlet
     *        (i.e. the outlet which produces the whole content),
     *        or null to remove the reference.
     */
    void setRootOutletReference(OutletReference rootOutletReference)
    {
        this.rootOutletReference = rootOutletReference;
    }


    /**
     * Sets the name space of the outlet which is currently active.
     *
     * @param namespace the namespace of the outlet which is currently
     *        active, or null to remove the name space.
     */
    void setOutletNamespace(Namespace namespace)
    {
        outletNamespace = namespace;
    }

    /**
     * Returns the namespace of the outlet which is currently active.
     *
     * @return the name space of the active outlet. May be null only
     *         if no generation is in progress.
     */
    public Namespace getOutletNamespace()
    {
        return outletNamespace;
    }

    /**
     * Calculates the value of an option in the current outlet's context.
     * The default namespace which is used when no namespace is given in
     * <code>name</code> is the namespace of the currently used outlet.
     *
     * @param name the name of the option, can contain a namespace.
     *
     * @return The value of the option, or null if no option with that name
     *         is visible from the given namespace.
     */
    public Object getOption(String name)
    {
        Options options = unitConfiguration.getOptions();
        QualifiedName qualifiedName = getQualifiedName(name);
        Option option = options.getInHierarchy(qualifiedName);
        Object result = null;
        if (option != null)
        {
            result = option.getValue();
        }
        return result;
    }

    /**
     * Calculates the value of an option in the current outlet's context.
     * The default namespace which is used when no namespace is given in
     * <code>name</code> is the namespace of the currently used outlet.
     *
     * @param optionName the object containing the name of the option,
     *        which can contain a namespace, not null.
     *
     * @return The value of the option, or null if no option with that name
     *         is visible from the given namespace.
     *
     * @throws NullPointerException if optionName is null.
     */
    public Object getOption(OptionName optionName)
    {
        return getOption(optionName.getName());
    }

    /**
     * Convenience method to return the value of an option as boolean.
     * The option is evaluated in the current outlet's context, see
     * getOption(String). <br/>
     * Uses Boolean.paseBoolean() for String->Boolean conversion.
     * @param name the name of the option, can contain a namespace.
     *
     * @return The value of the option as boolean, or false if no option
     *         with that name is visible from the given namespace,
     */
    public boolean getBooleanOption(String name)
    {
        Object option = getOption(name);
        if (option == null)
        {
            return false;
        }
        return Boolean.parseBoolean(option.toString());
    }

    /**
     * Convenience method to return the value of an option as boolean.
     * The option is evaluated in the current outlet's context, see
     * getOption(String). <br/>
     * Uses Boolean.paseBoolean() for String->Boolean conversion.
     *
     * @param optionName the object containing the name of the option,
     *        which can contain a namespace.
     *
     * @return The value of the option as boolean, or false if no option
     *         with that name is visible from the given namespace.
     *
     * @throws NullPointerExeption if optionName is null.
     */
    public boolean getBooleanOption(OptionName optionName)
    {
        return getBooleanOption(optionName.getName());
    }

    /**
     * Convenience method to return the value of an option as String.
     * The option is evaluated in the current outlet's context, see
     * getOption(String). <br/>
     *
     * @param name the name of the option, can contain a namespace.
     *
     * @return The value of the option as boolean, or false if no option
     *         with that name is visible from the given namespace,
     */
    public String getStringOption(String name)
    {
        Object option = getOption(name);
        if (option == null)
        {
            return null;
        }
        return option.toString();
    }

    /**
     * Convenience method to return the value of an option as String.
     * The option is evaluated in the current outlet's context, see
     * getOption(String). <br/>
     *
     * @param optionName the object containing the name of the option,
     *        which can contain a namespace.
     *
     * @return The value of the option as String, or null if no option
     *         with that name is visible from the given namespace,
     *
     * @throws NullPointerExeption if optionName is null.
     */
    public String getStringOption(OptionName optionName)
    {
        return getStringOption(optionName.getName());
    }

    /**
     * Returns all options which are visible from the current outlet's
     * namespace.
     *
     * @return all visible options, not null.
     */
    public Options getVisibleOptions()
    {
        return unitConfiguration.getOptions().getInHierarchy(
                outletNamespace);
    }

    /**
     * Returns the VariableStore where generation variables can be set.
     *
     * @return the variableStore, never null.
     */
    public VariableStore getVariableStore()
    {
        return variableStore;
    }

    /**
     * Converts a name to a QualifiedName, using the outlet namespace as
     * default namespace is fone is given.
     *
     * @param name the name to convert to a qualifiedName, not null.
     * @return the corresponding qualifiedName.
     *
     * @throws NullPointerException if name is null
     * @throws IllegalArgumentException if name is no valid qualifiedName.
     */
    public QualifiedName getQualifiedName(String name)
    {
        QualifiedName qualifiedName = new QualifiedName(
                name,
                outletNamespace);
        return qualifiedName;
    }

    /**
     * Returns the currently generated file.
     *
     * @return the current output file. May only be null if no
     *         output file is currently generated (e.g. if the file name
     *         is currently generated).
     */
    public File getOutputFile()
    {
        return outputFile;
    }

    /**
     * Sets the currently generated file.
     *
     * @param outputFilePath the currently generated file, or null to remove
     *        the current output file.
     */
    void setOutputFile(File outputFilePath)
    {
        this.outputFile = outputFilePath;
    }


    /**
     * Returns the currently used source file.
     *
     * @return the current source file. May be null if no
     *         source file is currently used (e.g. if the source is created
     *         by other means than reading a file).
     */
    public File getSourceFile()
    {
        return sourceFile;
    }

    /**
     * Sets the currently used source file.
     *
     * @param sourceFile the current source file, or null to remove the
     *        source file.
     */
    public void setSourceFile(File sourceFile)
    {
        this.sourceFile = sourceFile;
    }


    /**
     * Returns the configuration of the currently processed generation unit.
     *
     * @return the configuration of the currently processed generation unit.
     */
    public UnitConfiguration getUnitConfiguration()
    {
        return unitConfiguration;
    }

    /**
     * Sets the configuration of the currently processed generation unit.
     *
     * @param unitConfiguration the configuration of the currently processed
     *        generation unit.
     */
    public void setUnitConfiguration(UnitConfiguration unitConfiguration)
    {
        this.unitConfiguration = unitConfiguration;
    }

    @Override
    public String toString()
    {
        StringBuffer result = new StringBuffer();
        result.append("sourceProvider=").append(sourceProvider)
                .append("output=").append(output)
                .append("outputFilePath=").append(outputFile)
                .append("\noutletNamespace=").append(outletNamespace)
                .append("\noutlets=").append(outlets)
                .append("\nrootElement=").append(rootElement)
                .append("\nsourceElement").append(sourceElement)
                .append("\nrootOutletReference=")
                .append(rootOutletReference)
                .append("\nunitConfiguration=").append(unitConfiguration)
                .append("\nvariableStore=").append(variableStore);
        return result.toString();
    }
}
