package com.atlassian.bitbucket.mesh.git.ref;

import com.atlassian.bitbucket.mesh.rpc.v1.RpcProposedRefUpdate;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcRefUpdate;
import com.atlassian.bitbucket.mesh.util.BuilderSupport;
import com.atlassian.bitbucket.mesh.util.ShaUtils;
import com.google.protobuf.ByteString;

import javax.annotation.Nonnull;
import java.util.Objects;

import static java.util.Objects.requireNonNull;

public class RefUpdate {

    private final String newHash;
    private final String oldHash;
    private final String refId;

    private RefUpdate(Builder builder) {
        newHash = requireNonNull(builder.newHash, "newHash");
        oldHash = requireNonNull(builder.oldHash, "oldHash");
        refId = requireNonNull(builder.refId, "refId");
    }

    @Nonnull
    public static RefUpdate ofAdd(@Nonnull String refId, @Nonnull String newHash) {
        return new Builder(refId)
                .newHash(newHash)
                .oldHash(ShaUtils.NULL_SHA1)
                .build();
    }

    @Nonnull
    public static RefUpdate ofDelete(@Nonnull String refId, @Nonnull String oldHash) {
        return new Builder(refId)
                .newHash(ShaUtils.NULL_SHA1)
                .oldHash(oldHash)
                .build();
    }

    @Nonnull
    public static RefUpdate of(@Nonnull String refId, @Nonnull String oldHash, @Nonnull String newHash) {
        if (ShaUtils.hashesMatch(ShaUtils.NULL_SHA1, oldHash)) {
            return ofAdd(refId, newHash);
        }
        if (ShaUtils.hashesMatch(ShaUtils.NULL_SHA1, newHash)) {
            return ofDelete(refId, oldHash);
        }
        return new Builder(refId)
                .newHash(newHash)
                .oldHash(oldHash)
                .build();
    }

    @Nonnull
    public static RefUpdate of(@Nonnull RpcRefUpdate refUpdate) {
        return new Builder(requireNonNull(refUpdate, "refUpdate").getRefId().toStringUtf8())
                .newHash(refUpdate.getNewHash())
                .oldHash(refUpdate.getOldHash())
                .build();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        RefUpdate that = (RefUpdate) o;
        return newHash.equals(that.newHash) && oldHash.equals(that.oldHash) && refId.equals(that.refId);
    }

    @Nonnull
    public String getOldHash() {
        return oldHash;
    }

    @Nonnull
    public String getNewHash() {
        return newHash;
    }

    @Nonnull
    public String getRefId() {
        return refId;
    }

    @Override
    public int hashCode() {
        return Objects.hash(newHash, oldHash, refId);
    }

    public boolean isDark() {
        return Ref.isDark(refId);
    }

    @Nonnull
    public RpcProposedRefUpdate toProposedRefUpdate() {
        return RpcProposedRefUpdate.newBuilder().setRefId(ByteString.copyFromUtf8(refId))
                .setOldHash(oldHash)
                .setNewHash(newHash)
                .build();
    }

    @Nonnull
    public RpcRefUpdate toRefUpdate() {
        return RpcRefUpdate.newBuilder()
                .setRefId(ByteString.copyFromUtf8(refId))
                .setOldHash(oldHash)
                .setNewHash(newHash)
                .build();
    }

    @Override
    public String toString() {
        return refId + ": " + oldHash + " -> " + newHash;
    }

    public static class Builder extends BuilderSupport {

        private final String refId;

        private String newHash;
        private String oldHash;

        public Builder(@Nonnull String refId) {
            this.refId = requireNonNull(refId, "refId");
        }

        @Nonnull
        public RefUpdate build() {
            return new RefUpdate(this);
        }

        @Nonnull
        public Builder newHash(@Nonnull String value) {
            newHash = requireNonBlank(value, "newHash");
            return this;
        }

        @Nonnull
        public Builder oldHash(@Nonnull String value) {
            oldHash = requireNonBlank(value, "oldHash");
            return this;
        }
    }
}
