// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.sdk.internal.protocolparser.dynamicimpl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.chromium.sdk.internal.protocolparser.EnumValueCondition;
import org.chromium.sdk.internal.protocolparser.JsonProtocolModelParseException;
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.JavaCodeGenerator.FileScope;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.JavaCodeGenerator.MethodScope;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.JavaCodeGenerator.Util;

class EnumParser<T extends Enum<T>> extends QuickParser<T> {
  public static <T extends Enum<T>> EnumParser<T> create(Class<T> enumTypeClass,
      boolean isNullable) throws JsonProtocolModelParseException {
    return new EnumParser<T>(enumTypeClass, isNullable);
  }

  private final Method methodValueOf;
  private final boolean isNullable;
  private final Class<T> enumClass;

  private EnumParser(Class<T> enumClass, boolean isNullable)
      throws JsonProtocolModelParseException {
    this.enumClass = enumClass;
    this.isNullable = isNullable;

    try {
      this.methodValueOf = enumClass.getMethod("valueOf", String.class);
    } catch (NoSuchMethodException e) {
      throw new JsonProtocolModelParseException(
          "Failed to find valueOf method for parsing strings", e);
    }
  }

  @Override
  public T parseValueQuick(Object value) throws JsonProtocolParseException {
    if (isNullable && value == null) {
      return null;
    }
    if (value instanceof String == false) {
      throw new JsonProtocolParseException("String value expected");
    }
    String stringValue = (String) value;
    stringValue = EnumValueCondition.decorateEnumConstantName(stringValue);
    T result;
    try {
      result = enumClass.cast(methodValueOf.invoke(null, stringValue));
    } catch (IllegalArgumentException e) {
      throw new JsonProtocolParseException("Failed to parse enum constant " + stringValue, e);
    } catch (IllegalAccessException e) {
      throw new JsonProtocolParseException("Failed to call valueOf method", e);
    } catch (InvocationTargetException e) {
      throw new JsonProtocolParseException("Failed to call valueOf method", e);
    }
    if (result == null) {
      throw new JsonProtocolParseException("Failed to parse value " + value + " as enum");
    }
    return result;
  }

  @Override
  public void appendFinishedValueTypeNameJava(FileScope scope) {
    scope.append(enumClass.getCanonicalName());
  }

  @Override
  public void appendInternalValueTypeNameJava(FileScope scope) {
    appendFinishedValueTypeNameJava(scope);
  }

  @Override
  void writeParseQuickCode(MethodScope scope, String valueRef,
      String resultRef) {

    for (Object constant : enumClass.getEnumConstants()) {
      String name = constant.toString();
      if (!name.toUpperCase().equals(name)) {
        throw new RuntimeException();
      }
    }

    if (isNullable) {
      scope.startLine("if (" + valueRef + " == null) {\n");
      scope.startLine("  return null;\n");
      scope.startLine("}\n");
    }
    scope.startLine("if (" + valueRef + " instanceof String == false) {\n");
    scope.startLine("  throw new " + Util.BASE_PACKAGE +
        ".JsonProtocolParseException(\"String value expected\");\n");
    scope.startLine("}\n");
    scope.startLine("String stringValue = (String) " + valueRef + ";\n");
    scope.startLine("stringValue = stringValue.toUpperCase();\n");
    scope.startLine(enumClass.getCanonicalName() + " " + resultRef + " = " +
        enumClass.getCanonicalName() + ".valueOf(");
    scope.append("stringValue);\n");
  }

  @Override
  boolean javaCodeThrowsException() {
    return true;
  }
}
