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

import java.io.IOException;

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.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import ru.yandex.qe.dispenser.api.DtoBuilder;
import ru.yandex.qe.dispenser.api.util.JsonDeserializerBase;
import ru.yandex.qe.dispenser.api.util.JsonSerializerBase;
import ru.yandex.qe.dispenser.api.util.SerializationUtils;
import ru.yandex.qe.dispenser.api.v1.DiMetaValueSet;

@JsonSerialize(using = DiEntityUsage.Serializer.class)
@JsonDeserialize(using = DiEntityUsage.Deserializer.class)
public final class DiEntityUsage {
    @NotNull
    private final DiEntityReference entity;
    private final int usagesCount;
    @NotNull
    private final DiMetaValueSet metaValues;

    private DiEntityUsage(@NotNull final Builder builder) {
        entity = DiEntityReference.copy(builder.entity);
        usagesCount = builder.usagesCount;
        metaValues = builder.metaValues;
    }

    @NotNull
    public static Builder builder(@NotNull final DiEntityReference entity) {
        return new Builder(entity);
    }

    @NotNull
    public static DiEntityUsage singleOf(@NotNull final DiEntityReference entity) {
        return of(entity, 1);
    }

    @NotNull
    public static DiEntityUsage of(@NotNull final DiEntityReference entity, final int usagesCount) {
        return builder(entity).usages(usagesCount).build();
    }

    @NotNull
    public DiEntityReference getEntity() {
        return entity;
    }

    public int getUsagesCount() {
        return usagesCount;
    }

    @NotNull
    public DiMetaValueSet getMetaValues() {
        return metaValues;
    }

    @Override
    public boolean equals(@Nullable final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        return usagesCount == ((DiEntityUsage) o).usagesCount && entity.equals(((DiEntityUsage) o).entity);
    }

    @Override
    public int hashCode() {
        return 31 * entity.hashCode() + usagesCount;
    }

    public static final class Builder implements DtoBuilder<DiEntityUsage> {
        @NotNull
        private final DiEntityReference entity;
        private int usagesCount = 1;
        @NotNull
        private DiMetaValueSet metaValues = DiMetaValueSet.EMPTY;

        private Builder(@NotNull final DiEntityReference entity) {
            this.entity = entity;
        }

        @NotNull
        public Builder usages(final int count) {
            usagesCount = count;
            return this;
        }

        @NotNull
        private <T> Builder metaValues(@NotNull final DiMetaValueSet metaValues) {
            this.metaValues = metaValues;
            return this;
        }

        @NotNull
        @Override
        public DiEntityUsage build() {
            return new DiEntityUsage(this);
        }
    }

    static final class Serializer extends JsonSerializerBase<DiEntityUsage> {
        @Override
        public void serialize(@NotNull final DiEntityUsage usage,
                              @NotNull final JsonGenerator jg,
                              @NotNull final SerializerProvider sp) throws IOException {
            jg.writeStartObject();
            jg.writeObjectField("entity", usage.getEntity());
            jg.writeNumberField("usagesCount", usage.getUsagesCount());
            if (usage.metaValues != DiMetaValueSet.EMPTY) {
                jg.writeObjectField("meta", usage.metaValues);
            }
            jg.writeEndObject();
        }
    }

    static final class Deserializer extends JsonDeserializerBase<DiEntityUsage> {
        @NotNull
        @Override
        public DiEntityUsage deserialize(@NotNull final JsonParser jp,
                                         @NotNull final DeserializationContext dc) throws IOException {
            final JsonNode json = toJson(jp);
            final Builder builder = builder(SerializationUtils.convertValue(json.get("entity"), DiEntityReference.class));
            builder.usages(json.get("usagesCount").asInt());
            if (json.has("meta")) {
                builder.metaValues(SerializationUtils.convertValue(json.get("meta"), DiMetaValueSet.class));
            }
            return builder.build();
        }
    }
}
