// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.codec.codecs;

import com.hypixel.hytale.codec.exception.CodecException;
import java.util.Objects;
import com.hypixel.hytale.codec.schema.config.StringSchema;
import javax.annotation.Nullable;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.codec.schema.SchemaContext;
import java.io.IOException;
import com.hypixel.hytale.codec.util.RawJsonReader;
import com.hypixel.hytale.codec.ExtraInfo;
import org.bson.BsonValue;
import java.util.EnumMap;
import javax.annotation.Nonnull;
import com.hypixel.hytale.codec.Codec;

public class EnumCodec<T extends Enum<T>> implements Codec<T>
{
    @Nonnull
    private final Class<T> clazz;
    @Nonnull
    private final T[] enumConstants;
    @Nonnull
    private final String[] enumKeys;
    private final EnumStyle enumStyle;
    @Nonnull
    private final EnumMap<T, String> documentation;
    
    public EnumCodec(@Nonnull final Class<T> clazz) {
        this(clazz, EnumStyle.CAMEL_CASE);
    }
    
    public EnumCodec(@Nonnull final Class<T> clazz, final EnumStyle enumStyle) {
        this.clazz = clazz;
        this.enumConstants = clazz.getEnumConstants();
        this.enumStyle = enumStyle;
        this.documentation = new EnumMap<T, String>(clazz);
        final EnumStyle currentStyle = EnumStyle.detect(this.enumConstants);
        this.enumKeys = new String[this.enumConstants.length];
        for (int i = 0; i < this.enumConstants.length; ++i) {
            final T e = this.enumConstants[i];
            this.enumKeys[i] = currentStyle.formatCamelCase(e.name());
        }
    }
    
    @Nonnull
    public EnumCodec<T> documentKey(final T key, final String doc) {
        this.documentation.put(key, doc);
        return this;
    }
    
    @Nonnull
    @Override
    public T decode(@Nonnull final BsonValue bsonValue, final ExtraInfo extraInfo) {
        final String decode = EnumCodec.STRING.decode(bsonValue, extraInfo);
        final T value = this.getEnum(decode);
        if (value == null) {
            throw new IllegalArgumentException("Failed to apply function to '" + decode + "' decoded from '" + String.valueOf(bsonValue) + "'!");
        }
        return value;
    }
    
    @Nonnull
    @Override
    public BsonValue encode(@Nonnull final T r, final ExtraInfo extraInfo) {
        return switch (this.enumStyle.ordinal()) {
            default -> throw new MatchException(null, null);
            case 1 -> EnumCodec.STRING.encode(this.enumKeys[r.ordinal()], extraInfo);
            case 0 -> EnumCodec.STRING.encode(r.name(), extraInfo);
        };
    }
    
    @Nonnull
    @Override
    public T decodeJson(@Nonnull final RawJsonReader reader, final ExtraInfo extraInfo) throws IOException {
        final String decode = EnumCodec.STRING.decodeJson(reader, extraInfo);
        final T value = this.getEnum(decode);
        if (value == null) {
            throw new IllegalArgumentException("Failed to apply function to '" + decode + "'!");
        }
        return value;
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context) {
        return this.toSchema(context, (T)null);
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context, @Nullable final T def) {
        final StringSchema enumSchema = new StringSchema();
        enumSchema.setTitle(this.clazz.getSimpleName());
        enumSchema.setEnum(this.enumKeys);
        enumSchema.getHytale().setType("Enum");
        final String[] documentation = new String[this.enumKeys.length];
        for (int i = 0; i < this.enumKeys.length; ++i) {
            final String desc = this.documentation.get(this.enumConstants[i]);
            documentation[i] = Objects.requireNonNullElse(desc, "");
        }
        enumSchema.setMarkdownEnumDescriptions(documentation);
        if (def != null) {
            enumSchema.setDefault(this.enumKeys[def.ordinal()]);
        }
        return enumSchema;
    }
    
    @Nullable
    private T getEnum(final String value) {
        return this.enumStyle.match(this.enumConstants, this.enumKeys, value);
    }
    
    public enum EnumStyle
    {
        @Deprecated
        LEGACY, 
        CAMEL_CASE;
        
        @Nullable
        public <T extends Enum<T>> T match(@Nonnull final T[] enumConstants, @Nonnull final String[] enumKeys, final String value) {
            return this.match(enumConstants, enumKeys, value, false);
        }
        
        @Nullable
        public <T extends Enum<T>> T match(@Nonnull final T[] enumConstants, @Nonnull final String[] enumKeys, final String value, final boolean allowInvalid) {
            switch (this.ordinal()) {
                case 0: {
                    for (int i = 0; i < enumConstants.length; ++i) {
                        final T e = enumConstants[i];
                        if (e.name().equalsIgnoreCase(value)) {
                            return e;
                        }
                    }
                }
                case 1: {
                    for (int i = 0; i < enumKeys.length; ++i) {
                        final String key = enumKeys[i];
                        if (key.equals(value)) {
                            return enumConstants[i];
                        }
                    }
                    break;
                }
            }
            if (allowInvalid) {
                return null;
            }
            throw new CodecException("Failed to find enum value for " + value);
        }
        
        @Nonnull
        public String formatCamelCase(@Nonnull final String name) {
            return switch (this.ordinal()) {
                default -> throw new MatchException(null, null);
                case 0 -> {
                    final StringBuilder nameParts = new StringBuilder();
                    for (final String part : name.split("_")) {
                        nameParts.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1).toLowerCase());
                    }
                    yield nameParts.toString();
                }
                case 1 -> name;
            };
        }
        
        @Nonnull
        public static <T extends Enum<T>> EnumStyle detect(@Nonnull final T[] enumConstants) {
            for (final T e : enumConstants) {
                final String name = e.name();
                if (name.length() <= 1 || !Character.isUpperCase(name.charAt(1))) {
                    return EnumStyle.CAMEL_CASE;
                }
                for (int i = 1; i < name.length(); ++i) {
                    final char c = name.charAt(i);
                    if (Character.isLetter(c) && Character.isLowerCase(c)) {
                        return EnumStyle.CAMEL_CASE;
                    }
                }
            }
            return EnumStyle.LEGACY;
        }
    }
}
