/*
 * 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 SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.ejb.cfg;

import com.caucho.config.ConfigException;
import com.caucho.util.CharBuffer;
import com.caucho.util.L10N;

import java.util.ArrayList;

/**
 * A builtin SQL function expression
 */
public class FunctionSignature {
  static L10N L = new L10N(FunctionSignature.class);
  
  private String _signature;
  // function name
  private String _name;
  // arguments
  private Class []_parameterTypes;
  // return type
  private Class _returnType;

  private int _index;

  private String _sql;

  /**
   * Creates a function definition.
   *
   * @param signature the function signature in java syntax
   */
  public FunctionSignature(String signature)
    throws ConfigException
  {
    _signature = signature;

    parseSignature();
  }

  /**
   * Returns the function name.
   */
  public String getName()
  {
    return _name;
  }

  /**
   * Returns the function signature.
   */
  public String getSignature()
  {
    return _signature;
  }

  /**
   * Returns the function arguments.
   */
  public Class []getParameterTypes()
  {
    return _parameterTypes;
  }

  /**
   * Returns the return type;
   */
  public Class getReturnType()
  {
    return _returnType;
  }

  /**
   * Sets the SQL.
   */
  public void setSQL(String sql)
  {
    _sql = sql;
  }

  /**
   * Gets the SQL.
   */
  public String getSQL()
  {
    return _sql;
  }

  /**
   * Parses the function signature.
   */
  private void parseSignature()
    throws ConfigException
  {
    _index = 0;

    _returnType = parseType(skipWhitespace(read()));

    CharBuffer cb = CharBuffer.allocate();
    int ch = skipWhitespace(read());

    for (; Character.isJavaIdentifierPart((char) ch); ch = read())
      cb.append((char) ch);

    if (cb.length() == 0)
      throw new ConfigException(L.l("unexpected empty function name in '{0}'",
                                        _signature));

    _name = cb.toString();

    ch = skipWhitespace(ch);

    if (ch != '(')
      throw new ConfigException(L.l("function syntax is 'ret-type name(arg1, ..., argn)' in '{0}'",
                                        _signature));

    ArrayList<Class> argList = new ArrayList<Class>();
    
    ch = read();
    while (Character.isJavaIdentifierStart((char) (ch = skipWhitespace(ch)))) {
      Class type = parseType(ch);

      argList.add(type);

      ch = skipWhitespace(read());

      if (ch == ',')
        ch = read();
    }

    _parameterTypes = argList.toArray(new Class[argList.size()]);

    if (ch != ')')
      throw new ConfigException(L.l("function syntax is 'ret-type name(arg1, ..., argn)' in '{0}'",
                                        _signature));

    ch = skipWhitespace(read());

    if (ch != -1)
      throw new ConfigException(L.l("function syntax is 'ret-type name(arg1, ..., argn)' in '{0}'",
                                        _signature));
  }

  /**
   * Parses the type.
   */
  private Class parseType(int ch)
    throws ConfigException
  {
    CharBuffer cb = CharBuffer.allocate();

    for (; Character.isJavaIdentifierPart((char) ch); ch = read())
      cb.append((char) ch);

    if (cb.length() == 0)
      throw new ConfigException(L.l("unexpected empty type in '{0}'",
                                        _signature));

    String className = cb.toString();

    unread(ch);

    return findClass(className);
  }

  /**
   * Converts the type to a classname.
   */
  private Class findClass(String className)
    throws ConfigException
  {
    if ("int".equals(className))
      return int.class;
    else if ("boolean".equals(className))
      return boolean.class;
    else if ("double".equals(className))
      return double.class;
    else if ("String".equals(className))
      return String.class;
    else if ("Date".equals(className))
      return java.util.Date.class;
    else if ("any".equals(className))
      return Object.class;

    throw new ConfigException(L.l("unknown type '{0}' in '{1}'",
                                      className, _signature));
  }

  /**
   * Skips whitespace to get to the next valid value.
   */
  private int skipWhitespace(int ch)
  {
    for (; Character.isWhitespace((char) ch); ch = read()) {
    }

    return ch;
  }

  /**
   * Reads the next character.
   */
  private int read()
  {
    if (_index < _signature.length())
      return _signature.charAt(_index++);
    else
      return -1;
  }

  /**
   * Unreads the last character.
   */
  private void unread(int ch)
  {
    if (ch >= 0)
      _index--;
  }

  /**
   * Returns a hash-code.
   */
  public int hashCode()
  {
    return _name.hashCode();
  }

  /**
   * True if the function signatures are equal.
   */
  public boolean equals(Object o)
  {
    if (! (o instanceof FunctionSignature))
      return false;

    FunctionSignature sig = (FunctionSignature) o;

    if (! _name.equalsIgnoreCase(sig._name))
      return false;

    if (_parameterTypes.length != sig._parameterTypes.length)
      return false;

    for (int i = 0; i < _parameterTypes.length; i++)
      if (! _parameterTypes[i].equals(sig._parameterTypes[i]))
        return false;

    return true;
  }

  /**
   * Returns a string value.
   */
  public String toString()
  {
    return "Function[" + _signature + "]";
  }
}
