package com.alibaba.fastjson2.reader;

import com.alibaba.fastjson2.*;
import com.alibaba.fastjson2.function.Function;
import com.alibaba.fastjson2.util.Fnv;
import com.alibaba.fastjson2.util.TypeUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;

final class ObjectArrayTypedReader
        extends ObjectReaderPrimitive {
    final Class componentType;
    final Class componentClass;
    final long componentClassHash;
    final String typeName;
    final long typeNameHashCode;

    ObjectArrayTypedReader(Class objectClass) {
        super(objectClass);
        this.componentType = objectClass.getComponentType();
        String componentTypeName = TypeUtils.getTypeName(componentType);
        this.componentClassHash = Fnv.hashCode64(componentTypeName);
        this.typeName = '[' + componentTypeName;
        typeNameHashCode = Fnv.hashCode64(this.typeName);

        this.componentClass = TypeUtils.getClass(componentType);
    }

    @Override
    public Object readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
        if (jsonReader.jsonb) {
            return readJSONBObject(jsonReader, fieldType, fieldName, 0);
        }

        if (jsonReader.readIfNull()) {
            return null;
        }

        if (jsonReader.nextIfArrayStart()) {
            Object[] values = (Object[]) Array.newInstance(componentType, 16);
            int size = 0;
            for (; ; ) {
                if (jsonReader.nextIfArrayEnd()) {
                    break;
                }

                int minCapacity = size + 1;
                if (minCapacity - values.length > 0) {
                    int oldCapacity = values.length;
                    int newCapacity = oldCapacity + (oldCapacity >> 1);
                    if (newCapacity - minCapacity < 0) {
                        newCapacity = minCapacity;
                    }

                    values = Arrays.copyOf(values, newCapacity);
                }

                Object value = jsonReader.read(componentType);
                values[size++] = value;

                jsonReader.nextIfComma();
            }
            jsonReader.nextIfComma();

            return Arrays.copyOf(values, size);
        }

        if (jsonReader.current() == '"') {
            String str = jsonReader.readString();
            if (str.isEmpty()) {
                return null;
            }
        }

        throw new JSONException(jsonReader.info("TODO"));
    }

    @Override
    public Object readJSONBObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
        if (jsonReader.getType() == JSONB.Constants.BC_TYPED_ANY) {
            jsonReader.next();
            long typeHash = jsonReader.readTypeHashCode();
            if (typeHash == ObjectArrayReader.TYPE_HASH_CODE || typeHash == typeNameHashCode) {
                // skip
            } else {
                if (jsonReader.isSupportAutoType(features)) {
                    ObjectReader autoTypeObjectReader = jsonReader.getObjectReaderAutoType(typeHash, objectClass, features);
                    if (autoTypeObjectReader == null) {
                        throw new JSONException(jsonReader.info("auotype not support : " + jsonReader.getString()));
                    }

                    return autoTypeObjectReader.readObject(jsonReader, fieldType, fieldName, features);
                }

                throw new JSONException(jsonReader.info("not support autotype : " + jsonReader.getString()));
            }
        }

        int entryCnt = jsonReader.startArray();
        if (entryCnt == -1) {
            return null;
        }

        Object[] values = (Object[]) Array.newInstance(componentClass, entryCnt);
        for (int i = 0; i < entryCnt; ++i) {
            Object value;

            if (jsonReader.isReference()) {
                String reference = jsonReader.readReference();
                if ("..".equals(reference)) {
                    value = values;
                } else {
                    value = null;
                    jsonReader.addResolveTask(values, i, JSONPath.of(reference));
                }
            } else {
                ObjectReader autoTypeReader = jsonReader.checkAutoType(componentClass, componentClassHash, features);
                if (autoTypeReader != null) {
                    value = autoTypeReader.readJSONBObject(jsonReader, null, null, features);
                } else {
                    value = jsonReader.read(componentType);
                }
            }

            values[i] = value;
        }
        return values;
    }

    @Override
    public Object createInstance(Collection collection, long features) {
        Object[] values = (Object[]) Array.newInstance(componentClass, collection.size());
        int index = 0;
        for (Object item : collection) {
            if (item != null) {
                Class<?> valueClass = item.getClass();
                if (valueClass != componentType) {
                    ObjectReaderProvider provider = JSONFactory.defaultObjectReaderProvider;
                    Function typeConvert = provider.getTypeConvert(valueClass, componentType);
                    if (typeConvert != null) {
                        item = typeConvert.apply(item);
                    }
                }
            }

            if (!componentType.isInstance(item)) {
                ObjectReader objectReader = JSONFactory.defaultObjectReaderProvider.getObjectReader(componentType);
                if (item instanceof Map) {
                    item = objectReader.createInstance((Map) item);
                } else if (item instanceof Collection) {
                    item = objectReader.createInstance((Collection) item, features);
                } else if (item instanceof Object[]) {
                    item = objectReader.createInstance(JSONArray.of((Object[]) item), features);
                } else if (item != null) {
                    Class<?> itemClass = item.getClass();
                    if (itemClass.isArray()) {
                        int length = Array.getLength(item);
                        JSONArray array = new JSONArray(length);
                        for (int i = 0; i < length; i++) {
                            array.add(Array.get(item, i));
                        }
                        item = objectReader.createInstance(array, features);
                    } else {
                        throw new JSONException("component type not match, expect " + componentType.getName() + ", but " + itemClass);
                    }
                }
            }
            values[index++] = item;
        }
        return values;
    }
}
