/**
 * 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.drill.exec.expr;

import java.io.IOException;

import org.apache.drill.exec.compile.TemplateClassDefinition;
import org.apache.drill.exec.compile.sig.MappingSet;
import org.apache.drill.exec.expr.fn.FunctionImplementationRegistry;

import com.google.common.base.Preconditions;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;

/**
 * A code generator is responsible for generating the Java source code required to complete the implementation of an
 * abstract template. It is used with a class transformer to merge precompiled template code with runtime generated and
 * compiled query specific code to create a runtime instance.
 *
 * A code generator can contain one or more ClassGenerators that implement outer and inner classes associated with a
 * particular runtime generated instance.
 *
 * @param <T>
 *          The interface that results from compiling and merging the runtime code that is generated.
 */
public class CodeGenerator<T> {

  private static final String PACKAGE_NAME = "org.apache.drill.exec.test.generated";

  private final TemplateClassDefinition<T> definition;
  private final String className;
  private final String fqcn;

  private final JCodeModel model;
  private final ClassGenerator<T> rootGenerator;
  private String generatedCode;
  private String generifiedCode;

  CodeGenerator(TemplateClassDefinition<T> definition, FunctionImplementationRegistry funcRegistry) {
    this(ClassGenerator.getDefaultMapping(), definition, funcRegistry);
  }

  CodeGenerator(MappingSet mappingSet, TemplateClassDefinition<T> definition,
      FunctionImplementationRegistry funcRegistry) {
    Preconditions.checkNotNull(definition.getSignature(),
        "The signature for defintion %s was incorrectly initialized.", definition);
    this.definition = definition;
    this.className = definition.getExternalInterface().getSimpleName() + "Gen" + definition.getNextClassNumber();
    this.fqcn = PACKAGE_NAME + "." + className;
    try {
      this.model = new JCodeModel();
      JDefinedClass clazz = model._package(PACKAGE_NAME)._class(className);
      rootGenerator = new ClassGenerator<>(this, mappingSet, definition.getSignature(), new EvaluationVisitor(
          funcRegistry), clazz, model);
    } catch (JClassAlreadyExistsException e) {
      throw new IllegalStateException(e);
    }
  }

  public ClassGenerator<T> getRoot() {
    return rootGenerator;
  }

  public void generate() throws IOException {
    rootGenerator.flushCode();

    SingleClassStringWriter w = new SingleClassStringWriter();
    model.build(w);

    this.generatedCode = w.getCode().toString();
    this.generifiedCode = generatedCode.replaceAll(this.className, "GenericGenerated");

  }

  public String generateAndGet() throws IOException {
    generate();
    return generatedCode;
  }

  public String getGeneratedCode() {
    return generatedCode;
  }

  public TemplateClassDefinition<T> getDefinition() {
    return definition;
  }

  public String getMaterializedClassName() {
    return fqcn;
  }

  public static <T> CodeGenerator<T> get(TemplateClassDefinition<T> definition,
      FunctionImplementationRegistry funcRegistry) {
    return new CodeGenerator<T>(definition, funcRegistry);
  }

  public static <T> ClassGenerator<T> getRoot(TemplateClassDefinition<T> definition,
      FunctionImplementationRegistry funcRegistry) {
    return get(definition, funcRegistry).getRoot();
  }

  public static <T> ClassGenerator<T> getRoot(MappingSet mappingSet, TemplateClassDefinition<T> definition,
      FunctionImplementationRegistry funcRegistry) {
    return get(mappingSet, definition, funcRegistry).getRoot();
  }

  public static <T> CodeGenerator<T> get(MappingSet mappingSet, TemplateClassDefinition<T> definition,
      FunctionImplementationRegistry funcRegistry) {
    return new CodeGenerator<T>(mappingSet, definition, funcRegistry);
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((definition == null) ? 0 : definition.hashCode());
    result = prime * result + ((generatedCode == null) ? 0 : generatedCode.hashCode());
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    CodeGenerator other = (CodeGenerator) obj;
    if (definition == null) {
      if (other.definition != null) {
        return false;
      }
    } else if (!definition.equals(other.definition)) {
      return false;
    }
    if (generatedCode == null) {
      if (other.generatedCode != null) {
        return false;
      }
    } else if (!generatedCode.equals(other.generatedCode)) {
      return false;
    }
    return true;
  }

}

@@ -4968,26 +4968,27 @@
 lt + ((gener
-at
+ifi
 edCode == nu
@@ -4998,26 +4998,27 @@
  ? 0 : gener
-at
+ifi
 edCode.hashC
@@ -5115,25 +5115,24 @@
 this == obj)
- 
 %7B%0A      retu
@@ -5158,33 +5158,32 @@
 if (obj == null)
- 
 %7B%0A      return f
@@ -5231,17 +5231,16 @@
 Class())
- 
 %7B%0A      
@@ -5363,33 +5363,32 @@
 inition != null)
- 
 %7B%0A        return
@@ -5455,17 +5455,16 @@
 nition))
- 
 %7B%0A      
@@ -5492,26 +5492,27 @@
    if (gener
-at
+ifi
 edCode == nu
@@ -5534,26 +5534,27 @@
 (other.gener
-at
+ifi
 edCode != nu
@@ -5552,25 +5552,24 @@
 ode != null)
- 
 %7B%0A        re
@@ -5580,32 +5580,33 @@
  false;%0A      %7D%0A
+%0A
     %7D else if (!
@@ -5606,26 +5606,27 @@
 e if (!gener
-at
+ifi
 edCode.equal
@@ -5642,18 +5642,19 @@
 ener
-at
+ifi
 edCode))
  %7B%0A 
@@ -5649,17 +5649,16 @@
 edCode))
- 
 %7B%0A      

Fix code generator comparisons