package com.atlassian.bitbucket.repository;

import com.google.common.base.MoreObjects;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;

public class SimpleMinimalRef implements MinimalRef {

    private final String displayId;
    private final String id;
    private final RefType type;

    protected SimpleMinimalRef(@Nonnull AbstractMinimalRefBuilder<?, ?> builder, @Nonnull RefType type) {
        this.type = checkNotNull(type, "type");
        //Unlike SimpleMinimalCommit, where displayId can be inferred from id, for refs
        //id can be inferred from displayId
        displayId = checkNotNull(checkNotNull(builder, "builder").displayId, "displayId");
        id = MoreObjects.firstNonNull(builder.id, builder.displayId);
    }

    @Nonnull
    public String getDisplayId() {
        return displayId;
    }

    @Nonnull
    public String getId() {
        return id;
    }

    @Nonnull
    @Override
    public RefType getType() {
        return type;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o != null && getClass().equals(o.getClass())) {
            SimpleMinimalRef that = (SimpleMinimalRef) o;

            return id.equals(that.id);
        }

        return false;
    }

    @Override
    public int hashCode() {
        return getId().hashCode();
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).addValue(fieldsToString()).toString();
    }

    /**
     * Override this to add additional sub-class specific information to {@link #toString()}.
     *
     * @return String that will be used in this object's {@link #toString()}. The format is expected to be
     * {@code "field1=value1, field2=value2"}, the caller takes care of surrounding it with a class name and braces.
     */
    @Nonnull
    protected String fieldsToString() {
        return "id=" + getId() + ", displayId=" + getDisplayId();
    }

    public static class Builder extends AbstractMinimalRefBuilder<Builder, MinimalRef> {

        private RefType type;

        public Builder() {
        }

        public Builder(@Nonnull MinimalRef ref) {
            super(ref);

            type = ref.getType();
        }

        @Nonnull
        public SimpleMinimalRef build() {
            return new SimpleMinimalRef(this, type);
        }

        @Nonnull
        public Builder type(@Nonnull RefType value) {
            type = checkNotNull(value, "type");

            return self();
        }

        @Nonnull
        @Override
        protected Builder self() {
            return this;
        }
    }

    protected abstract static class AbstractMinimalRefBuilder<B extends AbstractMinimalRefBuilder<B, R>, R extends MinimalRef> {

        private String displayId;
        private String id;

        protected AbstractMinimalRefBuilder() {
        }

        protected AbstractMinimalRefBuilder(@Nonnull R ref) {
            displayId = checkNotNull(ref, "ref").getDisplayId();
            id = ref.getId();
        }

        /**
         * Builds a ref from the assembled components.
         *
         * @return the built ref
         */
        @Nonnull
        public abstract R build();

        @Nonnull
        public B displayId(@Nullable String value) {
            displayId = value;

            return self();
        }

        @Nonnull
        public B id(@Nullable String value) {
            id = value;

            return self();
        }

        @Nonnull
        protected abstract B self();
    }
}
