Printer.java

package visitor;

import calculator.Notation;

import calculator.operations.Operation;
import calculator.atoms.*;
import calculator.functions.BinaryFunction;
import calculator.functions.UnaryFunction;
import calculator.Expression;

/**
 * Printer is a Visitor that converts arithmetic expressions into Strings, using
 * a specified notation (prefix, infix or postfix).
 * It is used in the Calculator class to print arithmetic expressions in a
 * human-readable format.
 *
 * @see Visitor
 * @see calculator.Calculator
 */
public class Printer extends Visitor {

	/*
	 * The StringBuilder that is used to build the string representation of an
	 * arithmetic expression
	 */
	private final StringBuilder sb = new StringBuilder();
	/*
	 * The notation to be used for printing expressions (prefix, infix or postfix)
	 */
	private final Notation notation;

	/**
	 * Constructor of the Printer class, which takes a Notation as a parameter to
	 * specify the notation to be used for printing expressions.
	 * 
	 * @param notation The notation to be used for printing expressions (prefix,
	 *                 infix or postfix)
	 */
	public Printer(Notation notation) {
		this.notation = notation;
	}

	/**
	 * Constructor of the Printer class, which initializes the notation to infix by
	 * default.
	 */
	public Printer() {
		this.notation = Notation.INFIX; // Default notation is infix
	}

	/**
	 * The visit methods for Real.
	 * These methods build the string representation of the expression being
	 * visited, using the specified notation.
	 * 
	 * @param r The Real to be visited
	 */
	@Override
	public void visit(Real r) {
		sb.append(r.getValue().stripTrailingZeros());
	}

	@Override
	public void visit(IntegerAtom i) {
		sb.append(i.getValue());
	}

	@Override
	public void visit(Complex c) {
		double realPart = c.getValue().getReal();
		double imaginaryPart = c.getValue().getImaginary();

		switch (notation) {
			case PREFIX -> {
				sb.append("+ ").append(realPart).append(" ").append(imaginaryPart).append("i");
			}
			case POSTFIX -> {
				sb.append(realPart).append(" ").append(imaginaryPart).append("i +");
			}
			case INFIX -> {
				sb.append(realPart).append(" + ").append(imaginaryPart).append("i");
			}
		}
	}

	@Override
	public void visit(Rationnal q) {
		int num = q.getNumerator();
		int den = q.getDenominator();

		if (den == 1) {
			sb.append(num);
			return;
		}

		switch (notation) {
			case PREFIX -> {
				sb.append("/ ").append(num).append(" ").append(den);
			}
			case POSTFIX -> {
				sb.append(num).append(" ").append(den).append(" /");
			}
			case INFIX -> {
				sb.append(num).append("/").append(den);
			}
		}
	}

	/**
	 * The visit method for Operation. Needs to handle the different notations
	 * (prefix, infix and postfix) and the different number of arguments that an
	 * operation can have.
	 * It uses helper methods visitArgs and visitArgsInfix to handle the different
	 * notations.
	 * 
	 * @param o The Operation to be visited
	 */
	@Override
	public void visit(Operation o) {
		switch (notation) {
			case PREFIX -> {
				sb.append(o.getSymbol()).append(" (");
				visitArgs(o, ", ");
				sb.append(")");
			}
			case INFIX -> {
				sb.append("( ");
				visitArgs(o, " " + o.getSymbol() + " ");
				sb.append(" )");
			}
			case POSTFIX -> {
				sb.append("(");
				visitArgs(o, ", ");
				sb.append(") ").append(o.getSymbol());
			}
		}
	}

	@Override
	public void visit(UnaryFunction o) {
		switch (notation) {
			case PREFIX, INFIX -> {
				sb.append(o.getSymbol()).append("(");
				o.getArg().accept(this);
				sb.append(")");
			}
			case POSTFIX -> {
				sb.append("(");
				o.getArg().accept(this);
				sb.append(")").append(o.getSymbol());
			}
		}
	}

	/**
	 * Helper method to visit the arguments of an operation and build their string
	 * representation.
	 * 
	 * @param o         The operation whose arguments are to be visited
	 * @param separator The string used to separate the arguments
	 */
	private void visitArgs(Operation o, String separator) {
		for (Expression arg : o.getArgs()) {
			arg.accept(this);
			sb.append(separator);
		}
		// Remove the last separator
		if (sb.length() > 0) {
			sb.setLength(sb.length() - separator.length());
		}
	}

	/**
	 * Getter method to obtain the string representation of the expression that has
	 * been built by the visit methods.
	 * 
	 * @return The string representation of the expression that has been built by
	 *         the visit methods
	 */
	public String getResult() {
		return sb.toString();
	}

	@Override
	public void visit(BinaryFunction f) {
		switch (notation) {
			case PREFIX, INFIX -> {
				sb.append(f.getSymbol()).append("(");
				f.getFirstArg().accept(this);
				sb.append(", ");
				f.getSecondArg().accept(this);
				sb.append(")");
			}
			case POSTFIX -> {
				sb.append("(");
				f.getFirstArg().accept(this);
				sb.append(", ");
				f.getSecondArg().accept(this);
				sb.append(")").append(f.getSymbol());
			}
		}
	}

}