/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ws.tools;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.xerces.xs.StringList;
import org.apache.xerces.xs.XSSimpleTypeDefinition;
import org.jboss.ws.metadata.wsdl.WSDLUtils;
import org.jboss.ws.tools.XSDTypeToJava.VAR;

/**
 *  Handles writing of Java source files (XSD-> Java)
 *  @author <mailto:Anil.Saldhana@jboss.org>Anil Saldhana
 *  @since   May 6, 2005
 */
public class JavaWriter
{
   private String newline = "\n";

   protected WSDLUtils utils = WSDLUtils.getInstance();

   public JavaWriter()
   {
   }

   /**
    * Creates a Java Class that represents a Simple Type restricted by enumeration
    * @param fname
    * @param lst List of enumerated values (we support Strings only)
    * @param loc  Location where the file has to be created
    * @param pkgname Package Name
    * @throws IOException
    */

   public void createJavaFileForEnumeratedValues(String fname, StringList lst, File loc, String pkgname, XSSimpleTypeDefinition type) throws IOException
   {
      List importList = new ArrayList();
      importList.add("java.util.Map");
      importList.add("java.util.HashMap");
      File sei = utils.createPhysicalFile(loc, fname);
      StringBuilder buf = utils.createClassBasicStructure(pkgname, fname, type, importList, null);

      buf.append("private java.lang.String value;" + newline);
      buf.append("private static Map valueMap = new HashMap();" + newline);

      //For now, we will support Strings only
      int lenOfArr = lst != null ? lst.getLength() : 0;
      for (int i = 0; i < lenOfArr; i++)
      {
         String str = lst.item(i);
         buf.append("public static final String _" + str + "String = \"" + str + "\";" + newline);
      }

      for (int i = 0; i < lenOfArr; i++)
      {
         String str = lst.item(i);
         buf.append("public static final java.lang.String _" + str + " = new java.lang.String(_" + str + "String);");
         buf.append(newline);
      }

      for (int i = 0; i < lenOfArr; i++)
      {
         String str = lst.item(i);
         buf.append("public static final " + fname + " " + str + " = new " + fname + "(_" + str + ");");
         buf.append(newline);
      }

      //Create CTR
      buf.append(newline + newline + "protected " + fname + "(java.lang.String value) { " + newline);

      buf.append("this.value = value;valueMap.put(this.toString(), this); " + newline + "}");

      buf.append(newline + newline);

      //Create getValue method
      buf.append(newline + " public java.lang.String getValue() {" + newline + "  return value;" + newline + "}");

      buf.append(newline + newline);

      //Create fromValue method
      buf.append(newline + "public static " + fname + "  fromValue(java.lang.String value)" + newline);

      buf.append(" throws java.lang.IllegalStateException {" + newline);

      for (int i = 0; i < lenOfArr; i++)
      {
         String str = lst.item(i);
         if (i > 0)
            buf.append("else ");

         buf.append("if (" + str + ".value.equals(value)) {" + newline);

         buf.append("return " + str + ";" + newline);

         buf.append("}" + newline);
      }

      buf.append(" throw new IllegalArgumentException();" + newline + "}" + newline + newline);

      //End- fromValue method
      //create- fromString method

      buf.append(newline + "public static " + fname + "  fromString(String value)" + newline);

      buf.append(" throws java.lang.IllegalStateException {" + newline);

      buf.append(fname + " ret = (" + fname + ")valueMap.get(value);" + newline);

      buf.append("if (ret != null) {" + newline + " return ret;" + newline + " }" + newline + newline);

      for (int i = 0; i < lenOfArr; i++)
      {
         String str = lst.item(i);
         if (i > 0)
            buf.append("else ");
         buf.append("if (value.equals(_" + str + "String)) {" + newline);
         buf.append("return " + str + ";" + newline + "}");
      }

      buf.append(newline + " throw new IllegalArgumentException();" + newline + "}" + newline + newline);

      //End- fromString method
      //create - toString method

      buf.append(newline + " public String toString() {" + newline + " return value.toString();" + newline + "}" + newline);

      //End - toString method

      //create -readResolve method

      buf.append(newline + "private Object readResolve()" + newline + "        throws java.io.ObjectStreamException {" + newline
            + "        return fromValue(getValue());" + newline + "    } " + newline);
      //End - readResolve method

      //create - equals method

      buf.append(newline + "private boolean equals(Object obj){" + newline + "         if (!(obj instanceof " + fname + ")) {" + newline + "         return false;"
            + newline + "    } " + newline);
      buf.append("return ((" + fname + ")obj).value.equals(value);" + newline + "}" + newline);
      //End - equals method

      //create - hashCode method

      buf.append(newline + " public int hashCode() { " + newline + "        return value.hashCode(); " + newline + "    }" + newline);
      //End - hashCode method
      buf.append("}" + newline); //end of class

      FileWriter writer = new FileWriter(sei);
      writer.write(buf.toString());
      writer.flush();
      writer.close();
   }

   /**
    * A simple utility method that just creates a Java source file
    *
    * @param location Location where the Java source file needs to be written
    * @param filename File Name of the Java source
    * @param packageName
    * @param vars
    * @param importList List of strings that represent imports
    * @param baseTypeName Name of base class
    * @param isExceptionType  Exception types need special treatment
    * @param typeNameToBaseVARList Needed if we are dealing with an exception type
    * @throws IOException
    */
   public void createJavaFile(File location, String filename, String packageName, List<VAR> vars, List<String> importList, String baseTypeName,
         boolean isExceptionType, boolean isSerializable, Map<String, List> typeNameToBaseVARList) throws IOException
   {
      File newLoc = null;
      if (needToCreatePackageStructure(location, packageName))
         newLoc = utils.createPackage(location.getPath(), packageName);
      else newLoc = location;
      String classname = ToolsUtils.convertInvalidCharacters(utils.chop(filename, ".java"));
      File sei = utils.createPhysicalFile(newLoc, classname);
      StringBuilder buffer = new StringBuilder();
      utils.writeJbossHeader(buffer);

      //Create the package Name
      buffer.append(newline).append("package ").append(packageName).append(";");

      if (importList != null)
      {
         for (String imp : importList)
         {
            buffer.append(newline).append("import ").append(imp).append(";");
         }
      }
      buffer.append(newline).append(newline);
      buffer.append(newline).append("public class  ").append(classname).append(newline);
      if (baseTypeName != null && baseTypeName.length() > 0)
         buffer.append(" extends ").append(baseTypeName);
      if (isSerializable)
         buffer.append(" implements java.io.Serializable");
      buffer.append("{").append(newline);
      createVariables(buffer, vars, isExceptionType);
      createCTR(buffer, classname, vars, isExceptionType, typeNameToBaseVARList);
      buffer.append(newline);
      createAccessors(buffer, vars, isExceptionType);
      buffer.append("}").append(newline);
      //Create a FileWriter
      FileWriter writer = new FileWriter(sei);
      writer.write(buffer.toString());
      writer.flush();
      writer.close();
   }

   //PRIVATE METHODS
   private void createCTR(StringBuilder buf, String cname, List vars, boolean isExceptionType, Map<String, List> typeNameToBaseVARList)
   {
      if (vars.size() > 0 && isExceptionType == false)
      {
         buf.append("public " + cname + "(){}"); //Empty CTR
         buf.append(newline);
         buf.append(newline);
      }

      StringBuilder ctrvarbuf = new StringBuilder();
      StringBuilder ctrintbuf = new StringBuilder();

      boolean calledSuper = false;
      if (isExceptionType)
      {
         List<VAR> baseList = typeNameToBaseVARList.get(cname);
         int baseLen = baseList != null ? baseList.size() : 0;
         String arrStr = "[]";

         if (baseLen > 0)
         {
            calledSuper = true;
            ctrintbuf.append("super(");
            for (int i = 0; i < baseLen; i++)
            {

               if (i > 0)
               {
                  ctrvarbuf.append(", ");
                  ctrintbuf.append(", ");
               }
               VAR v = baseList.get(i);
               ctrvarbuf.append(v.getVartype());
               if (v.isArrayType)
                  ctrvarbuf.append(arrStr);

               ctrvarbuf.append(" ").append(v.getVarname());
               ctrintbuf.append(v.getVarname());
            }
            ctrintbuf.append(");").append(newline);
         }
      }
      Iterator iter = vars.iterator();
      int index = 0;
      while (iter.hasNext())
      {
         if (index++ > 0 || calledSuper)
         {
            ctrvarbuf.append(", ");
         }
         VAR v = (VAR)iter.next();
         String name = v.getVarname();
         if (JavaKeywords.isJavaKeyword(name))
         {
            name = "_" + name;
         }

         String type = v.getVartype();
         boolean isArr = v.isArrayType();
         ctrvarbuf.append(type);
         if (isArr)
            ctrvarbuf.append("[]");
         ctrvarbuf.append(" " + name);
         if (isExceptionType && calledSuper == false && index == 1 && v.getVartype().equals("java.lang.String"))
         {
            ctrintbuf.append("super(").append(v.getVarname()).append(");").append(newline);
            calledSuper = true;
         }

         ctrintbuf.append("this." + name + "=" + name + ";");
         ctrintbuf.append(newline);
      }
      buf.append("public " + cname + "(" + ctrvarbuf.toString() + "){");
      buf.append(newline);
      buf.append(ctrintbuf.toString());
      buf.append("}");
   }

   private void createAccessors(StringBuilder buf, List vars, boolean isExceptionType)
   {
      Iterator iter = vars.iterator();
      while (iter.hasNext())
      {
         VAR v = (VAR)iter.next();
         String name = v.getVarname();
         String internalName = name;
         if (JavaKeywords.isJavaKeyword(internalName))
         {
            internalName = "_" + internalName;
         }

         String type = v.getVartype();
         boolean isArr = v.isArrayType();
         //Add getter/setter also
         buf.append("public " + type);
         if (isArr)
            buf.append("[] ");
         String str = " get";
         //boolean case
         if (type.equals("boolean"))
            str = " is";
         buf.append(str + utils.getMixedCase(name) + "() { return " + internalName + " ;}");
         buf.append(newline);
         buf.append(newline);
         if (isExceptionType == false)
            writeSetter(buf, name, internalName, type, isArr);
         buf.append(newline);
         buf.append(newline);
      }
   }

   private List createVariables(StringBuilder buf, List vars, boolean isExceptionType)
   {
      if (vars == null)
         return vars;
      Iterator iter = vars.iterator();
      while (iter.hasNext())
      {
         VAR v = (VAR)iter.next();
         String name = v.getVarname();
         // JBWS-1170 Convert characters which are illegal in Java identifiers
         name = ToolsUtils.convertInvalidCharacters(name);

         if (JavaKeywords.isJavaKeyword(name))
         {
            name = "_" + name;
         }

         String type = v.getVartype();
         boolean isArr = v.isArrayType();
         buf.append(newline);
         if (isExceptionType)
            buf.append("private " + type);
         else buf.append("protected " + type);
         if (isArr)
            buf.append("[] ");
         buf.append(" " + name).append(";").append(newline);
      }
      return vars;
   }

   private boolean needToCreatePackageStructure(File location, String packageName)
   {
      packageName = packageName.replace('.', '/');
      try
      {
         String externalForm = location.toURL().toExternalForm();
         return externalForm.indexOf(packageName) < 0;
      }
      catch (MalformedURLException e)
      {
         // ignore
         return false;
      }
   }

   private void writeSetter(StringBuilder buf, String name, String internalName, String type, boolean isArr)
   {
      buf.append("public void ");
      buf.append("set" + utils.getMixedCase(name) + "(" + type);
      if (isArr)
         buf.append("[]");

      buf.append(" " + internalName + "){ this." + internalName + "=" + internalName + "; }");
   }
}