package ru.yandex.qe.dispenser.api.v1.field;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.jetbrains.annotations.NotNull;

import ru.yandex.qe.dispenser.api.util.JsonDeserializerBase;
import ru.yandex.qe.dispenser.api.util.JsonSerializerBase;

/**
 * Base class for a DTO containing some {@link DiField}s mapped to their values.
 * Serializable and deserializable to/from json.
 */
@ParametersAreNonnullByDefault
@JsonSerialize(using = DiFieldHolder.Serializer.class)
public abstract class DiFieldHolder {
    @Nonnull
    protected final Map<DiField<?>, ?> values;

    protected DiFieldHolder(final Map<DiField<?>, ?> values) {
        this.values = values;
    }

    @SuppressWarnings("unchecked")
    protected <T> T getFieldValue(final DiField<T> field) {
        return (T) values.get(field);
    }

    @Nonnull
    public Map<DiField<?>, ?> getValues() {
        return values;
    }

    public static abstract class Deserializer<T extends DiFieldHolder> extends JsonDeserializerBase<T> {

        @Nonnull
        protected abstract Collection<DiField<?>> getAllFields();

        @Nonnull
        protected abstract T create(Map<DiField<?>, ?> values);

        @NotNull
        @Override
        public T deserialize(@NotNull final JsonParser jp, @NotNull final DeserializationContext dc) throws IOException {
            final JsonNode json = toJson(jp);

            final Map<DiField<?>, Object> values = new HashMap<>();


            for (final DiField<?> field : getAllFields()) {
                final JsonNode jsonNode = json.get(field.getKey());
                if (jsonNode != null) {
                    values.put(field, jsonNode.isNull() ? null : field.fromJson(jsonNode));
                }
            }

            return create(values);
        }
    }

    public static final class Serializer extends JsonSerializerBase<DiFieldHolder> {
        @Override
        public void serialize(final @NotNull DiFieldHolder value, final @NotNull JsonGenerator jg,
                              final @NotNull SerializerProvider sp) throws IOException {

            jg.writeStartObject();
            final Map<DiField<?>, ?> values = value.getValues();
            for (final DiField<?> field : values.keySet()) {
                final Object fieldValue = values.get(field);
                jg.writeObjectField(field.getKey(), fieldValue);
            }

            jg.writeEndObject();
        }
    }
}
