/*
 * Copyright (c) 2016 Vivid Solutions.
 *
 * 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.testrunner;

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

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.util.Assert;
import org.locationtech.jtstest.geomop.GeometryOperation;
import org.locationtech.jtstest.util.StringUtil;


/**
 *  A test for two geometries.
 *
 * @version 1.7
 */
public class Test implements Runnable 
{
  private String description;
  private String operation;
  private Result expectedResult;
  private int testIndex;
  private String geometryIndex;
  private List<String> arguments;
  private TestCase testCase;
  private boolean passed;
  private double tolerance;
  
  // cache for actual computed result
  private Geometry targetGeometry;
  private Object[] operationArgs;
  private boolean isRun = false;
  private Result actualResult = null;
  private Exception exception = null;

  /**
   *  Creates a Test with the given description. The given operation (e.g.
   *  "equals") will be performed, the expected result of which is <tt>expectedResult</tt>.
   */
  public Test(TestCase testCase, int testIndex, String description, String operation, String geometryIndex,
      List<String> arguments, Result expectedResult, double tolerance) {
    this.tolerance = tolerance;
    this.description = description;
    this.operation = operation;
    this.expectedResult = expectedResult;
    this.testIndex = testIndex;
    this.geometryIndex = geometryIndex;
    this.arguments = new ArrayList<String>(arguments);
    this.testCase = testCase;
  }

  public void setResult(Result result) {
    this.expectedResult = result;
  }

  public void setArgument(int i, String value) {
    arguments.set(i, value);
  }

  public String getDescription() {
    return description;
  }

  public String getGeometryIndex() {
    return geometryIndex;
  }

  public Result getExpectedResult() {
    return expectedResult;
  }

  public boolean hasExpectedResult() {
    return expectedResult != null;
  }
  
  public String getOperation() {
    return operation;
  }

  public int getTestIndex() {
    return testIndex;
  }

  public String getArgument(int i) {
    return (String) arguments.get(i);
  }

  public int getArgumentCount() {
    return arguments.size();
  }

  /**
   *  Returns whether the Test is passed.
   */
  public boolean isPassed() {
    return passed;
  }

  public Exception getException() {
    return exception;
  }

  public TestCase getTestCase() {
    return testCase;
  }

  public void removeArgument(int i) {
    arguments.remove(i);
  }

  public void run() {
    try {
      exception = null;
      passed = computePassed();
    }
    catch (Exception e) {
      exception = e;
    }
  }

  public boolean isRun()
  {
  	return isRun;
  }
  
  public boolean computePassed()
      throws Exception
  {
    Result actualResult = getActualResult();
    
    // don't check expected if it wasn't provided
    if (! hasExpectedResult())
      return true;
    
    ResultMatcher matcher = testCase.getTestRun().getResultMatcher();
    
    // check that provided expected result geometry is valid
    // MD - disable except for testing
    //if (! isExpectedResultGeometryValid()) return false;
    
    return matcher.isMatch(targetGeometry, operation, operationArgs, 
    		actualResult, expectedResult, 
    		tolerance);
//    return expectedResult.equals(actualResult, tolerance);
  }

  private boolean isExpectedResultGeometryValid()
  {
    if (expectedResult instanceof GeometryResult) {
    	Geometry expectedGeom = ((GeometryResult) expectedResult).getGeometry();
    	return expectedGeom.isValid();
    }
    return true;
  }
  
  /**
   * Computes the actual result and caches the result value.
   * 
   * @return the actual result computed
   * @throws Exception if the operation fails
   */
  public Result getActualResult() throws Exception 
  {
  	if (isRun)
  		return actualResult;
  	
  	isRun = true;
    targetGeometry = geometryIndex.equalsIgnoreCase("A")
         ? testCase.getGeometryA()
         : testCase.getGeometryB();

         operationArgs = convertArgs(arguments);
    GeometryOperation op = getGeometryOperation();
    actualResult = op.invoke(operation, targetGeometry, operationArgs);
    return actualResult;
  }

  private GeometryOperation getGeometryOperation()
  {
  	return testCase.getTestRun().getGeometryOperation();
  }
  	
  public String toXml() {
    String xml = "";
    xml += "<test>" + StringUtil.newLine;
    if (description != null && description.length() > 0) {
      xml += "  <desc>" + StringUtil.escapeHTML(description) + "</desc>" +
          StringUtil.newLine;
    }
    xml += "  <op name=\"" + operation + "\"";
    xml += " arg1=\"" + geometryIndex + "\"";
    int j = 2;
    for (String argument : arguments ) {
      Assert.isTrue(argument != null);
      xml += " arg" + j + "=\"" + argument + "\"";
      j++;
    }

    xml += ">" + StringUtil.newLine;
    xml += StringUtil.indent(expectedResult.toFormattedString(), 4) + StringUtil.newLine;
    xml += "  </op>" + StringUtil.newLine;
    xml += "</test>" + StringUtil.newLine;
    return xml;
  }

  private Object[] convertArgs(List argStr)
  {
    Object[] args = new Object[argStr.size()];
    for (int i = 0; i < args.length; i++) {
      args[i] = convertArgToGeomOrString((String) argStr.get(i));
    }
    return args;
  }

  private Object convertArgToGeomOrString(String argStr)
  {
    if (argStr.equalsIgnoreCase("null")) {
      return null;
    }
    if (argStr.equalsIgnoreCase("A")) {
      return testCase.getGeometryA();
    }
    if (argStr.equalsIgnoreCase("B")) {
      return testCase.getGeometryB();
    }
    return argStr;
  }


}

