/*
 * Copyright (c) 2020 Martin Davis
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package org.locationtech.jtstest.geomfunction;

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

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jtstest.function.FunctionsUtil;
import org.locationtech.jtstest.util.ClassUtil;

/**
 * Repeats a function a given number of times.
 * If the function has a single numeric argument, 
 * the argument will be multiplied by the repeat counter for every call, 
 * and the function results will be accumulated 
 * into a collection to provide the final result.
 * 
 * @author Martin Davis
 *
 */
public class RepeaterGeometryFunction implements GeometryFunction {

  private GeometryFunction fun;
  private int count;
  private boolean hasRepeatableArg;

  public RepeaterGeometryFunction(GeometryFunction fun, int count) {
    this.fun = fun;
    this.count = count;
    hasRepeatableArg = hasRepeatableArg(fun);
  }
  
  public String getCategory() {
    return fun.getCategory();
  }

  public String getName() {
    return fun.getName() + repeatAnnotation();
  }

  public String getDescription() {
    return fun.getDescription();
  }

  public String[] getParameterNames() {
    return fun.getParameterNames();
  }

  public Class<?>[] getParameterTypes() {
    return fun.getParameterTypes();
  }

  public Class<?> getReturnType() {
    return fun.getReturnType();
  }

  public String getSignature() {
    return fun.getSignature();
  }

  private String repeatAnnotation() {
    return "*" + count;
  }

  public boolean isBinary() {
    return fun.isBinary();
  }
  
  public boolean isRequiredB() {
    return fun.isRequiredB();
  }
  
  public Object invoke(Geometry geom, Object[] args) {
    
    if (! isRepeatable(fun)) {
      throw new IllegalArgumentException("Cannot repeat function whose argumnent is not a double");
    }
    
    //TODO: handle repeating methods with integer arg
    int repeatArgIndex = repeatableArgIndex(fun);
    double argStart = 0;
    if (repeatArgIndex < args.length)
      argStart = ClassUtil.toDouble(args[repeatArgIndex]);
    return invokeRepeated(geom, args, argStart);
  }

  public static boolean isRepeatable(GeometryFunction fun) {
    if (! (fun.getReturnType() ==  Geometry.class )) return false;
    
    Class<?>[] paramType = fun.getParameterTypes();
    int repeatArgIndex = repeatableArgIndex(fun);
    
    // allow no repeatable arg
    if (paramType.length < repeatArgIndex + 1) return true;
    
    Class<?> type = paramType[repeatArgIndex];
    if (! ClassUtil.isDouble(type)) return false;
    
    return true;
  }

  public static boolean hasRepeatableArg(GeometryFunction fun) {
    Class<?>[] paramType = fun.getParameterTypes();
    int numParam = paramType.length;
    int numGeomParam = fun.isBinary() ? 1 : 0;
    return numParam > numGeomParam;
  }
  
  public static int repeatableArgIndex(GeometryFunction fun) {
    if (fun.isBinary()) return 1;
    return 0;
  }
  
  private Object invokeRepeated(Geometry geom, Object[] args, double argStart) {
    List<Geometry> results = new ArrayList<Geometry>();
    int repeatArgIndex = repeatableArgIndex(fun);
    for (int i = 1; i <= count; i++) {
      double val = argStart * i;
      Geometry result = (Geometry) fun.invoke(geom, copyArgs(args, repeatArgIndex, val));
      if (result == null) continue;
      //System.out.println("Repeat: " + i);
      if (hasRepeatableArg || i == 1) {
        FunctionsUtil.showIndicator(result);
        results.add(result);
      }
    }
    return geom.getFactory().buildGeometry(results);
  }

  private Object[] copyArgs(Object[] args, int replaceIndex, double val) {
    Object[] newArgs = args.clone();
    // only copy arg if there is a repeatable arg
    if (newArgs.length > replaceIndex)
      newArgs[replaceIndex] = val;
    return newArgs;
  }

}
