package nxm.sys.test.junit.lib;

import java.util.List;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import nxm.sys.lib.Midas;
import nxm.sys.lib.Position;
import nxm.sys.lib.ShapeFile;
import nxm.sys.lib.ShapeFile.Shape;
import nxm.sys.test.junit.NMTestCase;

/**
 * Test file for ShapeFile
 * @author Kevin Esterheld
 * @since NeXtMidas 3.9.0
 */
public class ShapeFileTest extends NMTestCase {
  private ShapeFile shp;

  /** public constructor to allow running this test case directly with JUnit. */
  public ShapeFileTest (String methodName) {
    this(methodName, null);
  }

  /**
   * This constructor is required by NeXtMidas Unit Testing framework.
   * @param methodName The name of the method being called.
   * @param midas The midas context to use when running the tests.
   */
  public ShapeFileTest (String methodName, Midas midas) {
    super(methodName, midas);
  }

  /**
   * This is run before each test. It will do any necessary startup.
   */
  @Before
  public void setUp () {
    shp = new ShapeFile(getMidasContext(), urlSysTest+"worldtestfile.shp");
    shp.open();
  }

  /**
   * tests that all shapes are returned.
   */
  @Test
  public void testGetAllShapes () {
    List<Shape> shapes = shp.getAllShapes();
    assertEquals(urlSysTest+"worldtestfile.shp should hold 246 shapes", 246, shapes.size());
  }

  /**
   * Test that we are able to get the shape we want out of the list.
   */
  @Test
  public void testGetDataTable () {
    Shape shape = shp.getShapes()[20];
    assertEquals("FIPS is BR", "BR", shape.getDataTable().get("FIPS"));
    assertEquals("country code is BR", "BR", shape.getDataTable().get("CODE"));
    assertEquals("3 letter country code is BRA", "BRA", shape.getDataTable().get("CODE_3"));
    assertEquals("Country name is Brazil", "Brazil", shape.getDataTable().get("NAME"));
    assertEquals("Brazil POPULATION=186830759.0", 186830759.0, shape.getDataTable().get("POPULATION"));
    assertEquals("Brazil LONGITUDE=-53.089", -53.089, shape.getDataTable().get("LONGITUDE"));
    assertEquals("Brazil LATITUDE=-10.772", -10.772, shape.getDataTable().get("LATITUDE"));
  }

  /**
   * Test that a point is inside the shape.
   */
  @Test
  public void testContains () {
    Position pos = new Position();
    pos.setGeo(0, -10, -45);
    assertTrue("The point is within Brazil", shp.getShapes()[20].contains(pos));
    assertFalse("The point is not within the United States", shp.getShapes()[208].contains(pos));

  }

  /**
   * Test that a point is near a shape using a given distance as the measurement.
   */
  @Test
  public void testIsNear () {

    // shapes to use for calculations
    Shape shapeUS = shp.getShapes()[208];
    Shape shapeMex = shp.getShapes()[119];
    Shape shapeBra = shp.getShapes()[20];

    // Off the coast of VA
    Position pos = Position.fromGeo(0, 35.7, -75.5); // adjusted for NXM-3622
    assertTrue("Shape is not within the polygon", !shapeUS.contains(pos)); // make sure it is not contained in the shape.
    assertTrue("Position is within 10000 meters", shapeUS.isNear(pos, 10000)); // Is it within 10000 meters

    // middle of Atlantic, not close
    pos = Position.fromGeo(0, -33, 32);
    assertFalse("Position is not in US", shapeUS.isNear(pos, 10000)); // Is it within 10000 meters
    assertFalse("Position is not within 10000 meters", shapeUS.isNear(pos, 10000)); // Is it within 10000 meters

    // In Brazil
    pos = Position.fromGeo(0, -10, -45);
    assertTrue("The point is within Brazil", shapeBra.contains(pos)); // The point is in Brazil, returns true
    assertTrue("The point is within Brazil and is near as well.", shp.getShapes()[20].contains(pos));

    // near Mexico and US
    pos = Position.fromGeo(0, 25.971, -97.124);
    assertFalse("The point is not in Mexico", shapeMex.contains(pos));
    assertFalse("The point is not in the U.S.", shapeUS.contains(pos));
    assertTrue("The point is near the U.S.", shapeUS.isNear(pos, 10000));
    assertTrue("The point is near Mexico.", shapeMex.isNear(pos, 10000));

    // Middle of Pacific - closer to P0 than any point in between p0 and p1
    pos = Position.fromGeo(0, -45, -147);
    assertFalse("The point is not contained in the U.S.", shapeUS.contains(pos));
    assertFalse("The point is not near the U.S.", shapeUS.isNear(pos, 10000));
    // Middle of Pacific - closer to P1 than any point in between p1 and p0
    pos = Position.fromGeo(0, -45, -179.9);
    assertFalse("The point is not contained in the U.S.", shapeUS.contains(pos));
    assertFalse("The point is not near the U.S.", shapeUS.isNear(pos, 10000));
    // Middle of Pacific - closer to a point in between p0 and p1
    pos = Position.fromGeo(0, -45, -179.12);
    assertFalse("The point is not contained in the U.S.", shapeUS.contains(pos));
    assertFalse("The point is not near the U.S.", shapeUS.isNear(pos, 10000));

  }

  /** Just the start of testing ShapeFile.getShapesNear() */
  public void testGetShapesNear () {
    List<Shape> onesNear = shp.getShapesNear(0, 46.52615215310537, 13.718178651141322, 1e3);
    assertEquals("ShapeFile.getShapesNear Austria,Italy,Slovenia:","Austria,Italy,Slovenia",namesOfCountries(onesNear));

    onesNear = shp.getShapesNear(0, 50.12394222425839, 6.132168761864673, 1e3);
    assertEquals("ShapeFile.getShapesNear Germany,Belgium,Luxembourg:","Germany,Belgium,Luxembourg",namesOfCountries(onesNear));

    onesNear = shp.getShapesNear(0, 69.06653867858098, 20.593441185442202, 1e3);
    assertEquals("ShapeFile.getShapesNear Finland,Norway,Sweden:","Finland,Norway,Sweden",namesOfCountries(onesNear));

    onesNear = shp.getShapesNear(0, 80.79035458051912, -67.61281667308657, 20e3);
    assertEquals("ShapeFile.getShapesNear Canada,Greenland:","Canada,Greenland",namesOfCountries(onesNear));

    onesNear = shp.getShapesNear(0, 65.74603520036884, -168.5977216373841, 25e3);
    assertEquals("ShapeFile.getShapesNear Russia,United States:","Russia,United States",namesOfCountries(onesNear));

    onesNear = shp.getShapesNear(0, 49.16925226269752, 87.84136733749594, 1e3);
    assertEquals("ShapeFile.getShapesNear China,Mongolia,Russia:","China,Mongolia,Russia",namesOfCountries(onesNear));

    onesNear = shp.getShapesNear(0, -17.506803920768824, -69.49732494515018, 1e3);
    assertEquals("ShapeFile.getShapesNear Bolivia,Chile,Peru:","Bolivia,Chile,Peru",namesOfCountries(onesNear));

    onesNear = shp.getShapesNear(0, -9.066419269142337, 125.16369014040262, 1000);
    assertEquals("ShapeFile.getShapesNear Indonesia,Timor-Leste:","Indonesia,Timor-Leste",namesOfCountries(onesNear));

    // Hawaii
    onesNear = shp.getShapesNear(0, 19.5, -155.60613071187902, 1);
    assertEquals("ShapeFile.getShapesNear United States:","United States",namesOfCountries(onesNear));

    // Off coast of Taiwan
    onesNear = shp.getShapesNear(0, 23.05, 120.09501056429959, 10e3);
    assertEquals("ShapeFile.getShapesNear Taiwan:","Taiwan",namesOfCountries(onesNear));
  }

  private String namesOfCountries (List<Shape> onesNear) {
    String countryNames = "";
    boolean waitingOnFirst = true;
    for (Shape shape : onesNear) {
      String thisCountry = shape.getDataTable().get("NAME").toString();
      if (waitingOnFirst) {
        countryNames = thisCountry;
        waitingOnFirst = false;
      }
      else {
        countryNames += "," + thisCountry;
      }
    }
    return countryNames;
  }

  /**
   * Test that point is in multiple shapes.
   * The Vatican is a good choice as it is in the Vatican and Italy.
   */
  @Test
  public void testGetShapesContaining () {
    Position vaticanCity = new Position();
    vaticanCity.setGeo(0, 41.9029, 12.4534);
    List<Shape> shapesForVatican = shp.getShapesContaining(vaticanCity);
    assertEquals(shapesForVatican.size(), 2);
    String nameFirst = shapesForVatican.get(0).getDataTable().getS("NAME").toUpperCase();
    String nameSecond = shapesForVatican.get(1).getDataTable().getS("NAME").toUpperCase();

    assertTrue(nameFirst.contains("ITALY") || nameSecond.contains("ITALY"));
    assertTrue(nameFirst.contains("VATICAN") || nameSecond.contains("VATICAN"));
  }

  /**
   * This is called after each test. It will do any necessary cleanup.
   */
  @After
  public void tearDown () {

    shp.close();
  }

}