package models.algebra;

import java.util.List;

public class Symbol {
	protected String name;
	protected String implName;
	protected int arity = 0;			// -1: variable number
	protected Type operatorType = Type.PREFIX;
	protected Type implOperatorType = Type.PREFIX;
	protected Symbol[] inverses = null;
	protected models.algebra.Type[] signature = null;
	protected int[] implParamOrder = null;
	protected IImplGenerator generator = null;
	protected ICalculator calculator = null;
	
	public Symbol(String name) {
		this.name = name;
		this.implName = name;
		this.arity = 0;
	}
	
	public Symbol(String name, int arity) {
		this.name = name;
		this.implName = name;
		this.arity = arity;
	}
	
	public Symbol(String name, int arity, Type operatorType) {
		this(name, arity);
		this.operatorType = operatorType;
		this.implOperatorType = operatorType;
	}
	
	public Symbol(String name, int arity, Type operatorType, String implName, Type implOperatorType) {
		this.name = name;
		this.implName = implName;
		this.arity = arity;
		this.operatorType = operatorType;
		this.implOperatorType = implOperatorType;
	}
	
	public Symbol(String name, int arity, Type operatorType, String implName, Type implOperatorType, int[] implParamOrder) {
		this.name = name;
		this.implName = implName;
		this.arity = arity;
		this.operatorType = operatorType;
		this.implOperatorType = implOperatorType;
		this.implParamOrder = implParamOrder;
	}
	
	public Symbol(String name, int arity, Type operatorType, IImplGenerator generator) {
		this.name = name;
		this.arity = arity;
		this.operatorType = operatorType;
		this.generator = generator;
		this.implOperatorType = Type.GENERATIVE;
	}
	
	public Symbol(String name, int arity, Type operatorType, IImplGenerator generator, boolean bSideEffect) {
		this.name = name;
		this.arity = arity;
		this.operatorType = operatorType;
		this.generator = generator;
		if (!bSideEffect) {
			this.implOperatorType = Type.GENERATIVE;
		} else {
			this.implOperatorType = Type.GENERATIVE_WITH_SIDE_EFFECT;
		}
	}
	
	public Symbol(String name, int arity, ICalculator calculator) {
		this.name = name;
		this.arity = arity;
		this.calculator = calculator;
	}
	
	public Symbol(String name, int arity, Type operatorType, ICalculator calculator) {
		this.name = name;
		this.arity = arity;
		this.operatorType = operatorType;
		this.calculator = calculator;
	}
	
	public Symbol(String name, int arity, Type operatorType, IImplGenerator generator, ICalculator calculator) {
		this.name = name;
		this.arity = arity;
		this.operatorType = operatorType;
		this.generator = generator;
		this.implOperatorType = Type.GENERATIVE;
		this.calculator = calculator;
	}
	
	public Symbol(String name, int arity, Type operatorType, String implName, Type implOperatorType, ICalculator calculator) {
		this.name = name;
		this.implName = implName;
		this.arity = arity;
		this.operatorType = operatorType;
		this.implOperatorType = implOperatorType;
		this.calculator = calculator;
	}
	
	public void setArity(int arity) {
		this.arity = arity;
	}

	public int getArity() {
		return arity;
	}

	public String getName() {
		return name;
	}

	public Type getOperatorType() {
		return operatorType;
	}
	
	public boolean isInfix() {
		return (operatorType == Type.INFIX);
	}
	
	public boolean isMethod() {
		return (operatorType == Type.METHOD || operatorType == Type.METHOD_WITH_SIDE_EFFECT);
	}
	
	public boolean isLambda() {
		return (operatorType == Type.LAMBDA);
	}

	public Symbol[] getInverses() {
		return inverses;
	}

	public void setInverses(Symbol[] inverses) {
		this.inverses = inverses;
	}

	public models.algebra.Type[] getSignature() {
		return signature;
	}

	public void setSignature(models.algebra.Type[] signature) {
		this.signature = signature;
	}

	public String getImplName() {
		return implName;
	}

	public void setImplName(String implName) {
		this.implName = implName;
	}

	public Type getImplOperatorType() {
		return implOperatorType;
	}
	
	public boolean isImplInfix() {
		return (implOperatorType == Type.INFIX);
	}
	
	public boolean isImplMethod() {
		return (implOperatorType == Type.METHOD || implOperatorType == Type.METHOD_WITH_SIDE_EFFECT);
	}
	
	public boolean isImplLambda() {
		return (implOperatorType == Type.LAMBDA || implOperatorType == Type.LAMBDA_WITH_SIDE_EFFECT);
	}
	
	public boolean isImplGenerative() {
		return (implOperatorType == Type.GENERATIVE || implOperatorType == Type.GENERATIVE_WITH_SIDE_EFFECT);
	}
	
	public boolean isImplWithSideEffect() {
		return (implOperatorType == Type.METHOD_WITH_SIDE_EFFECT 
				|| implOperatorType == Type.LAMBDA_WITH_SIDE_EFFECT 
				|| implOperatorType == Type.GENERATIVE_WITH_SIDE_EFFECT);
	}

	public void setImplOperatorType(Type implOperatorType) {
		this.implOperatorType = implOperatorType;
	}
	
	public int[] getImplParamOrder() {
		return implParamOrder;
	}
	
	public void setGenerator(IImplGenerator generator) {
		this.generator = generator; 
	}
	
	/**
	 * Generate the implementation of this symbol
	 * @param type the type of this symbol
	 * @param childrenTypes 
	 * @param childrenImpl the implementations of the children
	 * @param childrenSideEffects (input) an array of the side effects of the children
	 * @param sideEffect (output) an array of the side effect of this symbol
	 * @return the implementation
	 */
	public String generate(models.algebra.Type type, models.algebra.Type[] childrenTypes, String[] childrenImpl, String[] childrenSideEffects, String[] sideEffect) {
		if (generator != null) {
			return generator.generate(type, childrenTypes, childrenImpl, childrenSideEffects, sideEffect);
		}
		return null;
	}
	
	public boolean isCalculatable() {
		return (calculator != null);
	}
	
	public Expression calculate(List<Expression> args) {
		if (calculator != null) {
			return calculator.calculate(args);
		}
		return null;
	}

	public boolean equals(Object another) {
		if (!(another instanceof Symbol)) return false;
		return name.equals(((Symbol) another).name) && arity == ((Symbol) another).arity;
	}
	
	@Override
	public int hashCode() {
		return name.hashCode();
	}
	
	public String toString() {
		return name;
	}
	
	public String toImplementation() {
		return implName;
	}
	
	public enum Type {
		PREFIX,
		INFIX,
		METHOD,
		METHOD_WITH_SIDE_EFFECT,
		LAMBDA,
		LAMBDA_WITH_SIDE_EFFECT,
		GENERATIVE,
		GENERATIVE_WITH_SIDE_EFFECT
	}
	
	public Memento createMemento() {
		return new Memento(implName, implOperatorType);
	}
	
	public void setMemento(Memento memento) {
		this.implName = memento.implName;
		this.implOperatorType = memento.implOperatorType;
	}
	
	public static class Memento {
		private String implName;
		private Type implOperatorType = Type.PREFIX;
		
		public Memento(String implName, Type implOperatorType) {
			this.implName = implName;
			this.implOperatorType = implOperatorType;
		}
	}
	
	public interface IImplGenerator {
		/**
		 * Generate the implementation
		 * @param type the type of this expression
		 * @param childrenTypes 
		 * @param children the implementations of the children
		 * @param childrenSideEffects (input) an array of the side effects of the children
		 * @param sideEffect (output) an array of the side effect of this generator
		 * @return the generated implementation
		 */
		public String generate(models.algebra.Type type, models.algebra.Type[] childrenTypes, String children[], String[] childrenSideEffects, String[] sideEffect);
	}
	
	public interface ICalculator {
		public Expression calculate(List<Expression> args);
	}
}
