package ru.yandex.webmaster3.storage.util.yt;

import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Preconditions;
import org.jetbrains.annotations.Nullable;

/**
 * @author akhazhoyan 02/2018
 */
public final class YtColumn<T> {
    private static final ObjectMapper OM = new ObjectMapper();

    private final Type<T> columnType;
    private final String name;

    YtColumn(String name, Type<T> ytColumnType) {
        this.name = Preconditions.checkNotNull(name);
        this.columnType = Preconditions.checkNotNull(ytColumnType);
    }

    public String getName() {
        return name;
    }

    public void set(TableWriter tw, T value) {
        columnType.setter.set(tw, name, value);
    }

    @JsonValue
    ObjectNode toJson() {
        return OM.createObjectNode()
            .put("name", name)
            .put("type", columnType.name);
    }

    @Nullable
    public static YtColumn<?> fromString(String name, String type) {
        var columnType = Type.fromString(type);
        if (columnType == null) {
            return null;
        }

        return new YtColumn<>(name, columnType);
    }

    public static class Type<T> {
        public static final Type<Object> ANY = new Type<>(TableWriter::columnObject, "any");
        public static final Type<Boolean> BOOLEAN = new Type<>(TableWriter::columnObject, "boolean");
        public static final Type<Double> DOUBLE = new Type<>(TableWriter::columnObject, "double");
        public static final Type<Long> INT_64 = new Type<>(TableWriter::column, "int64");
        public static final Type<Integer> INT_32 = new Type<>(TableWriter::column, "int32");
        public static final Type<Short> INT_16 = new Type<>(TableWriter::column, "int16");
        public static final Type<Long> UINT_64 = new Type<>(TableWriter::column, "uint64");
        public static final Type<Long> UINT_32 = new Type<>(TableWriter::column, "uint32");
        public static final Type<Integer> UINT_16 = new Type<>(TableWriter::column, "uint16");
        public static final Type<String> STRING = new Type<>(TableWriter::column, "string");
        public static final Type<String> UTF_8 = new Type<>(TableWriter::column, "utf8");

        private static final Type<?>[] ALL_TYPES = {
                ANY, DOUBLE, BOOLEAN, INT_64, INT_32, INT_16, UINT_64, UINT_32, UINT_16, STRING, UTF_8
        };

        public static <T> Type<T> any() {
            return new Type<>(TableWriter::columnObject, "any");
        }

        private interface Setter<T> {
            void set(TableWriter tw, String columnType, T value);
        }

        private final Setter<T> setter;
        private final String name;

        private Type(Setter<T> setter, String name) {
            this.setter = setter;
            this.name = name;
        }

        public String getName() {
            return name;
        }

        @Nullable
        public static Type<?> fromString(String name) {
            for (var type : ALL_TYPES) {
                if (type.getName().equals(name)) {
                    return type;
                }
            }

            return null;
        }
    }
}
