package com.alibaba.fastjson2.reader;

import com.alibaba.fastjson2.*;
import com.alibaba.fastjson2.function.Supplier;
import com.alibaba.fastjson2.util.Fnv;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import static com.alibaba.fastjson2.JSONB.Constants.*;

public final class ObjectReaderImplObject
        extends ObjectReaderPrimitive {
    public static final ObjectReaderImplObject INSTANCE = new ObjectReaderImplObject();

    public ObjectReaderImplObject() {
        super(Object.class);
    }

    @Override
    public Object createInstance(long features) {
        return new JSONObject();
    }

    public Object createInstance(Collection collection, long features) {
        return collection;
    }

    @Override
    public Object createInstance(Map map, long features) {
        ObjectReaderProvider provider = JSONFactory.defaultObjectReaderProvider;
        Object typeKey = map.get(getTypeKey());

        if (typeKey instanceof String) {
            String typeName = (String) typeKey;
            long typeHash = Fnv.hashCode64(typeName);
            ObjectReader reader = null;
            if ((features & JSONReader.Feature.SupportAutoType.mask) != 0) {
                reader = autoType(provider, typeHash);
            }

            if (reader == null) {
                reader = provider.getObjectReader(
                        typeName, getObjectClass(), features | getFeatures()
                );

                if (reader == null) {
                    throw new JSONException("No suitable ObjectReader found for" + typeName);
                }
            }

            if (reader != this) {
                return reader.createInstance(map, features);
            }
        }

        return map;
    }

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

        JSONReader.Context context = jsonReader.context;
        long contextFeatures = features | context.features;

        String typeName = null;
        if (jsonReader.isObject()) {
            jsonReader.nextIfObjectStart();

            long hash = 0;
            if (jsonReader.isString()) {
                hash = jsonReader.readFieldNameHashCode();

                if (hash == HASH_TYPE) {
                    boolean supportAutoType = context.isEnabled(JSONReader.Feature.SupportAutoType);

                    ObjectReader autoTypeObjectReader;

                    if (supportAutoType) {
                        long typeHash = jsonReader.readTypeHashCode();
                        autoTypeObjectReader = context.getObjectReaderAutoType(typeHash);

                        if (autoTypeObjectReader != null) {
                            Class objectClass = autoTypeObjectReader.getObjectClass();
                            if (objectClass != null) {
                                ClassLoader objectClassLoader = objectClass.getClassLoader();
                                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
                                if (objectClassLoader != contextClassLoader) {
                                    Class contextClass = null;

                                    typeName = jsonReader.getString();
                                    try {
                                        contextClass = contextClassLoader.loadClass(typeName);
                                    } catch (ClassNotFoundException ignored) {
                                    }

                                    //明确contextClass类型与objectClass不一致时才更改reader
                                    if (contextClass != null && !objectClass.equals(contextClass)) {
                                        autoTypeObjectReader = context.getObjectReader(contextClass);
                                    }
                                }
                            }
                        }

                        if (autoTypeObjectReader == null) {
                            typeName = jsonReader.getString();
                            autoTypeObjectReader = context.getObjectReaderAutoType(typeName, null);
                        }
                    } else {
                        typeName = jsonReader.readString();
                        autoTypeObjectReader = context.getObjectReaderAutoType(typeName, null);

                        if (autoTypeObjectReader == null && jsonReader.context.isEnabled(JSONReader.Feature.ErrorOnNotSupportAutoType)) {
                            throw new JSONException(jsonReader.info("autoType not support : " + typeName));
                        }
                    }

                    if (autoTypeObjectReader != null) {
                        jsonReader.setTypeRedirect(true);

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

            Map object;
            Supplier<Map> objectSupplier = jsonReader.context.getObjectSupplier();
            if (objectSupplier != null) {
                object = objectSupplier.get();
            } else {
                if (((features | context.features) & JSONReader.Feature.UseNativeObject.mask) != 0) {
                    object = new HashMap();
                } else {
                    object = (Map) ObjectReaderImplMap.INSTANCE_OBJECT.createInstance(jsonReader.features(features));
                }
            }

            if (typeName != null) {
                switch (typeName) {
                    case "java.util.ImmutableCollections$Map1":
                    case "java.util.ImmutableCollections$MapN":
                        break;
                    default:
                        object.put("@type", typeName);
                        break;
                }
                hash = 0;
            }

            for (int i = 0; ; ++i) {
                if (jsonReader.nextIfObjectEnd()) {
                    break;
                }

                if (jsonReader.current() == '/') {
                    jsonReader.skipComment();
                }

                Object name;
                if (i == 0 && typeName == null && hash != 0) {
                    name = jsonReader.getFieldName();
                } else if (jsonReader.isNumber()) {
                    name = jsonReader.readNumber();
                    jsonReader.nextIfMatch(':');
                } else {
                    name = jsonReader.readFieldName();
                }
                if (name == null) {
                    char current = jsonReader.current();
                    if (current == '{' || current == '[') {
                        name = jsonReader.readAny();
                        if (!jsonReader.nextIfMatch(':')) {
                            throw new JSONException(jsonReader.info("illegal input"));
                        }
                    } else {
                        name = jsonReader.readFieldNameUnquote();
                        if (jsonReader.current() == ':') {
                            jsonReader.next();
                        }
                    }
                }

                Object value;
                switch (jsonReader.current()) {
                    case '-':
                    case '+':
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                    case '.':
                        value = jsonReader.readNumber();
                        break;
                    case '[':
                        value = jsonReader.readArray();
                        break;
                    case '{':
                        if (jsonReader.isReference()) {
                            String reference = jsonReader.readReference();
                            if ("..".equals(reference)) {
                                value = object;
                            } else {
                                jsonReader.addResolveTask(object, name, JSONPath.of(reference));
                                continue;
                            }
                        } else {
                            value = jsonReader.readObject();
                        }
                        break;
                    case '"':
                    case '\'':
                        value = jsonReader.readString();
                        break;
                    case 't':
                    case 'f':
                        value = jsonReader.readBoolValue();
                        break;
                    case 'n':
                        value = jsonReader.readNullOrNewDate();
                        break;
                    case 'S':
                        if (jsonReader.nextIfSet()) {
                            value = jsonReader.read(HashSet.class);
                        } else {
                            throw new JSONException(jsonReader.info());
                        }
                        break;
                    default:
                        throw new JSONException(jsonReader.info());
                }

                if (value == null && (contextFeatures & JSONReader.Feature.IgnoreNullPropertyValue.mask) != 0) {
                    continue;
                }

                Object origin = object.put(name, value);
                if (origin != null) {
                    if ((contextFeatures & JSONReader.Feature.DuplicateKeyValueAsArray.mask) != 0) {
                        if (origin instanceof Collection) {
                            ((Collection) origin).add(value);
                            object.put(name, origin);
                        } else {
                            JSONArray array = JSONArray.of(origin, value);
                            object.put(name, array);
                        }
                    }
                }
            }

            jsonReader.nextIfComma();

            return object;
        }

        char ch = jsonReader.current();
        if (ch == '/') {
            jsonReader.skipComment();
            ch = jsonReader.current();
        }

        Object value;
        switch (ch) {
            case '-':
            case '+':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '.':
                value = jsonReader.readNumber();
                break;
            case '[':
                value = jsonReader.readArray();
                break;
            case '"':
            case '\'':
                value = jsonReader.readString();
                break;
            case 't':
            case 'f':
                value = jsonReader.readBoolValue();
                break;
            case 'n':
                value = jsonReader.readNullOrNewDate();
                break;
            case 'x':
                value = jsonReader.readBinary();
                break;
            case 'S':
                if (jsonReader.nextIfSet()) {
                    HashSet<Object> set = new HashSet<>();
                    jsonReader.read(set);
                    value = set;
                } else {
                    throw new JSONException(jsonReader.info());
                }
                break;
            default:
                throw new JSONException(jsonReader.info());
        }

        return value;
    }

    @Override
    public Object readJSONBObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
        byte type = jsonReader.getType();
        if (type >= BC_STR_ASCII_FIX_MIN && type <= BC_STR_UTF16BE) {
            return jsonReader.readString();
        }

        if (type == BC_TYPED_ANY) {
            ObjectReader autoTypeObjectReader = jsonReader.checkAutoType(Object.class, 0, features);
            if (autoTypeObjectReader != null) {
                return autoTypeObjectReader.readJSONBObject(jsonReader, fieldType, fieldName, features);
            }
        }

        if (type == BC_NULL) {
            jsonReader.next();
            return null;
        }

        return jsonReader.readAny();
    }
}
