/*
** Module   : CodeTemplate.java
** Abstract : base class for reports that have dynamic executable Java code 
**
** Copyright (c) 2009, Golden Code Development Corporation.
**
** -#- -I- --Date-- -----------------------Description------------------------
** 001 GES 20090113 Instantiates a test from a test definition.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package com.goldencode.harness.test;

import com.goldencode.harness.*;

/**
 * Helper to emit a standardized class that implements the {@link DynamicCode}
 * interface and contains user-specified core logic, import statements and
 * variable references.
 */
public class CodeTemplate
{
   /** Package name. */
   public final static String PKG = "com.goldencode.harness.test.temporary";
   
   /** Standard import statements (from the harness itself). */
   private final static String[] stdImports =
   {
      "com.goldencode.harness.*",
      "com.goldencode.harness.terminal.*",
      "com.goldencode.harness.test.*"
   };
                         
   /** Top of the class and method. */
   private final static String[] top =
   {
      "implements DynamicCode",
      "{",
      "   public void execute(Variable[] vars, Terminal term, StringBuilder sb)",
      "   {"
   };
   
   /** Open of the try block. */
   private final static String[] tryTop =
   {
      "      try",
      "      {"
   };
   
   /** Close of the try block (open of finally). */
   private final static String[] tryBottom =
   {
      "      }",
      "",
      "      finally",
      "      {"
   };
   
   /** Close of the finally block, the method and the class. */
   private final static String[] bottom =
   {
      "      }",
      "   }",
      "}"
   };
   
   /**
    * Generate the entire source code for the class incorporating the specific
    * data given.
    *
    * @param    className
    *           The unqualified class name of the class being generated.
    * @param    imports
    *           The list of packages to import or <code>null</code> for no
    *           user-specific imports. The text itself must not have the
    *           "import" keyword nor should it have the trailing semicolon.
    * @param    core
    *           The user-specific source code with one line per element in the
    *           array.
    * @param    vars
    *           The list of variables referenced in the source code.
    * 
    * @return   Source code for the dynamic class.
    */
   public static String emit(String     className,
                             String[]   imports,
                             String[]   core,
                             Variable[] vars)
   {
      StringBuilder sb = new StringBuilder();
      
      // package stmt
      sb.append(String.format("package %s;\n\n", PKG));
      
      // imports
      writeImports(sb, stdImports);
      if (imports != null)
         writeImports(sb, imports);
      sb.append("\n");
      
      // class and method definition
      sb.append(String.format("public class %s\n", className));
      writeBlock(sb, null, top);
      
      // local variable definitions, read from resource pool
      if (vars != null)
      {
         for (int i = 0; i < vars.length; i++)
         {
            String getter = String.format("vars[%d].%s()",
                                          i,
                                          vars[i].getGetterName());
            
            if (!vars[i].isPrimitive())
            {
               getter = String.format("(%s) %s", vars[i].getTypeName(), getter);
            }
            
            sb.append(String.format("      %s %s = %s;\n",
                                    vars[i].getTypeName(),
                                    vars[i].getName(),
                                    getter));
         }
         sb.append("\n");
      }
      
      // open try block
      writeBlock(sb, null, tryTop);
      
      // main code
      writeBlock(sb, "         ", core);
      
      // close try block (open finally)
      writeBlock(sb, null, tryBottom);
      
      // write back variables to resource pool
      if (vars != null)
      {
         for (int j = 0; j < vars.length; j++)
         {
            sb.append(String.format("         vars[%d].setValue(%s);\n",
                                    j,
                                    vars[j].getName()));
         }
      }
      
      // close finally block, method and the class
      writeBlock(sb, null, bottom);
      
      return sb.toString();
   }
   
   /**
    * Write a list of <code>import</code> statements to the buffer with each
    * line of the array as a separate line of code.
    *
    * @param    sb
    *           Output buffer.
    * @param    imports
    *           The list of imports to emit.
    */
   private static void writeImports(StringBuilder sb, String[] imports)
   {
      for (String imp : imports)
      {
         sb.append(String.format("import %s;\n", imp));
      }
   }
   
   /**
    * Write a block of text to the buffer with each line of the array as a
    * separate line of code.
    *
    * @param    sb
    *           Output buffer.
    * @param    pad
    *           Any extra indent for each line. May be <code>null</code>.
    * @param    contents
    *           The code block to write.
    */
   private static void writeBlock(StringBuilder sb,
                                  String        pad,
                                  String[]      contents)
   {
      for (String next : contents)
      {
         if (pad != null)
            sb.append(pad);
         
         sb.append(next).append("\n");
      }
   }
}
