package org.reldb.rel.v0.storage;

import java.util.LinkedList;

import org.reldb.rel.exceptions.ExceptionSemantic;
import org.reldb.rel.v0.generator.Generator;
import org.reldb.rel.v0.generator.OperatorSignature;
import org.reldb.rel.v0.generator.References;
import org.reldb.rel.v0.interpreter.Interpreter;
import org.reldb.rel.v0.storage.catalog.Catalog;
import org.reldb.rel.v0.storage.catalog.RelvarTypes;
import org.reldb.rel.v0.types.Type;
import org.reldb.rel.v0.types.TypeAlpha;
import org.reldb.rel.v0.types.builtin.TypeBoolean;
import org.reldb.rel.v0.types.builtin.TypeCharacter;
import org.reldb.rel.v0.types.builtin.TypeInteger;
import org.reldb.rel.v0.types.builtin.TypeRational;
import org.reldb.rel.v0.values.TupleIterator;
import org.reldb.rel.v0.values.Value;
import org.reldb.rel.v0.values.ValueBoolean;
import org.reldb.rel.v0.values.ValueCharacter;
import org.reldb.rel.v0.values.ValueInteger;
import org.reldb.rel.v0.values.ValueRational;
import org.reldb.rel.v0.values.ValueRelation;
import org.reldb.rel.v0.values.ValueTuple;
import org.reldb.rel.v0.vm.Context;
import org.reldb.rel.v0.vm.NativeFunction;
import org.reldb.rel.v0.vm.VirtualMachine;

public class BuiltinTypeBuilder {
	public final static String EQUALS = "OP_EQUALS";
	public final static String NOTEQUALS = "OP_NOTEQUALS";
	public final static String GREATERTHAN = "OP_GREATERTHAN";
	public final static String LESSTHAN = "OP_LESSTHAN";
	public final static String GREATERTHANOREQUALS = "OP_GREATERTHANOREQUALS";
	public final static String LESSTHANOREQUALS = "OP_LESSTHANOREQUALS";
	public final static String MAX = "OP_MAX";
	public final static String MIN = "OP_MIN";
	public final static String XOR = "OP_XOR";
	public final static String OR = "OP_OR";
	public final static String AND = "OP_AND";
	public final static String NOT = "OP_NOT";
	public final static String PLUS = "OP_PLUS";
	public final static String MINUS = "OP_MINUS";
	public final static String TIMES = "OP_TIMES";
	public final static String DIVIDE = "OP_DIVIDE";
	public final static String MODULO = "OP_MODULO";
	public final static String UNARY_PLUS = "OP_UNARY_PLUS";
	public final static String UNARY_MINUS = "OP_UNARY_MINUS";
	public static final String SUBSET = "OP_SUBSET";
	public static final String SUBSETOREQUAL = "OP_SUBSETOREQUAL";
	public static final String SUPERSET = "OP_SUPERSET";
	public static final String SUPERSETOREQUAL = "OP_SUPERSETOREQUAL";

	private static LinkedList<String> subTypes = new LinkedList<String>();
	
	private RelDatabase database;
	private Generator generator;
	private TypeAlpha type;
	private boolean typeExists;

	private static void loadSubtypes(RelDatabase database) {
		Interpreter interpreter = new Interpreter(database, System.out);
		Generator generator = interpreter.getGenerator();
		for (String typeName: subTypes)
    		database.loadType(generator, typeName);
	}
	
	public BuiltinTypeBuilder(RelDatabase database, TypeAlpha type) {
		this.database = database;
		this.type = type;
		typeExists = false;
		Interpreter interpreter = new Interpreter(database, System.out);
		generator = interpreter.getGenerator();
		generator.beginCompilation();
    	String typeName = type.getTypeName();
		if (database.isTypeExists(generator, typeName)) {
			generator.beginTypeRetrieval();
			generator.createTypeBuiltin(type);
			generator.endTypeRetrieval();
	    	RelvarTypes typesRelvar = (RelvarTypes)database.openGlobalRelvar(Catalog.relvarTypes);
	    	ValueTuple typeTuple = typesRelvar.getTupleForKey(generator, typeName);
	    	ValueRelation subTypeNames = (ValueRelation)typeTuple.getValues()[5];
	    	for (TupleIterator it = subTypeNames.iterator(); it.hasNext();)
	    		subTypes.add(it.next().getValues()[0].stringValue());
	    	typeExists = true;
		} else {
			generator.createTypeBuiltin(type);
		}
	}
	
	public void checkTypeOperatorUnary(String name, TypeAlpha returnType, final NativeFunction operator) {
		String typeName = type.getTypeName();
		References referenceToType = new References();
		referenceToType.addReferenceToType(type.getTypeName());
		OperatorSignature signature = new OperatorSignature(name);
		signature.addParameter("%p0", type);
		signature.setReturnType(returnType);
		if (typeExists)
			generator.beginTypeRetrieval();
		generator.new OperatorAssociatedWithType(typeName, "Rel", signature, referenceToType) {
			public Value evaluate(Value[] arguments) {
				return operator.evaluate(arguments);
			}
		};
		if (typeExists)
			generator.endTypeRetrieval();
	}
	
	public void checkTypeOperatorBinary(String name, Type returnType, final NativeFunction operator) {
		String typeName = type.getTypeName();
		References referenceToType = new References();
		referenceToType.addReferenceToType(type.getTypeName());
		OperatorSignature signature = new OperatorSignature(name);
		signature.addParameter("%p0", type);
		signature.addParameter("%p1", type);
		signature.setReturnType(returnType);
		if (typeExists)
			generator.beginTypeRetrieval();
		generator.new OperatorAssociatedWithType(typeName, "Rel", signature, referenceToType) {
			public Value evaluate(Value[] arguments) {
				return operator.evaluate(arguments);
			}
		};
		if (typeExists)
			generator.endTypeRetrieval();
	}
    
    public void go() {
		generator.endCompilation().call(new Context(generator, new VirtualMachine(generator, database, System.out)));        	
    }

	public static void buildTypes(RelDatabase relDatabase) {
		final BuiltinTypeBuilder typeBuilderBoolean = new BuiltinTypeBuilder(relDatabase, TypeBoolean.getInstance());
    	final BuiltinTypeBuilder typeBuilderInteger = new BuiltinTypeBuilder(relDatabase, TypeInteger.getInstance());    	
    	final BuiltinTypeBuilder typeBuilderRational = new BuiltinTypeBuilder(relDatabase, TypeRational.getInstance());    	
    	final BuiltinTypeBuilder typeBuilderCharacter = new BuiltinTypeBuilder(relDatabase, TypeCharacter.getInstance());    	
		
    	typeBuilderBoolean.checkTypeOperatorBinary(EQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderBoolean.generator, arguments[0].booleanValue() == arguments[1].booleanValue());
			}
    	});
    	typeBuilderBoolean.checkTypeOperatorBinary(NOTEQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderBoolean.generator, arguments[0].booleanValue() != arguments[1].booleanValue());
			}
    	});    	
    	typeBuilderBoolean.checkTypeOperatorBinary(XOR, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderBoolean.generator, arguments[0].booleanValue() ^ arguments[1].booleanValue());
			}
    	});
    	typeBuilderBoolean.checkTypeOperatorBinary(OR, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderBoolean.generator, arguments[0].booleanValue() | arguments[1].booleanValue());
			}
    	});
    	typeBuilderBoolean.checkTypeOperatorBinary(AND, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderBoolean.generator, arguments[0].booleanValue() & arguments[1].booleanValue());
			}
    	});
    	typeBuilderBoolean.checkTypeOperatorUnary(NOT, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderBoolean.generator, !arguments[0].booleanValue());
			}
    	});

    	typeBuilderInteger.checkTypeOperatorBinary(EQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderInteger.generator, arguments[0].longValue() == arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(NOTEQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderInteger.generator, arguments[0].longValue() != arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(GREATERTHANOREQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderInteger.generator, arguments[0].longValue() >= arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(LESSTHANOREQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderInteger.generator, arguments[0].longValue() <= arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(LESSTHAN, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderInteger.generator, arguments[0].longValue() < arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(GREATERTHAN, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderInteger.generator, arguments[0].longValue() > arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(MAX, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				if (arguments[0].longValue() > arguments[1].longValue())
					return ValueInteger.select(typeBuilderInteger.generator, arguments[0].longValue());
				else
					return ValueInteger.select(typeBuilderInteger.generator, arguments[1].longValue());
			}	    		
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(MIN, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				if (arguments[0].longValue() < arguments[1].longValue())
					return ValueInteger.select(typeBuilderInteger.generator, arguments[0].longValue());
				else
					return ValueInteger.select(typeBuilderInteger.generator, arguments[1].longValue());
			}	    		
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(PLUS, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueInteger.select(typeBuilderInteger.generator, arguments[0].longValue() + arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(MINUS, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueInteger.select(typeBuilderInteger.generator, arguments[0].longValue() - arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(TIMES, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueInteger.select(typeBuilderInteger.generator, arguments[0].longValue() * arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(DIVIDE, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				if (arguments[1].longValue() == 0)
					throw new ExceptionSemantic("RS0207: Attempt to divide by zero.");
				return ValueInteger.select(typeBuilderInteger.generator, arguments[0].longValue() / arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorBinary(MODULO, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				if (arguments[1].longValue() == 0)
					throw new ExceptionSemantic("RS0419: Attempt to perform modulo by zero.");
				return ValueInteger.select(typeBuilderInteger.generator, arguments[0].longValue() % arguments[1].longValue());
			}
    	});
    	typeBuilderInteger.checkTypeOperatorUnary(UNARY_PLUS, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueInteger.select(typeBuilderInteger.generator, +arguments[0].longValue());
			}	    		
    	});
    	typeBuilderInteger.checkTypeOperatorUnary(UNARY_MINUS, TypeInteger.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueInteger.select(typeBuilderInteger.generator, -arguments[0].longValue());
			}
    	});

    	typeBuilderRational.checkTypeOperatorBinary(EQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderRational.generator, arguments[0].doubleValue() == arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(NOTEQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderRational.generator, arguments[0].doubleValue() != arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(GREATERTHANOREQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderRational.generator, arguments[0].doubleValue() >= arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(LESSTHANOREQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderRational.generator, arguments[0].doubleValue() <= arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(LESSTHAN, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderRational.generator, arguments[0].doubleValue() < arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(GREATERTHAN, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderRational.generator, arguments[0].doubleValue() > arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(MAX, TypeRational.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				if (arguments[0].doubleValue() > arguments[1].doubleValue())
					return ValueRational.select(typeBuilderRational.generator, arguments[0].doubleValue());
				else
					return ValueRational.select(typeBuilderRational.generator, arguments[1].doubleValue());
			}	    		
    	});
    	typeBuilderRational.checkTypeOperatorBinary(MIN, TypeRational.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				if (arguments[0].doubleValue() < arguments[1].doubleValue())
					return ValueRational.select(typeBuilderRational.generator, arguments[0].doubleValue());
				else
					return ValueRational.select(typeBuilderRational.generator, arguments[1].doubleValue());
			}	    		
    	});
    	typeBuilderRational.checkTypeOperatorBinary(PLUS, TypeRational.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueRational.select(typeBuilderRational.generator, arguments[0].doubleValue() + arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(MINUS, TypeRational.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueRational.select(typeBuilderRational.generator, arguments[0].doubleValue() - arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(TIMES, TypeRational.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueRational.select(typeBuilderRational.generator, arguments[0].doubleValue() * arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorBinary(DIVIDE, TypeRational.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueRational.select(typeBuilderRational.generator, arguments[0].doubleValue() / arguments[1].doubleValue());
			}
    	});
    	typeBuilderRational.checkTypeOperatorUnary(UNARY_PLUS, TypeRational.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueRational.select(typeBuilderRational.generator, +arguments[0].doubleValue());
			}	    		
    	});
    	typeBuilderRational.checkTypeOperatorUnary(UNARY_MINUS, TypeRational.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueRational.select(typeBuilderRational.generator, -arguments[0].doubleValue());
			}
    	});

    	typeBuilderCharacter.checkTypeOperatorBinary(EQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderCharacter.generator, arguments[0].stringValue().compareTo(arguments[1].stringValue())==0);
			}
    	});
    	typeBuilderCharacter.checkTypeOperatorBinary(NOTEQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderCharacter.generator, arguments[0].stringValue().compareTo(arguments[1].stringValue())!=0);
			}
    	});
    	typeBuilderCharacter.checkTypeOperatorBinary(GREATERTHANOREQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderCharacter.generator, arguments[0].stringValue().compareTo(arguments[1].stringValue())>=0);
			}
    	});
    	typeBuilderCharacter.checkTypeOperatorBinary(LESSTHANOREQUALS, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderCharacter.generator, arguments[0].stringValue().compareTo(arguments[1].stringValue())<=0);
			}
    	});
    	typeBuilderCharacter.checkTypeOperatorBinary(LESSTHAN, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderCharacter.generator, arguments[0].stringValue().compareTo(arguments[1].stringValue())<0);
			}
    	});
    	typeBuilderCharacter.checkTypeOperatorBinary(GREATERTHAN, TypeBoolean.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				return ValueBoolean.select(typeBuilderCharacter.generator, arguments[0].stringValue().compareTo(arguments[1].stringValue())>0);
			}
    	});
    	typeBuilderCharacter.checkTypeOperatorBinary(MAX, TypeCharacter.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				if (arguments[0].stringValue().compareTo(arguments[1].stringValue())>0)
					return ValueCharacter.select(typeBuilderCharacter.generator, arguments[0].stringValue());
				else
					return ValueCharacter.select(typeBuilderCharacter.generator, arguments[1].stringValue());
			}	    		
    	});
    	typeBuilderCharacter.checkTypeOperatorBinary(MIN, TypeCharacter.getInstance(), new NativeFunction() {
			public Value evaluate(Value[] arguments) {
				if (arguments[0].stringValue().compareTo(arguments[1].stringValue())<0)
					return ValueCharacter.select(typeBuilderCharacter.generator, arguments[0].stringValue());
				else
					return ValueCharacter.select(typeBuilderCharacter.generator, arguments[1].stringValue());
			}	    		
    	});
    	
    	typeBuilderBoolean.go();
    	typeBuilderInteger.go();
    	typeBuilderRational.go();
    	typeBuilderCharacter.go();
    	
    	loadSubtypes(relDatabase);
	}	
	
}