/**
  *
  *                      OOAS Compiler
  *
  *       Copyright 2015, AIT Austrian Institute of Technology.
  * This code is based on the C# Version of the OOAS Compiler, which is
  * copyright 2015 by the Institute of Software Technology, Graz University
  * of Technology with portions copyright by the AIT Austrian Institute of
  * Technology. All rights reserved.
  *
  * SEE THE "LICENSE" FILE FOR THE TERMS UNDER WHICH THIS FILE IS PROVIDED.
  *
  * If you modify the file please update the list of contributors below to in-
  * clude your name. Please also stick to the coding convention of using TABs
  * to do the basic (block-level) indentation and spaces for anything after
  * that. (Enable the display of special chars and it should be pretty obvious
  * what this means.) Also, remove all trailing whitespace.
  *
  * Contributors:
  *               Stefan Tiran (AIT)
  *               Willibald Krenn (AIT)
  *
  */


package org.momut.ooas.ast;


import java.util.LinkedList;
import java.util.List;

import org.momut.ooas.ast.expressions.AccessExpression;
import org.momut.ooas.ast.expressions.CallExpression;
import org.momut.ooas.ast.expressions.Expression;
import org.momut.ooas.ast.expressions.IdentifierExpression;
import org.momut.ooas.ast.expressions.LeafExpression;
import org.momut.ooas.ast.expressions.ListConstructor;
import org.momut.ooas.ast.expressions.ObjectConstructor;
import org.momut.ooas.ast.expressions.UnresolvedIdentifierExpression;
import org.momut.ooas.ast.expressions.ValueExpression;
import org.momut.ooas.ast.identifiers.AttributeIdentifier;
import org.momut.ooas.ast.identifiers.EnumIdentifier;
import org.momut.ooas.ast.identifiers.Identifier;
import org.momut.ooas.ast.identifiers.IdentifierKind;
import org.momut.ooas.ast.identifiers.IdentifierList;
import org.momut.ooas.ast.identifiers.MainModule;
import org.momut.ooas.ast.identifiers.MethodIdentifier;
import org.momut.ooas.ast.identifiers.NamedActionIdentifier;
import org.momut.ooas.ast.identifiers.NondetIdentifierList;
import org.momut.ooas.ast.identifiers.ParameterIdentifier;
import org.momut.ooas.ast.identifiers.PrioIdentifierList;
import org.momut.ooas.ast.identifiers.SelfTypeIdentifier;
import org.momut.ooas.ast.identifiers.TypeIdentifier;
import org.momut.ooas.ast.statements.Assignment;
import org.momut.ooas.ast.statements.Block;
import org.momut.ooas.ast.statements.Call;
import org.momut.ooas.ast.statements.NondetBlock;
import org.momut.ooas.ast.statements.SeqBlock;
import org.momut.ooas.ast.statements.SkipStatement;
import org.momut.ooas.ast.statements.Statement;
import org.momut.ooas.ast.types.BoolType;
import org.momut.ooas.ast.types.EnumType;
import org.momut.ooas.ast.types.FunctionType;
import org.momut.ooas.ast.types.FunctionType.FunctionTypeEnum;
import org.momut.ooas.ast.types.IntType;
import org.momut.ooas.ast.types.ListType;
import org.momut.ooas.ast.types.OoActionSystemType;
import org.momut.ooas.ast.types.OpaqueType;
import org.momut.ooas.ast.types.Type;
import org.momut.ooas.parser.ParserError;
import org.momut.ooas.parser.ParserState;


public class AstBuilder {

	public static void setSystemDescription(MainModule mainModule, Identifier systemDescription) {
		final IdentifierList aDescr = new NondetIdentifierList();
		aDescr.AddElement(systemDescription);
		mainModule.SetSystemDescription(aDescr);
	}

	public static Identifier createPrioritizedSAB(LinkedList<Identifier> systems){
		final IdentifierList systemDescription = new PrioIdentifierList();
		for (final Identifier system : systems){
			systemDescription.AddElement(system);
		}
		return systemDescription;
	}

	public static Identifier createNonDetChoiceSAB(LinkedList<Identifier> systems){
		final IdentifierList systemDescription = new NondetIdentifierList();
		for (final Identifier system : systems){
			systemDescription.AddElement(system);
		}
		return systemDescription;
	}

	public static TypeIdentifier createActionSystem(boolean autocons, String name, IScope scope){
		final OoActionSystemType type = new OoActionSystemType(autocons, null, null);
		final SelfTypeIdentifier self = new SelfTypeIdentifier("self", type, scope);
		type.AddIdentifier(self, null);
		type.SetupAnonymousName(); // need to be called before setting the type identifier
		final TypeIdentifier ident = new TypeIdentifier(name, type, scope);

		type.SetTypeIdentifier(ident);
		scope.AddIdentifier(ident, null);

		return ident;
	}


	public static AttributeIdentifier createObjectAttribute(String varName, Identifier aClass, boolean doInstantiate, Identifier root){
		final Type aType = aClass.type();
		aType.SetupAnonymousName(); //not sure if necessary
		Expression anExpr;
		if (doInstantiate){
			anExpr = new ObjectConstructor(aType, varName, 0, 0);
		} else {
			anExpr = new ValueExpression<Object>(null, 0, 0, Object.class);
		}

		final AttributeIdentifier attributeIdentifier = new AttributeIdentifier(varName, aType, ((IScope)(root).type()), anExpr, false, false, false );
		((IScope)(root).type()).AddIdentifier(attributeIdentifier, null);

		return attributeIdentifier;
	}


	public static Expression boolConstant(boolean b){
		return new ValueExpression<Boolean>(b, 0, 0, Boolean.class);
	}

	public static ListType createListType(Type elemType, int max, TypeIdentifier anIdentifier){
		final ListType result = new ListType(elemType, max, null);
		result.SetupAnonymousName();
		result.SetTypeIdentifier(anIdentifier);
		return result;
	}

	public static EnumType createEnumType(String name, List<String> ranges, IScope scope){
		final EnumType type = new EnumType(null);

		EnumIdentifier enumval;
		for (final String range : ranges){
			enumval = new EnumIdentifier(range, type, scope);
			type.AddEnumSymbol(enumval);
			scope.AddIdentifier(enumval, null);
		}
		type.SetupAnonymousName();
		final TypeIdentifier ident = new TypeIdentifier(name, type, scope);

		type.SetTypeIdentifier(ident);
		scope.AddIdentifier(ident, null);
		return type;
	}


	public static Type createIntType(int low, int high){
		return new IntType(low, high, null);
	}

	public static AttributeIdentifier createListAttribute(String varname, ListType type, Identifier root){
		final Expression anExpr = new ListConstructor(0, 0);
		final AttributeIdentifier attributeIdentifier = new AttributeIdentifier(varname, type, ((IScope)(root).type()), anExpr, false, false, false );
		((IScope)(root).type()).AddIdentifier(attributeIdentifier, null);

		return attributeIdentifier;
	}

	public static AttributeIdentifier createEnumAttribute(String varname, EnumType type, String init, Identifier root){
		final Identifier initIdent = root.definingScope().ResolveIdentifier(init);
		final Expression anExpr = new IdentifierExpression(initIdent, 0, 0);
		final AttributeIdentifier attributeIdentifier = new AttributeIdentifier(varname, type, ((IScope)(root).type()), anExpr, false, false, false );
		((IScope)(root).type()).AddIdentifier(attributeIdentifier, null);

		return attributeIdentifier;
	}


	public static AttributeIdentifier createBoolAttribute(String varname, boolean isStatic, boolean init, Identifier root){
		final Expression anExpr = new ValueExpression<Boolean>(init, 0, 0, Boolean.class);
		final Type aType = new BoolType(null);
		aType.SetupAnonymousName();
		final AttributeIdentifier attributeIdentifier = new AttributeIdentifier(varname, aType, ((IScope)(root).type()), anExpr, isStatic, false, false );
		((IScope)(root).type()).AddIdentifier(attributeIdentifier, null);

		return attributeIdentifier;
	}

	public static AttributeIdentifier createAttribute(String varname, boolean isStatic, Type aType, Expression anExpr, IScope scope){
		final AttributeIdentifier attributeIdentifier = new AttributeIdentifier(varname, aType, scope, anExpr, isStatic, false, false );
		scope.AddIdentifier(attributeIdentifier, null);

		return attributeIdentifier;
	}

	public static MethodIdentifier createMethod(
			String name,
			LinkedList<ParameterIdentifier> params,
			Type returnType,
			Identifier as,
			Statement body
			) {
		final LinkedList<Type> parameterTypes = new LinkedList<Type>();
		for (final ParameterIdentifier param : params) {
			parameterTypes.add(param.type());
		}

		final FunctionType methodType = new FunctionType(FunctionType.FunctionTypeEnum.Method, parameterTypes, returnType);

		final MethodIdentifier ident = new MethodIdentifier(name, methodType, (IScope)as.type());
		((IScope)(as.type())).AddIdentifier(ident, null);
		ident.SetParentScope((IScope)as.type());
		ident.SetBody(body);

		for (final ParameterIdentifier p : params) {
			ident.AddParameter(p);
		}
		methodType.SetupAnonymousName();

		return ident;
	}

	public static NamedActionIdentifier createActionWithoutBody(
			FunctionType.FunctionTypeEnum functionType,
			String name,
			LinkedList<Type> parameters,
			Identifier as)
	{
		final FunctionType actionType = new FunctionType(functionType, parameters, null);
		actionType.SetupAnonymousName();
		final NamedActionIdentifier ident = new NamedActionIdentifier(name, actionType, (IScope)as.type());
		((IScope)(as.type())).AddIdentifier(ident, null);
		return ident;
	}

	public static Identifier createAction(
			FunctionType.FunctionTypeEnum functionType,
			String name,
			LinkedList<Type> parameters,
			Identifier as,
			Statement body)
	{
		final FunctionType actionType = new FunctionType(functionType, parameters, null);
		actionType.SetupAnonymousName();
		final NamedActionIdentifier ident = new NamedActionIdentifier(name, actionType, (IScope)as.type());
		((IScope)(as.type())).AddIdentifier(ident, null);
		ident.SetBody(body);
		return ident;
	}

	public static Block createNonDetBlock(LinkedList<Identifier> actions){
		final Block block = new NondetBlock(0, 0);

		for (final Identifier action : actions) {
			final Block block2 = new SeqBlock(0, 0);
			final CallExpression ce = new CallExpression(
					new IdentifierExpression(action,0,0),
					null,
					0,
					0,
					block
					);
			block2.AddStatement(new Call(ce, 0, 0));
			block.AddStatement(block2);
		}

		return block;
	}


	public static MainModule buildHelloWorld(){
		// == (1) define the main module, i.e. main scope ==
		final MainModule mainModule = new MainModule();

		// == (2) let's define the single action system type that is contained/defined in this main module ==
		final OoActionSystemType greeterType = new OoActionSystemType(
				true,      // let's suppose this is auto-constructing
				null,      // refines no other action system
				null       // type identifier is set below
		);
		greeterType.AddIdentifier(new SelfTypeIdentifier("self", greeterType, greeterType), null);
		greeterType.SetupAnonymousName(); // need to be called before setting the type identifier

		// -- instantiate the type identifier for our action system type --
		final TypeIdentifier greeterTypeIdentifier = new TypeIdentifier(
				"greeter",  // name
				greeterType,// bind it to the type
				mainModule  // scope where this is defined
		);
		// -- wire up the type identifier with the type (doubly linked therefore this more cumbersome two-call approach)
		greeterType.SetTypeIdentifier(greeterTypeIdentifier);
		mainModule.AddIdentifier(greeterTypeIdentifier, null);


		// == (3) define the sole observable action of the type ==
		// -- define the (implicitly declared type of the action) --lBlock
		final FunctionType helloWorldActionType = new FunctionType(  // this type is anonymous and doesn't have a name!
				FunctionTypeEnum.Observable,   // This is observable, hence an action
				new LinkedList<Type>(), // no parameters
				null                           // void return
		);
		helloWorldActionType.SetupAnonymousName(); // need to be called before setting the type identifier
		// -- define the actual identifier incl. impl. --
		final NamedActionIdentifier helloWorldActionIdentifier = new NamedActionIdentifier(
				"HelloWorld",           // name of the action
				helloWorldActionType,   // type of the action
				greeterType             // defining scope
		);
		// -- register the new identifier with the current scope
		greeterType.AddIdentifier(helloWorldActionIdentifier, null);
		// -- add implementation of the action
		//helloWorldActionIdentifier.SetBody(new SkipStatement(0, 0));
		helloWorldActionIdentifier.SetBody(new SkipStatement(0, 0));


		// == (4) define the do-od block of the action system type
		final Block greeterDoOdBlock = new SeqBlock(0, 0);
		greeterType.SetDoOdBlock(greeterDoOdBlock);
		// -- add a call statement to the dood block --
		final CallExpression callHelloWorldExpression = new CallExpression(
				new IdentifierExpression(helloWorldActionIdentifier, 0, 0),  // what to call
				null,             // arguments
				0,                // line
				0,                // col
				greeterDoOdBlock  // scope
		);
		greeterDoOdBlock.AddStatement(new Call(callHelloWorldExpression, 0, 0));


		// == (5) add scheduling information (SAB block) ==
		final IdentifierList systemDescription = new NondetIdentifierList();
		systemDescription.AddElement(greeterTypeIdentifier);
		mainModule.SetSystemDescription(systemDescription);

		return mainModule;
	}


	public static LeafExpression createIntConstant(Integer i)
	{
		return new ValueExpression<Integer>(i, 0, 0, Integer.class);
	}

	public static Expression createIdentifierAccessExpressionWithoutResolve(String name, IScope scope){
		final IdentifierExpression uIdent = new UnresolvedIdentifierExpression(name, scope);
		final IdentifierExpression aself = new IdentifierExpression(scope.ResolveIdentifier("self"), 0, 0);
		aself.setIsSelf(true);
		final AccessExpression exp = new AccessExpression(aself, uIdent, 0, 0);
		return exp;
	}

	public static Expression createIdentifierAccessExpression(Identifier attr){
		final IdentifierExpression uIdent = new IdentifierExpression(attr, 0, 0);

		final IdentifierExpression aself = new IdentifierExpression(attr.definingScope().ResolveIdentifier("self"), 0, 0);
		aself.setIsSelf(true);
		final AccessExpression exp = new AccessExpression(aself, uIdent, 0, 0);
		return exp;
	}

	public static CallExpression createMethodAccessExpression(Expression subexpr, List<Expression> m_params, IScope scope){
		return new CallExpression(subexpr, m_params, 0, 0, scope);
	}

	public static Statement createAssignment(AttributeIdentifier attr, ListConstructor lc) {
		return new Assignment(createIdentifierAccessExpression(attr), lc, null, 0, 0);
	}

	public static Statement createAssignment(Expression exp1, Expression exp2) {
		return new Assignment(exp1, exp2, null, 0, 0);
	}

	public static Statement createAssignment(AttributeIdentifier attr, boolean b) {
		return new Assignment(createIdentifierAccessExpression(attr), new ValueExpression<Boolean>(b, 0, 0, Boolean.class),null,0,0);
	}

	public static Statement createAssignment2(
			AttributeIdentifier attr, Identifier instance) {
		//FIXXME here be dragons
		return new Assignment(createIdentifierAccessExpression(attr), new IdentifierExpression(instance, 0, 0), null, 0, 0);
	}

	public static Statement createAssignmentWithoutResolve(String leftName, IScope leftScope, Expression right){
		return new Assignment(new UnresolvedIdentifierExpression(leftName, leftScope), right , null, 0, 0);
	}

	public static Statement createAssignmentWithoutResolve(String leftName, IScope leftScope, String rightName, IScope rightScope){
		return new Assignment(new UnresolvedIdentifierExpression(leftName, leftScope), new UnresolvedIdentifierExpression(rightName, rightScope), null, 0, 0);
	}

	public static Statement createAssignment(
			AttributeIdentifier attr, Identifier instance) {
		return new Assignment(createIdentifierAccessExpression(attr), createIdentifierAccessExpression(instance), null, 0, 0);
	}

	public static Expression createMethodAccessExpressionWithLocalVar(String objectName,
			String methodName, List<Expression> m_params, IScope scope) {
		final Expression subexpr = new AccessExpression(
				new UnresolvedIdentifierExpression(objectName, scope),
				new UnresolvedIdentifierExpression(methodName, scope),
				0,0);
		return new CallExpression(subexpr, m_params, 0, 0, scope);
	}


	public static Expression createMethodAccessExpressionWithoutResolveAtAll(String objectName,
			String methodName, List<Expression> m_params, IScope scope) {
		final UnresolvedIdentifierExpression modelSelf = new UnresolvedIdentifierExpression("self", scope);
		modelSelf.setIsSelf(true);

		final Expression subexpr = new AccessExpression(
				new AccessExpression(
						modelSelf,
						new UnresolvedIdentifierExpression(objectName, scope),
						0, 0),
				new UnresolvedIdentifierExpression(methodName, scope),
				0,0);
		return new CallExpression(subexpr, m_params, 0, 0, scope);
	}


	public static Statement createAssignment(Identifier attr,
			Identifier instance) {
		return new Assignment(createIdentifierAccessExpression(attr), createIdentifierAccessExpression(instance), null, 0, 0);
	}

	public static Statement createAssignment(Identifier attr,
			Expression exp) {
		return new Assignment(createIdentifierAccessExpression(attr), exp, null, 0, 0);
	}

	public static LeafExpression createNullPointerConstant()
	{
		return new ValueExpression<Object>(null, 0, 0, Object.class);
	}

	public static Expression intConstant(int i) {
		return new ValueExpression<Integer>(i, 0, 0, Integer.class);
	}

	public static void FixupNamedTypes(ParserState pState)
	{
		/*named type refs that could not be resolved in the first run, have to
		  be resolved now. */
		for (final OpaqueType ntype: pState.typesToFixUp)
		{
			if (ntype.resolvedType() == null)
			{
				//ntype.identifier
				final Identifier asym = pState.Lookup(ntype.identifier().tokenText(), ntype.identifier().definingScope());
				if ((asym == null) || (asym.kind() != IdentifierKind.TypeIdentifier))
				{
					final ParserError error = new ParserError(pState.filename, ntype.identifier().line(), ntype.identifier().column(),
							String.format("Can not resolve %s to a named type", ntype.identifier().tokenText()));
					pState.AddErrorMessage(error);
				}
				else
					ntype.SetResolvedType(asym.type());
			}
		}
		pState.typesToFixUp.clear();
	}


}
