package io.vertx.codetrans;

import com.sun.source.tree.LambdaExpressionTree;
import io.vertx.codegen.type.ApiTypeInfo;
import io.vertx.codegen.type.ClassTypeInfo;
import io.vertx.codegen.type.EnumTypeInfo;
import io.vertx.codegen.type.ParameterizedTypeInfo;
import io.vertx.codegen.type.TypeInfo;
import io.vertx.codetrans.expression.ApiModel;
import io.vertx.codetrans.expression.ApiTypeModel;
import io.vertx.codetrans.expression.AsyncResultModel;
import io.vertx.codetrans.expression.BinaryExpressionModel;
import io.vertx.codetrans.expression.DataObjectClassModel;
import io.vertx.codetrans.expression.EnumExpressionModel;
import io.vertx.codetrans.expression.EnumFieldExpressionModel;
import io.vertx.codetrans.expression.ExpressionModel;
import io.vertx.codetrans.expression.JsonArrayClassModel;
import io.vertx.codetrans.expression.JsonObjectClassModel;
import io.vertx.codetrans.expression.StringLiteralModel;
import io.vertx.codetrans.expression.VariableScope;
import io.vertx.codetrans.expression.IdentifierModel;
import io.vertx.codetrans.expression.ThisModel;
import io.vertx.codetrans.statement.StatementModel;

import java.util.function.Consumer;

/**
 * @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
 */
public interface CodeBuilder {

  CodeWriter newWriter();

  String render(RunnableCompilationUnit unit, RenderMode renderMode);

  default ExpressionModel combine(ExpressionModel left, String op, ExpressionModel right) {
    return new BinaryExpressionModel(this, left, op, right);
  }

  default ExpressionModel asyncResult(String identifier, TypeInfo type) {
    return new AsyncResultModel(this, identifier, type);
  }

  ExpressionModel asyncResultHandler(LambdaExpressionTree.BodyKind bodyKind, ParameterizedTypeInfo resultType, String resultName, CodeModel body, CodeModel succeededBody, CodeModel failedBody);

  default DataObjectClassModel dataObjectClass(ClassTypeInfo type) {
    return new DataObjectClassModel(this, type);
  }

  default ApiTypeModel apiType(ApiTypeInfo type) {
    return new ApiTypeModel(this, type);
  }

  default ApiModel api(ExpressionModel expr) {
    return new ApiModel(this, expr);
  }

  default EnumExpressionModel enumType(EnumTypeInfo type) {
    return new EnumExpressionModel(this, type);
  }

  default ExpressionModel toDataObjectValue(EnumFieldExpressionModel enumField) {
    return enumField;
  }

  default ExpressionModel identifier(String name, VariableScope scope) {
    return new IdentifierModel(this, name, scope);
  }

  StatementModel variableDecl(VariableScope scope, TypeInfo type, String name, ExpressionModel initializer);

  StatementModel enhancedForLoop(String variableName, ExpressionModel expression, StatementModel body);

  StatementModel forLoop(StatementModel initializer, ExpressionModel condition, ExpressionModel update, StatementModel body);

  StatementModel sequenceForLoop(String variableName, ExpressionModel fromValue, ExpressionModel toValue, StatementModel body);

  default JsonObjectClassModel jsonObjectClassModel() {
    return new JsonObjectClassModel(this);
  }

  default JsonArrayClassModel jsonArrayClassModel() {
    return new JsonArrayClassModel(this);
  }

  default ExpressionModel jsonArrayEncoder(ExpressionModel expression) {
    return render(writer -> {
      writer.renderJsonArrayToString(expression);
    });
  }

  default ExpressionModel jsonObjectEncoder(ExpressionModel expression) {
    return render(writer -> {
      writer.renderJsonObjectToString(expression);
    });
  }

  //

  default ExpressionModel thisModel() {
    return new ThisModel(this);
  }

  default ExpressionModel forConditionalExpression(ExpressionModel condition, ExpressionModel trueExpression, ExpressionModel falseExpression) {
    return render((renderer) -> {
      renderer.renderConditionalExpression(condition, trueExpression, falseExpression);
    });
  }

  default ExpressionModel forAssign(ExpressionModel variable, ExpressionModel expression) {
    return render((renderer) -> {
      renderer.renderAssign(variable, expression);
    });
  }

  default ExpressionModel render(Consumer<CodeWriter> c) {
    CodeBuilder builder = this;
    return new ExpressionModel(builder) {
      @Override
      public void render(CodeWriter writer) {
        c.accept(writer);
      }
    };
  }
}
