// SPDX-FileCopyrightText: 2020 Roberto Posenato <roberto.posenato@univr.it>
//
// SPDX-License-Identifier: LGPL-3.0-or-later
package it.univr.di.cstnu.graph;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.univr.di.cstnu.algorithms.STN;
import it.univr.di.cstnu.util.LogNormalDistributionParameter;
import it.univr.di.labeledvalue.*;
import it.univr.di.labeledvalue.ALabelAlphabet.ALetter;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serial;

/**
 * LabeledNode class.
 *
 * @author posenato
 * @version $Rev$
 */
@SuppressWarnings("UnusedReturnValue")
public class LabeledNode extends AbstractNode {

	/**
	 * Possible status of a node during execution of some visiting algorithms.
	 *
	 * @author posenato
	 */
	public enum Status {
		/**
		 *
		 */
		LABELED,
		/**
		 *
		 */
		SCANNED,
		/**
		 *
		 */
		UNREACHED
	}

	//	@SuppressWarnings("hiding")
	//	static final Logger LOG = Logger.getLogger(LabeledNode.class.getName());

	/**
	 * Labeled value class used in the class.
	 */
	public static final Class<? extends LabeledIntMap> labeledValueMapImpl =
		LabeledIntMapSupplier.DEFAULT_LABELEDINTMAP_CLASS;

	/**
	 *
	 */
	@Serial
	private static final long serialVersionUID = 4L;
	/**
	 * Labeled potential values. This map can also represent Upper-case labeled potentials. Used in HP20 and derived
	 * algorithms.
	 */
	private final LabeledALabelIntTreeMap labeledPotential;
	/**
	 * Labeled upper potential values. This map can also represent Upper-case labeled potentials. Used in HP20 and
	 * derived algorithms.
	 */
	private final LabeledALabelIntTreeMap labeledUpperPotential;
	/**
	 * Possible proposition observed. Used in CSTNU/CSTN.
	 */
	char propositionObserved;
	/**
	 * Labeled potential count. This map counts how many values have been set for each label during a computation of any
	 * algorithm working with potential. Usually, in APSP or SSSP algorithms, when a value has been updated more than
	 * #nodes time, then there is a negative cycle.
	 */
	Object2IntMap<Label> labeledPotentialCount;
	/**
	 * Flag for contingent node. Used in STNU/CSTNU.
	 */
	boolean isContingent;
	/**
	 * Flag for parameter node. Used in PCSTNU.
	 */
	boolean isParameter;
	/**
	 * ALabel associated to this node. This field has the scope to speed up the DC checking since it represents the name
	 * of a contingent time point as ALabel instead of calculating it every time. Used in CSTNU.
	 */
	private ALabel aLabel;
	/**
	 * Label associated to this node. Used in CSTNU/CSTN.
	 */
	private Label label;
	/**
	 * Potential value. Used in STN.
	 */
	private int potential;
	/**
	 * Position Coordinates. It must be double even if it is not necessary for Jung library compatibility.
	 */
	private double x;
	/**
	 * Position Coordinates. It must be double even if it is not necessary for Jung library compatibility.
	 */
	private double y;
	/**
	 * Predecessor of parent node. It is used for delimiting the subtree to disassemble. A node X_i is the predecessor
	 * of node X_j, i.e., X_i=p(X_j), if the distance or potential d(X_j) has been updated to d(x_i) + delta_{ij}
	 */
	private LabeledNode predecessor;

	/**
	 * Node in the double-link list used in subtreeDisassembly.
	 */
	private LabeledNode before;

	/**
	 * Node in the double-link list used in subtreeDisassembly.
	 */
	private LabeledNode after;

	/**
	 * Status used by algorithm SSSP_BFCT.
	 */
	private Status status;

	/**
	 * LogNormalDistributionParameter used to represent the probability function of the duration of a contingent link. Since a probabilistic STN is approximated
	 * by a STNU, we prefer to maintain the representation of the network using the STNUEdges and to represent the location and std.dev of the probability
	 * distribution as a record in the contingent node.
	 */
	private LogNormalDistributionParameter logNormalDistributionParameter;

	/**
	 * Constructor for cloning.
	 *
	 * @param n the node to copy.
	 */
	public LabeledNode(final LabeledNode n) {// , Class<? extends LabeledIntMap> labeledIntMapImplementation
		super(n);
		setLabel(n.label);
		setObservable(n.propositionObserved);
		x = n.x;
		y = n.y;
//		setALabel(n.aLabel);
		isContingent = n.isContingent;
		potential = n.potential;
		isParameter = n.isParameter;
		labeledPotential = new LabeledALabelIntTreeMap(n.getULCaseLabeledPotential(), labeledValueMapImpl);
		labeledUpperPotential = new LabeledALabelIntTreeMap(n.getULCaseLabeledPotential(), labeledValueMapImpl);
		labeledPotentialCount = new Object2IntOpenHashMap<>();
		labeledPotentialCount.defaultReturnValue(Constants.INT_NULL);
		logNormalDistributionParameter = n.logNormalDistributionParameter;
	}

	/**
	 * Standard constructor for an observation node
	 *
	 * @param n           name of the node.
	 * @param proposition proposition observed by this node.
	 */
	public LabeledNode(final String n, final char proposition) {// , Class<C> labeledIntMapImplementation
		this(n);
		propositionObserved = (Literal.check(proposition)) ? proposition : Constants.UNKNOWN;
		potential = Constants.INT_NULL;
	}

	/**
	 * Constructor for LabeledNode.
	 *
	 * @param string a {@link java.lang.String} object.
	 */
	public LabeledNode(final String string) {// , Class<? extends LabeledIntMap> labeledIntMapImplementation
		super(string);
		label = Label.emptyLabel;
		x = y = 0;
		propositionObserved = Constants.UNKNOWN;
		potential = Constants.INT_NULL;
		aLabel = null;
		isContingent = false;
		isParameter = false;
		labeledPotential = new LabeledALabelIntTreeMap(labeledValueMapImpl);
//		labeledPotential.put(ALabel.emptyLabel, new LabeledIntMapSupplier<>(labeledValueMapImpl).get());
		labeledUpperPotential = new LabeledALabelIntTreeMap(labeledValueMapImpl);
//		labeledUpperPotential.put(ALabel.emptyLabel, new LabeledIntMapSupplier<>(labeledValueMapImpl).get());
		labeledPotentialCount = new Object2IntOpenHashMap<>();
		labeledPotentialCount.defaultReturnValue(Constants.INT_NULL);
		logNormalDistributionParameter = null;
	}

	/**
	 * Clears all fields but name.
	 */
	@Override
	public void clear() {
		super.clear();
		setLabel(Label.emptyLabel);
		setObservable(Constants.UNKNOWN);
		x = 0;
		y = 0;
//		setALabel(null);
		isContingent = false;
		logNormalDistributionParameter = null;
		clearPotential();
	}

	/**
	 * Clears all fields about potential values.
	 */
	public void clearPotential() {
		potential = Constants.INT_NULL;
		labeledPotential.clear();
//		labeledPotential.put(ALabel.emptyLabel, new LabeledIntMapSupplier<>(labeledValueMapImpl).get());
		labeledUpperPotential.clear();
//		labeledUpperPotential.put(ALabel.emptyLabel, new LabeledIntMapSupplier<>(labeledValueMapImpl).get());
		labeledPotentialCount.clear();
	}

	/**
	 * @return the alabel. It could be null  if the setter was not used before. It is not possible to determine the ALabel without knowing the ALabelAlphabet.
	 */
	@Nullable
	public ALabel getALabel() {
		return aLabel;
	}

	/**
	 * Sets the ALabel of the node. The contingent status is updated as side effect: contingent = inputALabel != null.<br>
	 * It is responsibility of programmer to maintain the correspondence between name and alabel.
	 * <p>
	 *     <b>Warning</b><br>
	 *      Since ALabelAlphabet can represents 64 distinct ALetters, setting the ALabel of a contingent node only when an algorithm must combine different contingent
	 *      names in one ALabel.
	 * </p>
	 * @param inputAlabel the alabel to set
	 */
	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "For efficiency reason, it includes an external mutable object.")
	public void setALabel(ALabel inputAlabel) {
		aLabel = inputAlabel;
		isContingent = aLabel != null;
	}

	/**
	 * @return the logNormalDistributionParameter (significative only for PSTN).
	 */
	public LogNormalDistributionParameter getLogNormalDistribution() {
		return logNormalDistributionParameter;
	}

	/**
	 * @param logNormalDistributionParameter the logNormalDistributionParameter associated to the contingent link that ends in this node (if the network is PSTN
	 *                                       and this node is a contingent node of a contingent link described by a logNormalDistributionParameter).
	 */
	@SuppressFBWarnings("EI_EXPOSE_REP2")
	public void setLogNormalDistributionParameter(LogNormalDistributionParameter logNormalDistributionParameter) {
		this.logNormalDistributionParameter = logNormalDistributionParameter;
	}

	/**
	 * @return the name as ALetter
	 */
	public ALetter getALetter() {
		return new ALetter(this.name);
	}

	/**
	 * @param aletter one A-Letter
	 *
	 * @return true if {@link #getName()} is represented by the {@code aletter}, false otherwise.
	 */
	public boolean hasNameEquals(ALetter aletter) {
		if (aletter == null) {
			return false;
		}
		return aletter.equals(this.getName());
	}

	/**
	 * @return the node after in the double linked representation of the predecessor graph. This field is managed by
	 * 	DisassemblyTree procedure
	 */
	public LabeledNode getAfter() {
		return after;
	}

	/**
	 * @param after1 new after-node in the double linked representation of the predecessor graph.
	 */
	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "EI_EXPOSE_REP2",
		justification = "For efficiency reason, it includes an external mutable object.")
	public void setAfter(LabeledNode after1) {
		after = after1;
	}

	/**
	 * @return the node before in the double linked representation of the predecessor graph. This field is managed by
	 * 	DisassemblyTree procedure
	 */
	public LabeledNode getBefore() {
		return before;
	}

	/**
	 * @param before1 new before node in the double linked representation of the predecessor graph.
	 */
	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "EI_EXPOSE_REP2",
		justification = "For efficiency reason, it includes an external mutable object.")
	public void setBefore(LabeledNode before1) {
		before = before1;
	}

	/**
	 * @return the label
	 */
	public Label getLabel() {
		return label;
	}

	/**
	 * @param inputLabel the label to set. If it is null, this label is set to
	 *                   {@link it.univr.di.labeledvalue.Label#emptyLabel}.
	 */
	public void setLabel(final Label inputLabel) {
		final String old = (label == null) ? "null" : label.toString();
		label = (inputLabel == null || inputLabel.isEmpty()) ? Label.emptyLabel : inputLabel;
		pcs.firePropertyChange("nodeLabel", old, inputLabel);
	}

	/**
	 * @param s the label to set
	 */
	public void setLabel(final String s) {
		setLabel(Label.parse(s));
	}

	/**
	 * Returns the potential associated to label l, if it exists; {@link it.univr.di.labeledvalue.Constants#INT_NULL}
	 * otherwise.
	 *
	 * @param l a {@link it.univr.di.labeledvalue.Label} object.
	 *
	 * @return the labeled value getPotential(ALabel.emptyLabel, Label) if it was present,
	 *    {@link it.univr.di.labeledvalue.Constants#INT_NULL} otherwise.
	 */
	public int getLabeledPotential(Label l) {
		return labeledPotential.getValue(l, ALabel.emptyLabel);
	}

	/**
	 * Returns the map of labeled potential of the node.
	 *
	 * @return an unmodifiable view of the labeled potential values
	 */
	public LabeledIntMap getLabeledPotential() {
		return labeledPotential.unmodifiable(ALabel.emptyLabel);
	}

	/**
	 * If potential is not null, it is used (not copied) as new potential of the node.
	 * If potential is null, it does nothing.
	 *
	 * @param potentialMap a {@link it.univr.di.labeledvalue.LabeledIntMap} object.
	 */
	public void setLabeledPotential(LabeledIntMap potentialMap) {
		if (potentialMap == null) {
			return;
		}
		for (final Object2IntMap.Entry<Label> entry : potentialMap.entrySet()) {
			labeledPotential.putTriple(entry.getKey(), ALabel.emptyLabel, entry.getIntValue());
		}
	}

	/**
	 * @param l the value to search
	 *
	 * @return the value associated to the label if it exists, {@link Constants#INT_NULL} otherwise.
	 */
	public final int getLabeledPotentialCount(Label l) {
		if (l == null) {
			return Constants.INT_NULL;
		}
		return labeledPotentialCount.getInt(l);
	}

	/**
	 * Returns the upper potential associated to label l, if it exists;
	 * {@link it.univr.di.labeledvalue.Constants#INT_NULL} otherwise.
	 *
	 * @param l a {@link it.univr.di.labeledvalue.Label} object.
	 *
	 * @return the labeled value getPotential(ALabel.emptyLabel, Label) if it was present,
	 *    {@link it.univr.di.labeledvalue.Constants#INT_NULL} otherwise.
	 */
	public int getLabeledUpperPotential(Label l) {
		return labeledUpperPotential.getValue(l, ALabel.emptyLabel);
	}

	/**
	 * Returns the map of labeled upper potential of the node.
	 *
	 * @return an unmodifiable view of the labeled potential values
	 */
	public LabeledIntMap getLabeledUpperPotential() {
		return labeledUpperPotential.unmodifiable(ALabel.emptyLabel);
	}

	/**
	 * If potential is not null, it is used (not copied) as new potential of the node. If potential is null, it does
	 * nothing.
	 *
	 * @param potentialMap a {@link it.univr.di.labeledvalue.LabeledIntMap} object.
	 */
	public void setLabeledUpperPotential(LabeledIntMap potentialMap) {
		this.setLabeledPotential(potentialMap);
	}

	/**
	 * @return the potential. If {@link it.univr.di.labeledvalue.Constants#INT_NULL}, it means that it was not
	 * 	determined.
	 */
	public int getPotential() {
		return potential;
	}

	/**
	 * @param potential1 the potential to set
	 */
	public void setPotential(int potential1) {
		potential = potential1;
	}

	/**
	 * @return the predecessor LabeledNode or null.
	 */
	public LabeledNode getPredecessor() {
		return predecessor;
	}

	/**
	 * Set the predecessor.
	 *
	 * @param p the predecessor node
	 */
	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "EI_EXPOSE_REP2",
		justification = "For efficiency reason, it includes an external mutable object.")
	public void setPredecessor(LabeledNode p) {
		predecessor = p;
	}

	/**
	 * @return the proposition under the control of this node. {@link it.univr.di.labeledvalue.Constants#UNKNOWN}, if no
	 * 	observation is made.
	 */
	public char getPropositionObserved() {
		return propositionObserved;
	}

	/**
	 * @return the status of the node during {@link STN#SSSP_BFCT(TNGraph, LabeledNode, STN.EdgeValue, int, STN.STNCheckStatus)}
	 * 	execution.
	 */
	public Status getStatus() {
		return status;
	}

	/**
	 * @param status1 new status of the node during {@link STN#SSSP_BFCT} execution.
	 */
	public void setStatus(Status status1) {
		status = status1;
	}

	/**
	 * Returns the map of upper/lower case labeled potential of the node.
	 *
	 * @return a unmodifiable {@link it.univr.di.labeledvalue.LabeledALabelIntTreeMap} object.
	 */
	public LabeledALabelIntTreeMap getULCaseLabeledPotential() {
		return labeledPotential.unmodifiable();
	}

	/**
	 * Returns the map of upper/lower case labeled UPPER potential of the node.
	 *
	 * @return a unmodifiable {@link it.univr.di.labeledvalue.LabeledALabelIntTreeMap} object.
	 */
	public LabeledALabelIntTreeMap getULCaseLabeledUpperPotential() {
		return labeledUpperPotential.unmodifiable();
	}

	/**
	 * @return the x
	 */
	public double getX() {
		return x;
	}

	/**
	 * @param x1 the x to set
	 */
	public void setX(final double x1) {
		x = x1;
	}

	/**
	 * @return the y
	 */
	public double getY() {
		return y;
	}

	/**
	 * @param y1 the y to set
	 */
	public void setY(final double y1) {
		y = y1;
	}

	/**
	 * Overriding method to ensure that name is coherent with ALabel
	 */
	@SuppressFBWarnings(value = {"RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"}, justification = "Silly cycle")
	@Nonnull
	@Override
	public String setName(@Nonnull String name) {
		if (this.name.equals(name)) {
			return this.name;
		}
		final String old = super.setName(name);
		this.setALabel(null);
		return old;
	}

	/**
	 * @return true if this node represents a contingent node. It is assumed that a node representing a contingent
	 * 	time-point has its field 'alabel' not null.
	 */
	@Override
	public boolean isContingent() {
		return isContingent;
	}

	/**
	 * @param b true if the node is becoming a contingent one.
	 */
	public void setContingent(boolean b) {
		isContingent = b;
	}

	/**
	 * @return true if this node is an observator one (it is associated to a proposition letter), false otherwise;
	 */
	public boolean isObserver() {
		return propositionObserved != Constants.UNKNOWN;
	}

	/**
	 * @return true if this node represents a parameter node.
	 */
	@Override
	public boolean isParameter() {
		return isParameter;
	}

	/**
	 * @param b true if the node is becoming a parameter one.
	 */
	public void setParameter(boolean b) {
		isParameter = b;
	}

	@Override
	public Node newInstance() {
		return new LabeledNode("");
	}

	@Override
	public Node newInstance(Node node) {
		return new LabeledNode((LabeledNode) node);
	}

	@Override
	public Node newInstance(String name1) {
		return new LabeledNode(name1);
	}

	/**
	 * Puts the labeled value (value, l) into the potential map.
	 *
	 * @param l     a {@link it.univr.di.labeledvalue.Label} object.
	 * @param value the new value.
	 *
	 * @return true if the pair has been merged.
	 */
	final public boolean putLabeledPotential(Label l, int value) {
		return labeledPotential.putTriple(l, ALabel.emptyLabel, value);
	}

	/**
	 * Puts the labeled value (value, l) into the upper potential map.
	 *
	 * @param l     a {@link it.univr.di.labeledvalue.Label} object.
	 * @param value the new value.
	 *
	 * @return true if the pair has been merged.
	 */
	final public boolean putLabeledUpperPotential(Label l, int value) {
		return labeledUpperPotential.putTriple(l, ALabel.emptyLabel, value);
	}

	/**
	 * Removes the labeled potential associated to label l, if it exists.
	 *
	 * @param l the label to remove
	 *
	 * @return the old value if it was present, {@link it.univr.di.labeledvalue.Constants#INT_NULL} otherwise.
	 */
	public int removeLabeledPotential(Label l) {
		return labeledPotential.remove(l, ALabel.emptyLabel);//ok
	}

	/**
	 * Removes the labeled upper potential associated to label l, if it exists.
	 *
	 * @param l the label to remove
	 *
	 * @return the old value if it was present, {@link it.univr.di.labeledvalue.Constants#INT_NULL} otherwise.
	 */
	public int removeLabeledUpperPotential(Label l) {
		return labeledUpperPotential.remove(l, ALabel.emptyLabel);//ok
	}

	/**
	 * Put the value with label l into the map.
	 *
	 * @param l     the label of new value
	 * @param value the new value
	 *
	 * @return the old value associated to label if it exists, {@link Constants#INT_NULL} otherwise.
	 */
	public final int setLabeledPotentialCount(Label l, int value) {
		if (l == null || value == Constants.INT_NULL) {
			return Constants.INT_NULL;
		}
		return labeledPotentialCount.put(l, value);
	}

	/**
	 * Set the proposition to be observed.
	 *
	 * @param c the proposition to observe. If {@link it.univr.di.labeledvalue.Constants#UNKNOWN}, the node became not
	 *          observable node.
	 */
	public void setObservable(final char c) {
		final char old = propositionObserved;
		propositionObserved = (Literal.check(c)) ? c : Constants.UNKNOWN;
		pcs.firePropertyChange("nodeProposition", Character.valueOf(old), Character.valueOf(c));
	}

	@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "False positive.")
	@Nonnull
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder(Constants.OPEN_TUPLE);
		sb.append(getName());
		if (!label.isEmpty()) {
			sb.append("; ");
			sb.append(label);
		}
		if (propositionObserved != Constants.UNKNOWN) {
			sb.append("; Obs: ");
			sb.append(propositionObserved);
		}
		if (!labeledPotential.isEmpty()) {
			sb.append("; Labeled Potential: ");
			sb.append(labeledPotential);
		}
		if (!labeledUpperPotential.isEmpty()) {
			sb.append("; Labeled Upper Potential: ");
			sb.append(labeledUpperPotential);
		}
		if (potential != Constants.INT_NULL) {
			sb.append("; Potential: ");
			sb.append(potential);
		}
		sb.append(Constants.CLOSE_TUPLE);
		return sb.toString();
	}
}