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

import com.atlassian.bitbucket.mesh.io.NullTerminatedReader;
import com.atlassian.bitbucket.mesh.io.NullTerminatedWriter;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcBatchChange;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcFile;
import com.atlassian.bitbucket.mesh.util.BuilderSupport;
import com.google.protobuf.ByteString;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;

import static java.util.Objects.requireNonNull;

/**
 * Metadata about a file of a batch change command.
 */
public class BatchChange {

    private final BatchChangeType changeType;
    private final String contentId;
    private final String path;
    private final long size;

    private BatchChange(String path, BatchChangeType changeType, String contentId, long size) {
        this.changeType = changeType;
        this.contentId = contentId;
        this.path = path;
        this.size = size;
    }

    @Nullable
    public static BatchChange read(NullTerminatedReader reader) throws IOException {
        String path = reader.readSegment();
        if (path == null) {
            return null;
        }
        String changeTypeString = reader.readSegment();
        if (changeTypeString == null) {
            return null;
        }
        BatchChangeType changeType = BatchChangeType.valueOf(changeTypeString);
        String contentId = reader.readSegment();
        if (contentId == null) {
            return null;
        }
        String sizeString = reader.readSegment();
        if (sizeString == null) {
            return null;
        }
        long size = Long.parseLong(sizeString);
        return new BatchChange(path, changeType, contentId, size);
    }

    public static void write(BatchChange change, long size, NullTerminatedWriter writer) throws IOException {
        write(change.getPath(), change.getChangeType(), change.getContentId(), size, writer);
    }

    public static void write(String path, BatchChangeType changeType, String contentId, long size,
                             NullTerminatedWriter writer) throws IOException {
        writer.writeSegment(path);
        writer.writeSegment(changeType.name());
        writer.writeSegment(contentId);
        writer.writeSegment(String.valueOf(size));
    }

    /**
     * @return how the file has changed
     */
    @Nonnull
    public BatchChangeType getChangeType() {
        return changeType;
    }

    /**
     * For {@link BatchChangeType#ADD added} or {@link BatchChangeType#MODIFY modified} files, returns a content ID for
     * the new content. For {@link BatchChangeType#DELETE deleted} files, the ID is for the content before deletion.
     *
     * @return an ID that identifies the contents of the file
     */
    @Nonnull
    public String getContentId() {
        return contentId;
    }

    /**
     * @return the file path
     */
    @Nonnull
    public String getPath() {
        return path;
    }

    /**
     * @return the file size in bytes, or {@code 0} for {@link BatchChangeType#DELETE deleted} files, or {@code -1}
     * for unspecified
     */
    public long getSize() {
        return size;
    }

    @Nonnull
    public RpcBatchChange toRpc() {
        return RpcBatchChange.newBuilder()
                .setChangeType(changeType.toRpc())
                .setFile(RpcFile.newBuilder()
                        .setContentId(contentId)
                        .setPath(ByteString.copyFromUtf8(path))
                        .setSize(size))
                .build();
    }

    public static class Builder extends BuilderSupport {

        private BatchChangeType changeType;
        private String contentId;
        private String path;
        private long size;

        @Nonnull
        public BatchChange build() {
            return new BatchChange(path, changeType, contentId, size);
        }

        @Nonnull
        public Builder changeType(@Nonnull BatchChangeType value) {
            changeType = requireNonNull(value, "changeType");
            return this;
        }

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

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

        @Nonnull
        public Builder size(long value) {
            size = value;
            return this;
        }
    }
}
