/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.plc4x.codegen.ast;

import java.util.Collections;
import java.util.List;

/**
 * General Factory method to use for Code Generation.
 * Generally, the Structure which is generated by this class is a (incomplete) Abstact Syntax Tree
 * which covers a subset of things which is possible in many programming languages.
 * This tree is the basis for the generation of code in many languages which is performed by
 * the implementations of the {@link Generator} interface.
 *
 * In this sense the AST is Language Agnostic and should be seen as such. It is oriented at Java
 * as the whole thing is java based but is a bit more generic that it would need to be for java alone.
 * This is something to keep in mind.
 *
 * So some examples on how to implement some things with the AST.
 *
 * Variable Definition / Assignment
 * <code>
 *   Expressions.declaration("a", Primitive.DOUBLE)
 * </code>
 * is similar to
 * <code>
 *   Double a;
 * </code>
 * in java, or in Python
 * <code>
 *   a: float = None
 * </code>
 * (we use Type Hints) and Python cannot declare variables without initialization.
 *
 * Another thing is to initialize a variable with a constant value:
 * <code>
 *   Expressions.declaration("a", Expressions.constant(5.0))
 * </code>
 * this will lead (in Java) to
 * <code>
 *   Double a = 5.0;
 * </code>
 * The type is inferred for the constant expression from 5.0 and is then passed to the variable.
 * In Python this will become
 * <code>
 *   a: float = 5.0
 * </code>
 *
 * If Statement:
 * A more complex example with an if statement is
 * <code>
 *   Block.build()
 *      .add(Expressions.declaration("pi", Primitive.DOUBLE))
 *      .add(
 *          Expressions.ifElse(
 *             Expressions.binaryExpression(
 *                 Expressions.BOOLEAN,
 *                 Expressions.parameter("x", Primitive.DOUBLE),
 *                 Expressions.constant(5.0),
 *                 BinaryExpression.Operation.EQ
 *             ),
 *             Block.build().add(
 *                 Expressions.assignment(
 *                     Expressions.parameter("pi"),
 *                     Expressions.constant(3.141)
 *                 )
 *            )
 *      )
 *      .toBlock()
 * </code>
 * This could will transform to something like (in Java)
 * <code>
 *   Double pi;
 *   if (x == 5.0) {
 *     pi = 3.141;
 *   }
 * </code>
 *
 * More Examples of (complex) structures can be found, e.g., in the
 * {@link org.apache.plc4x.codegen.util.PojoFactory} or in the {@link org.apache.plc4x.codegen.util.EnumFactory}
 * which are "layers" on top of this AST whcih make it easier to create POJOs or ENUM-like constructs.
 * They define the class and all fields / methods / getters / setters and constructor and such stuff.
 *
 * @author julian
 */
public class Expressions {

    private Expressions() {
        // do not instantiate
    }

    /**
     * Assign a value to a target (field or parameter)
     * @param target Where the value is assigned
     * @param value What to assign
     * @return Assignment Expression
     */
    public static Expression assignment(Expression target, Node value) {
        return new AssignementExpression(target, value);
    }

    /**
     * Base for all Binary Expression, i.e., Expressions which take
     * two inputs and return one Output.
     * Examples are Comparators, Math, ... .
     * @param type Type of the returned expression
     * @param left
     * @param right
     * @param op
     * @return
     */
    public static Expression binaryExpression(TypeDefinition type, Node left, Node right, BinaryExpression.Operation op) {
        return new BinaryExpression(type, left, right, op);
    }

    /**
     * A Block of code.
     * @param statements
     * @return
     */
    public static Statement block(List<Node> statements) {
        return new Block(statements);
    }

    /**
     * A block of code.
     * @param statements
     * @return
     */
    public static Statement block(Node... statements) {
        return new Block(statements);
    }

    /**
     * Regular (dynamic) call.
     * @param instance
     * @param method
     * @param arguments
     */
    public static Expression call(Node instance, Method method, Node... arguments) {
        return new CallExpression(method, instance, arguments);
    }

    /**
     * Static call (call to a static method)
     * 
     * TODO check if Method is static
     * 
     * @param method
     * @param arguments
     */
    public static Expression staticCall(Method method, Node... arguments) {
        return new CallExpression(method, null, arguments);
    }

    /**
     * Simple if-then-else.
     * If no else is needed, orElse can be null.
     */
    public static Statement ifElse(Expression condition, Block then, Block orElse) {
        return new IfStatement(condition, then, orElse);
    }

    /**
     * Simple if-then-else.
     * If no else is needed, orElse can be null.
     */
    public static Statement ifElse(Expression condition, Block then) {
        return new IfStatement(condition, then, null);
    }

    /**
     * Conditional Statement of the form
     * <code>
     * if (cond1) {
     *  ...
     * } else if (cond2) {
     *  ...
     * } else {
     *  ...
     * }
     * </code>
     */
    public static Statement conditionalStatement(List<Expression> condition, List<Block> blocks) {
        return new IfStatement(condition, blocks);
    }

    /**
     * Defines a compile time constant and infers the type based on what java would do.
     */
    public static Expression constant(Object value) {
        return new ConstantExpression(value);
    }

    /**
     * Define a compile time constant and also passes
     * the expected type for usage in the code generation later.
     */
    public static Expression constant(TypeDefinition type, Object value) {
        return new ConstantExpression(value);
    }

    /**
     * Declares a constant (no field).
     * Variable type is infered from the initializing expression.
     */
    public static Statement declaration(String variable, Expression initializer) {
        return new DeclarationStatement(parameter(variable, initializer.getType()), initializer);
    }

    public static Statement declaration(ParameterExpression variable, Expression initializer) {
        return new DeclarationStatement(variable, initializer);
    }

    /**
     * Declares a constant (no field), which is not initialized.
     */
    public static Statement declaration(String variable, TypeDefinition type) {
        return new DeclarationStatement(parameter(variable, type), null);
    }

    /**
     * Reference to a field in the surrounding class.
     */
    public static Expression field(String name) {
        return new FieldReference(UnknownType.INSTANCE, name);
    }

    /**
     * Reference to a field on the given target
     */
    public static Expression field(Node target, String name) {
        return new FieldReference(UnknownType.INSTANCE, name, target);
    }

    /**
     * Adds a line of comment.
     */
    public static Node comment(String comment) {
        return new LineComment(comment);
    }

    /**
     * Simple call to a method which throws no exception and that stuff.
     */
    public static Expression call(Node target, String methodName, Node... arguments) {
        return new CallExpression(
            new Method(UnknownType.INSTANCE, methodName, UnknownType.INSTANCE,
                Collections.<TypeDefinition>emptyList(), Collections.<ExceptionType>emptyList()),
            target,
            arguments
        );
    }

    public static Expression call(Method method, Node target, Node... arguments) {
        return new CallExpression(method, target, arguments);
    }

    /**
     * Static call.
     */
    public static Expression call(Method method, Node... arguments) {
        return new CallExpression(method, null, arguments);
    }

    /**
     * Reference to a Method, similar than field-reference
     * @return
     */
    public static Method method(TypeDefinition definingClass, String name, TypeDefinition returnType, List<TypeDefinition> parameterTypes, List<ExceptionType> exceptions) {
        return new Method(definingClass, name, returnType, parameterTypes, exceptions);
    }

    /**
     * References a variable.
     */
    public static ParameterExpression parameter(String name, TypeDefinition type) {
        return new ParameterExpression(type, name);
    }

    /**
     * New Instance of Class / Type.
     */
    public static Expression new_(TypeDefinition type, Node... arguments) {
        return new NewExpression(type, arguments);
    }

    /**
     * New Instance of Class / Type.
     */
    public static Expression new_(TypeDefinition type, List<Node> arguments) {
        return new NewExpression(type, arguments);
    }

    /**
     * Return Statements.
     */
    public static ReturnStatement return_(Expression value) {
        return new ReturnStatement(value);
    }

    /**
     * Declares a Class as type.
     */
    public static TypeDefinition typeOf(String className) {
        return new TypeDefinition(className);
    }
}
