/*
 * Copyright (c) 2021
 * 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.apps.jrpcgen;

/**
 * The complex type class is an abstract base class for complex type mappings
 * such as structs, unions, enumerations and type definitions. At this level
 * complex types get the following characteristics:
 * <ul>
 * <li>A complex type cannot be a base type at the same time.</li>
 * <li>The definition name in the x-file is used as name of the 
 * Java class, which gets generated by <em>jrpcgen</em>.</li>
 * <li>The Java class implements the interface {@link XdrAble} to
 * provide the required coding methods for the mapped complex type.</li>
 * <li>As a complex type gets mapped by a Java class, concrete complex types
 * have to implement the method {@code generateJavaFile()}, which will write
 * the implementation of the Java class.</li>
 * <li>Within an x-file complex types are formed by XDR definitions and therefore
 * are handled as extension to the class {@link JrpcgenXdrDefinition} within this
 * library.</li>    
 * </ul>
 * 
 * @author Harald Wirths {@literal <hwirths@nde.ag>}
 *
 */
public abstract class JrpcgenComplexType extends JrpcgenXdrDefinition implements JrpcgenTypeMapping {

	/**
	 * Constructs a complex type with the identifier and the type specifier passed.
	 * 
	 * @param identifier The identifier of the complex type.
	 * @param type The type specifier of the complex type, which will be one of
	 * <ul>
	 * <li>{@code JrpcgenXdrDefinition.Type.ENUM}</li>
	 * <li>{@code JrpcgenXdrDefinition.Type.STRUCT}</li>
	 * <li>{@code JrpcgenXdrDefinition.Type.UNION}</li>
	 * <li>{@code JrpcgenXdrDefinition.Type.TYPEDEF}</li>
	 * </ul>
	 */
	public JrpcgenComplexType(String identifier, Type type) {
		super(identifier, type);
		
		assert (type != null) && (type != Type.CONST);
	}

	/**
	 * Concrete implementations are asked to generate a Java file
	 * containing an implementation of the Java class mapping
	 * the complex type.
	 */
	public abstract void generateJavaFile();
	
	/**
	 * A complex type is always different from the type {@code void}.
	 * 
	 * @return {@code false}, always.
	 */
	@Override
	final public boolean isVoid() {
		return false;
	}
	
	/**
	 * A complex type cannot be a base type at the same time.
	 * 
	 * @return {@code false}, always.
	 */
	@Override
	final public boolean isBaseType() {
		return false;
	}
	
	/**
	 * A complex type is always different from the type {@code boolean}.
	 * 
	 * @return {@code false}, always.
	 */
	@Override
	final public boolean isBooleanType() {
		return false;
	}
	
	/**
	 * A complex type is always different from the type {@code String}.
	 * 
	 * @return {@code false}, always.
	 */
	@Override
	final public boolean isStringType() {
		return false;
	}
	
	/**
	 * A complex type is always different from the type {@code opaque}.
	 * 
	 * @return {@code false}, always.
	 */
	@Override
	final public boolean isOpaqueType() {
		return false;
	}
	
	/**
	 * Returns the identifier of the complex type.
	 * 
	 * @return The identifier of the complex type.
	 */
	@Override
	public String getDefinitionName() {
		return getIdentifier();
	}
	
	/**
	 * The Java name is equal to the definition name
	 * and therefore equal to the identifier of the complex
	 * type.
	 * 
	 * @return The identifier of the complex type.
	 */
	@Override
	public String getJavaName() {
		return getIdentifier();
	}
	
	/**
	 * The complex type is mapped by a Java class. The name of
	 * the class is equal to teh definition name of the complex
	 * type and therefore equal to the identifier of the complex
	 * type.
	 * 
	 * @return The identifier of the complex type.
	 */
	@Override
	public String getJavaClass() {
		return getIdentifier();
	}
	
	/**
	 * The Java class mapping the complex type usually implements
	 * the interface {@link XdrAble}. Therefore the name of the XDR
	 * class is equal to the definition name and the identifier of
	 * the complex type, respectively.
	 * 
	 * @return The identifier of the complex type.
	 */
	@Override
	public String getXdrClass() {
		return getIdentifier();
	}
	
	/**
	 * Writes a default constructor call or a constructor call with one parameter
	 * with the XDR class provided by this mapping. Passing {@code null} as value of
	 * the parameter {@code parameter} will result in a default constructor call
	 * written to the passed Java file. Otherwise the value of the parameter
	 * {@code parameter} will be written as parameter to the constuctor call.
	 * 
	 * @param javaFile The Java file, where the constructor call is going to be placed.
	 * @param parameter {@code null} to generate a default constructor call, a parameter
	 *        name to generate a constructor call with one parameter.
	 */
	@Override
	public void writeXdrConstructorCall(JrpcgenJavaFile javaFile, String parameter) {
		javaFile.keywordNew().space().append(getXdrClass()).leftParenthesis();
		
		if (parameter != null) {
			javaFile.append(parameter);
		}
		
		javaFile.rightParenthesis();
	}
	
	/**
	 * Writes a constructor call with the XDR class provided by this mapping. The parameter
	 * expression is intended to write an expression, which evaluates to a parameter to the
	 * constructor call.
	 * 
	 * @param javaFile The Java file, where the constructor call is going to be placed.
	 * @param parameterExpression An expression to be called with the passed Java file,
	 *        when the expression is going to be placed in the Java file.
	 */
	@Override
	public void writeXdrConstructorCall(JrpcgenJavaFile javaFile, JrpcgenJavaFile.Expression parameterExpression) {
		javaFile.keywordNew().space().append(getXdrClass()).leftParenthesis().expression(parameterExpression).rightParenthesis();		
	}
	
	/**
	 * At this level of a complex type the passed variable represents both the
	 * Java as well as the XDR representation and will be written as is to the
	 * passed Java file. No conversion will take place.
	 * 
	 * @param javaFile The Java file, where the variable is going to be placed.
	 * @param variable The name of a variable in Java representation.
	 */
	@Override
	public void writeJavaToXdr(JrpcgenJavaFile javaFile, String variable) {
		javaFile.append(variable);
	}
	
	/**
	 * Wirtes the passed expression to the passed Java file. At this level of a complex
	 * type no conversion takes place and therefore the passed expression will be
	 * written as is. The expression is expected to result in a value of the mapped
	 * type.
	 * 
	 * @param javaFile The Java file, where the passed expression is going to be placed.
	 * @param expression An expression to be called with the passed Java file.
	 */
	@Override
	public void writeJavaToXdr(JrpcgenJavaFile javaFile, JrpcgenJavaFile.Expression expression) {
		expression.writeTo(javaFile);
	}
	
	/**
	 * At this level of a complex type the passed variable represents both the
	 * Java as well as the XDR representation and will be written as is to the
	 * passed Java file. No conversion will take place.
	 * 
	 * @param javaFile The Java file, where the result is going to be placed.
	 * @param variable The name of a variable in XDR representation.
	 */
	@Override
	public void writeXdrToJava(JrpcgenJavaFile javaFile, String variable) {
		javaFile.append(variable);
	}
	
	/**
	 * Writes an XDR encoding call on the passed variable with the passed name of an XDR encoding
	 * stream to the passed Java file. At this level of complex types the mapping type
	 * implements the interface {@link XdrAble} and therefore the XDR encoding call will be
	 * similar to {@code variable.xdrEncode(xdrStream)}.
	 * 
	 * @param javaFile The Java file, where the XDR encoding call is going to be placed.
	 * @param xdrStream The name of the XDR encoding stream instance to be used in the statement.
	 * @param variable The name of the variable to be used in the statement.
	 */
    @Override
    public void writeXdrEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, String variable) {
    	javaFile.append(variable).append(".xdrEncode(").append(xdrStream).rightParenthesis();
    }
    
    /**
     * Writes an XDR encoding call on the result of the passed expression with the passed name of an XDR
     * encoding stream to the passed Java file. At this level of complex types the result of the expression
     * is expected to be of the mapping type, which implements the interface {@link XdrAble}. Therefore
     * the XDR encoding call will be similar to {@code expression.xdrEncode(xdrStream)}.
     * 
     * @param javaFile The Java file, where the XDR encoding call is going to be placed.
     * @param xdrStream The name of the XDR encoding stream instance to be used in the statement.
     * @param expression An expression to be called with the passed Java file, when the
     *        expression is going to be placed in the Java file. 
     */
    @Override
    public void writeXdrEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, JrpcgenJavaFile.Expression expression) {
    	javaFile.expression(expression).append(".xdrEncode(").append(xdrStream).rightParenthesis();
    }

    /**
     * Writes an XDR encoding call for a fixed vector of the mapping type to the passed Java file.
     * The passed name of an XDR encoding stream, the passed name of the vector variable and the
     * passed size string indicating the size of the fixed vector are used to form the XDR encoding
     * call. This method expects the existence of the static method
     * {@code xdrEncodeFixedVector(XdrEncodingStream, MappingType, int)}
     * in the generated code of the mapping type {@code MappingType} as done by a call to the method
     * {@link #writeXdrVectorCodingMethods(JrpcgenJavaFile, JrpcgenContext)} within the implementation
     * of the method {@link #generateJavaFile()}. Thereby {@code MappingType} is a placeholder
     * for the name of concrete mapping type at this point.
     * 
     * @param javaFile The Java file, where the XDR encoding call is going to be placed.
     * @param xdrStream The name of the XDR encoding instance to be used in the statement.
     * @param variable The name of the variable to be used in the statement.
     * @param size A string specifying the size of the fixed vector.
     */
    @Override
    public void writeXdrFixedVectorEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, String variable, String size) {
    	javaFile.append(getIdentifier()).dot().append("xdrEncodeFixedVector(").append(xdrStream).append(", ")
    		.append(variable).append(", ").append(size).rightParenthesis();
    }
    
    /**
     * Writes an XDR encoding call for a dynamic vector of the mapping type to the passed Java file.
     * The passed name of an XDR encoding stream and the passed name of the vector variable are used
     * to form the XDR encoding call. This method expects the existence of the static method
     * {@code xdrEncodeDynamicVector(XdrEncodingStream, MappingType)} in the generated code of the
     * mapping type as done by a call to the method
     * {@link #wirteXdrVectorCodingMethods(JrpcgenJavaFile, JrpcgenContext)} within the
     * implementation of the method {@link #generateJavaFile()}. Thereby {@code MappingType}
     * is a placeholder for the name of the concrete mapping type at this point.
     * 
     * @param javaFile The Java file, where the XDR encoding call is going to be placed.
     * @param xdrStream The name of the XDR encoding instance to be used in the statement.
     * @param variable The name of the variable to be used in the statement.
     */
    @Override
    public void writeXdrDynamicVectorEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, String variable) {
    	javaFile.append(getIdentifier()).dot().append("xdrEncodeDynamicVector(").append(xdrStream).append(", ")
    		.append(variable).rightParenthesis();
    }
    
    /**
     * Writes an XDR encoding call on the result of the passed expression with the passed name of an XDR
     * decoding stream to the passed Java file. At this level of complex types the mapping type implements
     * the interface {@link XdrAble} and therefore the XDR decoding call will be similar to
     * an constructor call with the name of the XDR decoding stream as parameter.
     * 
     * @param javaFile The Java file, where the XDR decoding call is going to be placed.
     * @param xdrStream The name of the XDR decoding stream instance to be used in the statement.
     */
	@Override
	public void writeXdrDecodingCall(JrpcgenJavaFile javaFile, String xdrStream) {
		javaFile.keywordNew().space().append(getJavaName()).leftParenthesis().append(xdrStream).rightParenthesis();
	}
	
	/**
	 * Writes an XDR decoding call for a fixed vector of the mapping type to the passed Java file.
	 * The passed name of an XDR decoding stream and the passed size string indicating the size of
	 * fixed vector are used to form the XDR decoding call. This method expects the existence of the
	 * static method {@code xdrDecodeFixedVector(XdrDecodingStream, int)} within the implementation
	 * of the generated code of the mapping type {@code MappingType} as done by a call to the method
	 * {@link #writeXdrVectorCodingMethods(JrpcgenJavaFile, JrpcgenContext)} within the implementation
	 * of the method {@link #generateJavaFile()}. Thereby {@code MappingType} is a placeholder
	 * for the name of the concrete mapping type at this point.
	 * 
	 * @param javaFile The Java file, where the XDR decoding call is going to be placed.
	 * @param xdrStream The name of the XDR decoding stream instance to be used in the statement.
	 * @param size A string specifying the size of the fixed vector.
	 */
    @Override
    public void writeXdrFixedVectorDecodingCall(JrpcgenJavaFile javaFile, String xdrStream, String size) {
    	javaFile.append(getIdentifier()).dot().append("xdrDecodeFixedVector(").append(xdrStream).append(", ")
    		.append(size).rightParenthesis();
    }
    
    /**
     * Writes an XDR decoding call for a dynamic vector of the mapping type to the passed Java file.
     * The passed name of an XDR decoding stream is used to to form the XDR decoding call. This 
     * method expects the existence of the static method {@code xdrDecodeDynamicVector(XdrDecodingStream)}
     * in the generated code of the mapping type as done by a call to method
     * {@link #writeXdrVectorCodingMethods(JrpcgenJavaFile, JrpcgenContext)} within the implementation
     * of the method {@link #generateJavaFile()}.
     * 
     * @param javaFile The Java file, where the XDR decoding call is going to be placed.
     * @param xdrStream The name of the XDR decoding stream instance to be used in the statement.
     */
    @Override
    public void writeXdrDynamicVectorDecodingCall(JrpcgenJavaFile javaFile, String xdrStream) {
    	javaFile.append(getIdentifier()).dot().append("xdrDecodeDynamicVector(").append(xdrStream).rightParenthesis();
    }

    /**
     * Writes an equals expression to the passed Java file using the passed names of the left hand side
     * and the right hand side variable. The negate parameter controls whether the statement
     * evaluates to {@code true} on equality or on inequality. At this level of complex types the
     * generated code will be similar to
     * <pre>
     * java.utils.Objects.equals(variableLeft, variableRight)
     * </pre>
     * for {@code negate=false} and
     * <pre>
     * ! java.utils.Objects.equals(variableLeft, variableRight)
     * </pre>
     * for {@code negate=true}. 
     * 
	 * @param javaFile The Java file, where the equality expression is going to be placed.
	 * @param variableLeft The name of the variable to be used as the left hand side in the statement.
	 * @param variableRight The name of the variable to be used as the right hand side in the statement.
	 * @param negate {@code false} to let the resulting statement return {@code true} on equality,
	 *        {@code true} to let the resulting statement return {@code true} on inequality.
     */
	@Override
	public void writeEqualsExpression(JrpcgenJavaFile javaFile, String variableLeft, String variableRight, boolean negate) {
		javaFile.append(negate ? "! " : "").append("java.util.Objects.equals(").append(variableLeft)
			.append(", ").append(variableRight).rightParenthesis();
	}
	
	/**
	 * Writes static encoding and decoding methods for fixed and dynamic vectors of the mapping
	 * to the passed Java file. A call to this method is intended during the generation of the
	 * class file of the mapping type.
	 * 
	 * <p>However, the static encoding and decoding methods for fixed and dynamic vectors of the
	 * mapping type will be written only if they are required as a result of the foregoing parsing
	 * process. During the parsing phase vector uses of the mapping type are recognized and stored
	 * in maps for types in fixed and dynamic vector uses, respectively. The rules of generation are
	 * as follows:
	 * <ul>
	 * <li>Nothing will be written, if the mapping type is neither listed in the map of types
	 * in fixed vector use nor listed in the map of the types in dynamic vector use.</li>
	 * <li>The static encoding and decoding methods for dynamic vectors of the mapping type will
	 * be written, if the mapping type is used at least in one of the both fixed or dynamic vectors.</li>
	 * <li>The static encoding methods for fixed and dynamic vectors of the mapping type will
	 * be written, if the mapping type is used at least in a fixed vector.</li>
	 * </ul>
	 * 
	 * @param javaFile The Java file, where the static methods are going to be placed.
	 * @param context The context of the current <em>jrpcgen</em> run.
	 */
	public void writeXdrVectorCodingMethods(JrpcgenJavaFile javaFile, JrpcgenContext context) {
		/*
		 * Is this type marked to be used in a fixed or dynamic vector context?
		 * First check, whether this type is used in a dynamic vector context and
		 * therefore the methods for coding dynamic vectors are requested. 
		 */
		boolean generateDynamicVectorCodingMethod = context.typesInDynamicVectorUse().contains(getIdentifier());
		
		/*
		 * Either the type is used in a dynamic vector context and both the coding methods for dynamic and fixed vectors
		 * are going to be generated, or the type max be used in a fixed vector context and the fixed vector coding methods
		 * are going to be generated, only. 
		 */
		if (generateDynamicVectorCodingMethod || context.typesInFixedVectorUse().contains(getIdentifier())) {

			/*
			 * Write the fixed vector encoding method.
			 */
			javaFile.newLine().beginPublicMethod().staticMethod().resultType("void").name("xdrEncodeFixedVector")
				.parameter("XdrEncodingStream", "xdr").parameter(getIdentifier().concat("[]"), "vector").parameter("int", "size").exceptions("OncRpcException", "IOException")
				.endSignature().beginBlock().println("if (vector.length != size) {")
				.beginLine().println("throw new IllegalArgumentException(\"array size does not match protocol specification\");").endBlock().println('}')
				.newLine().beginBlock().println("for (int index = 0; index < size; index++) {").beginLine();
			writeXdrEncodingCall(javaFile, "xdr", "vector[index]");
			javaFile.semicolon().newLine().endBlock().println('}').endMethod();

			/*
			 * Write the fixed vector decoding method.
			 */
			javaFile.newLine().beginPublicMethod().staticMethod().resultType(getIdentifier(), "[]").name("xdrDecodeFixedVector")
				.parameter("XdrDecodingStream", "xdr").parameter("int", "size").exceptions("OncRpcException", "IOException")
				.endSignature().beginLine().append(getIdentifier()).append("[] vector = new ").append(getIdentifier()).println("[size];")
				.newLine().beginBlock().println("for (int index = 0; index < size; index++) {")
				.beginLine().append("vector[index] = ");
			writeXdrDecodingCall(javaFile, "xdr");
			javaFile.semicolon().newLine().endBlock().println('}').newLine()
				.beginLine().keywordReturn().space().append("vector").semicolon().newLine().endMethod();

			/*
			 * The dynamic vector coding methods are added, if the type has been identified
			 * to be used in a dynamic vector context.
			 */
			if (generateDynamicVectorCodingMethod) {
				/*
				 * Write the dynamic vector encoding method.
				 */
				javaFile.newLine().beginPublicMethod().staticMethod().resultType("void").name("xdrEncodeDynamicVector")
					.parameter("XdrEncodingStream", "xdr").parameter(getIdentifier().concat("[]"), "vector").exceptions("OncRpcException", "IOException")
					.endSignature().beginLine();
				JrpcgenBaseType.INT.writeXdrEncodingCall(javaFile, "xdr", "vector.length");
				javaFile.semicolon().beginNewLine().println("xdrEncodeFixedVector(xdr, vector, vector.length);").endMethod();


				/*
				 * Write the dynamic vector decoding method.
				 */
				javaFile.newLine().beginPublicMethod().staticMethod().resultType(getIdentifier(), "[]").name("xdrDecodeDynamicVector")
					.parameter("XdrDecodingStream", "xdr").exceptions("OncRpcException", "IOException")
					.endSignature().beginLine().keywordReturn().space().append("xdrDecodeFixedVector(xdr, ");            
				JrpcgenBaseType.INT.writeXdrDecodingCall(javaFile, "xdr");
				javaFile.rightParenthesis().semicolon().newLine().endMethod();
			} /* endif (Generate the coding methods for dynamic vectors) */
		} /* endif (At least generate the coding methods for fixed vectors) */
	}
	
}
