package com.knecon.fforesight.service.layoutparser.processor.model.graph;

import static java.lang.String.format;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.IntStream;

import lombok.EqualsAndHashCode;
import lombok.Setter;

@Setter
@EqualsAndHashCode
@SuppressWarnings("PMD.AvoidFieldNameMatchingMethodName")
public class Boundary implements Comparable<Boundary> {

    private int start;
    private int end;


    public Boundary(int start, int end) {

        if (start > end) {
            throw new IllegalArgumentException(format("start: %d > end: %d", start, end));
        }
        this.start = start;
        this.end = end;
    }


    public int length() {

        return end - start;
    }


    public int start() {

        return start;
    }


    public int end() {

        return end;
    }


    public boolean contains(Boundary boundary) {

        return start <= boundary.start() && boundary.end() <= end;
    }


    public boolean containedBy(Boundary boundary) {

        return boundary.contains(this);
    }


    public boolean contains(int start, int end) {

        if (start > end) {
            throw new IllegalArgumentException(format("start: %d > end: %d", start, end));
        }
        return this.start <= start && end <= this.end;
    }


    public boolean containedBy(int start, int end) {

        if (start > end) {
            throw new IllegalArgumentException(format("start: %d > end: %d", start, end));
        }
        return start <= this.start && this.end <= end;
    }


    public boolean contains(int index) {

        return start <= index && index < end;
    }


    public boolean intersects(Boundary boundary) {

        return boundary.start() < this.end && this.start < boundary.end();
    }


    public List<Boundary> split(List<Integer> splitIndices) {

        if (splitIndices.stream().anyMatch(idx -> !this.contains(idx))) {
            throw new IndexOutOfBoundsException(format("%s splitting indices are out of range for %s", splitIndices.stream().filter(idx -> !this.contains(idx)).toList(), this));
        }
        List<Boundary> splitBoundaries = new LinkedList<>();
        int previousIndex = start;
        for (int splitIndex : splitIndices) {

            // skip split if it would produce a boundary of length 0
            if (splitIndex == previousIndex) {
                continue;
            }
            splitBoundaries.add(new Boundary(previousIndex, splitIndex));
            previousIndex = splitIndex;
        }
        splitBoundaries.add(new Boundary(previousIndex, end));
        return splitBoundaries;
    }

    public IntStream intStream() {

        return IntStream.range(start, end);
    }

    public static Boundary merge(Collection<Boundary> boundaries) {

        int minStart = boundaries.stream().mapToInt(Boundary::start).min().orElseThrow(IllegalArgumentException::new);
        int maxEnd = boundaries.stream().mapToInt(Boundary::end).max().orElseThrow(IllegalArgumentException::new);
        return new Boundary(minStart, maxEnd);
    }


    @Override
    public String toString() {

        return format("Boundary [%d|%d)", start, end);
    }


    @Override
    public int compareTo(Boundary boundary) {

        if (end < boundary.end() && start < boundary.start()) {
            return -1;
        }
        if (start > boundary.start() && end > boundary.end()) {
            return 1;
        }

        return 0;
    }

}
