/* Copyright (C) 1999-2003 by Peter Eastman

   This program 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.

   This program 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 General Public License for more details. */

package artofillusion.datahandling;

import java.lang.reflect.*;
import java.util.*;

/** Convenience implementation of ListOperation for operations on arrays or List
 *  like adding, modifying, removing of items.
 *  This version is focused on inplace modification instead of a copy operation.
 *
 * @author  Michael Butscher
 * @see AbstractCopyListOperation
 */
public abstract class AbstractInplaceListOperation implements ListOperation
{
  /** Returns an array of the same type as parameter array
    really has but of length len.
    <P>
    Example: int[] newint = (int[]) AbstractListOperation.createArrayOfType(new int[8], 10);
  */
  public static Object[] createArrayOfType(Object[] array, int len)
  {
    Class cmpclass = array.getClass().getComponentType();
    return (Object[]) Array.newInstance(cmpclass, len);
  }
  
  
  /** Changes data according to the type of subclass of AbstractListOperation
    and possibly additional data. This is the only method which must be
    overridden.
  */
  public abstract void applyInplace(List data);
  
  
  /** Apply specified list operation to arr. The returned array type
    can be casted to the same type the parameter array had.
    The returned array may be identical to the parameter array if length
    is not changed during operation. Returns null if array is null.
    This method calls applyInplace(List data) for the actual list operation.
    <P>
    This method may be overridden, mainly for speed reasons.
    
    @see #applyInplace(List)
  */
    
  public Object[] apply(Object[] array)
  {
    if (array == null)
      return null;
    List arrlist = new ArrayList(Arrays.asList(array));
    applyInplace(arrlist);
    
    if (arrlist.size() == array.length)
      return arrlist.toArray(array);
    else
    { // Ensure that a new array with the exactly right size is created
      Object[] template = createArrayOfType(array, 0);
      return arrlist.toArray(template);
    }
  }
  
  
  /** Specialized version for boolean arrays. This default version converts
    all array elements between primitive type and 
    Boolean and calls applyInplace(List data) for the actual list
    operation.
    <P>
    This method may be overridden, mainly for speed reasons.
    
    @see #applyInplace(List)
  */
  
  public boolean[] apply(boolean[] array)  // All lines with //! must be changed between different types
  {
    if (array == null)
      return null;
    Object[] op = new Object[array.length];
    for (int i = 0; i < array.length; ++i)
      op[i] = new Boolean(array[i]);                //!
    
    List arrlist = new ArrayList(Arrays.asList(op));
    applyInplace(arrlist);
    
    boolean[] result;                                   //!
    if (arrlist.size() != array.length)
      result = new boolean[arrlist.size()];             //!
    else
      result = array;
    
    for (int i = 0; i < arrlist.size(); ++i)
      result[i] = ((Boolean)arrlist.get(i)).booleanValue();  //!
    
    return result;
  }
  
  
  /** Specialized version for int arrays. This default version converts
    all array elements between primitive type and appropriate
    Number subclass and calls applyInplace(List data) for the actual list
    operation.
    <P>
    This method may be overridden, mainly for speed reasons.
    
    @see #applyInplace(List)
  */
  
  public int[] apply(int[] array)  // All lines with //! must be changed between different types
  {
    if (array == null)
      return null;
    Object[] op = new Object[array.length];
    for (int i = 0; i < array.length; ++i)
      op[i] = new Integer(array[i]);                //!
    
    List arrlist = new ArrayList(Arrays.asList(op));
    applyInplace(arrlist);
    
    int[] result;                                   //!
    if (arrlist.size() != array.length)
      result = new int[arrlist.size()];             //!
    else
      result = array;
    
    for (int i = 0; i < arrlist.size(); ++i)
      result[i] = ((Number)arrlist.get(i)).intValue();  //!
    
    return result;
  }
  
  
  /** Specialized version for float arrays. This default version converts
    all array elements between primitive type and appropriate
    Number subclass and calls applyInplace(List data) for the actual list
    operation.
    <P>
    This method may be overridden, mainly for speed reasons.
    
    @see #applyInplace(List)
  */
  
  public float[] apply(float[] array)  // All lines with //! must be changed between different types
  {
    if (array == null)
      return null;
    Object[] op = new Object[array.length];
    for (int i = 0; i < array.length; ++i)
      op[i] = new Float(array[i]);                //!
    
    List arrlist = new ArrayList(Arrays.asList(op));
    applyInplace(arrlist);
    
    float[] result;                                   //!
    if (arrlist.size() != array.length)
      result = new float[arrlist.size()];             //!
    else
      result = array;
    
    for (int i = 0; i < arrlist.size(); ++i)
      result[i] = ((Number)arrlist.get(i)).floatValue();  //!
    
    return result;
  }
  
  
  /** Specialized version for double arrays. This default version converts
    all array elements between primitive type and appropriate
    Number subclass and calls applyInplace(List data) for the actual list
    operation.
    <P>
    This method may be overridden, mainly for speed reasons.
    
    @see #applyInplace(List)
  */
  
  public double[] apply(double[] array)  // All lines with //! must be changed between different types
  {
    if (array == null)
      return null;
    Object[] op = new Object[array.length];
    for (int i = 0; i < array.length; ++i)
      op[i] = new Double(array[i]);                //!
    
    List arrlist = new ArrayList(Arrays.asList(op));
    applyInplace(arrlist);
    
    double[] result;                                   //!
    if (arrlist.size() != array.length)
      result = new double[arrlist.size()];             //!
    else
      result = array;
    
    for (int i = 0; i < arrlist.size(); ++i)
      result[i] = ((Number)arrlist.get(i)).doubleValue();  //!
    
    return result;
  }
}
