/*
 * Copyright 2013 Guidewire Software, Inc.
 */

package gw.lang.reflect.features;

import gw.lang.PublishedName;
import gw.lang.reflect.IFeatureInfo;
import gw.lang.reflect.IMethodInfo;
import gw.lang.reflect.IParameterInfo;
import gw.lang.reflect.IType;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class MethodChainReference<R, T> extends FeatureReference<R, T> implements IMethodReference<R, T>, IFeatureChain
{
  private FeatureReference _root;
  private IType _rootType;
  private IMethodInfo _mi;
  private Object[] _boundValues;

  public MethodChainReference( IType rootType, FeatureReference root, String funcName, IType[] params, Object[] boundValues )
  {
    _root = root;
    _rootType = rootType;
    _mi = MethodReference.getMethodInfo(rootType, funcName, params );
    _boundValues = boundValues;
  }

  public Object evaluate( Object... args )
  {
    return evaluate( Arrays.asList( args ).iterator() );
  }
  
  @Override
  protected Object evaluate( Iterator args ) {
    Object ctx = _root.evaluate(args);
    IMethodInfo mi = getMethodInfo();
    if( _boundValues != null )
    {
      args = Arrays.asList(_boundValues).iterator();
    }    
    Object[] argArray = new Object[mi.getParameters().length];
    for (int i = 0; i < argArray.length; i++) {
      argArray[i] = args.next();
    }
    return mi.getCallHandler().handleCall(ctx, argArray);
  }

  @Override
  public List<IType> getFullArgTypes() {
    List argTypes = _root.getFullArgTypes();
    if( _boundValues == null )
    {
      for (IParameterInfo pi : _mi.getParameters()) {
        argTypes.add(pi.getFeatureType());
      }
    }
    return argTypes;
  }

  @Override
  public IMethodInfo getMethodInfo() {
    return _mi;
  }

  @PublishedName("invoke")
  @Override
  public T getinvoke()
  {
    return toBlock();
  }

  @Override
  public T toBlock() {
    return (T) BlockWrapper.toBlock(this);    
  }

  @Override
  public Object[] getBoundArgValues()
  {
    return _boundValues;
  }

  @Override
  public IType getRootType() {
    return _rootType;
  }

  @Override
  public IFeatureInfo getFeatureInfo() {
    return getMethodInfo();
  }

  public IFeatureReference getRootFeatureReference() {
    return _root;
  }

  public Object[] getBoundValues() {
    return _boundValues;
  }

  @Override
  public boolean equals( Object o )
  {
    if( this == o )
    {
      return true;
    }
    if( o == null || getClass() != o.getClass() )
    {
      return false;
    }

    MethodChainReference that = (MethodChainReference)o;

    // Probably incorrect - comparing Object[] arrays with Arrays.equals
    if( !Arrays.equals( _boundValues, that._boundValues ) )
    {
      return false;
    }
    if( _mi != null ? !_mi.equals( that._mi ) : that._mi != null )
    {
      return false;
    }
    if( _root != null ? !_root.equals( that._root ) : that._root != null )
    {
      return false;
    }
    if( _rootType != null ? !_rootType.equals( that._rootType ) : that._rootType != null )
    {
      return false;
    }

    return true;
  }

  @Override
  public int hashCode()
  {
    int result = _root != null ? _root.hashCode() : 0;
    result = 31 * result + (_rootType != null ? _rootType.hashCode() : 0);
    result = 31 * result + (_mi != null ? _mi.hashCode() : 0);
    result = 31 * result + (_boundValues != null ? Arrays.hashCode( _boundValues ) : 0);
    return result;
  }
}
