/*
 * Copyright (c) 2014, 2015
 * NDE Netzdesign und -entwicklung AG, Hamburg, Germany
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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 Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program (see the file LICENSE.txt for more
 * details); if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package org.acplt.oncrpc.maven.plugin;

import java.io.File;
import java.io.IOException;

import org.acplt.oncrpc.apps.jrpcgen.JrpcgenContext;
import org.acplt.oncrpc.apps.jrpcgen.JrpcgenOptions;
import org.acplt.oncrpc.apps.jrpcgen.jrpcgen;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

/**
 * Base class of the maven plugin to run the ONC/RPC '.x' file compiler provided
 * in the Remote Tea library within Maven projects.
 *
 * <p>The Remote Tea library is a complete open source implementation of
 * the ONC/RPC standard, originally developed by the Chair of Process Control
 * Engineering of University of Aachen, Germany.
 * <p>Remote Tea can be found at
 * <a href="https://sourceforge.net/projects/remotetea/">
 * https://sourceforge.net/projects/remotetea/</a>.
 *
 * <p>The plugin parameters that can be specified within the element
 * {@literal configuration} in the POM are:
 * <ul>
 * <li>srcfile   : '.x' file to compile (mandatory)</li>
 * <li>destdir   : directory where generated files need to be placed
 *                 (mandatory). If a 'package' directive is used,
 *                 do <b>not</b> add the package directories to destDir
 *                 (it is done automatically by the task) </li>
 * <li>package   : package name to be used for generated files (optional)</li>
 * <li>createdir : indicates whether jrpcgen must create destdir if it does
 *                 not exist (optional). Defaults to no.</li>
 * <li>verbose   : indicates whether jrpcgen must be verbose (optional).
 *                 Defaults to no.</li>
 * <li>debug     : indicates whether jrpcgen must trace debug information
 *                 (optional). Defaults to no.</li>
 * <li>backup    : indicates whether jrpcgen must backup files (optional).
 *                 Defaults to no.</li>
 * <li>serverTcpOnly : indicates whether jrpcgen shall generate the server
 *                     stub for the TCP transport, only (optional).
 *                     Defaults to no.</li>
 * <li>serverUdpOnly : indicates whether jrpcgen shall generate the server
 *                     stub for the UDP transport, only (optional).
 *                     Defaults to no.</li>
 * <li>bean      : indicates whether jrpcgen shall generate the XDR datatype
 *                 classes with getters and setters for bean usage (optional).
 *                 Setting this attribute to {@code true} implies setting the
 *                 attribute {@code serializable} to {@code true} as well.
 *                 Defaults to no.</li>
 * <li>serializable : indicates whether jrpcgen shall tag the XDR datatype
 * 					  classes as serializable by adding the private static final
 *                    field {@code serialVersionUID} (optional). Defaults to no.</li>
 * <li>noValueCtor  : indicates whether jrpcgen shall skip the generation of
 *                    value constructors for the XDR datatype classes (optional).
 *                    Defaults to no.</li>
 * <li>noToString   : indicates whether jrpcgen shall skip the generation of
 *                    the {@code toString()} - methods for the XDR datatype
 *                    classes (optional). Defaults to no.</li>
 * <li>noEquals     : indicates whether jrpcgen shall skip the generation of
 *                    the {@code equals()} - and {@code hashCode()} - methods
 *                    for the XDR datatype classes (optional). Defaults to no.</li>
 * <li>noEnum       : indicates whether jrpcgen shall fall back to Java interfaces
 *                    instead of Java enumerations as mapping for enumerations
 *                    specified in an x-file (optional). Defaults to no.</li>
 * <li>initStrings  : indicates whether jrpcgen shall intialize string fields
 *                    with an empty string instead of {@code null} (optional).
 *                    Defaults to no.</li>
 * <li>noClamp      : indicates whether generated client calls shall get the version
 *                    from the client instance instead of getting a hard coded value
 *                    referring to the version number specified in the x-file (optional).
 *                    Defaults to no.</li>
 * <li>withCallInfo : indicates whether the generated server stub methods shall get the
 *                    ONC/RPC call information as first parameter (optional).
 *                    Defaults to no.</li>
 * </ul>
 * 
 * @author Harald Wirths {@literal <hwirths@nde.ag>}
 *
 */
public abstract class JRpcGeneratorBase extends AbstractMojo
{
    
    @Parameter( required=true )
    protected java.io.File xFile;
    
    @Parameter( required=true )
    protected java.io.File destDir;
    
    @Parameter( required=true )
    protected String packageName;

    @Parameter( defaultValue="false" )
    protected boolean debug = false;
    
    @Parameter( defaultValue="false" )
    protected boolean verbose = false;
    
    @Parameter( defaultValue="false" )
    protected boolean backup = false;
    
    @Parameter( defaultValue="false" )
    protected boolean createDir = false;
    
    @Parameter( defaultValue="false" )
    protected boolean serverTcpOnly = false;
    
    @Parameter( defaultValue="false" )
    protected boolean serverUdpOnly = false;
    
    @Parameter( defaultValue="false" )
    protected boolean bean = false;

    @Parameter( defaultValue="false" )
    protected boolean serializable = false;
    
    @Parameter(defaultValue = "false")
    protected boolean noValueCtor;
    
    @Parameter(defaultValue = "false")
    protected boolean noToString;
    
    @Parameter(defaultValue = "false")
    protected boolean noEquals;
    
    @Parameter(defaultValue = "false")
    protected boolean noEnum;
    
    @Parameter(defaultValue = "false")
    protected boolean initStrings;
    
    @Parameter(defaultValue = "false")
    protected boolean noClamp;
    
    @Parameter(defaultValue = "false")
    protected boolean withCallInfo;
    
    @Parameter( defaultValue="${project}" )
    private MavenProject project;
    
    protected JRpcGeneratorBase()
    {}
    
    protected void generateSources() throws MojoExecutionException
    {
    	/*
    	 * Define the options for this run.
    	 */
    	JrpcgenOptions options = new JrpcgenOptions();
    		
    	
    	// Remind the destination root path.
    	String destinationPath = destDir.getPath();
    	
        this.checkAttributes();

        try
        {
            this.logInfo("Compiling %s", this.xFile.getCanonicalPath());
        }
        catch (IOException ex) {
        }

        if(packageName != null) {
            options.packageName = packageName;

            try {
            	//Remind the destination path
            	destinationPath = destDir.getPath();
                // Add the package name to destination dir
                destDir = new File(
                    destDir.getCanonicalPath() +
                    File.separator +
                    packageName.replace('.', File.separatorChar));
            }
            catch (IOException ex) {
                throw new MojoExecutionException("",ex); // should never occur
            }
        }

        if (createDir) {
            // Create the destination dir if it does not exist
            try {
                if (!destDir.exists()) {
                    boolean dirsCreated = destDir.mkdirs();
                    if (!dirsCreated) {
                        throw new MojoExecutionException("Could not create destination dir" );
                    }
                }
            }
            catch (SecurityException ex) {
                throw new MojoExecutionException("",ex);
            }
        }

        /*
         * Pass the parameter values to the options object.
         */
        options.debug          = debug;
        options.verbose        = verbose;
        options.noBackups      = (!backup);
        options.destinationDir = destDir;
        options.serverTcpOnly  = serverTcpOnly;
        options.serverUdpOnly  = serverUdpOnly;
        options.makeBean = bean;
        
        if (bean) {
        	options.makeSerializable = true;
        } else {
        	options.makeSerializable = serializable;
        }
        
        options.noValueCtor = noValueCtor;
        options.noToString = noToString;
        options.noEquals = noEquals;
        options.noEnum = noEnum;
        options.initStrings = initStrings;
        options.withCallInfo = withCallInfo;
        
        if (debug)
            dumpState();

		/*
		 * Open a context, try to parse the x-file and to generate the requested Java files.
		 */
        try (JrpcgenContext context = JrpcgenContext.open(options, xFile, jrpcgen.VERSION)){
            jrpcgen generator = new jrpcgen(context);
            generator.doParse().generateJavaFiles();
            
            if (destinationPath != null) {
                this.logInfo("The destination path '%s' will be added as compile source root.", destinationPath);
                project.addCompileSourceRoot(destinationPath);
            }
        } catch ( Throwable t ) {
            this.logError(t.getMessage());
            t.printStackTrace();
            //
            // Exit plugin with a mojo execution exception.
            //
            throw new MojoExecutionException("",t);
        }
    }
    
    protected void checkAttributes() throws MojoExecutionException {
        if(xFile == null)
            throw new MojoExecutionException("srcfile has not been set");

        if(destDir == null)
            throw new MojoExecutionException("destdir has not been set");

        try {
            if(!xFile.isFile())
                throw new MojoExecutionException(String.format("%s : problem reading srcdir", xFile.getCanonicalPath()));

            if(!destDir.isDirectory())
            {
                if ( ! this.createDir )
                {
                    this.logError("Zielverzeichnis '%s' existiert nicht.", this.destDir.getCanonicalPath());
                    throw new MojoExecutionException("problem accessing srcdir");
                }
                else
                {
                    this.logInfo("Zielverzeichnis '%s' muss angelegt werden.", this.destDir.getCanonicalPath());
                }
            }
                
        }
        catch(IOException ex) {
            throw new MojoExecutionException("",ex);
        }
        catch(SecurityException ex) {
            throw new MojoExecutionException("",ex);
        }
    }

    protected void dumpState() throws MojoExecutionException {
        try
        {
            this.logDebug("Quelldatei: %s", this.xFile.getCanonicalPath());
            this.logDebug("Zielverzeichnis: %s", this.destDir.getCanonicalPath());
            this.logDebug("Paketname: %s", this.packageName);
            this.logDebug("Backup: %s", (this.backup ? "Ja" : "Nein"));
            this.logDebug("Verbosse: %s", (this.verbose ? "Ja" : "Nein"));
            this.logDebug("Zielverzeichnis wird erzeugt: %s", (this.createDir ? "Ja" : "Nein"));
        }
        catch(IOException ioException)
        {
            /*
             * Garnicht gut...
             */
            this.logError("Der Status kann nicht ausgegeben werden (%s).", ioException.getMessage());
            throw new MojoExecutionException("Fehler beim Ausgeben des Status", ioException);
        }
    }
    
    protected void logDebug(String format, Object... arguments)
    {
        if ( this.getLog().isDebugEnabled() )
        {
            String message = String.format(format, arguments);
            
            this.getLog().debug(message.subSequence(0, message.length()));
        }
    }
    
    protected void logInfo(String format, Object... arguments)
    {
        if (this.getLog().isInfoEnabled())
        {
            String message = String.format(format, arguments);
            
            this.getLog().info(message.subSequence(0, message.length()));
        }
    }

    protected void logWarn(String format, Object... arguments)
    {
        if ( this.getLog().isWarnEnabled() )
        {
            String message = String.format(format, arguments);
            
            this.getLog().warn(message.subSequence(0,  message.length()));
        }
    }
    
    protected void logError(String format, Object... arguments)
    {
        if ( this.getLog().isErrorEnabled() )
        {
            String message = String.format(format, arguments);
            
            this.getLog().error(message.subSequence(0, message.length()));
        }
    }
    
    protected final JrpcgenOptions options = new JrpcgenOptions();

}
