/*
 * Copyright 2006 Google Inc.
 * 
 * Licensed 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 com.google.gwt.user.rebind;

import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory.JavaSourceCategory;

import java.io.PrintWriter;

class ClassSourceFileComposer implements SourceWriter {

  /**
   * For the interior of a '*' style comment.
   */
  private static final String STAR_COMMENT_LINE = " * ";

  private boolean atStart;

  /**
   * Either STAR/BLOCK comment line, not pulled out into a ENUM class because
   * only used by this class.
   */
  private String commentIndicator;

  private final GeneratorContext ctx;

  /**
   * Are you currently in a comment?
   */
  private boolean inComment;

  private int indent;

  private final PrintWriter printWriter;

  ClassSourceFileComposer(GeneratorContext ctx, PrintWriter printWriter,
      String targetPackageName, String[] annotationDeclarations,
      String targetClassShortName, String superClassName,
      String[] interfaceNames, String[] imports, JavaSourceCategory category,
      String classJavaDocComment) {
    this.ctx = ctx;
    this.printWriter = printWriter;
    if (targetPackageName == null) {
      throw new IllegalArgumentException("Cannot supply a null package name to"
          + targetClassShortName);
    }
    // TODO: support a user-specified file header
    if (targetPackageName.length() > 0) {
      println("package " + targetPackageName + ";");
    }

    if (imports != null && imports.length > 0) {
      println();
      for (int i = 0, n = imports.length; i < n; ++i) {
        println("import " + imports[i] + ";");
      }
    }
    if (classJavaDocComment != null) {
      beginJavaDocComment();
      print(classJavaDocComment);
      endJavaDocComment();
    } else {
      // beginJavaDocComment adds its own leading newline, make up for it here.
      println();
    }
    for (String annotation : annotationDeclarations) {
      println(annotation);
    }
    if (category == JavaSourceCategory.CLASS) {
      emitClassDecl(targetClassShortName, superClassName, interfaceNames);
    } else {
      emitInterfaceDecl(targetClassShortName, superClassName, interfaceNames);
    }
    println(" {");
    indent();
  }

  /**
   * Begin emitting a JavaDoc comment.
   */
  public void beginJavaDocComment() {
    println("\n/**");
    inComment = true;
    commentIndicator = STAR_COMMENT_LINE;
  }

  public void commit(TreeLogger logger) {
    outdent();
    println("}");
    printWriter.close();
    // If generating a class on the command line, may not have a
    if (ctx != null) {
      ctx.commit(logger, printWriter);
    }
  }

  /**
   * End emitting a JavaDoc comment.
   */
  public void endJavaDocComment() {
    inComment = false;
    println("\n */");
  }

  public void indent() {
    ++indent;
  }

  public void indentln(String s) {
    indent();
    println(s);
    outdent();
  }

  public void indentln(String s, Object... args) {
    indentln(String.format(s, args));
  }

  public void outdent() {
    --indent;
  }

  public void print(String s) {
    int pos = 0;
    for (;;) {
      // If we just printed a newline, print an indent.
      if (atStart) {
        for (int j = 0; j < indent; ++j) {
          printWriter.print("  ");
        }
        if (inComment) {
          printWriter.print(commentIndicator);
        }
        atStart = false;
      }

      // Now find the next newline.
      int nl = s.indexOf('\n', pos);

      // If there's no newline or it's at the end of the string, just write
      // the rest of the string, and we're done.
      if (nl == -1 || nl == s.length() - 1) {
        printWriter.write(s, pos, s.length() - pos);
        return;
      }

      // Otherwise, write up to and including the newline, note that we just
      // printed a newline, and loop on the rest of the string.
      printWriter.write(s, pos, nl + 1 - pos);
      atStart = true;
      pos = nl + 1;
    }
  }

  public void print(String s, Object... args) {
    print(String.format(s, args));
  }

  public void println() {
    print("\n");
    atStart = true;
  }

  public void println(String s) {
    print(s + "\n");
    atStart = true;
  }

  public void println(String s, Object... args) {
    println(String.format(s, args));
  }

  private void emitClassDecl(String targetClassShortName,
      String superClassName, String[] interfaceNames) {
    print("public class " + targetClassShortName);
    if (superClassName != null) {
      print(" extends " + superClassName);
    }
    if (interfaceNames != null && interfaceNames.length > 0) {
      print(" implements ");
      for (int i = 0, n = interfaceNames.length; i < n; ++i) {
        if (i > 0) {
          print(", ");
        }
        print(interfaceNames[i]);
      }
    }
  }

  private void emitInterfaceDecl(String targetClassShortName,
      String superClassName, String[] interfaceNames) {
    if (superClassName != null) {
      throw new IllegalArgumentException("Cannot set superclass name "
          + superClassName + " on a interface.");
    }
    print("public interface " + targetClassShortName);
    if (interfaceNames != null && interfaceNames.length > 0) {
      print(" extends ");
      for (int i = 0; i < interfaceNames.length; ++i) {
        if (i > 0) {
          print(", ");
        }
        print(interfaceNames[i]);
      }
    }
  }
}
