/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.amber.field;

import com.caucho.amber.manager.AmberPersistenceUnit;
import com.caucho.amber.table.AmberColumn;
import com.caucho.amber.type.AmberBeanType;
import com.caucho.amber.type.EmbeddableType;
import com.caucho.amber.type.EntityType;
import com.caucho.amber.type.AmberType;
import com.caucho.java.JavaWriter;
import com.caucho.util.CharBuffer;
import com.caucho.util.L10N;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.logging.Logger;


/**
 * Configuration for a bean's field
 */
public class CompositeId extends Id {
  private static final L10N L = new L10N(CompositeId.class);
  private static final Logger log
    = Logger.getLogger(CompositeId.class.getName());

  private Class _tKeyClass;

  public CompositeId(EntityType ownerType,
                     ArrayList<IdField> keys)
  {
    super(ownerType, keys);
  }

  protected CompositeId(EntityType ownerType)
  {
    super(ownerType);
  }

  /**
   * Sets the foreign key type.
   */
  public void setKeyClass(Class keyClass)
  {
    _tKeyClass = keyClass;

    getOwnerType().addDependency(keyClass);
  }

  /**
   * Returns the foreign type.
   */
  @Override
  public String getForeignTypeName()
  {
    if (_tKeyClass != null)
      return _tKeyClass.getName();
    else if (isEmbeddedId())
      return getEmbeddedIdField().getJavaTypeName();
    else
      return getOwnerType().getName();
  }

  /**
   * Returns the foreign type.
   */
  public String getForeignMakeKeyName()
  {
    return getOwnerType().getName().replace('.', '_').replace('/', '_');
  }

  /**
   * Generates any prologue.
   */
  @Override
  public void generatePrologue(JavaWriter out, HashSet<Object> completedSet)
    throws IOException
  {
    super.generatePrologue(out, completedSet);

    generatePrologue(out, completedSet, getForeignMakeKeyName());
  }

  /**
   * Generates any prologue.
   */
  @Override
  public void generatePrologue(JavaWriter out,
                               HashSet<Object> completedSet,
                               String name)
    throws IOException
  {
    // jpa/0u21
    out.println();
    out.println("private transient " + getForeignTypeName() + " __caucho_compound_key = new " + getForeignTypeName() + "();");
      
    generatePrologueMake(out, completedSet);
    generatePrologueLoad(out, completedSet);
  }

  /**
   * Generates any prologue.
   */
  public void generatePrologueMake(JavaWriter out,
                                   HashSet<Object> completedSet)
    throws IOException
  {
    String makeName = "__caucho_make_key_" + getForeignMakeKeyName();

    if (completedSet.contains(makeName))
      return;

    completedSet.add(makeName);

    out.println();
    out.print("private static ");
    out.print(getForeignTypeName() + " " + makeName);
    out.print("(");

    if (! isEmbeddedId()) {
      ArrayList<IdField> keys = getKeys();
      for (int i = 0; i < keys.size(); i++) {
        if (i != 0)
          out.print(", ");

        IdField key = keys.get(i);

        out.print(key.getJavaTypeName() + " a" + i);
      }
    }
    else {
      EmbeddableType embeddable = (EmbeddableType) getEmbeddedIdField().getType();

      ArrayList<AmberField> fields = embeddable.getFields();
      for (int i = 0; i < fields.size(); i++) {
        if (i != 0)
          out.print(", ");

        AmberField field = fields.get(i);

        out.print(field.getJavaTypeName() + " a" + i);
      }
    }

    out.println(")");
    out.println("{");
    out.pushDepth();

    out.println();
    out.println(getForeignTypeName() + " key = new " + getForeignTypeName() + "();");

    if (getOwnerType().getPersistenceUnit().isJPA() && ! isEmbeddedId()) {
      String args = "";

      ArrayList<IdField> keys = getKeys();

      for (int i = 0; i < keys.size(); i++) {
        KeyPropertyField key = (KeyPropertyField) keys.get(i);

        String name = key.getName();

        char ch = Character.toUpperCase(name.charAt(0));
        if (name.length() == 1)
          name = "get" + ch;
        else
          name = "get" + ch + key.getName().substring(1);

        Method method = AmberBeanType.getGetter(_tKeyClass, name);

        if (key.isKeyField() || (method != null)) {
          out.println(key.generateSetKeyProperty("key", "a" + i) + ";");
        }
        else {
          // Arg. constructor jpa/0u21
          if (i != 0)
            args += ", ";

          args += " a" + i;

          out.println("if (a" + i + " == null)");
          out.println("  return new " + getForeignTypeName() + "();");

          if (i + 1 == keys.size())
            out.print("key = new " + getForeignTypeName() + "(" + args + ");");
        }
      }

    }
    else {
      ArrayList fields;

      if (getEmbeddedIdField() == null) {
        // ejb/06x2
        fields = getKeys();
      }
      else {
        EmbeddableType embeddable
          = (EmbeddableType) getEmbeddedIdField().getType();

        fields = embeddable.getFields();
      }

      for (int i = 0; i < fields.size(); i++) {
        AmberField field = (AmberField) fields.get(i);

        if (getOwnerType().isFieldAccess())
          out.println(field.generateSet("key", "a" + i) + ";");
        else {
          String setter = field.getName();

          if (getOwnerType().getPersistenceUnit().isJPA()) {
            setter = "set" + Character.toUpperCase(setter.charAt(0)) +
              (setter.length() == 1 ? "" : setter.substring(1));

            out.println("key." + setter + "(a" + i + ");");
          }
          else // XXX: ejb/06x2, ejb/06if
            out.println("key." + setter + " = a" + i + ";");
        }
      }
    }

    out.println("return key;");

    out.popDepth();
    out.println("}");
  }

  /**
   * Generates any prologue.
   */
  public void generatePrologueLoad(JavaWriter out,
                                   HashSet<Object> completedSet)
    throws IOException
  {
    String loadName = "__caucho_load_key_" + getForeignMakeKeyName();

    if (completedSet.contains(loadName))
      return;

    completedSet.add(loadName);

    out.println();
    out.print("private static ");
    out.print(getForeignTypeName() + " " + loadName);
    out.println("(com.caucho.amber.manager.AmberConnection aConn, java.sql.ResultSet rs, int index)");
    out.println("  throws java.sql.SQLException");

    out.println("{");
    out.pushDepth();

    int index = 0;
    ArrayList<IdField> keys = getKeys();
    for (int i = 0; i < keys.size(); i++) {
      IdField key = keys.get(i);

      String javaType = key.getJavaTypeName();
      out.print(javaType + " a" + i + " = (" + javaType + ") ");
      index = key.getType().generateLoad(out, "rs", "index", index);
      out.println(";");

      out.println("if (rs.wasNull())");
      out.println("  return null;");
    }

    out.print(getForeignTypeName() + " key = new " + getForeignTypeName() + "(");

    if (isEmbeddedId() || ! getOwnerType().getPersistenceUnit().isJPA()) {
      out.println(");");

      // ejb/06x2
      for (int i = 0; i < keys.size(); i++) {
        out.println(keys.get(i).generateSetKeyProperty("key", "a" + i) + ";");
      }
    }
    else {
      for (int i = 0; i < keys.size(); i++) {
        KeyPropertyField key = (KeyPropertyField) keys.get(i);

        String name = key.getName();

        char ch = Character.toUpperCase(name.charAt(0));
        if (name.length() == 1)
          name = "get" + ch;
        else
          name = "get" + ch + key.getName().substring(1);

        Method method = AmberBeanType.getGetter(_tKeyClass, name);

        if (key.isKeyField() || (method != null)) {
          if (i == 0)
            out.println(");");

          out.println(key.generateSetKeyProperty("key", "a" + i) + ";");
        }
        else {
          // Arg. constructor jpa/0u21
          if (i != 0)
            out.print(", ");

          out.print(" a" + i);

          if (i + 1 == keys.size())
            out.println(");");
        }
      }
    }

    out.println("return key;");

    out.popDepth();
    out.println("}");
  }

  /**
   * Returns the foreign type.
   */
  @Override
  public int generateLoadForeign(JavaWriter out, String rs,
                                 String indexVar, int index)
    throws IOException
  {
    return generateLoadForeign(out, rs, indexVar, index,
                               getForeignTypeName().replace('.', '_'));
  }

  /**
   * Returns the foreign type.
   */
  @Override
  public int generateLoadForeign(JavaWriter out, String rs,
                                 String indexVar, int index,
                                 String name)
    throws IOException
  {
    out.print("__caucho_load_key_" + getForeignMakeKeyName());
    out.print("(aConn, " + rs + ", " + indexVar + " + " + index + ")");

    ArrayList<IdField> keys = getKeys();

    index += keys.size();

    return index;
  }

  /**
   * Generates the select clause.
   */
  @Override
  public String generateSelect(String id)
  {
    ArrayList<IdField> keys = getKeys();

    CharBuffer cb = CharBuffer.allocate();

    for (int i = 0; i < keys.size(); i++) {
      if (i != 0)
        cb.append(", ");

      cb.append(keys.get(i).generateSelect(id));
    }

    return cb.close();
  }

  /**
   * Generates the JPA QL select clause.
   */
  @Override
  public String generateJavaSelect(String id)
  {
    ArrayList<IdField> keys = getKeys();

    CharBuffer cb = CharBuffer.allocate();

    for (int i = 0; i < keys.size(); i++) {
      if (i != 0)
        cb.append(", ");

      cb.append(keys.get(i).generateJavaSelect(id));
    }

    return cb.close();
  }

  /**
   * Generates the select clause.
   */
  @Override
  public String generateLoadSelect(String id)
  {
    return null;
  }

  /**
   * Returns the key for the value
   */
  @Override
  public String generateGet(String value)
  {
    CharBuffer cb = CharBuffer.allocate();

    cb.append("__caucho_make_key_" + getForeignMakeKeyName());
    cb.append("(");

    ArrayList<IdField> keys = getKeys();

    for (int i = 0; i < keys.size(); i++) {
      if (i != 0)
        cb.append(", ");

      cb.append(keys.get(i).generateGet(value));
    }

    cb.append(")");

    return cb.close();
  }

  /**
   * Generates loading cache
   */
  @Override
  public void generateSet(JavaWriter out, String objThis, String value)
    throws IOException
  {
    out.println("if (" + value + " != null) {");
    out.pushDepth();

    AmberPersistenceUnit persistenceUnit
      = getOwnerType().getPersistenceUnit();

    // ejb/06ie
    if (persistenceUnit.isJPA() && ! isEmbeddedId()) {

      // jpa/0u21

      EmbeddableType embeddable
        = persistenceUnit.getEmbeddable(_tKeyClass.getName());

      // jpa/0u21 ArrayList<IdField> keys = getKeys();
      ArrayList<AmberField> keys = embeddable.getFields();

      for (int i = 0; i < keys.size(); i++) {
        PropertyField key = (PropertyField) keys.get(i);

        String getter = "__caucho_get_field(" + i + ")";

        String subValue
          = "((com.caucho.amber.entity.Embeddable) key)." + getter;

        out.println("Object field" + i + " = " + subValue + ";");

        out.println("if (field" + i + " == null)");
        out.println("  return;");

        KeyPropertyField prop = null;

        AmberColumn column = key.getColumn();

        // jpa/0j55
        if (true || column == null) {
          ArrayList<IdField> fields = getKeys();
          for (int j = 0; j < fields.size(); j++) {
            IdField id = fields.get(j);
            if (id.getName().equals(key.getName()))
              if (id instanceof KeyPropertyField)
                prop = (KeyPropertyField) id;
          }
        }

        if (prop != null)
          key = prop;

        AmberType columnType = key.getColumn().getType();

        value = columnType.generateCastFromObject("field" + i);

        key.generateSet(out, objThis, value);
      }

      // jpa/0u21
      // out.println("__caucho_compound_key  = (" + getForeignTypeName() + ") " + value + ";");

      /*
      for (int i = 0; i < keys.size(); i++) {
        IdField key = keys.get(i);

        key.generateStatementSet(out, key.generateGetKeyProperty(obj + "_key"));
      }
      */
    }
    else {
      out.println(getForeignTypeName() + " " + value + "_key = (" + getForeignTypeName() + ") " + value + ";");

      if (getEmbeddedIdField() == null) {
        // ejb/06ie

        ArrayList<IdField> keys = getKeys();

        for (int i = 0; i < keys.size(); i++) {
          IdField key = keys.get(i);

          key.generateSet(out, objThis, key.generateGetKeyProperty(objThis + "_key"));
        }
      }
      else
        getEmbeddedIdField().generateSet(out, objThis, value + "_key");
    }

    out.popDepth();
    out.println("}");
  }

  /**
   * Returns the key for the value
   */
  public String generateGetProxyProperty(String value)
  {
    CharBuffer cb = CharBuffer.allocate();

    cb.append("__caucho_make_key_" + getForeignMakeKeyName());
    cb.append("(");

    ArrayList<IdField> keys = getKeys();

    for (int i = 0; i < keys.size(); i++) {
      if (i != 0)
        cb.append(", ");

      cb.append(keys.get(i).generateGetProxyProperty(value));
    }

    cb.append(")");

    return cb.close();
  }

  /**
   * Generates loading cache
   */
  @Override
  public void generateLoadFromObject(JavaWriter out, String obj)
    throws IOException
  {
    ArrayList<IdField> keys = getKeys();

    for (int i = 0; i < keys.size(); i++) {
      keys.get(i).generateLoadFromObject(out, obj);
    }
  }

  /**
   * Generates loading cache
   */
  @Override
  public void generateUpdateFromObject(JavaWriter out, String obj)
    throws IOException
  {
    ArrayList<IdField> keys = getKeys();

    for (int i = 0; i < keys.size(); i++) {
      keys.get(i).generateUpdateFromObject(out, obj);
    }
  }

  /**
   * Generates the where clause.
   */
  public String generateWhere(String id)
  {
    ArrayList<IdField> keys = getKeys();

    CharBuffer cb = CharBuffer.allocate();

    for (int i = 0; i < keys.size(); i++) {
      if (i != 0)
        cb.append(" and ");

      cb.append(keys.get(i).generateWhere(id));
    }

    return cb.close();
  }

  /**
   * Generates the where clause.
   */
  @Override
  public String generateCreateTableSQL(AmberPersistenceUnit manager)
  {
    return null;
  }

  /**
   * Generates the set clause.
   */
  @Override
  public void generateSetKey(JavaWriter out, String pstmt,
                             String obj, String index)
    throws IOException
  {
    generateSet(out, pstmt, obj, index);
  }

  /**
   * Generates code to convert to the type from the object.
   */
  @Override
  public String generateCastFromObject(String value)
  {
    return value;
  }

  /**
   * Generates code for a match.
   */
  @Override
  public void generateMatch(JavaWriter out, String key)
    throws IOException
  {
    out.println("return __caucho_getPrimaryKey().equals(" + key + ");");
  }

  /**
   * Generates code to test the equals.
   */
  @Override
  public String generateEquals(String leftBase, String value)
  {
    return leftBase + ".equals(" + value + ")";
  }
  /**
   * Generates the set clause.
   */
  @Override
  public void generateCheckCreateKey(JavaWriter out)
    throws IOException
  {
  }

  /**
   * Generates the set clause.
   */
  /*
    public void generateSetGeneratedKeys(JavaWriter out, String pstmt)
    throws IOException
    {
    }
  */

  /**
   * Generates code to convert to the object.
   */
  @Override
  public String toObject(String value)
  {
    return value;
  }
}
