package com.alibaba.fastjson2.writer;

import com.alibaba.fastjson2.*;
import com.alibaba.fastjson2.annotation.JSONField;
import com.alibaba.fastjson2.codec.FieldInfo;
import com.alibaba.fastjson2.time.*;
import com.alibaba.fastjson2.util.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.zip.GZIPOutputStream;

import static com.alibaba.fastjson2.JSONWriter.Feature.*;

public abstract class FieldWriter<T>
        implements Comparable {
    public final String fieldName;
    public final Type fieldType;
    public final Class fieldClass;
    public final long features;
    public final int ordinal;
    public final String format;
    public final DecimalFormat decimalFormat;
    public final String label;
    public final Field field;
    public final Method method;
    protected final long fieldOffset;
    protected final boolean primitive;

    final long hashCode;
    final byte[] nameWithColonUTF8;
    final char[] nameWithColonUTF16;
    final byte[] nameJSONB;
    long nameSymbolCache;

    final boolean fieldClassSerializable;
    final JSONWriter.Path rootParentPath;

    final boolean symbol;
    final boolean trim;
    final boolean raw;
    final boolean managedReference;
    final boolean backReference;

    transient JSONWriter.Path path;
    volatile ObjectWriter initObjectWriter;

    static final AtomicReferenceFieldUpdater<FieldWriter, ObjectWriter>
            initObjectWriterUpdater = AtomicReferenceFieldUpdater.newUpdater(
            FieldWriter.class,
            ObjectWriter.class,
            "initObjectWriter"
    );

    FieldWriter(
            String name,
            int ordinal,
            long features,
            String format,
            String label,
            Type fieldType,
            Class fieldClass,
            Field field,
            Method method
    ) {
        if ("string".equals(format) && fieldClass != String.class) {
            features |= WriteNonStringValueAsString.mask;
        }

        this.fieldName = name;
        this.ordinal = ordinal;
        this.format = format;
        this.label = label;
        this.hashCode = Fnv.hashCode64(name);
        this.features = features;
        this.fieldType = TypeUtils.intern(fieldType);
        this.fieldClass = fieldClass;
        this.fieldClassSerializable = fieldClass != null && (Serializable.class.isAssignableFrom(fieldClass) || !Modifier.isFinal(fieldClass.getModifiers()));
        this.field = field;
        this.method = method;
        this.primitive = fieldClass.isPrimitive();

        this.nameJSONB = JSONB.toBytes(fieldName);

        DecimalFormat decimalFormat = null;
        if (format != null
                && (fieldClass == float.class
                || fieldClass == float[].class
                || fieldClass == Float.class
                || fieldClass == Float[].class
                || fieldClass == double.class
                || fieldClass == double[].class
                || fieldClass == Double.class
                || fieldClass == Double[].class
                || fieldClass == BigDecimal.class
                || fieldClass == BigDecimal[].class)
        ) {
            decimalFormat = new DecimalFormat(format);
        }
        this.decimalFormat = decimalFormat;

        long fieldOffset = -1L;
        if (field != null && (features & FieldInfo.DISABLE_UNSAFE) == 0) {
            if (JDKUtils.ANDROID_SDK_INT >= 24
                    || fieldClass == int.class
                    || fieldClass == long.class
                    || !fieldClass.isPrimitive()
            ) {
                fieldOffset = JDKUtils.UNSAFE.objectFieldOffset(field);
            }
        }
        this.fieldOffset = fieldOffset;
        if (fieldOffset == -1 && field != null && method == null) {
            try {
                field.setAccessible(true);
            } catch (Throwable ignored) {
                // ignored
            }
        }

        this.symbol = "symbol".equals(format);
        this.trim = "trim".equals(format);
        this.raw = (features & FieldInfo.RAW_VALUE_MASK) != 0;
        this.managedReference = (features & ReferenceDetection.mask) != 0;
        this.backReference = (features & FieldInfo.BACKR_EFERENCE) != 0;
        this.rootParentPath = new JSONWriter.Path(JSONWriter.Path.ROOT, name);

        int nameLength = name.length();
        int utflen = nameLength + 3;
        for (int i = 0; i < nameLength; ++i) {
            char c = name.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                // skip
            } else if (c > 0x07FF) {
                utflen += 2;
            } else {
                utflen += 1;
            }
        }

        byte[] bytes = new byte[utflen];
        int off = 0;
        bytes[off++] = '"';
        for (int i = 0; i < nameLength; ++i) {
            char c = name.charAt(i);
            if ((c >= 0x0001) && (c <= 0x007F)) {
                bytes[off++] = (byte) c;
            } else if (c > 0x07FF) {
                // 2 bytes, 11 bits
                bytes[off++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
                bytes[off++] = (byte) (0x80 | ((c >> 6) & 0x3F));
                bytes[off++] = (byte) (0x80 | (c & 0x3F));
            } else {
                bytes[off++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
                bytes[off++] = (byte) (0x80 | (c & 0x3F));
            }
        }
        bytes[off++] = '"';
        bytes[off] = ':';
        nameWithColonUTF8 = bytes;

        char[] chars = new char[nameLength + 3];
        chars[0] = '"';
        name.getChars(0, name.length(), chars, 1);
        chars[chars.length - 2] = '"';
        chars[chars.length - 1] = ':';
        nameWithColonUTF16 = chars;
    }

    public boolean isFieldClassSerializable() {
        return fieldClassSerializable;
    }

    public boolean isDateFormatMillis() {
        return false;
    }

    public boolean isDateFormatISO8601() {
        return false;
    }

    public void writeEnumJSONB(JSONWriter jsonWriter, Enum e) {
        throw new UnsupportedOperationException();
    }

    public ObjectWriter getInitWriter() {
        return null;
    }

    public boolean unwrapped() {
        return false;
    }

    public final void writeFieldName(JSONWriter jsonWriter) {
        if (jsonWriter.jsonb) {
            SymbolTable symbolTable = jsonWriter.symbolTable;
            if (symbolTable != null) {
                if (writeFieldNameSymbol(jsonWriter, symbolTable)) {
                    return;
                }
            }
            jsonWriter.writeNameRaw(nameJSONB, hashCode);
            return;
        }

        if (!jsonWriter.useSingleQuote && (jsonWriter.context.getFeatures() & UnquoteFieldName.mask) == 0) {
            if (jsonWriter.utf8) {
                jsonWriter.writeNameRaw(nameWithColonUTF8);
                return;
            }

            if (jsonWriter.utf16) {
                jsonWriter.writeNameRaw(nameWithColonUTF16);
                return;
            }
        }

        jsonWriter.writeName(fieldName);
        jsonWriter.writeColon();
    }

    private boolean writeFieldNameSymbol(JSONWriter jsonWriter, SymbolTable symbolTable) {
        int symbolTableIdentity = System.identityHashCode(symbolTable);

        int symbol;
        if (nameSymbolCache == 0) {
            symbol = symbolTable.getOrdinalByHashCode(hashCode);
            nameSymbolCache = ((long) symbol << 32) | symbolTableIdentity;
        } else {
            if ((int) nameSymbolCache == symbolTableIdentity) {
                symbol = (int) (nameSymbolCache >> 32);
            } else {
                symbol = symbolTable.getOrdinalByHashCode(hashCode);
                nameSymbolCache = ((long) symbol << 32) | symbolTableIdentity;
            }
        }

        if (symbol != -1) {
            jsonWriter.writeSymbol(-symbol);
            return true;
        }
        return false;
    }

    public final JSONWriter.Path getRootParentPath() {
        return rootParentPath;
    }

    public final JSONWriter.Path getPath(JSONWriter.Path parent) {
        if (path == null) {
            return path = new JSONWriter.Path(parent, fieldName);
        }

        if (path.parent == parent) {
            return path;
        }

        return new JSONWriter.Path(parent, fieldName);
    }

    public Type getItemType() {
        return null;
    }

    public Class getItemClass() {
        return null;
    }

    @Override
    public String toString() {
        return fieldName;
    }

    public Object getFieldValue(T object) {
        if (object == null) {
            throw new JSONException("field.get error, " + fieldName);
        }

        if (field != null) {
            try {
                Object value;
                if (fieldOffset != -1 && !primitive) {
                    value = JDKUtils.UNSAFE.getObject(object, fieldOffset);
                } else {
                    value = field.get(object);
                }
                return value;
            } catch (IllegalArgumentException | IllegalAccessException e) {
                throw new JSONException("field.get error, " + fieldName, e);
            }
        }

        throw new UnsupportedOperationException();
    }

    @Override
    public int compareTo(Object o) {
        FieldWriter other = (FieldWriter) o;

        int thisOrdinal = this.ordinal;
        int otherOrdinal = other.ordinal;
        if (thisOrdinal < otherOrdinal) {
            return -1;
        }
        if (thisOrdinal > otherOrdinal) {
            return 1;
        }

        int nameCompare = this.fieldName.compareTo(other.fieldName);

        if (nameCompare != 0) {
            return nameCompare;
        }

        Member thisMember;
        Member otherMember;
        if (this.method == null || (this.field != null && Modifier.isPublic(this.field.getModifiers()))) {
            thisMember = this.field;
        } else {
            thisMember = this.method;
        }
        if (other.method == null || (other.field != null && Modifier.isPublic(other.field.getModifiers()))) {
            otherMember = other.field;
        } else {
            otherMember = other.method;
        }

        if (thisMember != null && otherMember != null) {
            Class otherDeclaringClass = otherMember.getDeclaringClass();
            Class thisDeclaringClass = thisMember.getDeclaringClass();
            if (thisDeclaringClass != otherDeclaringClass) {
                if (thisDeclaringClass.isAssignableFrom(otherDeclaringClass)) {
                    return 1;
                } else if (otherDeclaringClass.isAssignableFrom(thisDeclaringClass)) {
                    return -1;
                }
            }

            JSONField thisField = null;
            JSONField otherField = null;
            if (thisMember instanceof Field) {
                thisField = ((Field) thisMember).getAnnotation(JSONField.class);
            } else if (thisMember instanceof Method) {
                thisField = ((Method) thisMember).getAnnotation(JSONField.class);
            }
            if (otherMember instanceof Field) {
                otherField = ((Field) otherMember).getAnnotation(JSONField.class);
            } else if (thisMember instanceof Method) {
                otherField = ((Method) otherMember).getAnnotation(JSONField.class);
            }

            if (thisField != null && otherField == null) {
                return -1;
            }
            if (thisField == null && otherField != null) {
                return 1;
            }
        }

        if (thisMember instanceof Field
                && otherMember instanceof Method
                && ((Field) thisMember).getType() == ((Method) otherMember).getReturnType()
        ) {
            return -1;
        }

        if (thisMember instanceof Method
                && otherMember instanceof Field
                && ((Method) thisMember).getReturnType() == ((Field) otherMember).getType()
        ) {
            return 1;
        }

        Class otherFieldClass = other.fieldClass;
        Class thisFieldClass = this.fieldClass;
        if (thisFieldClass != otherFieldClass && thisFieldClass != null && otherFieldClass != null) {
            if (thisFieldClass.isAssignableFrom(otherFieldClass)) {
                return 1;
            } else if (otherFieldClass.isAssignableFrom(thisFieldClass)) {
                return -1;
            }
        }

        if (thisFieldClass == boolean.class && otherFieldClass != boolean.class) {
            return 1;
        }

        if (thisFieldClass == Boolean.class
                && otherFieldClass == Boolean.class
                && thisMember instanceof Method
                && otherMember instanceof Method
        ) {
            String thisMethodName = thisMember.getName();
            String otherMethodName = otherMember.getName();
            if (thisMethodName.startsWith("is") && otherMethodName.startsWith("get")) {
                return 1;
            }
            if (thisMethodName.startsWith("get") && otherMethodName.startsWith("is")) {
                return -1;
            }
        }

        if (thisMember instanceof Method && otherMember instanceof Method) {
            String thisMethodName = thisMember.getName();
            String otherMethodName = otherMember.getName();
            if (!thisMethodName.equals(otherMethodName)) {
                String thisSetterName = BeanUtils.getterName(thisMethodName, null);
                String otherSetterName = BeanUtils.getterName(otherMethodName, null);
                if (this.fieldName.equals(thisSetterName) && !other.fieldName.equals(otherSetterName)) {
                    return 1;
                }
                if (this.fieldName.equals(otherSetterName) && !other.fieldName.equals(thisSetterName)) {
                    return -1;
                }
            }
        }

        if (thisFieldClass.isPrimitive() && !otherFieldClass.isPrimitive()) {
            return -1;
        }

        if (!thisFieldClass.isPrimitive() && otherFieldClass.isPrimitive()) {
            return 1;
        }

        if (thisFieldClass.getName().startsWith("java.") && !otherFieldClass.getName().startsWith("java.")) {
            return -1;
        }

        if (!thisFieldClass.getName().startsWith("java.") && otherFieldClass.getName().startsWith("java.")) {
            return 1;
        }

        if (this.method != null && other.method == null) {
            return -1;
        }

        if (this.method == null && other.method != null) {
            return 1;
        }

        return nameCompare;
    }

    public void writeEnum(JSONWriter jsonWriter, Enum e) {
        writeFieldName(jsonWriter);
        jsonWriter.writeEnum(e);
    }

    public void writeBinary(JSONWriter jsonWriter, byte[] value) {
        if (value == null) {
            if (!jsonWriter.isWriteNulls()) {
                return;
            }
            writeFieldName(jsonWriter);
            jsonWriter.writeArrayNull();
            return;
        }

        writeFieldName(jsonWriter);
        if ((features & WriteNonStringValueAsString.mask) != 0) {
            jsonWriter.writeString(value);
        } else if ("base64".equals(format)
                || (format == null && (jsonWriter.getFeatures(this.features) & WriteByteArrayAsBase64.mask) != 0)
        ) {
            jsonWriter.writeBase64(value);
        } else if ("hex".equals(format)) {
            jsonWriter.writeHex(value);
        } else if ("gzip,base64".equals(format) || "gzip".equals(format)) {
            GZIPOutputStream gzipOut = null;
            try {
                ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
                if (value.length < 512) {
                    gzipOut = new GZIPOutputStream(byteOut, value.length);
                } else {
                    gzipOut = new GZIPOutputStream(byteOut);
                }
                gzipOut.write(value);
                gzipOut.finish();
                value = byteOut.toByteArray();
            } catch (IOException ex) {
                throw new JSONException("write gzipBytes error", ex);
            } finally {
                IOUtils.close(gzipOut);
            }

            jsonWriter.writeBase64(value);
        } else {
            jsonWriter.writeBinary(value);
        }
    }

    public void writeInt16(JSONWriter jsonWriter, short[] value) {
        if (value == null && !jsonWriter.isWriteNulls()) {
            return;
        }

        writeFieldName(jsonWriter);
        jsonWriter.writeInt16(value);
    }

    public void writeInt32(JSONWriter jsonWriter, int value) {
        writeFieldName(jsonWriter);
        jsonWriter.writeInt32(value);
    }

    public void writeInt64(JSONWriter jsonWriter, long value) {
        writeFieldName(jsonWriter);
        if ((features & WriteNonStringValueAsString.mask) != 0) {
            jsonWriter.writeString(Long.toString(value));
        } else {
            jsonWriter.writeInt64(value);
        }
    }

    public void writeString(JSONWriter jsonWriter, String value) {
        writeFieldName(jsonWriter);

        if (value == null && (features & (NullAsDefaultValue.mask | WriteNullStringAsEmpty.mask)) != 0) {
            jsonWriter.writeString("");
            return;
        }

        if (trim && value != null) {
            value = value.trim();
        }

        if (symbol && jsonWriter.jsonb) {
            jsonWriter.writeSymbol(value);
        } else {
            if (raw) {
                jsonWriter.writeRaw(value);
            } else {
                jsonWriter.writeString(value);
            }
        }
    }

    public void writeString(JSONWriter jsonWriter, char[] value) {
        if (value == null && !jsonWriter.isWriteNulls()) {
            return;
        }

        writeFieldName(jsonWriter);
        if (value == null) {
            jsonWriter.writeStringNull();
            return;
        }

        jsonWriter.writeString(value, 0, value.length);
    }

    public void writeFloat(JSONWriter jsonWriter, float value) {
        writeFieldName(jsonWriter);
        if (decimalFormat != null) {
            jsonWriter.writeFloat(value, decimalFormat);
        } else {
            jsonWriter.writeFloat(value);
        }
    }

    public void writeDouble(JSONWriter jsonWriter, double value) {
        writeFieldName(jsonWriter);
        if (decimalFormat != null) {
            jsonWriter.writeDouble(value, decimalFormat);
        } else {
            jsonWriter.writeDouble(value);
        }
    }

    public void writeBool(JSONWriter jsonWriter, boolean value) {
        throw new UnsupportedOperationException();
    }

    public void writeBool(JSONWriter jsonWriter, boolean[] value) {
        if (value == null && !jsonWriter.isWriteNulls()) {
            return;
        }

        writeFieldName(jsonWriter);
        jsonWriter.writeBool(value);
    }

    public void writeFloat(JSONWriter jsonWriter, float[] value) {
        if (value == null && !jsonWriter.isWriteNulls()) {
            return;
        }

        writeFieldName(jsonWriter);
        jsonWriter.writeFloat(value);
    }

    public void writeDouble(JSONWriter jsonWriter, double[] value) {
        if (value == null && !jsonWriter.isWriteNulls()) {
            return;
        }

        writeFieldName(jsonWriter);
        jsonWriter.writeDouble(value);
    }

    public void writeDouble(JSONWriter jsonWriter, Double value) {
        if (value == null) {
            long features = jsonWriter.getFeatures(this.features);
            if ((features & JSONWriter.Feature.WriteNulls.mask) != 0
                    && (features & JSONWriter.Feature.NotWriteDefaultValue.mask) == 0
            ) {
                writeFieldName(jsonWriter);
                jsonWriter.writeNumberNull();
            }
            return;
        }

        writeFieldName(jsonWriter);
        jsonWriter.writeDouble(value.doubleValue());
    }

    public void writeDate(JSONWriter jsonWriter, boolean writeFieldName, Date value) {
        if (value == null) {
            if (writeFieldName) {
                writeFieldName(jsonWriter);
            }

            jsonWriter.writeNull();
            return;
        }

        writeDate(jsonWriter, writeFieldName, value.getTime());
    }

    public void writeDate(JSONWriter jsonWriter, long millis) {
        writeDate(jsonWriter, true, millis);
    }

    public void writeDate(JSONWriter jsonWriter, boolean writeFieldName, long millis) {
        if (jsonWriter.jsonb) {
            jsonWriter.writeMillis(millis);
            return;
        }

        final int SECONDS_PER_DAY = 60 * 60 * 24;

        JSONWriter.Context ctx = jsonWriter.context;
        if (isDateFormatMillis() || ctx.isDateFormatMillis()) {
            if (writeFieldName) {
                writeFieldName(jsonWriter);
            }
            jsonWriter.writeInt64(millis);
            return;
        }

        ZoneId zoneId = ctx.getZoneId();

        String dateFormat = ctx.getDateFormat();
        if (dateFormat == null) {
            Instant instant = Instant.ofEpochMilli(millis);
            long epochSecond = instant.epochSecond;
            long localSecond = epochSecond + zoneId.getOffsetTotalSeconds(instant);
            long localEpochDay = IOUtils.floorDiv(localSecond, (long) SECONDS_PER_DAY);
            int secsOfDay = (int) IOUtils.floorMod(localSecond, (long) SECONDS_PER_DAY);
            int year, month, dayOfMonth;
            {
                final int DAYS_PER_CYCLE = 146097;
                final long DAYS_0000_TO_1970 = (DAYS_PER_CYCLE * 5L) - (30L * 365L + 7L);

                long zeroDay = localEpochDay + DAYS_0000_TO_1970;
                // find the march-based year
                zeroDay -= 60;  // adjust to 0000-03-01 so leap day is at end of four year cycle
                long adjust = 0;
                if (zeroDay < 0) {
                    // adjust negative years to positive for calculation
                    long adjustCycles = (zeroDay + 1) / DAYS_PER_CYCLE - 1;
                    adjust = adjustCycles * 400;
                    zeroDay += -adjustCycles * DAYS_PER_CYCLE;
                }
                long yearEst = (400 * zeroDay + 591) / DAYS_PER_CYCLE;
                long doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
                if (doyEst < 0) {
                    // fix estimate
                    yearEst--;
                    doyEst = zeroDay - (365 * yearEst + yearEst / 4 - yearEst / 100 + yearEst / 400);
                }
                yearEst += adjust;  // reset any negative year
                int marchDoy0 = (int) doyEst;

                // convert march-based values back to january-based
                int marchMonth0 = (marchDoy0 * 5 + 2) / 153;
                month = (marchMonth0 + 2) % 12 + 1;
                dayOfMonth = marchDoy0 - (marchMonth0 * 306 + 5) / 10 + 1;
                yearEst += marchMonth0 / 10;

                // check year now we are certain it is correct
                year = LocalDateTime.checkYear(yearEst);
            }

            int hour, minute, second;
            {
                final int MINUTES_PER_HOUR = 60;
                final int SECONDS_PER_MINUTE = 60;
                final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;

                long secondOfDay = secsOfDay;
                LocalDateTime.checkSecondOfDay(secondOfDay);
                int hours = (int) (secondOfDay / SECONDS_PER_HOUR);
                secondOfDay -= hours * SECONDS_PER_HOUR;
                int minutes = (int) (secondOfDay / SECONDS_PER_MINUTE);
                secondOfDay -= minutes * SECONDS_PER_MINUTE;

                hour = hours;
                minute = minutes;
                second = (int) secondOfDay;
            }

            if (writeFieldName) {
                writeFieldName(jsonWriter);
            }
            jsonWriter.writeDateTime19(year, month, dayOfMonth, hour, minute, second);
        } else {
            ZonedDateTime zdt = ZonedDateTime
                    .ofInstant(
                            Instant.ofEpochMilli(millis), zoneId);

            if (isDateFormatISO8601() || ctx.isDateFormatISO8601()) {
                int year = zdt.dateTime.date.year;
                int month = zdt.dateTime.date.monthValue;
                int dayOfMonth = zdt.dateTime.date.dayOfMonth;
                int hour = zdt.dateTime.time.hour;
                int minute = zdt.dateTime.time.minute;
                int second = zdt.dateTime.time.second;
                int milliSeconds = zdt.dateTime.time.nano / 1000_000;
                int offsetSeconds = zdt.offsetSeconds;
                jsonWriter.writeDateTimeISO8601(year, month, dayOfMonth, hour, minute, second, milliSeconds, offsetSeconds, true);
                return;
            }

            String str = ctx.getDateFormatter().format(zdt);

            if (writeFieldName) {
                writeFieldName(jsonWriter);
            }
            jsonWriter.writeString(str);
        }
    }

    public ObjectWriter getItemWriter(JSONWriter writer, Type itemType) {
        return writer
                .getObjectWriter(itemType, null);
    }

    public abstract void writeValue(JSONWriter jsonWriter, T object);

    public abstract boolean write(JSONWriter jsonWriter, T o);

    public ObjectWriter getObjectWriter(JSONWriter jsonWriter, Class valueClass) {
        if (valueClass == Float[].class) {
            if (decimalFormat != null) {
                return new ObjectWriterArrayFinal(Float.class, decimalFormat);
            } else {
                return ObjectWriterArrayFinal.FLOAT_ARRAY;
            }
        }

        if (valueClass == Double[].class) {
            if (decimalFormat != null) {
                return new ObjectWriterArrayFinal(Double.class, decimalFormat);
            } else {
                return ObjectWriterArrayFinal.DOUBLE_ARRAY;
            }
        }

        if (valueClass == BigDecimal[].class) {
            if (decimalFormat != null) {
                return new ObjectWriterArrayFinal(BigDecimal.class, decimalFormat);
            } else {
                return ObjectWriterArrayFinal.DECIMAL_ARRAY;
            }
        }

        return jsonWriter.getObjectWriter(valueClass);
    }

    public void writeList(JSONWriter jsonWriter, boolean writeFieldName, List list) {
        throw new UnsupportedOperationException();
    }

    public void writeListStr(JSONWriter jsonWriter, boolean writeFieldName, List<String> list) {
        throw new UnsupportedOperationException();
    }

    static ObjectWriter getObjectWriter(
            Type fieldType,
            Class fieldClass,
            String format,
            Locale locale,
            Class valueClass
    ) {
        if (Map.class.isAssignableFrom(valueClass)) {
            if (fieldClass.isAssignableFrom(valueClass)) {
                return ObjectWriterImplMap.of(fieldType, valueClass);
            } else {
                return ObjectWriterImplMap.of(valueClass);
            }
        } else {
            if (Calendar.class.isAssignableFrom(valueClass)) {
                if (format == null || format.isEmpty()) {
                    return ObjectWriterImplCalendar.INSTANCE;
                }
                return new ObjectWriterImplCalendar(format, locale);
            }

            if (BigDecimal.class == valueClass) {
                if (format == null || format.isEmpty()) {
                    return ObjectWriterImplBigDecimal.INSTANCE;
                } else {
                    return new ObjectWriterImplBigDecimal(new DecimalFormat(format), null);
                }
            }

            if (BigDecimal[].class == valueClass) {
                if (format == null || format.isEmpty()) {
                    return new ObjectWriterArrayFinal(BigDecimal.class, null);
                } else {
                    return new ObjectWriterArrayFinal(BigDecimal.class, new DecimalFormat(format));
                }
            }

            String className = valueClass.getName();
            switch (className) {
                case "java.sql.Time":
                    return JdbcSupport.TimeWriter.of(format);
                case "java.sql.Date":
                    return new ObjectWriterImplDate(format, locale);
                case "java.sql.Timestamp":
                    return new JdbcSupport.TimestampWriter(format);
                default:
                    break;
            }
        }

        return null;
    }

    public void writeListValue(JSONWriter jsonWriter, List list) {
        throw new UnsupportedOperationException();
    }

    public void writeListJSONB(JSONWriter jsonWriter, List list) {
        throw new UnsupportedOperationException();
    }

    public void writeListValueJSONB(JSONWriter jsonWriter, List list) {
        throw new UnsupportedOperationException();
    }

    protected final boolean writeFloatNull(JSONWriter jsonWriter) {
        long features = jsonWriter.getFeatures(this.features);
        if ((features & (JSONWriter.Feature.WriteNulls.mask | JSONWriter.Feature.NullAsDefaultValue.mask | JSONWriter.Feature.WriteNullNumberAsZero.mask)) == 0) {
            return false;
        }
        writeFieldName(jsonWriter);
        if ((features & NullAsDefaultValue.mask) != 0) {
            jsonWriter.writeFloat(0.0F);
        } else if ((features & WriteNullNumberAsZero.mask) != 0) {
            jsonWriter.writeInt32(0);
        } else {
            jsonWriter.writeNull();
        }
        return true;
    }

    protected final boolean writeIntNull(JSONWriter jsonWriter) {
        long features = this.features | jsonWriter.getFeatures();
        if ((features & (JSONWriter.Feature.WriteNulls.mask | JSONWriter.Feature.NullAsDefaultValue.mask | JSONWriter.Feature.WriteNullNumberAsZero.mask)) == 0) {
            return false;
        }
        writeFieldName(jsonWriter);

        if ((features & (JSONWriter.Feature.NullAsDefaultValue.mask | JSONWriter.Feature.WriteNullNumberAsZero.mask)) != 0) {
            jsonWriter.writeInt32(0);
        } else {
            jsonWriter.writeNull();
        }
        return true;
    }
}
