package dk.nversion.copybook.serializers;

import dk.nversion.copybook.exceptions.CopyBookException;

import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FullSerializer extends CopyBookSerializerBase {
    private int maxRecordSize;
    private Map<CopyBookField, Integer> fieldRecursiveSizes = new HashMap<>();

    public FullSerializer(CopyBookSerializerConfig config) {
        super(config);
        this.maxRecordSize = calculateMaxSize(config.getFields(), 0, this.debug);
    }

    private int calculateMaxSize(List<CopyBookField> fields, int level, boolean debug) {
        int result = 0;
        for(CopyBookField field : fields) {
            if(debug) {
                for (String line : field.getLines()) {
                    System.out.println(new String(new char[level * 2]).replace("\0", " ") + line);
                }
            }
            int size;
            if(field.isArray()) {
                if(field.hasSubCopyBookFields()) {
                    size = calculateMaxSize(field.getSubCopyBookFields(), level + 1, debug) * field.getMaxOccurs();

                } else {
                    size = field.getSize() * field.getMaxOccurs();
                }

            } else if(field.hasSubCopyBookFields()) {
                size = calculateMaxSize(field.getSubCopyBookFields(), level + 1, debug);

            } else {
                size = field.getSize();
            }
            this.fieldRecursiveSizes.put(field, size);
            result += size;
        }
        return result;
    }


    public <T> byte[] serialize(T obj) throws CopyBookException {
        ByteBuffer buffer = ByteBuffer.wrap(new byte[this.maxRecordSize]);
        writeFieldsToBuffer(this.fields, buffer, obj);
        return buffer.array();
    }


    private <T> void writeFieldsToBuffer(List<CopyBookField> fields, ByteBuffer buffer, T obj) throws CopyBookException {
        for(CopyBookField field : fields) {
            if(field.isArray()) {
                if(field.hasSubCopyBookFields()) {
                    // Complex array types fx. Request[]
                    for (int i = 0; i < field.getMaxOccurs(); i++) {
                        writeFieldsToBuffer(field.getSubCopyBookFields(), buffer, field.getObject(obj, i));
                    }

                } else {
                    // Simple array types, fx. int[]
                    for (int i = 0; i < field.getMaxOccurs(); i++) {
                        buffer.put(field.getBytes(obj, i, true));
                    }
                }

            } else if(field.hasSubCopyBookFields()) {
                // Complex type fx, Request
                writeFieldsToBuffer(field.getSubCopyBookFields(), buffer, field.getObject(obj));

            } else {
                // Simple type fx. int
                buffer.put(field.getBytes(obj, true));
            }
        }
    }

    public <T> T deserialize(byte[] bytes, Class<T> type) throws CopyBookException {
        try {
            T obj = type.newInstance();
            ByteBuffer buffer = ByteBuffer.wrap(bytes);
            readFieldsFromBuffer(this.fields, buffer, obj, new HashMap<>());
            return obj;

        } catch (IllegalAccessException | InstantiationException e) {
            throw new CopyBookException("Failed to create new object", e);
        }
    }

    private void readFieldsFromBuffer(List<CopyBookField> fields, ByteBuffer buffer, Object obj, Map<String, Integer> counters) throws CopyBookException {
        for(CopyBookField field : fields) {
            if(field.isArray()) {
                int arraySize = field.getMaxOccurs();
                if(counters.containsKey(field.getFieldName() + "_count")) {
                    arraySize = counters.get(field.getFieldName() + "_count");
                }
                Object array = field.createArrayObject(obj, arraySize);
                if(field.hasSubCopyBookFields()) {
                    // Complex array types fx. Request[]
                    for (int i = 0; i < arraySize; i++) {
                        readFieldsFromBuffer(field.getSubCopyBookFields(), buffer, field.createObject(array, i), counters);
                    }

                    // Move position in buffer to next location with data
                    if(field.getMaxOccurs() > arraySize) {
                        int skipSize = (field.getMaxOccurs() - arraySize) * this.fieldRecursiveSizes.get(field) / field.getMaxOccurs();
                        buffer.position(buffer.position() + skipSize);
                    }

                } else {
                    // Simple array types, fx. int[]
                    for (int i = 0; i < arraySize; i++) {
                        field.setBytes(array, i, buffer, true);
                    }

                    // Move position in buffer to next location with data
                    if(field.getMaxOccurs() > arraySize) {
                        buffer.position(buffer.position() + (field.getMaxOccurs() - arraySize) * field.getSize());
                    }
                }

            } else if(field.hasSubCopyBookFields()) {
                // Complex type fx, Request
                readFieldsFromBuffer(field.getSubCopyBookFields(), buffer, field.createObject(obj), counters);

            } else {
                // Simple type fx. int, String or types we support with TypeConverters
                Object value = field.setBytes(obj, buffer, true);

                // Save counter value for later use
                if(field.getField().getType().equals(Integer.TYPE) && field.getFieldName().endsWith("_count")) {
                    counters.put(field.getFieldName(), (int)value);
                }
            }
        }
    }

}

@@ -3209,16 +3209,20 @@
 er, obj,
+ %22%22,
  new Has
@@ -3515,16 +3515,29 @@
 ect obj,
+ String name,
  Map%3CStr
@@ -3643,37 +3643,159 @@
         
-if(field.isArray()) %7B
+String fieldName = name + %22.%22 + field.getFieldName();%0A            if(field.isArray()) %7B%0A                // Support field naming as counter keys
 %0A       
@@ -3882,39 +3882,28 @@
 insKey(field
-.getField
 Name
-()
  + %22_count%22)
@@ -3960,34 +3960,220 @@
 ield
-.getFieldName() + %22_count%22
+Name + %22_count%22);%0A%0A                // Support depending on cobol syntax for counters%0A                %7D else if(counters.containsKey(field.getName())) %7B%0A                    arraySize = counters.get(field.getName()
 );%0A 
@@ -4181,32 +4181,33 @@
               %7D%0A
+%0A
                 
@@ -4540,16 +4540,27 @@
 ray, i),
+ fieldName,
  counter
@@ -4685,34 +4685,34 @@
    if(field.getM
-ax
+in
 Occurs() %3E array
@@ -5271,34 +5271,34 @@
    if(field.getM
-ax
+in
 Occurs() %3E array
@@ -5658,16 +5658,27 @@
 ct(obj),
+ fieldName,
  counter
@@ -5881,16 +5881,31 @@
 // Save 
+field name and 
 counter 
@@ -5999,31 +5999,20 @@
 && field
-.getField
 Name
-()
 .endsWit
@@ -6060,33 +6060,144 @@
 rs.put(field
-.getField
+Name, (int)value);%0A                %7D%0A%0A                if(field.isCounter()) %7B%0A                    counters.put(field.get
 Name(), (int

Make sure the keys are unique and start adding support for depending on