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

package com.hypixel.hytale.metrics;

import javax.annotation.CheckForNull;
import org.bson.json.JsonMode;
import org.bson.json.StrictJsonWriter;
import java.time.temporal.TemporalAccessor;
import java.time.LocalDateTime;
import com.hypixel.hytale.logger.backend.HytaleFileHandler;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.LinkOption;
import java.nio.file.Paths;
import java.io.BufferedWriter;
import org.bson.BsonWriter;
import java.io.Writer;
import org.bson.json.JsonWriter;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.io.IOException;
import java.nio.file.Path;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.codec.schema.SchemaContext;
import java.util.Iterator;
import org.bson.BsonDocument;
import com.hypixel.hytale.codec.ExtraInfo;
import org.bson.BsonValue;
import javax.annotation.Nonnull;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import java.util.Map;
import java.util.concurrent.locks.StampedLock;
import javax.annotation.Nullable;
import java.util.function.Function;
import org.bson.codecs.BsonDocumentCodec;
import org.bson.codecs.EncoderContext;
import org.bson.json.JsonWriterSettings;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.codec.Codec;

public class MetricsRegistry<T> implements Codec<T>
{
    private static final HytaleLogger LOGGER;
    public static final JsonWriterSettings JSON_SETTINGS;
    private static final EncoderContext ENCODER_CONTEXT;
    private static final BsonDocumentCodec BSON_DOCUMENT_CODEC;
    @Nullable
    private final Function<T, MetricProvider> appendFunc;
    private final StampedLock lock;
    private final Map<String, Metric<T, ?>> map;
    
    public MetricsRegistry() {
        this.lock = new StampedLock();
        this.map = new Object2ObjectLinkedOpenHashMap<String, Metric<T, ?>>();
        this.appendFunc = null;
    }
    
    public MetricsRegistry(final Function<T, MetricProvider> appendFunc) {
        this.lock = new StampedLock();
        this.map = new Object2ObjectLinkedOpenHashMap<String, Metric<T, ?>>();
        this.appendFunc = appendFunc;
    }
    
    public MetricsRegistry<T> register(final String id, final MetricsRegistry<Void> metricsRegistry) {
        final long stamp = this.lock.writeLock();
        try {
            if (this.map.putIfAbsent(id, new Metric<T, Object>(null, (Codec<Object>)metricsRegistry)) != null) {
                throw new IllegalArgumentException("Metric already registered: " + id);
            }
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
        return this;
    }
    
    public <R> MetricsRegistry<T> register(final String id, final Function<T, R> func, final Codec<R> codec) {
        final long stamp = this.lock.writeLock();
        try {
            if (this.map.putIfAbsent(id, new Metric<T, Object>((Function<T, Object>)func, (Codec<Object>)codec)) != null) {
                throw new IllegalArgumentException("Metric already registered: " + id);
            }
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
        return this;
    }
    
    public <R extends MetricProvider> MetricsRegistry<T> register(final String id, @Nonnull final Function<T, R> func) {
        return this.register(id, func.andThen(r -> (r == null) ? null : r.toMetricResults()), MetricResults.CODEC);
    }
    
    @Deprecated
    public <R> MetricsRegistry<T> register(final String id, final Function<T, R> func, final Function<R, MetricsRegistry<R>> codecFunc) {
        final long stamp = this.lock.writeLock();
        try {
            if (this.map.putIfAbsent(id, new Metric<T, Object>((Function<T, Object>)func, (Function<Object, MetricsRegistry<Object>>)codecFunc)) != null) {
                throw new IllegalArgumentException("Metric already registered: " + id);
            }
        }
        finally {
            this.lock.unlockWrite(stamp);
        }
        return this;
    }
    
    @Override
    public T decode(final BsonValue bsonValue, final ExtraInfo extraInfo) {
        throw new UnsupportedOperationException("Not implemented");
    }
    
    @Override
    public BsonValue encode(final T t, final ExtraInfo extraInfo) {
        final BsonDocument document = new BsonDocument();
        final long stamp = this.lock.readLock();
        try {
            for (final Map.Entry<String, Metric<T, ?>> entry : this.map.entrySet()) {
                final String key = entry.getKey();
                final BsonValue value = entry.getValue().encode((Object)t, extraInfo);
                if (value != null) {
                    document.put(key, value);
                }
            }
        }
        finally {
            this.lock.unlockRead(stamp);
        }
        if (this.appendFunc != null) {
            final MetricProvider metricProvider = this.appendFunc.apply(t);
            if (metricProvider != null) {
                final MetricResults metricResults = metricProvider.toMetricResults();
                if (metricResults != null) {
                    document.putAll(metricResults.getBson());
                }
            }
        }
        return document;
    }
    
    @Nonnull
    @Override
    public Schema toSchema(@Nonnull final SchemaContext context) {
        throw new UnsupportedOperationException("Not implemented");
    }
    
    @Nonnull
    public MetricResults toMetricResults(final T t) {
        return new MetricResults(this.dumpToBson(t).asDocument());
    }
    
    public BsonValue dumpToBson(final T t) {
        final ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
        final BsonDocument bson = this.encode(t, extraInfo).asDocument();
        extraInfo.getValidationResults().logOrThrowValidatorExceptions(MetricsRegistry.LOGGER);
        return bson;
    }
    
    @Nonnull
    public Path dumpToJson(final T t) throws IOException {
        final Path path = createDumpPath(".dump.json");
        this.dumpToJson(path, t);
        return path;
    }
    
    public void dumpToJson(@Nonnull final Path path, final T t) throws IOException {
        final BsonValue bson = this.dumpToBson(t);
        try (final BufferedWriter writer = Files.newBufferedWriter(path, new OpenOption[0])) {
            MetricsRegistry.BSON_DOCUMENT_CODEC.encode((BsonWriter)new JsonWriter(writer, MetricsRegistry.JSON_SETTINGS), bson.asDocument(), MetricsRegistry.ENCODER_CONTEXT);
        }
    }
    
    @Nonnull
    public static Path createDumpPath(@Nullable final String ext) throws IOException {
        return createDumpPath((String)null, ext);
    }
    
    @Nonnull
    public static Path createDumpPath(@Nonnull final Path dir, @Nullable final String ext) {
        return createDatePath(dir, null, ext);
    }
    
    @Nonnull
    public static Path createDumpPath(@Nullable final String prefix, @Nullable final String ext) throws IOException {
        final Path path = Paths.get("dumps", new String[0]);
        if (!Files.exists(path, new LinkOption[0])) {
            Files.createDirectories(path, (FileAttribute<?>[])new FileAttribute[0]);
        }
        return createDatePath(path, prefix, ext);
    }
    
    @Nonnull
    public static Path createDatePath(@Nonnull final Path dir, @Nullable final String prefix, @Nullable final String suffix) {
        String name = HytaleFileHandler.LOG_FILE_DATE_FORMAT.format(LocalDateTime.now());
        if (prefix != null) {
            name = prefix + name;
        }
        Path file = (suffix != null) ? dir.resolve(name + suffix) : dir.resolve(name);
        int i = 0;
        while (Files.exists(file, new LinkOption[0])) {
            if (suffix != null) {
                file = dir.resolve(name + "_" + i++ + suffix);
            }
            else {
                file = dir.resolve(name + "_" + i++);
            }
        }
        return file;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        JSON_SETTINGS = JsonWriterSettings.builder().outputMode(JsonMode.STRICT).indent(false).newLineCharacters("\n").int64Converter((value, writer) -> writer.writeNumber(Long.toString(value))).build();
        ENCODER_CONTEXT = EncoderContext.builder().build();
        BSON_DOCUMENT_CODEC = new BsonDocumentCodec();
    }
    
    private static class Metric<T, R>
    {
        @Nullable
        private final Function<T, R> func;
        @CheckForNull
        private final Codec<R> codec;
        @CheckForNull
        private final Function<R, MetricsRegistry<R>> codecFunc;
        
        public Metric(@Nullable final Function<T, R> func, @Nullable final Codec<R> codec) {
            this.func = func;
            this.codec = codec;
            this.codecFunc = null;
        }
        
        public Metric(@Nullable final Function<T, R> func, @Nullable final Function<R, MetricsRegistry<R>> codecFunc) {
            this.func = func;
            this.codec = null;
            this.codecFunc = codecFunc;
        }
        
        @Nullable
        public BsonValue encode(final T t, final ExtraInfo extraInfo) {
            if (this.func != null) {
                final R value = this.func.apply(t);
                return (value == null) ? null : this.getCodec(value).encode(value, extraInfo);
            }
            assert this.codec != null;
            return this.codec.encode(null, extraInfo);
        }
        
        @Nonnull
        public Codec<R> getCodec(final R value) {
            if (this.codec != null) {
                return this.codec;
            }
            assert this.codecFunc != null;
            return this.codecFunc.apply(value);
        }
    }
}
