package cfg;

import java.util.Arrays;
import java.util.Objects;

import cfg.CFG.Nonterminal;
import cfg.CFG.Symbol;

public class EarleyState implements Comparable<EarleyState> {
    public Nonterminal lhs;
    public Symbol[] rhs;
    public int rhsIdx;
    public int startIdx;

    public EarleyState leftParent;
    public EarleyState rightParent;

    public EarleyState(Nonterminal lhs, Symbol[] rhs, int rhsIdx, int startIdx) {
        this.lhs = lhs;
        this.rhs = rhs;
        this.rhsIdx = rhsIdx;
        this.startIdx = startIdx;
    }

    public EarleyState(Nonterminal lhs, Symbol[] rhs, int startIdx) {
        this(lhs, rhs, 0, startIdx);
    }

    @Override
    public int hashCode() {
        return Objects.hash(lhs, Arrays.hashCode(rhs), rhsIdx, startIdx);
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof EarleyState))
            return false;

        return compareTo((EarleyState) obj) == 0;
    }

    public int compareTo(EarleyState x) {
        // Order rules by state first
        if (startIdx + rhsIdx != x.startIdx + x.rhsIdx) {
            return (startIdx + rhsIdx) - (x.startIdx + x.rhsIdx);
        }

        // Completed rules come first
        if (isDone() != x.isDone()) {
            return (rhs.length - rhsIdx) - (x.rhs.length - x.rhsIdx);
        }

        // \shrug, give up and arbitrary-compare at this point
        return toString().compareTo(x.toString());
    }

    public EarleyState advance() {
        return new EarleyState(this.lhs, this.rhs, this.rhsIdx + 1, this.startIdx);
    }

    public boolean isDone() {
        return this.rhsIdx == this.rhs.length;
    }

    public Symbol nextSymbol() {
        return this.rhs[this.rhsIdx];
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder(lhs.toString());
        res.append(" ->");
        for (int i = 0; i < rhs.length; i++) {
            if (i == rhsIdx) {
                res.append(" .");
            }
            res.append(" ").append(rhs[i].toString());
        }
        res.append(" ").append(startIdx);
        return res.toString();
    }
}