/*
 * 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;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public class JrpcgenTypeInfo implements JrpcgenTypeMapping {

	public static JrpcgenTypeInfo getTypeInfo(final JrpcgenContext context, final String type) {
		return new JrpcgenTypeInfo(context, type, JrpcgenBaseType.getBaseType(type));
	}
 	
	public static void generateCodingSupplement(JrpcgenContext context) {
		/*
		 * A java file containing coding supplement for predefined types
		 * will be written, if at least one type name is given in the
		 * set of predefined types.
		 */
		List<String> typesInQuestion = new LinkedList<String>();
		
		/*
		 * First a list of type names in question is built.
		 */
		for (String typename : context.predefinedTypes()) {
			if (context.typesInDynamicVectorUse().contains(typename)
					|| context.typesInFixedVectorUse().contains(typename)) {
				typesInQuestion.add(typename);
			}
		}
		
		/*
		 * If the list of types in question contains at least one element,
		 * a coding supplement class will be written. 
		 */
		if (context.predefinedTypes().size() > 0) {
			String className = context.options().baseClassname.concat("CodingSupplement");
			
	        //
	        // Create new source code file containing a Java interface representing
	        // the XDR enumeration.
	        //
	    	try (JrpcgenJavaFile javaFile = JrpcgenJavaFile.open(className, context)) {

	    		javaFile.writeHeader(true);
	    		
	    		javaFile.beginTypedefinition("public class ").append(className).println(" {");

	    		/*
	    		 * For each type in the list the required vector coding methods are written.
	    		 */
	    		for (String typename : context.predefinedTypes()) {	    			
	    			PredefinedType predefinedType = new PredefinedType(typename, className);
	    			
	    			/*
	    			 * Is this type marked to be used in a fixed or dynamic vector context?
	    			 * First check, whether this type is used in a danmic vector context and
	    			 * therefore the methods for coding dynamic vectors are requested. 
	    			 */
	    			boolean generateDynamicVectorCodingMethod = context.typesInDynamicVectorUse().contains(typename);
	    			
	    			/*
	    			 * Write the XDR decoding call.
	    			 */
	    			javaFile.newLine().beginPublicMethod().staticMethod().resultType(typename).name("xdrDecode_".concat(typename))
	    				.parameter("XdrDecodingStream", "xdr").exceptions("OncRpcException", "IOException").endSignature()
	    				.beginLine().append(typename).space().append("$result = ").keywordNew().space().append(typename).println("();")
	    				.beginLine().println("$result.xdrDecode(xdr);")
	    				.beginLine().keywordReturn().space().append("$result").semicolon().newLine().endMethod();
	    			
	    			
	    			/*
	    			 * 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 vectro context and the fixed vector coding methods
	    			 * are going to be generated, only. 
	    			 */
	    			if (generateDynamicVectorCodingMethod || context.typesInFixedVectorUse().contains(typename)) {
	    				/*
	    				 * Write the fixed vector encoding method.
	    				 */
	    				javaFile.newLine().beginPublicMethod().staticMethod().resultType("void").name("xdrEncodeFixedVector_".concat(typename))
		    				.parameter("XdrEncodingStream", "xdr").parameter(typename.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();
	    				predefinedType.writeXdrEncodingCall(javaFile, "xdr", "vector[index]");
	    				javaFile.semicolon().newLine().endBlock().println('}').endMethod();

	    				/*
	    				 * Write the fixed vector decoding method.
	    				 */
	    				javaFile.newLine().beginPublicMethod().staticMethod().resultType(typename, "[]").name("xdrDecodeFixedVector_".concat(typename))
		    				.parameter("XdrDecodingStream", "xdr").parameter("int", "size").exceptions("OncRpcException", "IOException")
		    				.endSignature().beginLine().append(typename).append("[] vector = new ").append(typename).println("[size];")
		    				.newLine().beginBlock().println("for (int index = 0; index < size; index++) {")
		    				.beginLine().append("vector[index] = ");
	    				predefinedType.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_".concat(typename))
		    					.parameter("XdrEncodingStream", "xdr").parameter(typename.concat("[]"), "vector").exceptions("OncRpcException", "IOException")
		    					.endSignature().beginLine();
	    					JrpcgenBaseType.INT.writeXdrEncodingCall(javaFile, "xdr", "vector.length");
	    					javaFile.semicolon().beginNewLine().append("xdrEncodeFixedVector_").append(typename)
	    						.println("(xdr, vector, vector.length);").endMethod();

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

	    			} /* endif (At least the fixed vector coding methods aree generated) */
	    		} /* endfor (Loop over the types in question) */
	    		
	    		/*
	    		 * End of class is reached.
	    		 */
	    		javaFile.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());
	    	}
		} /* endif (At least there is one type in question) */
	}
	
	@Override
	final public boolean isVoid() {
		return JrpcgenBaseType.VOID.equals(mapping);
	}
	
	@Override
	final public boolean isBaseType() {
		return (mapping instanceof JrpcgenBaseType);
	}
	
	@Override
	final public boolean isBooleanType() {
		return JrpcgenBaseType.BOOL.equals(mapping);
	}
	
	@Override
	final public boolean isStringType() {
		return JrpcgenBaseType.STRING.equals(mapping);
	}
	
	@Override
	final public boolean isOpaqueType() {
		return JrpcgenBaseType.OPAQUE.equals(mapping);
	}
	
	@Override
	final public String getDefinitionName() {
		return definitionName;
	}
	
	@Override
	final public String getJavaName() {
		return getMapping().getJavaName();
	}
	
	@Override
	final public String getJavaClass() {
		return getMapping().getJavaClass();
	}
	
	@Override
	final public String getXdrClass() {
		return getMapping().getXdrClass();
	}
	
	@Override
	final public void writeXdrConstructorCall(JrpcgenJavaFile javaFile, String parameter) {
		getMapping().writeXdrConstructorCall(javaFile, parameter);
	}
	
	@Override
	final public void writeXdrConstructorCall(JrpcgenJavaFile javaFile, JrpcgenJavaFile.Expression parameterExpression) {
		getMapping().writeXdrConstructorCall(javaFile, parameterExpression);
	}
	
	@Override
	final public void writeJavaToXdr(JrpcgenJavaFile javaFile, String variable) {
		getMapping().writeJavaToXdr(javaFile,  variable);
	}
	
	@Override
	final public void writeJavaToXdr(JrpcgenJavaFile javaFile, JrpcgenJavaFile.Expression expression) {
		getMapping().writeJavaToXdr(javaFile, expression);
	}
	
	@Override
 	final public void writeXdrToJava(JrpcgenJavaFile javaFile, String variable) {
		getMapping().writeXdrToJava(javaFile, variable);
	}
	
	@Override
	final public void writeXdrEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, String variable) {
		getMapping().writeXdrEncodingCall(javaFile, xdrStream, variable);
	}
	
	@Override
	final public void writeXdrEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, JrpcgenJavaFile.Expression expression) {
		getMapping().writeXdrEncodingCall(javaFile, xdrStream, expression);
	}
	
	@Override
	final public void writeXdrFixedVectorEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, String variable, String size) {
		getMapping().writeXdrFixedVectorEncodingCall(javaFile, xdrStream, variable, size);
	}
	
	@Override
	final public void writeXdrDynamicVectorEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, String variable) {
		getMapping().writeXdrDynamicVectorEncodingCall(javaFile, xdrStream, variable);
	}
	
	@Override
	final public void writeXdrDecodingCall(JrpcgenJavaFile javaFile, String xdrStream) {
		getMapping().writeXdrDecodingCall(javaFile, xdrStream);
	}
	
	@Override
	final public void writeXdrFixedVectorDecodingCall(JrpcgenJavaFile javaFile, String xdrStream, String size) {
		getMapping().writeXdrFixedVectorDecodingCall(javaFile, xdrStream, size);
	}
	
	@Override
	final public void writeXdrDynamicVectorDecodingCall(JrpcgenJavaFile javaFile, String xdrStream) {
		getMapping().writeXdrDynamicVectorDecodingCall(javaFile, xdrStream);		
	}
	
	@Override
	final public void writeEqualsExpression(JrpcgenJavaFile javaFile, String variableLeft, String variableRight, boolean negate) {
		getMapping().writeEqualsExpression(javaFile, variableLeft, variableRight, negate);
	}
	
	private JrpcgenTypeInfo(JrpcgenContext context, String definitionName, JrpcgenTypeMapping mapping) {
		this.context = context;
		this.definitionName = definitionName;
		this.mapping = mapping;
	}
	
	private JrpcgenTypeMapping getMapping() {
		if (mapping == null) {
			mapping = context.globalDefinitions().getItem(JrpcgenComplexType.class, definitionName);
			
			if (mapping == null) {
				context.predefinedTypes().add(definitionName);
				
				mapping = new PredefinedType(definitionName, context.options().baseClassname.concat("CodingSupplement"));
			}
		}
		
		return mapping;
	}
	
	private static class PredefinedType extends JrpcgenComplexType {

		public PredefinedType(String identifier, String codingSupplementIdentifier) {
			super(identifier, Type.STRUCT);
			this.codingSupplementIdentifier = codingSupplementIdentifier;
		}

		@Override
		public void generateJavaFile() {
			/*
			 * Nothing to do.
			 */
		}
		
	    @Override
	    public void writeXdrDecodingCall(JrpcgenJavaFile javaFile, String xdrStream) {
	    	javaFile.append(codingSupplementIdentifier).dot().append("xdrDecode_").append(getIdentifier())
	    		.leftParenthesis().append(xdrStream).rightParenthesis();
	    }
	    

	    @Override
	    public void writeXdrFixedVectorEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, String variable, String size) {
	    	javaFile.append(codingSupplementIdentifier).dot().append("xdrEncodeFixedVector_").append(getIdentifier())
	    		.leftParenthesis().append(xdrStream).append(", ").append(variable).append(", ").append(size).rightParenthesis();
	    }
	    
	    @Override
	    public void writeXdrDynamicVectorEncodingCall(JrpcgenJavaFile javaFile, String xdrStream, String variable) {
	    	javaFile.append(codingSupplementIdentifier).dot().append("xdrEncodeDynamicVector_").append(getIdentifier())
	    		.leftParenthesis().append(xdrStream).append(", ").append(variable).rightParenthesis();
	    }
	    
	    @Override
	    public void writeXdrFixedVectorDecodingCall(JrpcgenJavaFile javaFile, String xdrStream, String size) {
	    	javaFile.append(codingSupplementIdentifier).dot().append("xdrDecodeFixedVector_").append(getIdentifier())
	    		.leftParenthesis().append(xdrStream).append(", ").append(size).rightParenthesis();
	    }
	    
	    @Override
	    public void writeXdrDynamicVectorDecodingCall(JrpcgenJavaFile javaFile, String xdrStream) {
	    	javaFile.append(codingSupplementIdentifier).dot().append("xdrDecodeDynamicVector_").append(getIdentifier())
	    		.leftParenthesis().append(xdrStream).rightParenthesis();
	    }

	    private final String codingSupplementIdentifier;
	}
	
	private final JrpcgenContext context;
	private final String definitionName;
	private JrpcgenTypeMapping mapping;
}
