package com.r35157.nenjim.service.journal.valuetypes;

import com.r35157.libs.valuetypes.basic.SemanticVersion;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

/** An immutable normalized union of version expressions. */
public final class VersionExpressionSet {
    private final List<VersionExpression> expressions;

    public VersionExpressionSet(Collection<VersionExpression> expressions) {
        Objects.requireNonNull(expressions, "expressions");
        ArrayList<VersionExpression> sorted = new ArrayList<>(expressions.size());
        for (VersionExpression expression : expressions) {
            sorted.add(Objects.requireNonNull(expression, "expression"));
        }
        sorted.sort(Comparator.comparing(VersionExpression::lowerInclusive));

        ArrayList<VersionExpression> normalized = new ArrayList<>();
        for (VersionExpression expression : sorted) {
            if (normalized.isEmpty()) {
                normalized.add(expression);
                continue;
            }
            VersionExpression current = normalized.getLast();
            if (current.upperExclusive().compareTo(expression.lowerInclusive()) >= 0) {
                normalized.set(normalized.size() - 1, current.merge(expression));
            } else {
                normalized.add(expression);
            }
        }
        this.expressions = List.copyOf(normalized);
    }

    public static VersionExpressionSet empty() {
        return new VersionExpressionSet(List.of());
    }

    public static VersionExpressionSet of(VersionExpression... expressions) {
        return new VersionExpressionSet(List.of(expressions));
    }

    public List<VersionExpression> expressions() {
        return expressions;
    }

    public boolean isEmpty() {
        return expressions.isEmpty();
    }

    public boolean contains(SemanticVersion version) {
        return expressions.stream().anyMatch(expression -> expression.contains(version));
    }

    public VersionExpressionSet union(VersionExpressionSet other) {
        Objects.requireNonNull(other, "other");
        ArrayList<VersionExpression> combined = new ArrayList<>(expressions);
        combined.addAll(other.expressions);
        return new VersionExpressionSet(combined);
    }

    public VersionExpressionSet subtract(VersionExpressionSet exclusions) {
        Objects.requireNonNull(exclusions, "exclusions");
        ArrayList<VersionExpression> remaining = new ArrayList<>();

        for (VersionExpression included : expressions) {
            VersionExpression.Boundary cursor = included.lowerInclusive();
            for (VersionExpression excluded : exclusions.expressions) {
                if (excluded.upperExclusive().compareTo(cursor) <= 0) continue;
                if (excluded.lowerInclusive().compareTo(included.upperExclusive()) >= 0) break;

                if (cursor.compareTo(excluded.lowerInclusive()) < 0) {
                    VersionExpression.Boundary fragmentUpper = minimum(
                            excluded.lowerInclusive(), included.upperExclusive());
                    if (cursor.compareTo(fragmentUpper) < 0) {
                        remaining.add(new VersionExpression(cursor, fragmentUpper));
                    }
                }
                if (excluded.upperExclusive().compareTo(cursor) > 0) {
                    cursor = maximum(cursor, excluded.upperExclusive());
                }
                if (cursor.compareTo(included.upperExclusive()) >= 0) break;
            }
            if (cursor.compareTo(included.upperExclusive()) < 0) {
                remaining.add(new VersionExpression(cursor, included.upperExclusive()));
            }
        }
        return new VersionExpressionSet(remaining);
    }

    public boolean containsAll(VersionExpressionSet candidate) {
        Objects.requireNonNull(candidate, "candidate");
        return candidate.subtract(this).isEmpty();
    }

    @Override
    public boolean equals(Object other) {
        return this == other || other instanceof VersionExpressionSet set
                && expressions.equals(set.expressions);
    }

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

    @Override
    public String toString() {
        return expressions.toString();
    }

    private static VersionExpression.Boundary minimum(
            VersionExpression.Boundary left,
            VersionExpression.Boundary right) {
        return left.compareTo(right) <= 0 ? left : right;
    }

    private static VersionExpression.Boundary maximum(
            VersionExpression.Boundary left,
            VersionExpression.Boundary right) {
        return left.compareTo(right) >= 0 ? left : right;
    }
}
