/*
 * 
 * Copyright 2002-2004 The Ant-Contrib project
 *
 *  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 net.sf.antcontrib.cpptasks.compiler;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Vector;
import net.sf.antcontrib.cpptasks.CCTask;
import net.sf.antcontrib.cpptasks.CUtil;
import net.sf.antcontrib.cpptasks.CompilerDef;
import net.sf.antcontrib.cpptasks.DependencyInfo;
import net.sf.antcontrib.cpptasks.ProcessorDef;
import net.sf.antcontrib.cpptasks.parser.Parser;
import net.sf.antcontrib.cpptasks.TargetDef;

/**
 * An abstract compiler implementation.
 * 
 * @author Adam Murdoch
 * @author Curt Arnold
 */
public abstract class AbstractCompiler extends AbstractProcessor
        implements
            Compiler {
    private static final String[] emptyIncludeArray = new String[0];
    private String outputSuffix;
    protected AbstractCompiler(String[] sourceExtensions,
            String[] headerExtensions, String outputSuffix) {
        super(sourceExtensions, headerExtensions);
        this.outputSuffix = outputSuffix;
    }
    /**
     * Checks file name to see if parse should be attempted
     * 
     * Default implementation returns false for files with extensions '.dll',
     * 'tlb', '.res'
     *  
     */
    protected boolean canParse(File sourceFile) {
        String sourceName = sourceFile.toString();
        int lastPeriod = sourceName.lastIndexOf('.');
        if (lastPeriod >= 0 && lastPeriod == sourceName.length() - 4) {
            String ext = sourceName.substring(lastPeriod).toUpperCase();
            if (ext.equals(".DLL") || ext.equals(".TLB") || ext.equals(".RES")) {
                return false;
            }
        }
        return true;
    }
    abstract protected CompilerConfiguration createConfiguration(CCTask task,
            LinkType linkType, ProcessorDef[] baseConfigs,
            CompilerDef specificConfig, TargetDef targetPlatform);
    public ProcessorConfiguration createConfiguration(CCTask task,
            LinkType linkType, ProcessorDef[] baseConfigs,
            ProcessorDef specificConfig, TargetDef targetPlatform) {
        if (specificConfig == null) {
            throw new NullPointerException("specificConfig");
        }
        return createConfiguration(task, linkType, baseConfigs,
                (CompilerDef) specificConfig, targetPlatform);
    }
    abstract protected Parser createParser(File sourceFile);
    protected String getBaseOutputName(String inputFile) {
        int lastSlash = inputFile.lastIndexOf('/');
        int lastReverse = inputFile.lastIndexOf('\\');
        int lastSep = inputFile.lastIndexOf(File.separatorChar);
        if (lastReverse > lastSlash) {
            lastSlash = lastReverse;
        }
        if (lastSep > lastSlash) {
            lastSlash = lastSep;
        }
        int lastPeriod = inputFile.lastIndexOf('.');
        if (lastPeriod < 0) {
            lastPeriod = inputFile.length();
        }
        return inputFile.substring(lastSlash + 1, lastPeriod);
    }
    public String getOutputFileName(String inputFile) {
        //
        //  if a recognized input file
        //
        if (bid(inputFile) > 1) {
            String baseName = getBaseOutputName(inputFile);
            return baseName + outputSuffix;
        }
        return null;
    }
    /**
     * Returns dependency info for the specified source file
     * 
     * @param task
     *            task for any diagnostic output
     * @param source
     *            file to be parsed
     * @param includePath
     *            include path to be used to resolve included files
     * 
     * @param sysIncludePath
     *            sysinclude path from build file, files resolved using
     *            sysInclude path will not participate in dependency analysis
     * 
     * @param envIncludePath
     *            include path from environment variable, files resolved with
     *            envIncludePath will not participate in dependency analysis
     * 
     * @param baseDir
     *            used to produce relative paths in DependencyInfo
     * @param includePathIdentifier
     *            used to distinguish DependencyInfo's from different include
     *            path settings
     * 
     * @author Curt Arnold
     */
    public final DependencyInfo parseIncludes(CCTask task, File source,
            File[] includePath, File[] sysIncludePath, File[] envIncludePath,
            File baseDir, String includePathIdentifier) {
        //
        //  if any of the include files can not be identified
        //      change the sourceLastModified to Long.MAX_VALUE to
        //      force recompilation of anything that depends on it
        long sourceLastModified = source.lastModified();
        File[] sourcePath = new File[1];
        sourcePath[0] = new File(source.getParent());
        Vector onIncludePath = new Vector();
        Vector onSysIncludePath = new Vector();
        String baseDirPath;
        try {
            baseDirPath = baseDir.getCanonicalPath();
        } catch (IOException ex) {
            baseDirPath = baseDir.toString();
        }
        String relativeSource = CUtil.getRelativePath(baseDirPath, source);
        String[] includes = emptyIncludeArray;
        if (canParse(source)) {
            Parser parser = createParser(source);
            try {
                Reader reader = new BufferedReader(new FileReader(source));
                parser.parse(reader);
                includes = parser.getIncludes();
            } catch (IOException ex) {
                task.log("Error parsing " + source.toString() + ":"
                        + ex.toString());
                includes = new String[0];
            }
        }
        for (int i = 0; i < includes.length; i++) {
            String includeName = includes[i];
            if (!resolveInclude(includeName, sourcePath, onIncludePath)) {
                if (!resolveInclude(includeName, includePath, onIncludePath)) {
                    if (!resolveInclude(includeName, sysIncludePath,
                            onSysIncludePath)) {
                        if (!resolveInclude(includeName, envIncludePath,
                                onSysIncludePath)) {
                            //
                            //  this should be enough to require us to reparse
                            //     the file with the missing include for dependency
                            //     information without forcing a rebuild
                            sourceLastModified++;
                        }
                    }
                }
            }
        }
        for (int i = 0; i < onIncludePath.size(); i++) {
            String relativeInclude = CUtil.getRelativePath(baseDirPath,
                    (File) onIncludePath.elementAt(i));
            onIncludePath.setElementAt(relativeInclude, i);
        }
        for (int i = 0; i < onSysIncludePath.size(); i++) {
            String relativeInclude = CUtil.getRelativePath(baseDirPath,
                    (File) onSysIncludePath.elementAt(i));
            onSysIncludePath.setElementAt(relativeInclude, i);
        }
        return new DependencyInfo(includePathIdentifier, relativeSource,
                sourceLastModified, onIncludePath, onSysIncludePath);
    }
    protected boolean resolveInclude(String includeName, File[] includePath,
            Vector onThisPath) {
        for (int i = 0; i < includePath.length; i++) {
            File includeFile = new File(includePath[i], includeName);
            if (includeFile.exists()) {
                onThisPath.addElement(includeFile);
                return true;
            }
        }
        return false;
    }
}
