package de.renew.util;


/**
 * An ID, as implemented by this class, is an identifier that
 * consists of a sequence of integers.
 * <p>
 * IDs are immutable after creation. All alterations to an existing ID object create a new ID object with the alteration
 * applied.
 */
public class ID implements java.io.Serializable {
    /**
     * A sequence of integers, used to generate the hash.
     */
    private final int[] _seq;
    /**
     * A hash integer computed from {@link ID}
     */
    private int _hash;

    /**
     * Private constructor for ID. Ingests a sequence of integers and from this, computes a hash representing
     * the ID.
     * @param seq Integer Array that represents the whole ID as a sequence of ints
     */
    private ID(int[] seq) {
        this._seq = seq;


        // Precompute _hash code.
        _hash = 637;
        for (int j : seq) {
            _hash = _hash * 59 + j;
        }
    }

    /**
     * Factory method, generating a new ID object with a single integer value
     * @param num The single integer value representing the ID
     * @return a new ID object with the generated ID
     */
    public static ID create(int num) {
        return new ID(new int[] { num });
    }

    /**
     * Factory method, generating a new ID object with two integer values
     * @param num1 the first int value of the new ID
     * @param num2 the second int value of the new ID
     * @return the produced ID object
     */
    public static ID create(int num1, int num2) {
        return new ID(new int[] { num1, num2 });
    }

    /**
     * Factory method, generating a new ID object with three integer values
     * @param num1 first int value of the ID sequence
     * @param num2 second int value of the ID sequence
     * @param num3 third int value of the ID sequence
     * @return the produced ID object
     */
    public static ID create(int num1, int num2, int num3) {
        return new ID(new int[] { num1, num2, num3 });
    }

    /**
     * Factory method, generating a new ID object with the submitted int array
     * @param seq int array that will become the integer sequence representing the ID
     * @return the produced ID object
     */
    public static ID create(int[] seq) {
        return new ID(seq.clone());
    }

    /**
     * For this immutable ID object, produce a new ID object with the submitted int appended to the sequence of
     * this ID object.
     * @param num the integer to append
     * @return the new ID object with the int appended to the sequence
     */
    public ID appending(int num) {
        int[] newSeq = new int[_seq.length + 1];
        System.arraycopy(_seq, 0, newSeq, 0, _seq.length);
        newSeq[_seq.length] = num;
        return new ID(newSeq);
    }

    /**
     * For this immutable ID object, produce a new ID object with the whole ID sequence of the submitted ID object
     * appended to the sequence of this ID object.
     * @param id another ID object whose identifier sequence to append to this ID object
     * @return a new ID object with the sequence appended
     */
    public ID appending(ID id) {
        return appending(id._seq);
    }

    /**
     * For this immutable ID object, produce a new ID object with the integer sequence appended to
     * the sequence of this ID object.
     * @param app the int sequence to append to the sequence of this object
     * @return A new ID object with the appended sequence
     */
    public ID appending(int[] app) {
        int[] newSeq = new int[_seq.length + app.length];
        System.arraycopy(_seq, 0, newSeq, 0, _seq.length);
        System.arraycopy(_seq, 0, newSeq, _seq.length, app.length);
        return new ID(newSeq);
    }

    /**
     * For this immutable ID object, produce a new ID object with the last integer of the sequence removed.
     * @return The new ID object with the last int of the sequence removed.
     */
    public ID clipped() {
        if (_seq.length == 0) {
            throw new RuntimeException("Cannot clip: ID already empty.");
        }
        int[] newSeq = new int[_seq.length - 1];
        System.arraycopy(_seq, 0, newSeq, 0, _seq.length - 1);
        return new ID(newSeq);
    }

    /**
     * Check for equality of another object of type <code>ID</code> with this ID object. Equality is defined as having
     * two sequences of equal length, with the i-th integer of both sequences matching between the objects.
     * @param obj the other ID with which to compare
     * @return True if the definition matches; false otherwise.
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof ID that) {
            if (that._seq.length == _seq.length) {
                for (int i = 0; i < _seq.length; i++) {
                    if (that._seq[i] != _seq[i]) {
                        return false;
                    }
                }
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Return the computed _hash representation of the integer sequence computed on creation.
     * @return the _hash code representation of the sequence.
     */
    @Override
    public int hashCode() {
        return _hash;
    }

    /**
     * Prints a dot-separated String representation of all the integers in the sequence of this ID object. <code>340291.0.9820.9230.19</code> is an example of such a String representation.
     * @return the formatted string representation.
     */
    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        for (int i = 0; i < _seq.length; i++) {
            if (i > 0) {
                buf.append(".");
            }
            buf.append(_seq[i]);
        }
        return buf.toString();
    }
}