/**
 * MVEL 2.0
 * Copyright (C) 2007 The Codehaus
 * Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mvel2.ast;

import org.mvel2.CompileException;
import org.mvel2.ParserContext;
import org.mvel2.compiler.ExecutableStatement;
import org.mvel2.integration.VariableResolverFactory;
import org.mvel2.integration.impl.DefaultLocalVariableResolverFactory;
import org.mvel2.integration.impl.ItemResolverFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.mvel2.util.CompilerTools.expectType;
import static org.mvel2.util.ParseTools.*;

public class Fold extends ASTNode {
  private ExecutableStatement subEx;
  private ExecutableStatement dataEx;
  private ExecutableStatement constraintEx;

  public Fold(char[] expr, int start, int offset, int fields, ParserContext pCtx) {
    super(pCtx);
    this.expr = expr;
    this.start = start;
    this.offset = offset;

    int cursor = start;
    int end = start + offset;
    for (; cursor < end; cursor++) {
      if (isWhitespace(expr[cursor])) {
        while (cursor < end && isWhitespace(expr[cursor])) cursor++;

        if (expr[cursor] == 'i' && expr[cursor + 1] == 'n' && isJunct(expr[cursor + 2])) {
          break;
        }
      }
    }

    subEx = (ExecutableStatement) subCompileExpression(expr, start, cursor - start - 1, pCtx);
    int st = cursor += 2; // skip 'in'

    for (; cursor < end; cursor++) {
      if (isWhitespace(expr[cursor])) {
        while (cursor < end && isWhitespace(expr[cursor])) cursor++;

        if (expr[cursor] == 'i' && expr[cursor + 1] == 'f' && isJunct(expr[cursor + 2])) {
          int s = cursor + 2;
          constraintEx = (ExecutableStatement) subCompileExpression(expr, s, end - s, pCtx);
          break;
        }
      }
    }

    while (isWhitespace(expr[cursor])) cursor--;

    expectType(pCtx, dataEx = (ExecutableStatement) subCompileExpression(expr, st, cursor - st, pCtx),
        Collection.class, ((fields & COMPILE_IMMEDIATE) != 0));
  }

  public Object getReducedValueAccelerated(Object ctx, Object thisValue, VariableResolverFactory factory) {
    ItemResolverFactory.ItemResolver itemR = new ItemResolverFactory.ItemResolver("$");
    ItemResolverFactory itemFactory = new ItemResolverFactory(itemR, new DefaultLocalVariableResolverFactory(factory));

    List list;

    if (constraintEx != null) {
      Collection col = ((Collection) dataEx.getValue(ctx, thisValue, factory));
      list = new ArrayList(col.size());

      for (Object o : col) {
        itemR.value = o;
        if ((Boolean) constraintEx.getValue(ctx, thisValue, itemFactory)) {
          list.add(subEx.getValue(o, thisValue, itemFactory));
        }
      }

    }
    else {
      Collection col = ((Collection) dataEx.getValue(ctx, thisValue, factory));
      list = new ArrayList(col.size());
      for (Object o : col) {
        list.add(subEx.getValue(itemR.value = o, thisValue, itemFactory));
      }
    }
    return list;
  }

  public Object getReducedValue(Object ctx, Object thisValue, VariableResolverFactory factory) {
    ItemResolverFactory.ItemResolver itemR = new ItemResolverFactory.ItemResolver("$");
    ItemResolverFactory itemFactory = new ItemResolverFactory(itemR, new DefaultLocalVariableResolverFactory(factory));

    List list;

    if (constraintEx != null) {
      Object x = dataEx.getValue(ctx, thisValue, factory);

      if (!(x instanceof Collection))
        throw new CompileException("was expecting type: Collection; but found type: "
            + (x == null ? "null" : x.getClass().getName()), expr, start);

      list = new ArrayList(((Collection) x).size());
      for (Object o : (Collection) x) {
        itemR.value = o;
        if ((Boolean) constraintEx.getValue(ctx, thisValue, itemFactory)) {
          list.add(subEx.getValue(o, thisValue, itemFactory));
        }
      }
    }
    else {
      Object x = dataEx.getValue(ctx, thisValue, factory);

      if (!(x instanceof Collection))
        throw new CompileException("was expecting type: Collection; but found type: "
            + (x == null ? "null" : x.getClass().getName()), expr, start);

      list = new ArrayList(((Collection) x).size());
      for (Object o : (Collection) x) {
        list.add(subEx.getValue(itemR.value = o, thisValue, itemFactory));
      }
    }

    return list;
  }

  public Class getEgressType() {
    return Collection.class;
  }
}
