/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002-2006, GeoTools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.maven.xmlcodegen;

import org.eclipse.xsd.XSDSchema;
import org.geotools.xml.Schemas;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;


/**
 * Abstract base class for code generators.
 *
 * @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
 *
 */
public abstract class AbstractGenerator {
    static Logger logger = org.geotools.util.logging.Logging.getLogger("org.geotools.xml");

    /**
     * Package base
     */
    String packageBase;

    /**
     * location to write out files
     */
    String location;

    /**
     * Flag determining if generator will overwrite existing files.
     */
    boolean overwriting = false;

    /**
     * lookup directories for schemas
     */
    File[] schemaLookupDirectories;
    
    /**
     * Sets the base package for generated classes.
     *
     * @param packageBase Dot seperate package name, or <code>null</code> for
     * no package.
     */
    public void setPackageBase(String packageBase) {
        this.packageBase = packageBase;
    }

    public String getPackageBase() {
        return packageBase;
    }

    /**
     * Sets the location to write out generated java classes.
     *
     * @param location A file path.
     */
    public void setLocation(String location) {
        this.location = location;
    }

    public String getLocation() {
        return location;
    }

    /**
     * Flag controlling the behaviour of the generator when a generated file
     * already exists.
     * <p>
     * If set to <code>true</code>, the generator will overwrite existing files.
     * if set to <code>false</code>, the generator will not overwrite the file
     * and issue a warning.
     * </p>
     *
     * @param overwriting overwrite flag.
     */
    public void setOverwriting(boolean overwriting) {
        this.overwriting = overwriting;
    }

    /**
     * Sets the directories to use when attempting to locate a schema via a 
     * relative reference.
     * 
     * @param schemaLookupDirectories An array of directories.
     */
    public void setSchemaLookupDirectories(File[] schemaLookupDirectories) {
		this.schemaLookupDirectories = schemaLookupDirectories;
	}
   
    /**
     * Writes out a string to a file.
     * <p>
     * THe file written out is located under {@link #location}, with the path
     * generated from {@link #packageBase} appended.
     * </p>
     *
     * @param result Result to write to the files.
     * @param className The name of the file to write out.
     */
    protected void write(String result, String className)
        throws IOException {
        //convert package to a path
        File location = outputLocation();

        location.mkdirs();
        location = new File(location, className + ".java");

        //check for existing file
        if (location.exists() && !overwriting) {
            logger.warning("Generated file: " + location + " already exists.");

            return;
        }

        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(
                    location));

        if (packageBase != null) {
            out.write(("package " + packageBase + ";\n\n").getBytes());
        }

        out.write(result.getBytes());

        out.flush();
        out.close();
    }

    /**
     * Copues a file to the output location.
     * <p>
     * THe file written out is located under {@link #location}, with the path
     * generated from {@link #packageBase} appended.
     * </p>
     *
     * @param file The file to copy.
     */
    protected void copy(File file) throws IOException {
        File dest = new File(outputLocation(), file.getName());

        logger.info( "Copying " + file + " to " + dest );
        
        //check for existing file
        if (dest.exists() && !overwriting) {
            logger.warning("Generated file: " + dest + " already exists.");

            return;
        }

        InputStream in = new BufferedInputStream(new FileInputStream(file));
        OutputStream out = new BufferedOutputStream(new FileOutputStream(dest));

        int b = -1;

        while ((b = in.read()) != -1)
            out.write(b);

        out.flush();
        out.close();
        in.close();
    }
    
    /**
     * Attempts to locate a schema file by name by iterating through 
     * {@link #schemaLookupDirectories}.
     * 
     * @param path The path of the file.
     * 
     */
    protected File findSchemaFile( String path ) throws IOException {
    	File file = new File( path );
    	if ( file.isAbsolute() ) {
    		return file;
    	}
    	
    	if ( schemaLookupDirectories != null ) {
    		for ( int i = 0; i < schemaLookupDirectories.length; i++ ) {
    			File dir = schemaLookupDirectories[i];
    			file = new File( dir, path );
    			if ( file.exists() ) {
    				return file;
    			}
    		}
    	}
    	
    	return null;
    }

    /**
     * Convenience method for generating the output location of generated files based on
     * {@link #getLocation()}
     */
    protected File outputLocation() {
        File location = null;

        if (this.location != null) {
            location = new File(this.location);
        } else {
            location = new File(System.getProperty("user.dir"));
        }

        if (packageBase != null) {
            String path = packageBase.replace('.', File.separatorChar);
            location = new File(location, path);
        }

        return location;
    }

    /**
     * Executes a code generation template.
     * <p>
     * The class of the template is formed by prepending
     * <code>org.geotools.xml.codegen.</code> to <code>name</code>.
     * <p>
     *
     * @param templateName The non-qualified class name of the template.
     * @param input        The input to the template.
     *
     * @return The result of the code generator
     *
     * @throws ClassNotFoundException If the template class could not be
     * found.
     *
     * @throws RuntimeException If any exceptions ( ex, relection) occur.
     * while attempting to execute the template.
     *
     */
    protected String execute(String templateName, Object input)
        throws ClassNotFoundException, RuntimeException {
        Class c = Class.forName("org.geotools.maven.xmlcodegen.templates."
                + templateName);

        try {
            Object template = c.newInstance();

            Method generate = c.getMethod("generate",
                    new Class[] { Object.class });

            return (String) generate.invoke(template, new Object[] { input });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    String prefix(XSDSchema schema) {
       return Schemas.getTargetPrefix( schema );
    }
}
