// Path: src/main/java/ast/Variable.java
// public class Variable extends Expression {
// 	private String name;
// 
// 	public Variable(String name) {
// 		this.name = name;
// 	}
// 
// 	public String getName() {
// 		return name;
// 	}
// 
// 	public void setName(String name) {
// 		this.name = name;
// 	}
// 
// 
// 	@Override
// 	public Type infer(TypeInferenceEnv env) {
// 		return env.lookup(this).instantiate(env);
// 	}
// 
// 
// 	//Object override
// 	@Override
// 	public boolean equals(Object o) {
// 		if (this == o) return true;
// 		if (o == null || getClass() != o.getClass()) return false;
// 
// 		Variable variable = (Variable) o;
// 
// 		return name != null ? name.equals(variable.name) : variable.name == null;
// 
// 	}
// 
// 	@Override
// 	public int hashCode() {
// 		return name != null ? name.hashCode() : 0;
// 	}
// 
// 	@Override
// 	public String toString() {
// 		return name;
// 	}
// }
// 
// Path: src/main/java/inference/Substitution.java
// public class Substitution extends Substitutable<Substitution> {
//     private HashMap<TVariable, Type> subs = new HashMap<>();
// 
//     public Substitution() {}
// 
//     public Substitution(TVariable variable, Type type) {
//         subs.put(variable, type);
//     }
// 
//     private Substitution(Substitution previous) {
//         subs.putAll(previous.subs);
//     }
// 
//     public Substitution(List<TVariable> vars, List<Type> substitute) {
//         if(vars.size() != substitute.size())
//             throw new IllegalArgumentException("Number of variables have to match number of types");
// 
//         for (int i = 0; i < vars.size(); i++) subs.put(vars.get(i), substitute.get(i));
//     }
// 
//     public List<TVariable> variables() {
//         return new LinkedList<>(subs.keySet());
//     }
// 
//     public Type substituteOf(TVariable var) {
//         return subs.get(var);
//     }
// 
//     public Substitution composeWith(Substitution sub) {
//         Substitution substituted = new Substitution(sub);
//         substituted.apply(this);
//         subs.putAll(substituted.subs);
//         return this;
//     }
// 
//     @Override
//     public HashSet<TVariable> ftv() {
//         HashSet<TVariable> freeVars = new HashSet<>();
//         for (TVariable var : variables()) freeVars.addAll(substituteOf(var).ftv());
//         return freeVars;
//     }
// 
//     @Override
//     public Substitution substitute(TVariable var, Type t) {
//         for (TVariable variable : variables())
//             subs.replace(variable, substituteOf(variable).substitute(var, t));
//         return this;
//     }
// 
//     @Override
//     public String toString() {
//         return subs.toString();
//     }
// 
//     @Override
//     public Substitution identity() {
//         return this;
//     }
// }
// 
// Path: src/main/java/types/TFunction.java
// public static TFunction function(Type left, Type right) {
//     int hash = hash(left, right);
//     TFunction f = instantiated.get(hash);
//     if(f == null) {
//         f = new TFunction(left, right);
//         instantiated.put(hash, f);
//     }
//     return f;
// }
// 
// Path: src/main/java/types/TVariable.java
// public static TVariable variable(String name) {
//     TVariable var = new TVariable(name);
//     if(!instantiated.add(var)) System.out.println("Creation avoidable : TVariable(" + name + ")");
//     return var;
// }
// 
// Path: src/main/java/types/TVariable.java
// public static List<TVariable> variables(String... names) {
//     List<TVariable> variables = new LinkedList<>();
//     for (String name : names) variables.add(new TVariable(name));
//     return variables;
// }


import ast.Variable;
import inference.Substitution;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
import static org.junit.Assert.*;
import static types.TFunction.function;
import static types.TVariable.variable;
import static types.TVariable.variables;
package types;




/**
 * Created by valentin on 20/10/2016.
 */
public class TFunctionTest {
    private static final String name = "TEST";

    @Test
    public void createFunction() throws Exception {
        Type left = variable(name);
        Type right = variable(name);

        TFunction fun = function(left, right);

        assertEquals("Should create a fresh function with correct left type", left, fun.left());
        assertEquals("Should create a fresh function with correct right type", right, fun .right());
    }

    @Test
    public void applyNotMatching() throws Exception {
        Substitution sub = new Substitution(variable("NOT MATCHING"), variable("NOT MATCHING"));

        TFunction fun = function(variable(name), variable(name + 1));
        Type result = fun.apply(sub);

        assertEquals("Should not modify anything", fun, result);
    }

    @Test
    public void applyMatching() throws Exception {
        TVariable expected = variable("EXPECTED");
        Substitution sub1 = new Substitution(variable(name), expected);
        Substitution sub2 = new Substitution(variable(name + 1), expected);

        TFunction fun = function(variable(name), variable(name + 1));

        Type result1 = fun.apply(sub1);
        Type result2 = fun.apply(sub2);

        assertEquals("Should apply substitution on right side", function(expected, variable(name + 1)), result1);
        assertEquals("Should apply substitution on left side", function(variable(name), expected), result2);
    }

    @Test
// Path: src/main/java/ast/Variable.java
// public class Variable extends Expression {
// 	private String name;
// 
// 	public Variable(String name) {
// 		this.name = name;
// 	}
// 
// 	public String getName() {
// 		return name;
// 	}
// 
// 	public void setName(String name) {
// 		this.name = name;
// 	}
// 
// 
// 	@Override
// 	public Type infer(TypeInferenceEnv env) {
// 		return env.lookup(this).instantiate(env);
// 	}
// 
// 
// 	//Object override
// 	@Override
// 	public boolean equals(Object o) {
// 		if (this == o) return true;
// 		if (o == null || getClass() != o.getClass()) return false;
// 
// 		Variable variable = (Variable) o;
// 
// 		return name != null ? name.equals(variable.name) : variable.name == null;
// 
// 	}
// 
// 	@Override
// 	public int hashCode() {
// 		return name != null ? name.hashCode() : 0;
// 	}
// 
// 	@Override
// 	public String toString() {
// 		return name;
// 	}
// }
// 
// Path: src/main/java/inference/Substitution.java
// public class Substitution extends Substitutable<Substitution> {
//     private HashMap<TVariable, Type> subs = new HashMap<>();
// 
//     public Substitution() {}
// 
//     public Substitution(TVariable variable, Type type) {
//         subs.put(variable, type);
//     }
// 
//     private Substitution(Substitution previous) {
//         subs.putAll(previous.subs);
//     }
// 
//     public Substitution(List<TVariable> vars, List<Type> substitute) {
//         if(vars.size() != substitute.size())
//             throw new IllegalArgumentException("Number of variables have to match number of types");
// 
//         for (int i = 0; i < vars.size(); i++) subs.put(vars.get(i), substitute.get(i));
//     }
// 
//     public List<TVariable> variables() {
//         return new LinkedList<>(subs.keySet());
//     }
// 
//     public Type substituteOf(TVariable var) {
//         return subs.get(var);
//     }
// 
//     public Substitution composeWith(Substitution sub) {
//         Substitution substituted = new Substitution(sub);
//         substituted.apply(this);
//         subs.putAll(substituted.subs);
//         return this;
//     }
// 
//     @Override
//     public HashSet<TVariable> ftv() {
//         HashSet<TVariable> freeVars = new HashSet<>();
//         for (TVariable var : variables()) freeVars.addAll(substituteOf(var).ftv());
//         return freeVars;
//     }
// 
//     @Override
//     public Substitution substitute(TVariable var, Type t) {
//         for (TVariable variable : variables())
//             subs.replace(variable, substituteOf(variable).substitute(var, t));
//         return this;
//     }
// 
//     @Override
//     public String toString() {
//         return subs.toString();
//     }
// 
//     @Override
//     public Substitution identity() {
//         return this;
//     }
// }
// 
// Path: src/main/java/types/TFunction.java
// public static TFunction function(Type left, Type right) {
//     int hash = hash(left, right);
//     TFunction f = instantiated.get(hash);
//     if(f == null) {
//         f = new TFunction(left, right);
//         instantiated.put(hash, f);
//     }
//     return f;
// }
// 
// Path: src/main/java/types/TVariable.java
// public static TVariable variable(String name) {
//     TVariable var = new TVariable(name);
//     if(!instantiated.add(var)) System.out.println("Creation avoidable : TVariable(" + name + ")");
//     return var;
// }
// 
// Path: src/main/java/types/TVariable.java
// public static List<TVariable> variables(String... names) {
//     List<TVariable> variables = new LinkedList<>();
//     for (String name : names) variables.add(new TVariable(name));
//     return variables;
// }


// Path: src/test/java/types/TFunctionTest.java
import ast.Variable;
import inference.Substitution;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
import static org.junit.Assert.*;
import static types.TFunction.function;
import static types.TVariable.variable;
import static types.TVariable.variables;

package types;




/**
 * Created by valentin on 20/10/2016.
 */
public class TFunctionTest {
    private static final String name = "TEST";

    @Test
    public void createFunction() throws Exception {
        Type left = variable(name);
        Type right = variable(name);

        TFunction fun = function(left, right);

        assertEquals("Should create a fresh function with correct left type", left, fun.left());
        assertEquals("Should create a fresh function with correct right type", right, fun .right());
    }

    @Test
    public void applyNotMatching() throws Exception {
        Substitution sub = new Substitution(variable("NOT MATCHING"), variable("NOT MATCHING"));

        TFunction fun = function(variable(name), variable(name + 1));
        Type result = fun.apply(sub);

        assertEquals("Should not modify anything", fun, result);
    }

    @Test
    public void applyMatching() throws Exception {
        TVariable expected = variable("EXPECTED");
        Substitution sub1 = new Substitution(variable(name), expected);
        Substitution sub2 = new Substitution(variable(name + 1), expected);

        TFunction fun = function(variable(name), variable(name + 1));

        Type result1 = fun.apply(sub1);
        Type result2 = fun.apply(sub2);

        assertEquals("Should apply substitution on right side", function(expected, variable(name + 1)), result1);
        assertEquals("Should apply substitution on left side", function(variable(name), expected), result2);
    }

    @Test
    public void multipleApply() throws Exception {