package com.alterdekim.game.message.amf;

import com.alterdekim.game.message.SerializerUtils;
import com.alterdekim.game.message.serializer.BinaryMessageSerializer;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import java.util.*;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class AMFDeserializer {
    private List<Byte> data;

    private final LinkedList<Byte> bytes;
    private final List<AMFObject> objects;

    public List<AMFObject> deserialize() {
        AMFObject current;
        while( !bytes.isEmpty() ) {
            current = new AMFObject();
            current.setType(AMFValueType.toValueType(bytes.remove()));
            current.setValue(processSingle(current.getType()));
            objects.add(current);
        }
        return objects;
    }

    private Object processSingle(AMFValueType type) {
        return switch (type) {
                case NUMBER -> processDouble();
                case STRING -> processString();
                case OBJECT -> processObject();
                case MIXED_ARRAY -> processArray();
                case DATE -> processDate();
                case BOOLEAN -> bytes.remove() == 0x01;
                case NULL -> null;
            };
    }

    private Long processDate() {
        Long timestamp = processDouble().longValue();
        bytes.subList(0, 2).clear();
        return timestamp;
    }

    private Double processDouble() {
        var l = bytes.subList(0, 8);
        Double d = Double.longBitsToDouble(BinaryMessageSerializer.deserializeLong(l));
        l.clear();
        return d;
    }

    private String processString() {
        List<Byte> b = bytes.subList(0, 2);
        int len = BinaryMessageSerializer.deserializeLongUnsafe(b).intValue();
        b.clear();
        b = bytes.subList(0, Math.min(len, bytes.size()));
        String s = new String(SerializerUtils.bytesToPrimitive(b));
        b.clear();
        return s;
    }

    private Map<String, AMFObject> processObject() {
        Map<String, AMFObject> map = new HashMap<>();
        boolean endMarker = false;
        while(!endMarker) {
            if( bytes.size() < 2 ) {
                bytes.clear();
                break;
            }
            var a = bytes.subList(0, 2);
            int keyLen = BinaryMessageSerializer.deserializeInteger(a);
            a.clear();
            if( keyLen == 0 ) {
                endMarker = true;
                bytes.remove();
                continue;
            }
            a = bytes.subList(0, Math.min(keyLen, bytes.size()));
            String key = new String(SerializerUtils.bytesToPrimitive(a));
            a.clear();
            AMFObject object = new AMFObject();
            if(!bytes.isEmpty()) {
                object.setType(AMFValueType.toValueType(bytes.remove()));
                object.setValue(processSingle(object.getType()));
                map.put(key, object);
            } else {
                break;
            }
        }
        return map;
    }

    private List<AMFObject> processArray() {
        List<AMFObject> list = new LinkedList<>();
        bytes.subList(0, 4).clear();
        boolean endMarker = false;
        while(!endMarker) {
            if( bytes.size() < 2 ) {
                bytes.clear();
                break;
            }
            var a = bytes.subList(0, 2);
            int keyLen = BinaryMessageSerializer.deserializeInteger(a);
            a.clear();
            if( keyLen == 0 ) {
                endMarker = true;
                bytes.remove();
                continue;
            }
            bytes.subList(0, Math.min(keyLen, bytes.size())).clear();
            AMFObject object = new AMFObject();
            object.setType(AMFValueType.toValueType(bytes.remove()));
            object.setValue(processSingle(object.getType()));
            list.add(object);
        }
        return list;
    }

    public static AMFDeserializer newInstance(List<Byte> bytes) {
        return new AMFDeserializer(new LinkedList<>(bytes), new LinkedList<>());
    }
}
