<#--
  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you under the Apache License, Version 2.0 (the
  "License"); you may not use this file except in compliance
  with the License.  You may obtain a copy of the License at

      https://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing,
  software distributed under the License is distributed on an
  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  KIND, either express or implied.  See the License for the
  specific language governing permissions and limitations
  under the License.
-->
<#-- Prevent freemarker from escaping stuff -->
<#outputformat "undefined">
<#-- Declare the name and type of variables passed in to the template -->
<#-- @ftlvariable name="languageName" type="java.lang.String" -->
<#-- @ftlvariable name="protocolName" type="java.lang.String" -->
<#-- @ftlvariable name="outputFlavor" type="java.lang.String" -->
<#-- @ftlvariable name="helper" type="org.apache.plc4x.language.java.JavaLanguageTemplateHelper" -->
<#-- @ftlvariable name="tracer" type="org.apache.plc4x.plugins.codegenerator.protocol.freemarker.Tracer" -->
<#-- @ftlvariable name="type" type="org.apache.plc4x.plugins.codegenerator.types.definitions.ComplexTypeDefinition" -->
${helper.packageName(protocolName, languageName, outputFlavor)?replace(".", "/")}/${type.name}.java
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package ${helper.packageName(protocolName, languageName, outputFlavor)};

import static org.apache.plc4x.java.spi.generation.StaticHelper.*;

import org.apache.plc4x.java.api.model.PlcField;
import org.apache.plc4x.java.api.value.*;
import org.apache.plc4x.java.spi.generation.EvaluationHelper;
import org.apache.plc4x.java.spi.generation.ParseException;
import org.apache.plc4x.java.spi.generation.SerializationException;
import org.apache.plc4x.java.spi.generation.ReadBuffer;
import org.apache.plc4x.java.spi.generation.WriteBuffer;
import org.apache.plc4x.java.spi.generation.ByteOrder;
import ${helper.packageName(protocolName, languageName, outputFlavor)}.*;
import org.apache.plc4x.java.spi.values.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.time.*;
import java.util.*;
import java.util.function.Supplier;

// Code generated by code-generation. DO NOT EDIT.

<#-- TODO: the code below implies that parserArguments will be null if not present... not pretty  -->
<#if type.parserArguments.isPresent()><#assign parserArguments=type.parserArguments.orElseThrow()></#if>
public class ${type.name} {

    private static final Logger LOGGER = LoggerFactory.getLogger(${type.name}.class);
    public static PlcValue staticParse(ReadBuffer readBuffer<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) throws ParseException {
        <#assign defaultCaseOutput=false>
        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
            <@compress single_line=true>
                <#if case.discriminatorValueTerms?has_content>
                    if(
                    <#list case.discriminatorValueTerms as discriminatorValueTerm>
                        <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
                        <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
                        EvaluationHelper.equals(
                        ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
                        <#if discriminatorType.isEnumTypeReference()>
                        ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
                        <#else>
                        ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
                        </#if>
                    )
                        <#sep> && </#sep>
                    </#list>
                    )
                <#else>
                    <#assign defaultCaseOutput=true>
                </#if>
            </@compress> { // ${case.name}
            <#assign valueDefined=false>
            <#list case.fields as field>
                <#switch field.typeName>
                    <#case "array">
                        <#assign arrayField = field.asArrayField().orElseThrow()>
                        <#assign elementTypeReference=arrayField.type.elementTypeReference>
            // Array field (${arrayField.name})
            <#-- Only update curPos if the length expression uses it -->
                        <#if arrayField.loopExpression.contains("curPos")>
            curPos = readBuffer.getPos() - startPos;
                        </#if>
            <#-- If this is a count array, we can directly initialize an array with the given size -->
                        <#if field.isCountArrayField()>
            // Count array
            if(${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)} > Integer.MAX_VALUE) {
                throw new ParseException("Array count of " + (${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)}) + " exceeds the maximum allowed count of " + Integer.MAX_VALUE);
            }
            List<PlcValue> ${arrayField.name};
            {
                int itemCount = (int) ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)};
                ${arrayField.name} = new LinkedList<>();
                for(int curItem = 0; curItem < itemCount; curItem++) {
                    ${arrayField.name}.add(new ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}((${helper.getLanguageTypeNameForTypeReference(elementTypeReference, false)}) <#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)})<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if elementTypeReference.params.isPresent()>, <#list elementTypeReference.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index), true)}) (${helper.toParseExpression(arrayField, elementTypeReference, parserArgument,parserArguments)})<#sep>, </#sep></#list></#if>)</#if>);
                }
            }
            <#-- In all other cases do we have to work with a list, that is later converted to an array -->
                        <#else>
            <#-- For a length array, we read data till the read position of the buffer reaches a given position -->
                            <#if arrayField.isLengthArrayField()>
            // Length array
            long _${arrayField.name}Length = ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression,parserArguments)};
            long ${arrayField.name}EndPos = readBuffer.getPos() + _${arrayField.name}Length;
            List<PlcValue> value = new LinkedList<>();
            while(readBuffer.getPos() < ${arrayField.name}EndPos) {
                value.add(
                <#if elementTypeReference.isSimpleTypeReference()>
                    new ${helper.getPlcValueTypeForTypeReference(elementTypeReference)}(${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)})
                <#else>${elementTypeReference.asNonSimpleTypeReference().orElseThrow().name}IO.staticParse(readBuffer
                    <#if elementTypeReference.params.isPresent()>,
                        <#list elementTypeReference.params.orElseThrow() as parserArgument>
                            (${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index), true)}) (${helper.toParseExpression(arrayField, elementTypeReference, parserArgument,parserArguments)})
                            <#sep>, </#sep>
                        </#list>
                    </#if>
                    )
                </#if>
                );
            }
            <#-- A terminated array keeps on reading data as long as the termination expression evaluates to false -->
                            <#elseif arrayField.isTerminatedArrayField()>
            // Terminated array
            ${helper.getNonPrimitiveLanguageTypeNameForField(arrayField)} ${arrayField.name} = new LinkedList<>();
            while(!((boolean) (${helper.toParseExpression(arrayField, helper.boolTypeReference, arrayField.loopExpression,parserArguments)}))) {
                ${arrayField.name}.add(<#if elementTypeReference.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "", arrayField)}<#else>${elementTypeReference.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if arrayField.params.isPresent()>, <#list arrayField.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(elementTypeReference, parserArgument?index), true)}) (${helper.toParseExpression(arrayField, parserArgument,parserArguments)})<#sep>, </#sep></#list></#if>)</#if>);

                <#-- After parsing, update the current position, but only if it's needed -->
                                <#if arrayField.loopExpression.contains("curPos")>
                curPos = readBuffer.getPos() - startPos;
                                </#if>
            }
                            </#if>
                        </#if>
                        <#if arrayField.name == "value">
                            <#assign valueDefined=true>
                        </#if>
                    <#break>
                    <#case "const">
                        <#assign constField=field.asConstField().orElseThrow()>

            // Const Field (${constField.name})
            ${helper.getNonPrimitiveLanguageTypeNameForField(constField)} ${constField.name} = ${helper.getReadBufferReadMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), "", constField)};
            if(${constField.name} != ${dataIoTypeDefinition.name}.${constField.name?upper_case}) {
                throw new ParseException("Expected constant value " + ${dataIoTypeDefinition.name}.${constField.name?upper_case} + " but got " + ${constField.name});
            }
                        <#if constField.name == "value">
                            <#assign valueDefined=true>
                        </#if>
                    <#break>
                    <#case "enum">
                        <#assign enumField=field.asEnumField().orElseThrow()>

            // Enum field (${enumField.name})
            ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)} ${enumField.name} = ${helper.getNonPrimitiveLanguageTypeNameForField(enumField)}.enumForValue(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(enumField.type.asSimpleTypeReference().orElseThrow()), "", enumField)});
                        <#if enumField.name == "value">
                            <#assign valueDefined=true>
                        </#if>
                    <#break>
                    <#case "manual">
                        <#assign manualField=field.asManualField().orElseThrow()>

            // Manual Field (${manualField.name})
            ${helper.getLanguageTypeNameForField(field)} ${manualField.name} = (${helper.getLanguageTypeNameForField(manualField)}) (${helper.toParseExpression(manualField, manualField.type, manualField.parseExpression,parserArguments)});
                        <#if manualField.name == "value">
                            <#assign valueDefined=true>
                        </#if>
                    <#break>
                    <#case "reserved">
                        <#assign reservedField=field.asReservedField().orElseThrow()>

            // Reserved Field (Compartmentalized so the "reserved" variable can't leak)
            {
                ${helper.getLanguageTypeNameForField(field)} reserved = ${helper.getReadBufferReadMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), "", reservedField)};
                if(reserved != ${helper.getReservedValue(reservedField)}) {
                    LOGGER.info("Expected constant value " + ${reservedField.referenceValue} + " but got " + reserved + " for reserved field.");
                }
            }
                    <#break>
                    <#case "simple">
                        <#assign simpleField=field.asSimpleField().orElseThrow()>

                        <#if helper.isEnumField(simpleField)>
            // Enum field (${simpleField.name})
            ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} ${simpleField.name} = ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)}.enumForValue(${helper.getReadBufferReadMethodCall(helper.getEnumBaseTypeReference(simpleField.type), "", simpleField)});
                        <#else>
            // Simple Field (${simpleField.name})
            ${helper.getNonPrimitiveLanguageTypeNameForField(simpleField)} ${simpleField.name} = <#if simpleField.type.isSimpleTypeReference()>${helper.getReadBufferReadMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "", simpleField)}<#else>${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.staticParse(readBuffer<#if simpleField.params.isPresent()>, <#list field.params.orElseThrow() as parserArgument>(${helper.getLanguageTypeNameForTypeReference(helper.getArgumentType(simpleField.type, parserArgument?index), true)}) (${helper.toParseExpression(simpleField, simpleField.type, parserArgument,parserArguments)})<#sep>, </#sep></#list></#if>)</#if>;
                        </#if>
                        <#if case.name == "Struct" ||
                            ((case.name == "DATE_AND_TIME") && ((simpleField.name == "year") || (simpleField.name == "month") || (simpleField.name == "day") || (simpleField.name == "hour") || (simpleField.name == "minutes") || (simpleField.name == "seconds"))) ||
                            ((case.name == "DATE_AND_TIME") && (simpleField.name == "secondsSinceEpoch")) ||
                            ((case.name == "DATE") && ((simpleField.name == "year") || (simpleField.name == "month") || (simpleField.name == "day"))) ||
                            ((case.name == "TIME_OF_DAY") && ((simpleField.name == "hour") || (simpleField.name == "minutes") || (simpleField.name == "seconds"))) ||
                        simpleField.name == "value">
                            <#assign valueDefined=true>
                        </#if>
                    <#break>
                </#switch>
            </#list>
            <#if case.name == "Struct">

                <#-- In this case we need to wrap each field in a PlcValue that matches it's natural type -->
            Map<String, PlcValue> _map = new HashMap<>();
                <#list case.fields as field>
                    <#if field.isArrayField()>
                        <#assign field=field.asArrayField().orElseThrow()>
            _map.put("${field.name}", new PlcList(${field.name}));
                    <#elseif field.isPropertyField()>
                        <#assign field=field.asPropertyField().orElseThrow()>
                        <#switch helper.getLanguageTypeNameForTypeReference(field.type)>
                            <#case "Boolean">
            _map.put("${field.name}", new PlcBOOL(${field.name}));
                                <#break>
                            <#case "Byte">
            _map.put("${field.name}", new PlcSINT(${field.name}));
                                <#break>
                            <#case "Short">
            _map.put("${field.name}", new PlcINT(${field.name}));
                                <#break>
                            <#case "Integer">
            _map.put("${field.name}", new PlcDINT(${field.name}));
                                <#break>
                            <#case "Long">
            _map.put("${field.name}", new PlcLINT(${field.name}));
                                <#break>
                            <#case "BigInteger">
            _map.put("${field.name}", new PlcBigInteger(${field.name}));
                                <#break>
                            <#case "Float">
            _map.put("${field.name}", new PlcREAL(${field.name}));
                                <#break>
                            <#case "Double">
            _map.put("${field.name}", new PlcLREAL(${field.name}));
                                <#break>
                            <#case "BigDecimal">
            _map.put("${field.name}", new PlcBigDecimal(${field.name}));
                                <#break>
                            <#case "String">
            _map.put("${field.name}", new PlcSTRING(${field.name}));
                                <#break>
                            <#case "LocalTime">
            _map.put("${field.name}", new PlcTIME_OF_DAY(${field.name}));
                                <#break>
                            <#case "LocalDate">
            _map.put("${field.name}", new PlcDATE(${field.name}));
                                <#break>
                            <#case "LocalDateTime">
            _map.put("${field.name}", new PlcDATE_AND_TIME(${field.name}));
                                <#break>
                        </#switch>
                    </#if>
                </#list>
                <#assign valueDefined=true>
            </#if>

            <#if valueDefined>
                <#switch case.name>
                    <#case "TIME">
            return new PlcTIME(value);
                    <#break>
                    <#case "DATE">
                    <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day")>
            LocalDate value = LocalDate.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue());
                    </#if>
            return new PlcDATE(value);
                    <#break>
                    <#case "TIME_OF_DAY">
                    <#if helper.hasFieldsWithNames(case.fields, "hour", "minutes", "seconds", "nanos")>
            LocalTime value = LocalTime.of(hour.intValue(), minutes.intValue(), seconds.intValue(), nanos.intValue());
                    <#elseif helper.hasFieldsWithNames(case.fields, "hour", "minutes", "seconds")>
            LocalTime value = LocalTime.of(hour.intValue(), minutes.intValue(), seconds.intValue());
                    </#if>
            return new PlcTIME_OF_DAY(value);
                    <#break>
                    <#case "DATE_AND_TIME">
                    <#if helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds", "nanos")>
            LocalDateTime value = LocalDateTime.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue(), hour.intValue(), minutes.intValue(), seconds.intValue(), nanos.intValue());
                    <#elseif helper.hasFieldsWithNames(case.fields, "year", "month", "day", "hour", "minutes", "seconds")>
            LocalDateTime value = LocalDateTime.of(year.intValue(), (month == 0) ? 1 : month.intValue(), (day == 0) ? 1 : day.intValue(), hour.intValue(), minutes.intValue(), seconds.intValue());
                    <#elseif helper.hasFieldsWithNames(case.fields, "secondsSinceEpoch")>
            LocalDateTime value = LocalDateTime.ofEpochSecond(secondsSinceEpoch, 0, ZoneOffset.UTC);
                    </#if>
            return new PlcDATE_AND_TIME(value);
                    <#break>
                    <#case "Struct">
            return new PlcStruct(_map);
                    <#break>
                    <#case "List">
            return new PlcList(value);
                    <#break>
                    <#default>
            return new Plc${case.name}(value);
                </#switch>
            </#if>
        }<#sep> else </#sep></#list>
        <#if !defaultCaseOutput>
        return null;
        </#if>
    }

<#if outputFlavor != "passive">
    public static void staticSerialize(WriteBuffer writeBuffer, PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) throws SerializationException {
        staticSerialize(writeBuffer, _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>, ByteOrder.BIG_ENDIAN);
    }

    public static void staticSerialize(WriteBuffer writeBuffer, PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>, ByteOrder byteOrder) throws SerializationException {
        <#assign defaultCaseOutput=false>
        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
        <@compress single_line=true>
            <#if case.discriminatorValueTerms?has_content>
                if(
                <#list case.discriminatorValueTerms as discriminatorValueTerm>
                    <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
                    <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
                    EvaluationHelper.equals(
                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
                    <#if discriminatorType.isEnumTypeReference()>
                    ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
                    <#else>
                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
                    </#if>
                    )
                    <#sep> && </#sep>
                </#list>
                )
            <#else>
                <#assign defaultCaseOutput=true>
            </#if>
        </@compress> { // ${case.name}
            <#list case.fields as field>
                <#switch field.typeName>
                    <#case "array">
                        <#assign arrayField=field.asArrayField().orElseThrow()>
                        <#assign elementTypeReference=arrayField.type.elementTypeReference>
            PlcList values = (PlcList) _value;

                        <#if case.name == "Struct">
            for (PlcValue val : ((List<PlcValue>) values.getStruct().get("${arrayField.name}").getList())) {
                <#if elementTypeReference.isByteBased()>
                ${helper.getLanguageTypeNameForField(arrayField)} value = (${helper.getLanguageTypeNameForField(arrayField)}) val.getRaw();
                writeBuffer.writeByteArray("", value);
                <#else>
                ${helper.getLanguageTypeNameForField(arrayField)} value = (${helper.getLanguageTypeNameForField(arrayField)}) val.get${helper.getLanguageTypeNameForField(arrayField)?cap_first}();
                ${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "value", arrayField)};
                </#if>
            }
                        <#else>
            for (PlcValue val : ((List<PlcValue>) values.getList())) {
                <#if elementTypeReference.isByteBased()>
                byte[] value = (byte[]) val.getRaw();
                writeBuffer.writeByteArray("", value);
                <#else>
                ${helper.getLanguageTypeNameForTypeReference(elementTypeReference)} value = (${helper.getLanguageTypeNameForTypeReference(elementTypeReference)}) val.get${helper.getLanguageTypeNameForTypeReference(elementTypeReference)?cap_first}();
                ${helper.getWriteBufferWriteMethodCall(elementTypeReference.asSimpleTypeReference().orElseThrow(), "(" + arrayField.name + ")", arrayField)};
                </#if>
            }
                        </#if>

                    <#if case.name == "BOOL">
            while (writeBuffer.getPos() < writeBuffer.getData().length) {
                writeBuffer.writeBit(false);
            }
                    </#if>
                    <#break>
                    <#case "const">
                        <#assign constField=field.asConstField().orElseThrow()>
            // Const Field (${constField.name})
            ${helper.getWriteBufferWriteMethodCall(constField.type.asSimpleTypeReference().orElseThrow(), constField.referenceValue, constField)};
                    <#break>
                    <#case "enum">
                        <#assign enumField=field.asEnumField().orElseThrow()>
            // Enum field (${enumField.name})
            ${helper.getLanguageTypeNameForField(field)} ${enumField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${enumField.name?cap_first}();
            ${helper.getWriteBufferWriteMethodCall(helper.getEnumBaseTypeReference(field.asTypedField().orElseThrow().type), "(" + enumField.name + ".getValue())", enumField)};
                    <#break>
                    <#case "manual">
                        <#assign manualField=field.asManualField().orElseThrow()>
            // Manual Field (${manualField.name})
            ${helper.toSerializationExpression(manualField, manualField.type, manualField.serializeExpression, type.parserArguments.orElse(null))};
                    <#break>
                    <#case "reserved">
                        <#assign reservedField=field.asReservedField().orElseThrow()>
            // Reserved Field
            ${helper.getWriteBufferWriteMethodCall(reservedField.type.asSimpleTypeReference().orElseThrow(), helper.getReservedValue(reservedField), reservedField)};
                    <#break>
                    <#case "simple">
                        <#assign simpleField=field.asSimpleField().orElseThrow()>
            // Simple Field (${simpleField.name})
                        <#if case.name == "Struct">
            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.getStruct().get("${simpleField.name}").get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
                        <#else>
                            <#if simpleField.name == "value">
            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
                            <#elseif simpleField.name == "secondsSinceEpoch">
            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
                            <#else>
                                <#-- Just for now -->
            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = ${helper.getNullValueForTypeReference(simpleField.type)};
                            </#if>
                        </#if>
                        <#if simpleField.type.isSimpleTypeReference()>
            ${helper.getWriteBufferWriteMethodCall(simpleField.type.asSimpleTypeReference().orElseThrow(), "(" + simpleField.name + ")", simpleField)};
                        <#else>
            ${simpleField.type.asComplexTypeReference().orElseThrow().name}IO.staticSerialize(writeBuffer, ${simpleField.name});
                        </#if>
                    <#break>
                </#switch>
            </#list>
        }<#sep> else </#sep></#list>
    }
</#if>

    public static int getLengthInBytes(PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) {
        return (int) Math.ceil((float) getLengthInBits(_value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>) / 8.0);
    }

    public static int getLengthInBits(PlcValue _value<#if type.parserArguments.isPresent()>, <#list type.parserArguments.orElseThrow() as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) {
        int sizeInBits = 0;
        <#assign defaultCaseOutput=false>
        <#assign dataIoTypeDefinition=type.asDataIoTypeDefinition().orElseThrow()>
        <#list dataIoTypeDefinition.switchField.orElseThrow().cases as case>
        <@compress single_line=true>
        <#if case.discriminatorValueTerms?has_content>
        if(
            <#list case.discriminatorValueTerms as discriminatorValueTerm>
            <#assign discriminatorExpression=dataIoTypeDefinition.switchField.orElseThrow().discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
            <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.name]>
                EvaluationHelper.equals(
                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorExpression, parserArguments)},
                    <#if discriminatorType.isEnumTypeReference()>
                    ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
                    <#else>
                    ${helper.toParseExpression(dataIoTypeDefinition.switchField.orElseThrow(), discriminatorType, discriminatorValueTerm, parserArguments)}
                    </#if>
                )
            <#sep> && </#sep>
            </#list>
        )
        <#else>
            <#assign defaultCaseOutput=true>
        </#if>
        </@compress> { // ${case.name}
            <#list case.fields as field>
            <#switch field.typeName>
            <#case "array">
                <#assign arrayField=field.asArrayField().orElseThrow()>
                <#assign elementTypeReference=arrayField.type.elementTypeReference>
                PlcList values = (PlcList) _value;
                <#if case.name == "Struct">
                // TODO: Finish this!
                <#elseif elementTypeReference.isComplexTypeReference()>
                // TODO: Finish this!
                <#else>
                sizeInBits += values.getList().size() * ${elementTypeReference.asSimpleTypeReference().orElseThrow().sizeInBits};
                </#if>
                <#break>
            <#case "const">
                <#assign constField=field.asConstField().orElseThrow()>
                // Const Field (${constField.name})
                <#-- const fields are only simple type -->
                sizeInBits += ${constField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
                <#break>
            <#case "enum">
                <#assign enumField=field.asEnumField().orElseThrow()>
                // Enum field (${enumField.name})
                sizeInBits += ${helper.getEnumFieldSimpleTypeReference(enumField.type, enumField.fieldName).sizeInBits};
                <#break>
            <#case "manual">
                <#assign manualField=field.asManualField().orElseThrow()>
                // Manual Field (${manualField.name})
                sizeInBits += ${helper.toSerializationExpression(manualField, helper.intTypeReference, manualField.lengthExpression, type.parserArguments.orElse(null))};
                <#break>
            <#case "reserved">
                <#assign reservedField=field.asReservedField().orElseThrow()>
                // Reserved Field
                sizeInBits += ${reservedField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
                <#break>
            <#case "simple">
                <#assign simpleField=field.asSimpleField().orElseThrow()>
                // Simple Field (${simpleField.name})
                sizeInBits += ${simpleField.type.asSimpleTypeReference().orElseThrow().sizeInBits};
                <#break>
            </#switch>
            </#list>
        }<#sep> else </#sep></#list>
        return sizeInBits;
    }

}
</#outputformat>
