/*
 * Copyright (c) 1998-2008 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.quercus.env;

import com.caucho.quercus.expr.Expr;
import com.caucho.util.L10N;

/**
 * Represents the introspected static function information.
 */
public class JavaOverloadMethod extends AbstractJavaMethod {
  private static final L10N L = new L10N(JavaOverloadMethod.class);
  
  private AbstractJavaMethod [][]_methodTable
    = new AbstractJavaMethod[0][];

  private AbstractJavaMethod [][]_restMethodTable
    = new AbstractJavaMethod[0][];

  public JavaOverloadMethod(AbstractJavaMethod fun)
  {
    overload(fun);
  }

  @Override
  public int getMaxArgLength()
  {
    throw new UnsupportedOperationException();
  }
  
  @Override
  public int getMinArgLength()
  {
    throw new UnsupportedOperationException();
  }
  
  @Override
  public boolean getHasRestArgs()
  {
    throw new UnsupportedOperationException();
  }
  
  /**
   * Returns an overloaded java method.
   */
  public AbstractJavaMethod overload(AbstractJavaMethod fun)
  {
    if (fun.getHasRestArgs()) {
      int len = fun.getMinArgLength();
      
      if (_restMethodTable.length <= len) {
        AbstractJavaMethod [][]restMethodTable
          = new AbstractJavaMethod[len + 1][];

        System.arraycopy(_restMethodTable, 0,
                         restMethodTable, 0, _restMethodTable.length);

        _restMethodTable = restMethodTable;
      }

      AbstractJavaMethod []methods = _restMethodTable[len];

      if (methods == null)
        _restMethodTable[len] = new AbstractJavaMethod[] { fun };
      else {
        AbstractJavaMethod []newMethods
          = new AbstractJavaMethod[methods.length + 1];

        System.arraycopy(methods, 0, newMethods, 0, methods.length);

        newMethods[methods.length] = fun;

        _restMethodTable[len] = newMethods;
      }
      
      return this;
    }
    else {
      int maxLen = fun.getMaxArgLength();
      
      if (_methodTable.length <= maxLen) {
        AbstractJavaMethod [][]methodTable
          = new AbstractJavaMethod[maxLen + 1][];

        System.arraycopy(_methodTable, 0, methodTable, 0, _methodTable.length);

        _methodTable = methodTable;
      }
      
      for (int len = fun.getMinArgLength(); len <= maxLen; len++) {
        AbstractJavaMethod []methods = _methodTable[len];

        if (methods == null)
          _methodTable[len] = new AbstractJavaMethod[] { fun };
        else {
          AbstractJavaMethod []newMethods
            = new AbstractJavaMethod[methods.length + 1];

          System.arraycopy(methods, 0, newMethods, 0, methods.length);

          newMethods[methods.length] = fun;

          _methodTable[len] = newMethods;
        }
      }

      return this;
    }
  }

  /**
   * Evaluates the function.
   */
  @Override
  public Value callMethod(Env env, Value qThis, Value []args)
  {
    if (_methodTable.length <= args.length) {

      if (_restMethodTable.length == 0)
        return env.error(L.l("'{0}' overloaded method call with {1} arguments has too many arguments", getName(), args.length));
      else {
        AbstractJavaMethod method =
          getBestFitJavaMethod(null, _restMethodTable, args);

        return method.callMethod(env, qThis, args);
      }
    }
    else {
      AbstractJavaMethod []methods = _methodTable[args.length];

      if (methods != null) {
	if (methods.length == 1)
	  return methods[0].callMethod(env, qThis, args);
	else {
	  AbstractJavaMethod method
	    = getBestFitJavaMethod(methods, _restMethodTable, args);

	  return method.callMethod(env, qThis, args);
	}
      }
      else {
	if (_restMethodTable.length == 0) {
	  return env.error(L.l("'{0}' overloaded method call with {1} arguments does not match any overloaded method", getName(), args.length));
	}

	AbstractJavaMethod method
	  = getBestFitJavaMethod(methods, _restMethodTable, args);

	return method.callMethod(env, qThis, args);
      }
    }
  }
  
  /**
   * Returns the Java function that matches the args passed in.
   */
  private AbstractJavaMethod
    getBestFitJavaMethod(AbstractJavaMethod []methods,
			 AbstractJavaMethod [][]restMethodTable,
			 Value []args)
  {

    AbstractJavaMethod minCostJavaMethod = null;
    int minCost = Integer.MAX_VALUE;

    if (methods != null) {
      for (int i = 0; i < methods.length; i++) {
        AbstractJavaMethod javaMethod = methods[i];

        int cost = javaMethod.getMarshalingCost(args);

        if (cost == 0)
          return javaMethod;

        if (cost <= minCost) {
          minCost = cost;
          minCostJavaMethod = javaMethod;
        }
      }
    }

    for (int i = Math.min(args.length, restMethodTable.length) - 1;
	 i >= 0;
	 i--) {
      if (restMethodTable[i] == null)
        continue;
      
      for (int j = 0; j < restMethodTable[i].length; j++) {
        AbstractJavaMethod javaMethod = restMethodTable[i][j];
        
        int cost = javaMethod.getMarshalingCost(args);

        if (cost == 0)
          return javaMethod;

        if (cost <= minCost) {
          minCost = cost;
          minCostJavaMethod = javaMethod;
        }
      }
    }

    return minCostJavaMethod;
  }
  
  /**
   * Returns the cost of marshaling for this method given the args.
   */
  public int getMarshalingCost(Value []args)
  {
    throw new UnsupportedOperationException();
    /*
    int size = _methods.size();
    int minCost = Integer.MAX_VALUE;
    
    for (int i = 0; i < size; i++) {
      int cost = _methods.get(i).getMarshalingCost(args);
      
      if (cost < minCost)
        minCost = cost;
    }

    return minCost;
    */
  }

  @Override
  public String getName()
  {
    AbstractJavaMethod method;

    for (int i = 0; i < _methodTable.length; i++) {
      if (_methodTable[i] != null)
        return _methodTable[i][0].getName();
    }

    return "unknown";
  }

  public String toString()
  {

    return "JavaOverloadMethod[" + getName() + "]";
  }
}
