package com.atlassian.stash.internal.pull;

import com.atlassian.bitbucket.pull.*;
import com.atlassian.stash.internal.activity.ActivityType;
import com.atlassian.bitbucket.validation.annotation.RequiredString;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.hibernate.Hibernate;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import javax.annotation.Nonnull;
import javax.persistence.*;
import java.util.Set;

/**
 * @since 1.3
 */
@DiscriminatorValue(ActivityType.PULL_REQUEST_RESCOPE_ACTIVITY_DISCRIMINATOR)
@Entity
@PrimaryKeyJoinColumn(name = "activity_id", referencedColumnName = "activity_id",
        foreignKey = @ForeignKey(name = "fk_sta_pr_rescope_act_id"))
@Table(name = InternalPullRequestRescopeActivity.TABLE)
public class InternalPullRequestRescopeActivity
        extends InternalPullRequestActivity
        implements PullRequestRescopeActivity {

    public static final String TABLE = "sta_pr_rescope_activity";

    @Transient
    private SimpleRescopeDetails added;

    @CollectionTable(name = "sta_pr_rescope_commit", joinColumns = @JoinColumn(name = "activity_id", nullable = false))
    @ElementCollection
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    private final Set<InternalPullRequestRescopeCommit> commits;

    @Column(name = "from_hash", nullable = false, length = 40, updatable = false)
    @RequiredString(size = 40)
    private final String fromHash;

    @Column(name = "prev_from_hash", nullable = false, length = 40, updatable = false)
    @RequiredString(size = 40)
    private final String previousFromHash;

    @Column(name = "prev_to_hash", nullable = false, length = 40, updatable = false)
    @RequiredString(size = 40)
    private final String previousToHash;

    @Transient
    private SimpleRescopeDetails removed;

    @Column(name = "to_hash", nullable = false, length = 40, updatable = false)
    @RequiredString(size = 40)
    private final String toHash;

    @Column(name = "commits_added")
    private final Integer totalAdded;

    @Column(name = "commits_removed")
    private final Integer totalRemoved;

    @SuppressWarnings("unused") //Required for Hibernate
    protected InternalPullRequestRescopeActivity() {
        added = removed = SimpleRescopeDetails.EMPTY;
        commits = Sets.newHashSet();
        fromHash = previousFromHash = previousToHash = toHash = null;
        totalAdded = totalRemoved = null;
    }

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

        commits = builder.commits.build();
        fromHash = builder.fromHash;
        previousFromHash = builder.previousFromHash;
        previousToHash = builder.previousToHash;
        toHash = builder.toHash;
        totalAdded = builder.totalAdded;
        totalRemoved = builder.totalRemoved;

        added = removed = SimpleRescopeDetails.EMPTY;
    }

    @Override
    public void accept(@Nonnull PullRequestActivityVisitor visitor) {
        visitor.visit(this);
    }

    @Nonnull
    @Override
    public RescopeDetails getAdded() {
        return added;
    }

    public Set<InternalPullRequestRescopeCommit> getCommits() {
        return commits;
    }

    @Nonnull
    @Override
    public String getFromHash() {
        return fromHash;
    }

    @Nonnull
    @Override
    public String getPreviousFromHash() {
        return previousFromHash;
    }

    @Nonnull
    @Override
    public String getPreviousToHash() {
        return previousToHash;
    }

    @Nonnull
    @Override
    public RescopeDetails getRemoved() {
        return removed;
    }

    @Nonnull
    @Override
    public String getToHash() {
        return toHash;
    }

    public int getTotalAdded() {
        return totalAdded == null ? 0 : totalAdded;
    }

    public int getTotalRemoved() {
        return totalRemoved == null ? 0 : totalRemoved;
    }

    @Override
    public void initialize() {
        super.initialize();

        Hibernate.initialize(getCommits()); //Initialize the commits collection; no need to initialize the entries
    }

    public boolean isPrecalculated() {
        return totalAdded != null;
    }

    public void setAdded(SimpleRescopeDetails value) {
        if (added != SimpleRescopeDetails.EMPTY) { //Instance equality intentional
            throw new IllegalStateException("You cannot set the added details after they've been initialized");
        }
        added = value;
    }

    public void setRemoved(SimpleRescopeDetails value) {
        if (removed != SimpleRescopeDetails.EMPTY) { //Instance equality intentional
            throw new IllegalStateException("You cannot set the removed details after they've been initialized");
        }
        removed = value;
    }

    public static class Builder extends AbstractPullRequestActivityBuilder<Builder, InternalPullRequestRescopeActivity> {

        private final ImmutableSet.Builder<InternalPullRequestRescopeCommit> commits = ImmutableSet.builder();

        private String fromHash;
        private String previousFromHash;
        private String previousToHash;
        private String toHash;
        private Integer totalAdded;
        private Integer totalRemoved;

        public Builder(@Nonnull InternalPullRequest pullRequest) {
            super(pullRequest);

            action(PullRequestAction.RESCOPED);
        }

        public Builder(@Nonnull InternalPullRequestRescopeActivity activity) {
            super(activity);

            //Do not copy over the commits and totals. The expectation is that they're about to be set. Copying
            //them just results in the sets having to dedupe their entries
            fromHash = activity.getFromHash();
            previousFromHash = activity.getPreviousFromHash();
            previousToHash = activity.getPreviousToHash();
            toHash = activity.getToHash();
        }

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

        @Nonnull
        public Builder commit(InternalPullRequestRescopeCommit value) {
            return commits(value);
        }

        @Nonnull
        public Builder commits(InternalPullRequestRescopeCommit value, InternalPullRequestRescopeCommit... values) {
            commits.add(value).add(values);

            return self();
        }

        @Nonnull
        public Builder commits(Iterable<InternalPullRequestRescopeCommit> value) {
            commits.addAll(value);

            return self();
        }

        @Nonnull
        public Builder fromHash(@Nonnull String value) {
            fromHash = checkNotBlank(value, "fromHash");

            return self();
        }

        @Nonnull
        public Builder previousFromHash(@Nonnull String value) {
            previousFromHash = checkNotBlank(value, "previousFromHash");

            return self();
        }

        @Nonnull
        public Builder previousToHash(String value) {
            previousToHash = checkNotBlank(value, "previousToHash");

            return self();
        }

        @Nonnull
        public Builder toHash(String value) {
            toHash = checkNotBlank(value, "toHash");

            return self();
        }

        @Nonnull
        public Builder totalAdded(int value) {
            totalAdded = value;

            return self();
        }

        @Nonnull
        public Builder totalRemoved(int value) {
            totalRemoved = value;

            return self();
        }

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