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

import com.atlassian.bitbucket.mesh.git.GitChangeType;
import com.atlassian.bitbucket.mesh.git.GitNodeType;
import com.atlassian.bitbucket.mesh.git.commit.Commit;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcChange;

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

import static java.util.Objects.requireNonNull;

/**
 * Describes a single change made by a {@link Commit commit}.
 */
public class Change extends MinimalChange {

    private final String contentId;
    private final String fromContentId;
    private final Boolean executable;
    private final GitNodeType nodeType;
    private final Boolean srcExecutable;

    private Change(Builder builder) {
        super(builder);

        contentId = builder.contentId;
        executable = builder.executable;
        fromContentId = builder.fromContentId;
        nodeType = requireNonNull(builder.nodeType, "nodeType");
        srcExecutable = builder.srcExecutable;
    }

    /**
     * An identifier for the content which was changed.
     * <p>
     * For {@link GitChangeType#DELETE deletions}, the returned content ID is for the content prior to deletion, since
     * there is no identifier available afterward. For all other {@link GitChangeType types}, the identifier is for the
     * content after the change.
     *
     * @return a content identifier
     */
    @Nonnull
    public String getContentId() {
        return contentId;
    }

    /**
     * The executable permission for the file <i>after</i> the {@link #getType() change}.
     * <p>
     * If the file was {@link GitChangeType#DELETE deleted} this value will be {@code null}, since a deleted file is
     * neither executable nor non-executable. Otherwise, it will be {@code true} or {@code false} according to the
     * permissions on the file.
     *
     * @return {@code true} or {@code false} for any change other than a {@link GitChangeType#DELETE deletion};
     *         {@code null} for deleted files
     */
    @Nullable
    public Boolean getExecutable() {
        return executable;
    }

    /**
     * An identifier from which the content was changed.
     *
     * @return a content identifier
     */
    @Nonnull
    public String getFromContentId() {
        return fromContentId;
    }

    /**
     * The type of the file node, which is most likely to be a {@link GitNodeType#FILE file} or possibly a
     * {@link GitNodeType#SUBMODULE submodule}. A {@link GitNodeType#DIRECTORY directory} type
     * is never returned as they are transient and can be recreated from the {@link #getPath() path}.
     *
     * @return the node type
     */
    @Nonnull
    public GitNodeType getNodeType() {
        return nodeType;
    }

    /**
     * The executable permission for the file <i>before</i> the {@link #getType() change}.
     * <p>
     * If the file was {@link GitChangeType#ADD created} this value will be {@code null}, since the file was neither
     * executable nor non-executable before it was created. Otherwise, it will be {@code true} or {@code false}
     * according to the permissions on the file.
     *
     * @return {@code true} or {@code false} for any change other than a {@link GitChangeType#ADD creation};
     *         {@code null} for newly-created files
     */
    @Nullable
    public Boolean getSrcExecutable() {
        return srcExecutable;
    }

    @Override
    public String toString() {
        return "SimpleChange{path='" + getPath() + "', srcPath='" + getSrcPath() + "', type=" + getType() + "}";
    }

    @Override
    protected RpcChange.Builder prepareRpcBuilder() {
        RpcChange.Builder builder = super.prepareRpcBuilder()
                .setContentId(contentId)
                .setFromContentId(fromContentId)
                .setNodeType(nodeType.toRpc());
        if (executable != null) {
            builder.setExecutable(executable);
        }
        if (srcExecutable != null) {
            builder.setSrcExecutable(srcExecutable);
        }

        return builder;
    }

    public static final class Builder extends MinimalChange.AbstractBuilder<Builder> {

        private String contentId;
        private Boolean executable;
        private String fromContentId;
        private GitNodeType nodeType;
        private Boolean srcExecutable;

        public Builder() {
            nodeType = GitNodeType.FILE;
        }

        public Builder(@Nonnull Change change) {
            super(change);

            contentId = change.getContentId();
            executable = change.getExecutable();
            fromContentId = change.getFromContentId();
            nodeType = change.getNodeType();
            srcExecutable = change.getSrcExecutable();
        }

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

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

            return this;
        }

        @Nonnull
        public Builder executable(@Nullable Boolean value) {
            executable = value;

            return this;
        }

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

            return this;
        }

        @Nonnull
        public Builder nodeType(@Nonnull GitNodeType value) {
            nodeType = requireNonNull(value, "nodeType");

            return this;
        }

        @Nonnull
        public Builder srcExecutable(@Nullable Boolean value) {
            srcExecutable = value;

            return this;
        }

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