<#--
  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.cs.CsLanguageTemplateHelper" -->
<#-- @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.fileName(protocolName, languageName, outputFlavor)?replace(".", "/")}/model/io/${type.name}.cs
/*
 * 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.
 */

// 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.allParserArguments.orElseThrow()></#if>
public class ${type.name}IO implements MessageInput<${type.name}> {

    private static final Logger LOGGER = LoggerFactory.getLogger(${type.name}IO.class);

<#-- The parse and serialize methods here are just proxies for forwardning the requests to static counterparts -->
<#if !type.isDiscriminatedChildTypeDefinition()>
    @Override
    public ${type.name} parse(ReadBuffer readBuffer, Object... args) throws ParseException {
        <#if parserArguments?has_content>
        if((args == null) || (args.length != ${parserArguments?size})) {
            throw new PlcRuntimeException("Wrong number of arguments, expected ${parserArguments?size}, but got " + args.length);
        }
            <#list parserArguments as parserArgument>
        ${helper.getLanguageTypeNameForTypeReference(parserArgument.type)} ${parserArgument.name};
        if(args[${parserArgument?index}] instanceof ${helper.getLanguageTypeNameForTypeReference(parserArgument.type)}) {
            ${parserArgument.name} = (${helper.getLanguageTypeNameForTypeReference(parserArgument.type)}) args[${parserArgument?index}];
            <#if parserArgument.type.isSimpleTypeReference()>
        } else if (args[${parserArgument?index}] instanceof String) {
            ${parserArgument.name} = ${helper.getLanguageTypeNameForTypeReference(parserArgument.type)}.valueOf((String) args[${parserArgument?index}]);
            </#if>
        } else {
            throw new PlcRuntimeException("Argument ${parserArgument?index} expected to be of type ${helper.getLanguageTypeNameForTypeReference(parserArgument.type)} or a string which is parseable but was " + args[${parserArgument?index}].getClass().getName());
        }
            </#list>
        </#if>
        return ${type.name}IO.staticParse(readBuffer<#if parserArguments?has_content>, <#list parserArguments as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if>);
    }

<#else>
    @Override
    public ${type.name} parse(ReadBuffer readBuffer, Object... args) throws ParseException {
        return (${type.name}) new ${type.parentType.orElseThrow().name}IO().parse(readBuffer, args);
    }

</#if>
<#-- Here come the actual parse and serialize methods that actually do the parsing and serlaizing -->
    <#assign hasParserArguments=parserArguments?has_content/>
    <#assign parserArgumentList><#if hasParserArguments><#list parserArguments as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type)} ${parserArgument.name}<#sep>, </#sep></#list></#if></#assign>
    public static ${type.name}<#if type.isDiscriminatedChildTypeDefinition()>Builder</#if> staticParse(ReadBuffer readBuffer<#if hasParserArguments>, ${parserArgumentList}</#if>) throws ParseException {
        readBuffer.pullContext("${type.name}");
        int startPos = readBuffer.getPos();
        int curPos;
<#list type.fields as field>
<#switch field.typeName>
    <#case "array">
        <#assign arrayField = field.asArrayField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        <#if typedField.type.isByteBased()>
            <#if !field.isCountArrayField() && !field.isLengthArrayField()>
        throw new ParseException("array fields of type byte only support 'count' and 'length' loop-types.");
            </#if>
        byte[] ${namedField.name} = readBuffer.readByteArray("${namedField.name}", (int) ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
        <#else>
        <#-- If this is a count array, we can directly initialize an array with the given size -->
            <#if field.isCountArrayField()>
        List<${helper.getLanguageTypeNameForField(arrayField)}> ${arrayField.name} = readCountArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
        <#-- 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 field.isLengthArrayField()>
        List<${helper.getLanguageTypeNameForField(arrayField)}> ${arrayField.name} = readLengthArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, ${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
            <#-- A terminated array keeps on reading data as long as the termination expression evaluates to false -->
                <#elseif field.isTerminatedArrayField()>
        List<${helper.getLanguageTypeNameForField(arrayField)}> ${arrayField.name} = readTerminatedArrayField("${arrayField.name}", ${helper.getDataReaderCall(arrayField.type)}, () -> ((boolean) (${helper.toParseExpression(arrayField, helper.intTypeReference, arrayField.loopExpression, parserArguments)}))${helper.getFieldOptions(typedField, parserArguments)});
                </#if>
            </#if>
        </#if>
        <#break>
    <#case "assert">
        <#assign assertField = field.asAssertField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, ${helper.toParseExpression(assertField, assertField.type, assertField.conditionExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "checksum">
        <#assign checksumField = field.asChecksumField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, ${helper.toParseExpression(checksumField, checksumField.type, checksumField.checksumExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "const">
        <#assign constField = field.asConstField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}, ${type.name}.${namedField.name?upper_case}${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "discriminator">
        <#assign discriminatorField = field.asDiscriminatorField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "enum">
        <#assign enumField = field.asEnumField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", "${enumField.type.asNonSimpleTypeReference().orElseThrow().name}", readEnum(${enumField.type.asNonSimpleTypeReference().orElseThrow().name}::firstEnumForField${enumField.fieldName?cap_first}, ${helper.getDataReaderCall(helper.getEnumFieldTypeReference(enumField.type, enumField.fieldName))})${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "implicit">
        <#assign implicitField = field.asImplicitField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "manualArray">
        <#assign manualArrayField = field.asManualArrayField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        <#if manualArrayField.type.isByteBased()>
        byte[] ${namedField.name} = readManualByteArrayField("${namedField.name}", readBuffer, () -> (boolean) (${helper.toParseExpression(manualArrayField, helper.boolTypeReference, manualArrayField.loopExpression, parserArguments)}), () -> (${helper.getLanguageTypeNameForField(manualArrayField)}) (${helper.toParseExpression(manualArrayField, manualArrayField.type, manualArrayField.parseExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
        <#else>
        List<${helper.getLanguageTypeNameForField(manualArrayField)}> ${namedField.name} = readManualArrayField("${namedField.name}", readBuffer, () -> (boolean) (${helper.toParseExpression(manualArrayField, helper.boolTypeReference, manualArrayField.loopExpression, parserArguments)}), () -> (${helper.getLanguageTypeNameForField(manualArrayField)}) (${helper.toParseExpression(manualArrayField, manualArrayField.type, manualArrayField.parseExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
        </#if>
        <#break>
    <#case "manual">
        <#assign manualField = field.asManualField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${manualField.name} = readManualField("${namedField.name}", readBuffer, () -> (${helper.getLanguageTypeNameForField(manualField)}) (${helper.toParseExpression(manualField, manualField.type, manualField.parseExpression, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "optional">
        <#assign optionalField = field.asOptionalField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}<#if optionalField.conditionExpression.present>, ${helper.toParseExpression(optionalField, helper.boolTypeReference, optionalField.conditionExpression.get(), parserArguments)}</#if>${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "padding">
        <#assign paddingField = field.asPaddingField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign simpleTypeReference = paddingField.type.asSimpleTypeReference().orElseThrow()>

        read${field.typeName?cap_first}Field(${helper.getDataReaderCall(typedField.type)}, (int) (${helper.toParseExpression(paddingField, paddingField.type, paddingField.paddingCondition, parserArguments)})${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "reserved">
        <#assign reservedField = field.asReservedField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>

        read${field.typeName?cap_first}Field("reserved", ${helper.getDataReaderCall(typedField.type)}, ${helper.getReservedValue(reservedField)}${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "simple">
        <#assign simpleField = field.asSimpleField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = <#if typedField.type.isEnumTypeReference()>readEnumField("${namedField.name}", "${helper.getLanguageTypeNameForField(field)}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});<#else>read${field.typeName?cap_first}Field("${namedField.name}", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});</#if>
        <#break>
    <#case "switch">
        <#assign switchField = field.asSwitchField().orElseThrow()>

        // Switch Field (Depending on the discriminator values, passes the instantiation to a sub-type)
        ${type.name}Builder builder = null;
        <#list switchField.cases as case>
            <@compress single_line=true>
            <#if case.discriminatorValueTerms?has_content>
                if(
                <#list case.discriminatorValueTerms as discriminatorValueTerm>
                    <#assign discriminatorExpression=switchField.discriminatorExpressions[discriminatorValueTerm?index].asLiteral().orElseThrow().asVariableLiteral().orElseThrow()>
                    <#assign discriminatorType=helper.getDiscriminatorTypes()[discriminatorExpression.discriminatorName]>
                    EvaluationHelper.equals(
                    ${helper.toParseExpression(switchField, discriminatorType, discriminatorExpression, parserArguments)},
                    <#if discriminatorType.isEnumTypeReference()>
                    ${helper.getLanguageTypeNameForTypeReference(discriminatorType)}.${helper.toParseExpression(switchField, discriminatorType, discriminatorValueTerm, parserArguments)}
                    <#else>
                    ${helper.toParseExpression(switchField, discriminatorType, discriminatorValueTerm, parserArguments)}
                    </#if>
                    )
                    <#sep> && </#sep>
                    </#list>
                )
            </#if>{
            </@compress>
            <@compress single_line=true>
            <#assign hasCaseParseArguments=case.allParserArguments.isPresent() && case.allParserArguments.orElseThrow()?has_content>
            <#assign caseParseArguments><#if hasCaseParseArguments><#list case.allParserArguments.orElseThrow() as parserArgument>${parserArgument.name}<#sep>, </#sep></#list></#if></#assign>
            builder = ${case.name}IO.staticParse(readBuffer<#if hasCaseParseArguments>, ${tracer.dive("case parse arguments")} ${caseParseArguments}</#if>);
            </@compress>
        }<#sep> else </#sep>
        </#list>
        if (builder == null) {
            throw new ParseException("Unsupported case for discriminated type");
        }
        <#break>
    <#case "unknown">
        <#assign unknownField = field.asUnknownField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>

        read${field.typeName?cap_first}Field("unknown", ${helper.getDataReaderCall(typedField.type)}${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
    <#case "virtual">
        <#assign virtualField = field.asVirtualField().orElseThrow()>
        <#assign typedField = field.asTypedField().orElseThrow()>
        <#assign namedField = field.asNamedField().orElseThrow()>

        ${helper.getLanguageTypeNameForField(field)} ${namedField.name} = read${field.typeName?cap_first}Field(${helper.getLanguageTypeNameForField(field)}.class, ${helper.toParseExpression(virtualField, virtualField.type, virtualField.valueExpression, parserArguments)}${helper.getFieldOptions(typedField, parserArguments)});
        <#break>
</#switch>
</#list>

        readBuffer.closeContext("${type.name}");
        // Create the instance
        <#if type.isDiscriminatedChildTypeDefinition()>
        return new ${type.name}Builder(<#list type.propertyFields as field>${field.name}<#sep>, </#sep></#list>);
        <#elseif type.isDiscriminatedParentTypeDefinition()>
        return builder.build(<#list type.propertyFields as field>${field.name}<#sep>, </#sep></#list>);
        <#else>
        return new ${type.name}(<#list type.propertyFields as field>${field.name}<#sep>, </#sep></#list>);
        </#if>
    }

<#if type.isDiscriminatedParentTypeDefinition()>
    public static interface ${type.name}Builder {
        ${type.name} build(<#list type.propertyFields as field><#if field.loopType??>List<${helper.getLanguageTypeNameForField(field)}><#else>${helper.getLanguageTypeNameForField(field)}</#if> ${field.name}<#sep>, </#sep></#list>);
    }

</#if>
<#if type.isDiscriminatedChildTypeDefinition()>
    public static class ${type.name}Builder implements ${type.parentType.orElseThrow().name}IO.${type.parentType.orElseThrow().name}Builder {
        <#if type.propertyFields?has_content>
            <#list type.propertyFields as field>
        private final <#if field.loopType?? && field.type.isByteBased()>byte[]<#elseif field.loopType??>List<${helper.getLanguageTypeNameForField(field)}><#else>${helper.getLanguageTypeNameForField(field)}</#if> ${field.name};
            </#list>
        </#if>

        public ${type.name}Builder(<#list type.propertyFields as field><#if field.loopType?? && field.type.isByteBased()>byte[]<#elseif field.loopType??>List<${helper.getLanguageTypeNameForField(field)}><#else>${helper.getLanguageTypeNameForField(field)}</#if> ${field.name}<#sep>, </#sep></#list>) {
            <#list type.propertyFields as field>
            this.${field.name} = ${field.name};
            </#list>
        }

        public ${type.name} build(<#list type.parentType.orElseThrow().asComplexTypeDefinition().orElseThrow().propertyFields as field><#if field.loopType?? && field.type.isByteBased()>byte[]<#elseif field.loopType??>List<${helper.getLanguageTypeNameForField(field)}><#else>${helper.getLanguageTypeNameForField(field)}</#if> ${field.name}<#sep>, </#sep></#list>) {
            return new ${type.name}(<#list type.allPropertyFields as field>${field.name}<#sep>, </#sep></#list>);
        }
    }

</#if>
}
</#outputformat>
