/*
 * $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/apps/jrpcgen/JrpcgenProgramInfo.java,v 1.1 2003/08/13 12:03:46 haraldalbrecht Exp $
 *
 * Copyright (c) 1999, 2000
 * Lehrstuhl fuer Prozessleittechnik (PLT), RWTH Aachen
 * D-52064 Aachen, 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.apps.jrpcgen;

import java.io.IOException;

/**
 * The <code>JrpcgenProgramInfo</code> class contains information about a
 * single ONC/RPC program as defined in an rpcgen "x"-file.
 *
 * @version $Revision: 1.1 $ $Date: 2003/08/13 12:03:46 $ $State: Exp $ $Locker:  $
 * @author Harald Albrecht
 */
class JrpcgenProgramInfo extends JrpcgenDocumentable implements JrpcgenItem {

	public static class Table extends JrpcgenItemTable<JrpcgenProgramInfo> {}
    /**
     * Program number assigned to an ONC/RPC program. This attribute contains
     * either an integer literal or an identifier (which must resolve to an
     * integer).
     */
    public String programNumber;

    /**
     * Identifier assigned to the program number of an ONC/RPC program.
     */
    public String programId;

    /**
     * Set of versions specified for a particular ONC/RPC program.
     * The elements in the set are of class {@link JrpcgenVersionInfo}.
     */
    public JrpcgenVersionInfo.Table versions;
    
    /**
     * Construct a new <code>JrpcgenProgramInfo</code> object containing the
     * programs's identifier and number, as well as the versions defined
     * for this particular ONC/RPC program.
     *
     * @param programId Identifier defined for this ONC/RPC program.
     * @param programNumber Program number assigned to this ONC/RPC program.
     * @param versions Vector of versions defined for this ONC/RPC program.
     */
    public JrpcgenProgramInfo(JrpcgenContext context, String programId, String programNumber, JrpcgenVersionInfo.Table versions) {
    	this.context = context;
        this.programId = programId;
        this.programNumber = programNumber;
        this.versions = versions;
        
        /*
         * At this point the constants defined for each procedure
         * can be completed.
         */
        completeProcedureConstants();
    }
    
    @Override
    public String getIdentifier() {
    	return programId;
    }
    
    /**
     * create an interface that contains all service methods, for use at client side.
     * the client stub implements all this methods.
     * the interface may be used by proxy or Test Mocks.
     */
    public void writeProgramInterface() {
    	String serviceClass = context.getInterfaceName(programId);
    	
    	try (JrpcgenJavaFile javaFile = JrpcgenJavaFile.open(serviceClass, context)) {
    		javaFile.writeHeader(true);
    		
    		writeDocumentation(javaFile);
    		javaFile.newLine().beginTypedefinition("public interface ").append(serviceClass).println(" {");
    		javaFile.newLine();
    		
            //
            // Generate the stub methods for all specified remote procedures.
            //
            for (JrpcgenVersionInfo version : versions) {
            	for (JrpcgenProcedureInfo procedure : version.procedures) {
            		procedure.writeInterfaceDeclaration(javaFile, context);
            	}
          	  // dumpServerStubMethods(out, version, false);
            }

            //
            // Close interface...done!
            //
            javaFile.newLine().endTypedefinition();
    	} catch (IOException ioException) {
    		/*
    		 * The IOexception is thrown by the close()-method
    		 * of the Java source file only.
    		 */
    		System.err.println("Cannot close source code file: "
    				+ ioException.getLocalizedMessage());
    	}
    }
    
    public void writeClientStub() {
        String clientClass = context.options().clientClass;
        String baseClassname = context.options().baseClassname;
        int versionMax = Integer.MIN_VALUE;
        
        /*
         * Define the clinet class if not set.
         */
        if ( clientClass == null ) {
            clientClass = "" + baseClassname + "_" + programId + "_Client";
            System.out.println("CLIENT: " + clientClass);
        }

        //
        // When several versions of a program are defined, we search for the
        // latest and greatest one. This highest version number ist then
        // used to create the necessary <code>OncRpcClient</code> for
        // communication when the client proxy stub is constructed.
        //
    	for (JrpcgenVersionInfo version : versions) {
    		int versionCurrent = Integer.parseInt(version.versionNumber);
    		
    		if (versionCurrent > versionMax) {
    			versionMax = versionCurrent;
    		}
    	}

    	/*
    	 * Open the file and write down the class.
    	 */
    	try (JrpcgenJavaFile javaFile = JrpcgenJavaFile.open(clientClass, context)) {
    		javaFile.writeHeader(true);
    		
            javaFile.beginLine().println("import java.net.InetAddress;");
            
            javaFile.beginNewLine().println("/**");
            javaFile.beginLine().println(" * The class <code>" + clientClass + "</code> implements the client stub proxy");
            javaFile.beginLine().println(" * for the " + programId + " remote program. It provides method stubs");
            javaFile.beginLine().println(" * which, when called, in turn call the appropriate remote method (procedure).");
            javaFile.beginLine().println(" */");
            
            javaFile.beginTypedefinition("public class ").append(clientClass)
            	.append(" extends OncRpcClientStub implements ").append(context.getInterfaceName(programId))
            	.println(" {");

            //
            // Generate constructors...
            //
            javaFile.beginNewLine().println("/**");
            javaFile.beginLine().append(" * Constructs a <code>").append(clientClass).println("</code> client stub proxy dummy.");
            javaFile.beginLine().println(" * @throws OncRpcException if an ONC/RPC error occurs.");
            javaFile.beginLine().println(" * @throws IOException if an I/O error occurs.");
            javaFile.beginLine().println(" */");
            javaFile.beginPublicConstructor(clientClass).exceptions("OncRpcException", "IOException")
            	.endSignature().beginLine().println("super(null);");
            javaFile.endMethod();
            
            javaFile.beginNewLine().println("/**");
            javaFile.beginLine().append(" * Constructs a <code>").append(clientClass).println("</code> client stub proxy object");
            javaFile.beginLine().append(" * from which the ").append(programId).println(" remote program can be accessed.");
            javaFile.beginLine().println(" * @param host Internet address of host where to contact the remote program.");
            javaFile.beginLine().println(" * @param protocol {@link org.acplt.oncrpc.OncRpcProtocols Protocol} to be");
            javaFile.beginLine().println(" *   used for ONC/RPC calls.");
            javaFile.beginLine().println(" * @throws OncRpcException if an ONC/RPC error occurs.");
            javaFile.beginLine().println(" * @throws IOException if an I/O error occurs.");
            javaFile.beginLine().println(" */");
            javaFile.beginPublicConstructor(clientClass).parameter("InetAddress", "host").parameter("int", "protocol")
            	.exceptions("OncRpcException", "IOException").endSignature()
            	.beginLine().append("super(host, ").append(baseClassname).append('.').append(programId)
            	.append(", ").append(String.valueOf(versionMax)).println(", 0, protocol);");
            javaFile.endMethod();

            javaFile.beginNewLine().println("/**");
            javaFile.beginLine().append(" * Constructs a <code>").append(clientClass).println("</code> client stub proxy object");
            javaFile.beginLine().append(" * from which the ").append(programId).println(" remote program can be accessed.");
            javaFile.beginLine().println(" * @param host Internet address of host where to contact the remote program.");
            javaFile.beginLine().println(" * @param port Port number at host where the remote program can be reached.");
            javaFile.beginLine().println(" * @param protocol {@link org.acplt.oncrpc.OncRpcProtocols Protocol} to be");
            javaFile.beginLine().println(" *   used for ONC/RPC calls.");
            javaFile.beginLine().println(" * @throws OncRpcException if an ONC/RPC error occurs.");
            javaFile.beginLine().println(" * @throws IOException if an I/O error occurs.");
            javaFile.beginLine().println(" */");
            javaFile.beginPublicConstructor(clientClass).parameter("InetAddress", "host").parameter("int", "port")
            	.parameter("int", "protocol").exceptions("OncRpcException", "IOException").endSignature()
            	.beginLine().append("super(host, ").append(baseClassname).append('.').append(programId)
            	.append(", ").append(String.valueOf(versionMax)).println(", port, protocol);");
            javaFile.endMethod();

            javaFile.beginNewLine().println("/**");
            javaFile.beginLine().append(" * Constructs a <code>").append(clientClass).println("</code> client stub proxy object");
            javaFile.beginLine().append(" * from which the ").append(programId).println(" remote program can be accessed.");
            javaFile.beginLine().println(" * @param client ONC/RPC client connection object implementing a particular");
            javaFile.beginLine().println(" *   protocol.");
            javaFile.beginLine().println(" * @throws OncRpcException if an ONC/RPC error occurs.");
            javaFile.beginLine().println(" * @throws IOException if an I/O error occurs.");
            javaFile.beginLine().println(" */");
            javaFile.beginPublicConstructor(clientClass).parameter("OncRpcClient", "client").exceptions("OncRpcException", "IOException")
            	.endSignature().beginLine().println("super(client);");
            javaFile.endMethod();

            javaFile.beginNewLine().println("/**");
            javaFile.beginLine().append(" * Constructs a <code>").append(clientClass).println("</code> client stub proxy object");
            javaFile.beginLine().append(" * from which the ").append(programId).println(" remote program can be accessed.");
            javaFile.beginLine().println(" * @param host Internet address of host where to contact the remote program.");
            javaFile.beginLine().println(" * @param program Remote program number.");
            javaFile.beginLine().println(" * @param version Remote program version number.");
            javaFile.beginLine().println(" * @param protocol {@link org.acplt.oncrpc.OncRpcProtocols Protocol} to be");
            javaFile.beginLine().println(" *   used for ONC/RPC calls.");
            javaFile.beginLine().println(" * @throws OncRpcException if an ONC/RPC error occurs.");
            javaFile.beginLine().println(" * @throws IOException if an I/O error occurs.");
            javaFile.beginLine().println(" */");
            javaFile.beginPublicConstructor(clientClass).parameter("InetAddress", "host").parameter("int", "program")
            	.parameter("int", "version").parameter("int", "protocol").exceptions("OncRpcException", "IOException")
            	.endSignature().beginLine().println("super(host, program, version, 0, protocol);");
            javaFile.endMethod();

            javaFile.beginNewLine().println("/**");
            javaFile.beginLine().append(" * Constructs a <code>").append(clientClass).println("</code> client stub proxy object");
            javaFile.beginLine().append(" * from which the ").append(programId).println(" remote program can be accessed.");
            javaFile.beginLine().println(" * @param host Internet address of host where to contact the remote program.");
            javaFile.beginLine().println(" * @param program Remote program number.");
            javaFile.beginLine().println(" * @param version Remote program version number.");
            javaFile.beginLine().println(" * @param port Port number at host where the remote program can be reached.");
            javaFile.beginLine().println(" * @param protocol {@link org.acplt.oncrpc.OncRpcProtocols Protocol} to be");
            javaFile.beginLine().println(" *   used for ONC/RPC calls.");
            javaFile.beginLine().println(" * @throws OncRpcException if an ONC/RPC error occurs.");
            javaFile.beginLine().println(" * @throws IOException if an I/O error occurs.");
            javaFile.beginLine().println(" */");
            javaFile.beginPublicConstructor(clientClass).parameter("InetAddress", "host").parameter("int", "program")
            	.parameter("int", "version").parameter("int", "port").parameter("int", "protocol").exceptions("OncRpcException", "IOException")
            	.endSignature().beginLine().println("super(host, program, version, port, protocol);");
            javaFile.endMethod();
            	
        	for (JrpcgenVersionInfo version : versions) {
        		version.writeClientStubMethods(javaFile);
        	}
        	
        	javaFile.newLine().endTypedefinition();

    	} catch (IOException ioException) {
    		/*
    		 * The IO exception is thrown by the close()-method of
    		 * the Java source file only.
    		 */
    		System.err.println("Cannot close source code file: "
                    + ioException.getLocalizedMessage());
    	}
    }
    
    public void writeServerStub() {
    	JrpcgenOptions options = context.options();
        String serverClass = options.serverClass;
        String baseClassname = options.baseClassname;
        
        /*
         * Define the clinet class if not set.
         */
        if ( serverClass == null ) {
            serverClass = "" + baseClassname + "_" + programId + "_ServerStub";
            System.out.println("SERVER: " + serverClass);
        }

    	/*
    	 * Open the file and write down the class.
    	 */
    	try (JrpcgenJavaFile javaFile = JrpcgenJavaFile.open(serverClass, context)) {
    		javaFile.writeHeader(true);
    		
    		javaFile.beginLine().println("import java.net.InetAddress;");
    		javaFile.beginNewLine().println("import org.acplt.oncrpc.server.*;");
    		
            javaFile.beginNewLine().println("/**");
            javaFile.beginLine().println(" */");
            javaFile.beginTypedefinition("public abstract class ").append(serverClass)
                        .println(" extends OncRpcServerStub implements OncRpcDispatchable {");

            //
            // Generate constructor(s)...
            //
            javaFile.newLine().beginPublicConstructor(serverClass).exceptions("OncRpcException", "IOException")
            	.endSignature().beginLine().println("this(0);").endMethod();

            javaFile.newLine().beginPublicConstructor(serverClass).parameter("int", "port")
            	.exceptions("OncRpcException", "IOException").endSignature()
            	.beginLine().println("this(null, port);").endMethod();

            javaFile.newLine().beginPublicConstructor(serverClass).parameter("InetAddress", "bindAddr")
            	.parameter("int", "port").exceptions("OncRpcException", "IOException").endSignature();

            //
            // For every version specified, create both UDP and TCP-based
            // transports.
            //
            javaFile.beginBlock().println("info = new OncRpcServerTransportRegistrationInfo [] {");
            
            for (JrpcgenVersionInfo version : versions) {
            	if (version != versions.getLastItem()) {
                	javaFile.beginLine().append("new OncRpcServerTransportRegistrationInfo(")
                		.append(baseClassname).dot().append(programId).append(", ")
                		.append(version.versionNumber).println("),");
            	} else {
                	javaFile.beginLine().append("new OncRpcServerTransportRegistrationInfo(")
            		.append(baseClassname).dot().append(programId).append(", ")
            		.append(version.versionNumber).println(')');
            	}
            }
            
            javaFile.endBlock().println("};");
            javaFile.beginBlock().println("transports = new OncRpcServerTransport [] {");

            
            if (options.generateUdpTransport()) {
            	javaFile.beginLine().append("new OncRpcUdpServerTransport(this, bindAddr, port, info, 32768)");
            	
            	if (options.generateTcpTransport()) {
            		javaFile.println(',').beginLine().println("new OncRpcTcpServerTransport(this, bindAddr, port, info, 32768)");
            	} else {
            		javaFile.newLine();
            	}
            } else if (options.generateTcpTransport()) {
        		javaFile.beginLine().println("new OncRpcTcpServerTransport(this, bindAddr, port, info, 32768)");
            }
            
            javaFile.endBlock().println("};");
            
            //
            // Finish constructor method...
            //
            javaFile.endMethod();
            javaFile.newLine();

            //
            // Generate dispatcher code...
            //
            javaFile.beginPublicMethod().resultType("void").name("dispatchOncRpcCall").parameter("OncRpcCallInformation", "call")
            	.parameter("int", "program").parameter("int", "version").parameter("int", "procedure")
            	.exceptions("OncRpcException", "IOException").endSignature();
            
            for (JrpcgenVersionInfo version : versions) {
            	
            	if (version == versions.getFirstItem()) {
            		javaFile.beginBlock().append("if ( version == ").append(version.versionNumber).println(" ) {");
            	} else {
            		javaFile.elseBlock().append("} else if ( version == ").append(version.versionNumber).println(" ) {");
            	}
            	
            	
//                out.print(versionInfo == programInfo.versions.getFirstItem() ? "        " : "        } else ");
//                out.println("if ( version == " + versionInfo.versionNumber + " ) {");
//                int procSize = versionInfo.procedures.size();
            	
//                out.println("            switch ( procedure ) {");
            	
            	version.writeServerStubMethodCalls(javaFile);
//                for (JrpcgenProcedureInfo procInfo : versionInfo.procedures) {
//                    //
//                    // Emit case arms for every procedure defined. We have to
//                    // take care that the procedure number might be a constant
//                    // comming from an enumeration: in this case we need also to
//                    // dump the enclosure.
//                    //
//                    out.println("            case " + checkForEnumValue(procInfo.procedureNumber) + ": {");
//                    dumpServerStubMethodCall(javaFile, procInfo);
//                    out.println("                break;");
//                    out.println("            }");
//                }
            }

            javaFile.elseBlock().println("} else {")
            	.beginLine().println("call.failProgramUnavailable();")
            	.endBlock().println('}')
            	.endMethod();

            javaFile.newLine();

            for (JrpcgenVersionInfo version : versions) {
            	version.writeServerStubMethods(javaFile);
            }
            
            javaFile.endTypedefinition();

    	} catch (IOException ioException) {
    		/*
    		 * The IO exception is thrown by the close()-method of
    		 * the Java source file only.
    		 */
    		System.err.println("Cannot close source code file: "
                    + ioException.getLocalizedMessage());
    	}

    }
    
    @Override
    public String toString() {
    	return dump(new StringBuilder()).toString();
    }
    
    public void dump() {
    	dump(System.out).println();
    }
    
    public <T extends Appendable> T dump(T appendable) {
    	try {
        	appendable.append("PROGRAM ").append(programId).append(" = ").append(programNumber);
    	} catch (IOException ioException) {
    		// Ignored at this place.
    	}
    	
    	return appendable;
    }
    
    private void completeProcedureConstants() {
    	for (JrpcgenVersionInfo version : versions) {
    		for (JrpcgenConst procedureConstant : version.getProcedureConstants()) {
				procedureConstant.setDocumentation("/** RPC procedure number of {@linkplain "
						+ context.getInterfaceName(programId) + "#" + procedureConstant.getIdentifier() + "}"
						+ JrpcgenContext.newline() + " * value = " + procedureConstant.getValue() + " */");
    		}
    	}
    }
    
    private final JrpcgenContext context;

}

// End of JrpcgenProgramInfo.java
