/*
 * Copyright 2005 Werner Guttmann
 *
 * 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 org.exolab.castor.builder.factory;

import java.util.Enumeration;

import org.castor.xml.JavaNaming;
import org.exolab.castor.builder.BuilderConfiguration;
import org.exolab.castor.builder.GroupNaming;
import org.exolab.castor.builder.SourceGenerator;
import org.exolab.castor.xml.schema.Annotated;
import org.exolab.castor.xml.schema.Annotation;
import org.exolab.castor.xml.schema.AttributeDecl;
import org.exolab.castor.xml.schema.Documentation;
import org.exolab.castor.xml.schema.ElementDecl;
import org.exolab.castor.xml.schema.Structure;

/**
 * This class defines a base type for the source generator code factory classes.
 *
 * @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a>
 * @version $Revision: 6287 $ $Date: $
 */
public class BaseFactory {

    /** The BuilderConfiguration instance, for callbacks to obtain certain
     *  configured properties. */
    private final BuilderConfiguration _config;
    
    /** The type factory. */
    private final FieldInfoFactory _infoFactory;
    
    /** A GroupNaming helper class used to named anonymous groups. */
    private GroupNaming _groupNaming = null;
    
    /**
     * The calling instance of {@link SourceGenerator}.
     */
    private SourceGenerator _sourceGenerator;

    /**
     * Creates an instance of this class.
     * @param config XML code generator configuration
     * @param infoFactory the FieldInfoFactory to use
     * @param groupNaming Group naming scheme to be used.
     * @param sourceGenerator The calling source generator.
     */
    public BaseFactory(final BuilderConfiguration config,
            final FieldInfoFactory infoFactory, 
            final GroupNaming groupNaming,
            final SourceGenerator sourceGenerator) {
        if (config == null) {
            String err = "The 'BuilderConfiguration' argument must not be null.";
            throw new IllegalArgumentException(err);
        }
        _config = config;

        if (infoFactory == null) {
            this._infoFactory = new FieldInfoFactory();
        } else {
            this._infoFactory = infoFactory;
        }
        _groupNaming = groupNaming;
        _sourceGenerator = sourceGenerator;
    }
    
    /**
     * Get BuilderConfiguration instance, for callbacks to obtain certain configured properties.
     * 
     * @return BuilderConfiguration instance.
     */
    protected final BuilderConfiguration getConfig() {
        return _config;
    }
    
    /**
     * Get type factory.
     * 
     * @return Type factory.
     */
    protected final FieldInfoFactory getInfoFactory() {
        return _infoFactory;
    }
    
    /**
     * Normalizes the given string for use in comments.
     *
     * @param value
     *            the String to normalize
     * @return the given string, normalized, for use in comments.
     */
    protected final String normalize(final String value) {
        if (value == null) {
            return null;
        }

        char[]  chars    = value.toCharArray();
        char[]  newChars = new char[chars.length * 2];
        int     count    = 0;
        int     i        = 0;
        boolean skip     = false;

        while (i < chars.length) {
            char ch = chars[i++];

            if ((ch == ' ') || (ch == '\t')) {
                if ((!skip) && (count != 0)) {
                    newChars[count++] = ' ';
                }
                skip = true;
            } else if (ch == '*') {
                if (i < chars.length && chars[i] == '/') {
                    newChars[count++] = ch;
                    newChars[count++] = '\\';
                }
            } else {
                if (count == 0) {
                    //-- ignore new lines only if count == 0
                    if ((ch == '\r') || (ch == '\n')) {
                        continue;
                    }
                }
                newChars[count++] = ch;
                skip = false;
            }
        }
        return new String(newChars, 0, count);
    }

    /**
     * Returns the group naming helper class for naming nested anonymous groups.
     * 
     * @return the group naming helper class for naming nested anonymous groups.
     */
    public final GroupNaming getGroupNaming() {
        return _groupNaming;
    }

    /**
     * Sets the group naming helper class for naming nested anonymous groups.
     * @param groupNaming the group naming helper class for naming nested anonymous groups.
     */
    public final void setGroupNaming(final GroupNaming groupNaming) {
        _groupNaming = groupNaming;
    }

    /**
     * Returns the calling {@link SourceGenerator} instance.
     * @return the calling source generator 
     */
    protected SourceGenerator getSourceGenerator() {
        return _sourceGenerator;
    }

    /**
     * Creates and returns a Javadoc comment from the given annotations.
     * @param annotated The {@link Annotated} instance holding annotations.
     * @return The Javadoc comment created from the annotations.  
     */
    protected String createComment(final Annotated annotated) {
        //-- process annotations
        Enumeration<Annotation> enumeration = annotated.getAnnotations();
        if (enumeration.hasMoreElements()) {
            //-- just use first annotation
            return createComment(enumeration.nextElement());
        }
        //-- there were no annotations...try possible references
        switch(annotated.getStructureType()) {
            case Structure.ELEMENT:
                ElementDecl elem = (ElementDecl) annotated;
                if (elem.isReference()) {
                    return createComment(elem.getReference());
                }
                break;
            case Structure.ATTRIBUTE:
                AttributeDecl att = (AttributeDecl) annotated;
                if (att.isReference()) {
                    return createComment(att.getReference());
                }
                break;
            default:
                break;
        }
        return null;
    } //-- createComment

    /**
     * Creates and returns a Javadoc comment from a given {@link Annotation}.
     * @param annotation The {@link Annotation} instance to be used for comment creation.
     * @return The Javdoc comment assembled from the AnnotationItem instance
     */
    private String createComment(final Annotation annotation) {
        if (annotation == null) {
            return null;
        }
    
        Enumeration<Documentation> enumeration = annotation.getDocumentation();
        if (enumeration.hasMoreElements()) {
            //-- just use first <info>
            Documentation documentation = enumeration.nextElement();
            return normalize(documentation.getContent());
        }
        return null;
    }

    /**
     * Creates Comments from Schema annotations.
     * @param annotated the Annotated structure to process
     * @return the generated comment.
     * 
     */
    // TODO: refactor to avoid duplication with createComment() methods
    protected String extractCommentsFromAnnotations(final Annotated annotated) {
        //-- process annotations
        Enumeration<Annotation> enumeration = annotated.getAnnotations();
        if (enumeration.hasMoreElements()) {
            StringBuilder comment = new StringBuilder();
            while (enumeration.hasMoreElements()) {
                Annotation ann = enumeration.nextElement();
                Enumeration<Documentation> documentations = ann.getDocumentation();
                while (documentations.hasMoreElements()) {
                    Documentation documentation = documentations.nextElement();
                    String content = documentation.getContent();
                    if (content != null) {
                        comment.append(content);
                    }
                }
            }
            return normalize(comment.toString());
        }
        return null;
    }

    /**
     * To get the {@link JavaNaming} to be used.
     * @return {@link JavaNaming} instance to be used
     * @since 1.1.3
     */
    public final JavaNaming getJavaNaming() {
        return _config.getJavaNaming();
    }
}
