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

import com.atlassian.bitbucket.mesh.rpc.v1.RpcCommit;
import com.atlassian.bitbucket.mesh.util.BuilderSupport;
import com.atlassian.bitbucket.mesh.util.Person;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.ByteString;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.List;

import static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Objects.requireNonNull;

public class Commit {

    private final Person author;
    private final long authorTimestamp;
    private final Person committer;
    private final long committerTimestamp;
    private final String id;
    private final String message;
    private final List<String> parents;

    private Commit(@Nonnull Builder builder) {
        author = requireNonNull(builder.author, "author");
        authorTimestamp = builder.authorTimestamp;
        committer = firstNonNull(builder.committer, author);
        committerTimestamp = builder.committer == null ? builder.authorTimestamp : builder.committerTimestamp;
        id = requireNonNull(builder.id, "id");
        message = builder.message;
        parents = builder.parents.build();
    }

    @Nonnull
    public Person getAuthor() {
        return author;
    }

    public long getAuthorTimestamp() {
        return authorTimestamp;
    }

    @Nonnull
    public Person getCommitter() {
        return committer;
    }

    public long getCommitterTimestamp() {
        return committerTimestamp;
    }

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

    @Nullable
    public String getMessage() {
        return message;
    }

    @Nonnull
    public Collection<String> getParents() {
        return parents;
    }

    @Nonnull
    public RpcCommit toCommit() {
        RpcCommit.Builder builder = RpcCommit.newBuilder()
                .addAllParents(parents)
                .setAuthor(author.toPerson())
                .setAuthorEpoch(authorTimestamp)
                .setCommitter(committer.toPerson())
                .setCommitterEpoch(committerTimestamp)
                .setId(id);

        //If an _empty_ message is set explicitly, it needs to be sent to the client. "optional" tracks
        //_explicit presence_, so "" set explicitly still triggers hasMessage() to be true where leaving
        //the default "" doesn't, resulting in different state on the client after marshaling
        if (message != null) {
            builder.setMessage(ByteString.copyFromUtf8(message));
        }

        return builder.build();
    }

    public static class Builder extends BuilderSupport {

        private final ImmutableList.Builder<String> parents;

        private Person author;
        private long authorTimestamp;
        private Person committer;
        private long committerTimestamp;
        private String id;
        private String message;

        public Builder() {
            parents = ImmutableList.builder();
        }

        public Builder(@Nonnull RpcCommit commit) {
            this();

            author = new Person(requireNonNull(commit, "commit").getAuthor());
            authorTimestamp = commit.getAuthorEpoch();
            committer = new Person(commit.getCommitter());
            committerTimestamp = commit.getCommitterEpoch();
            id = commit.getId();
            message = commit.hasMessage() ? commit.getMessage().toStringUtf8() : null;

            parents.addAll(commit.getParentsList());
        }

        @Nonnull
        public Builder author(@Nonnull Person value) {
            author = requireNonNull(value, "author");

            return this;
        }

        @Nonnull
        public Builder authorTimestamp(long value) {
            authorTimestamp = value;

            return this;
        }

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

        @Nonnull
        public Builder committer(@Nonnull Person value) {
            committer = requireNonNull(value, "committer");

            return this;
        }

        @Nonnull
        public Builder committerTimestamp(long value) {
            committerTimestamp = value;

            return this;
        }

        @Nonnull
        public Builder id(@Nonnull String value) {
            id = requireNonNull(value, "id");

            return this;
        }

        @Nonnull
        public Builder message(@Nonnull String value) {
            message = requireNonNull(value, "message");

            return this;
        }

        @Nonnull
        public Builder parent(@Nonnull String value) {
            addIf(NOT_BLANK, parents, value);

            return this;
        }

        @Nonnull
        public Builder parents(@Nonnull String value, @Nonnull String... values) {
            addIf(NOT_BLANK, parents, value, values);

            return this;
        }
    }
}
