package commons.application.ctx.functor;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import commons.application.ctx.Context;
import basics.unexpected.Problem;
/**
 * Verarbeitet Listen, Arrays, Tokenizers. Fuehrt ein predicate (das eine kette von predikaten sein
 * kann) auf allen elementen einer liste aus. Am Ende der Verarbeitungskette wird das verarbeitete
 * Objekt wieder in die Liste kopiert. Wenn removeNullValues=true werden Nullwerte nicht wieder auch
 * zurueckgeschrieben, das hat zur Folge, dass die Liste kopiert und Nullwerte uebersprungen werden.
 * die ausfuehrung wird gestoppt, wenn ein predicate wahr zurueckgibt
 * @author ks
 */
public class LProcessor
{

// private static Logger logger = Logger.getLogger(LProcessor.class);
LPredicate                   predicate        = null;
Context                      context          = null;
boolean                      removeNullValues = true;
private static final boolean CONT             = false;
private static final boolean STOP             = true;
private boolean              state;

public LProcessor(LPredicate p)
{
   this.predicate = p;
   this.state = CONT;
}

public LProcessor(LPredicate p, Context c)
{
   this.predicate = p;
   this.context = c;
   this.state = CONT;
}

public LProcessor(LPredicate p, boolean removeNullValues)
{
   this.predicate = p;
   this.removeNullValues = removeNullValues;
   this.state = CONT;
}

public LProcessor(LPredicate p, Context c, boolean removeNullValues)
{
   this.predicate = p;
   this.context = c;
   this.removeNullValues = removeNullValues;
   this.state = CONT;
}

/**
 * durchlaeuft die list und fuehrt das predicate mit jedem element aus.
 * @param list
 */
public List<Object> process (List<Object> list)
{
   if (list == null || list.size() == 0) return null;
   predicate.setSize(list.size());
   List<Object> newList = removeNullValues ? new ArrayList<Object>() : null;
   for (int i = 0; i < list.size() && state == CONT; i++)
   {
      Object current = list.get(i);
      current = rprocess(predicate, current);
      if (removeNullValues)
      {
         if (current != null) newList.add(current);
      }
      else
      {
         list.set(i, current);
      }
   }
   return removeNullValues ? newList : list;
}

/**
 * durchlaeuft das array und fuehrt das predicate mit jedem element aus.
 * @param array mit den elementen
 */
public Object[] process (Object[] array)
{
   if (array == null || array.length == 0) return null;
   predicate.setSize(array.length);
   Object[] newArray = null;
   if (removeNullValues) newArray = (Object[]) Array.newInstance(array[0].getClass(), 0);
   for (int i = 0; i < array.length && state == CONT; i++)
   {
      Object current = array[i];
      current = rprocess(predicate, current);
      if (removeNullValues)
      {
         if (current != null)
         {
            newArray = (Object[]) grow(newArray, 1);
            newArray[newArray.length - 1] = current;
         }
      }
      else
      {
         array[i] = current;
      }
   }
   return removeNullValues ? newArray : array;
}

/**
 * durchlaeuft den string mit einem tokenizer und fuehrt das predicate mit jedem element aus.
 * @param string mit den elementen
 */
public String process (String input, String delim)
{
   if (input == null) return null;
   StringTokenizer tokenizer = new StringTokenizer(input, delim);
   int elements = tokenizer.countTokens();
   predicate.setSize(elements);
   StringBuilder result = new StringBuilder();
   while (tokenizer.hasMoreTokens() && state == CONT)
   {
      Object current = tokenizer.nextToken();
      current = rprocess(predicate, current);
      if (!removeNullValues)
      {
         result.append(current);
         result.append(delim);
      }
   }
   return result.toString();
}

/**
 * durchlaeuft eine string zeiche fuer zeichen und fuehrt das predicate mit jedem element aus.
 * @param input mit den elementen
 */
public String process (String input)
{
   if (input == null || input.length() == 0) return null;
   int len = input.length();
   predicate.setSize(input.length());
   StringBuilder output = null;
   if (removeNullValues) output = new StringBuilder();
   for (int i = 0; i < len && state == CONT; i++)
   {
      Object current = input.substring(i, i + 1);
      current = rprocess(predicate, current);
      if (!removeNullValues || current != null) output.append(current);
   }
   return output.toString();
}

private Object rprocess (LPredicate currentPredicate, Object object)
{
   if (state == STOP) return object;
   if (currentPredicate instanceof LCriteria && currentPredicate.isTrue(object, context))
   {
      if (currentPredicate instanceof LSkipCriteria)
      {
         // logger.info("skipped (" + object +") due to " +
         // currentPredicate.getClass().getSimpleName());
         return object;
      }
      if (currentPredicate instanceof LStopCriteria)
      {
         // logger.info("stopped (" + object +") due to " +
         // currentPredicate.getClass().getSimpleName());
         state = STOP;
         return object;
      }
   }
   object = currentPredicate.operate(object, context);
   if (currentPredicate != null && currentPredicate.nextPredicate != null) object = rprocess(currentPredicate.nextPredicate, object);
   return object;
}

@SuppressWarnings("unchecked")
private Object grow (Object obj, int i)
{
   Class class1 = obj.getClass();
   if (!class1.isArray())
   {
      return null;
   }
   else
   {
      Class class2 = obj.getClass().getComponentType();
      int j = Array.getLength(obj);
      int k = j + i;
      Object obj1 = Array.newInstance(class2, k);
      System.arraycopy(obj, 0, obj1, 0, j);
      return obj1;
   }
}

@SuppressWarnings("unchecked")
public static LPredicate instance (String classname) throws Problem
{
   LPredicate instance = null;
   String classToLoad = classname;
   Class clazz = null;
   try
   {
      clazz = Class.forName(classToLoad);
      instance = (LPredicate) clazz.newInstance();
   }
   catch (ClassNotFoundException e)
   {
      throw new Problem("Not found: " + classToLoad);
   }
   catch (IllegalAccessException e)
   {
      throw new Problem("No access: " + clazz.getName());
   }
   catch (InstantiationException e)
   {
      throw new Problem("No instance: " + clazz.getName());
   }
   return instance;
}
}
