/**
 * Copyright (c) 2012, Andy Janata
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this list of conditions
 *   and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice, this list of
 *   conditions and the following disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package net.socialgamer.cah;

import java.io.File;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import net.socialgamer.cah.Constants.DoubleLocalizable;
import net.socialgamer.cah.Constants.Localizable;


/**
 * Analyze the server enums using reflection and create a Javascript version for the client to use.
 * 
 * @author Andy Janata (ajanata@socialgamer.net)
 */
public class UpdateJsConstants {

  private static final String enumHeaderFmt =
      "cah.$.%s = function() {\r\n  // Dummy constructor to make Eclipse auto-complete.\r\n};\r\n";
  private static final String enumDummyFmt =
      "cah.$.%s.prototype.dummyForAutocomplete = undefined;\r\n";
  private static final String enumValueFmt = "cah.$.%s.%s = \"%s\";\r\n";
  private static final String msgHeaderFmt = "cah.$.%s_msg = {};\r\n";
  private static final String msgValueFmt = "cah.$.%s_msg['%s'] = \"%s\";\r\n";
  private static final String msg2HeaderFmt = "cah.$.%s_msg_2 = {};\r\n";
  private static final String msg2ValueFmt = "cah.$.%s_msg_2['%s'] = \"%s\";\r\n";

  /**
   * Run the enum updater. The working directory for this program should be the project's root.
   * 
   * @param args
   */
  @SuppressWarnings("rawtypes")
  public static void main(final String[] args) throws Exception {
    final String dir = "WebContent/js/";
    final File outFile = new File(dir + "cah.constants.js");
    assert outFile.canWrite();
    assert outFile.delete();
    assert outFile.createNewFile();
    final PrintWriter writer = new PrintWriter(outFile);

    writer.println("// This file is automatically generated. Do not edit.");
    writer.println();
    writer.println("cah.$ = {};");
    writer.println();

    final Class[] classes = Constants.class.getClasses();
    for (final Class<?> c : classes) {
      // We only care about enums.
      if (!c.isEnum()) {
        continue;
      }
      // We need to know the name of the enum itself, not that it's Constants$Foo.
      final String cName = c.getName().split("\\$")[1];
      System.out.println(cName);
      writer.format(enumHeaderFmt, cName);
      writer.format(enumDummyFmt, cName);
      final Map<String, String> values = getEnumValues(c);
      for (final String key : values.keySet()) {
        final String value = values.get(key);
        writer.format(enumValueFmt, cName, key, value);
      }
      if (Localizable.class.isAssignableFrom(c) || DoubleLocalizable.class.isAssignableFrom(c)) {
        System.out.println(cName + "_msg");
        writer.format(msgHeaderFmt, cName);
        final Map<String, String> messages = getEnumMessageValues(c);
        for (final String key : messages.keySet()) {
          final String value = messages.get(key);
          writer.format(msgValueFmt, cName, key, value);
        }
      }
      if (DoubleLocalizable.class.isAssignableFrom(c)) {
        System.out.println(cName + "_msg_2");
        writer.format(msg2HeaderFmt, cName);
        final Map<String, String> messages = getEnumMessage2Values(c);
        for (final String key : messages.keySet()) {
          final String value = messages.get(key);
          writer.format(msg2ValueFmt, cName, key, value);
        }
      }
      writer.println();
    }
    writer.flush();
    writer.close();
  }

  /**
   * Return a map of enum values in an Enum class, with the enum field names as keys and the values
   * of toString() as the values.
   * 
   * @param enumClass
   *          The Enum to examine.
   * @return Map of field name -> toString values.
   * @throws IllegalArgumentException
   *           Thrown if {@code enumClass} isn't actually an enum.
   * @throws IllegalAccessException
   *           If the value was unable to be retrieved.
   */
  private static Map<String, String> getEnumValues(final Class<?> enumClass)
      throws IllegalArgumentException, IllegalAccessException {
    if (!enumClass.isEnum()) {
      throw new IllegalArgumentException(enumClass.getName() + " is not an enum");
    }

    final Field[] flds = enumClass.getDeclaredFields();
    final HashMap<String, String> enumMap = new HashMap<String, String>();
    for (final Field f : flds) {
      if (f.isEnumConstant()) {
        enumMap.put(f.getName(), f.get(null).toString());
      }
    }
    return enumMap;
  }

  /**
   * Return a map of {@code Localizable} message values in an Enum class, with the enum field names
   * as keys and the values of getString() as the values.
   * 
   * @param enumClass
   *          The Enum to examine.
   * @return Map of field name -> getString values.
   * @throws IllegalArgumentException
   *           Thrown if {@code enumClass} isn't actually an enum.
   * @throws IllegalAccessException
   *           If the value was unable to be retrieved.
   */
  private static Map<String, String> getEnumMessageValues(final Class<?> enumClass)
      throws IllegalArgumentException, IllegalAccessException {
    if (!enumClass.isEnum()) {
      throw new IllegalArgumentException(enumClass.getName() + " is not an enum");
    } else if (!Localizable.class.isAssignableFrom(enumClass)
        && !DoubleLocalizable.class.isAssignableFrom(enumClass)) {
      throw new IllegalArgumentException(enumClass.getName()
          + " does not implement Localizable or DoubleLocalizable.");
    }

    final Field[] flds = enumClass.getDeclaredFields();
    final HashMap<String, String> messageMap = new HashMap<String, String>();
    for (final Field f : flds) {
      if (f.isEnumConstant()) {
        if (Localizable.class.isAssignableFrom(enumClass)) {
          messageMap.put(f.get(null).toString(), ((Localizable) f.get(null)).getString());
        } else {
          messageMap.put(f.get(null).toString(), ((DoubleLocalizable) f.get(null)).getString());
        }
      }
    }
    return messageMap;
  }

  /**
   * Return a map of {@code DoubleLocalizable} message values in an Enum class, with the enum field
   * names as keys and the values of getString2() as the values.
   * 
   * @param enumClass
   *          The Enum to examine.
   * @return Map of field name -> getString2 values.
   * @throws IllegalArgumentException
   *           Thrown if {@code enumClass} isn't actually an enum.
   * @throws IllegalAccessException
   *           If the value was unable to be retrieved.
   */
  private static Map<String, String> getEnumMessage2Values(final Class<?> enumClass)
      throws IllegalArgumentException, IllegalAccessException {
    if (!enumClass.isEnum()) {
      throw new IllegalArgumentException(enumClass.getName() + " is not an enum");
    } else if (!DoubleLocalizable.class.isAssignableFrom(enumClass)) {
      throw new IllegalArgumentException(enumClass.getName()
          + " does not implement DoubleLocalizable.");
    }

    final Field[] flds = enumClass.getDeclaredFields();
    final HashMap<String, String> messageMap = new HashMap<String, String>();
    for (final Field f : flds) {
      if (f.isEnumConstant()) {
        messageMap.put(f.get(null).toString(), ((DoubleLocalizable) f.get(null)).getString2());
      }
    }
    return messageMap;
  }
}
