/*
 * 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.jts.io;

import java.io.IOException;
import java.util.EnumSet;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateFilter;
import org.locationtech.jts.geom.CoordinateSequenceComparator;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
import org.locationtech.jts.util.GeometricShapeFactory;

import junit.framework.TestCase;
import junit.textui.TestRunner;


/**
 * Tests the {@link WKBReader} and {@link WKBWriter}.
 * Tests all geometries with both 2 and 3 dimensions and both byte orderings.
 */
public class WKBTest
    extends TestCase
{
  public static void main(String args[]) {
    TestRunner.run(WKBTest.class);
  }

  private GeometryFactory geomFactory = new GeometryFactory();
  private WKTReader rdr = new WKTReader(geomFactory);

  public WKBTest(String name)
  {
    super(name);
  }

  public void testFirst()
  throws IOException, ParseException
  {
    runWKBTest("MULTIPOINT ((0 0), (1 4), (100 200))");
  }

	public void testPointPCS() throws IOException, ParseException {
		runWKBTestPackedCoordinate("POINT (1 2)");
	}
	
	public void testPoint() throws IOException, ParseException {
		runWKBTest("POINT (1 2)");
	}
	
  public void testPointEmpty() throws IOException, ParseException {
    runWKBTest("POINT EMPTY");
  }
  
	public void testLineString()
      throws IOException, ParseException
  {
    runWKBTest("LINESTRING (1 2, 10 20, 100 200)");
  }
  public void testPolygon()
      throws IOException, ParseException
  {
    runWKBTest("POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0))");
  }
  public void testPolygonWithHole()
      throws IOException, ParseException
  {
    runWKBTest("POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0), (1 1, 1 10, 10 10, 10 1, 1 1) )");
  }
  public void testMultiPoint()
      throws IOException, ParseException
  {
    runWKBTest("MULTIPOINT ((0 0), (1 4), (100 200))");
  }
  public void testMultiLineString()
      throws IOException, ParseException
  {
    runWKBTest("MULTILINESTRING ((0 0, 1 10), (10 10, 20 30), (123 123, 456 789))");
  }
  public void testMultiPolygon()
      throws IOException, ParseException
  {
    runWKBTest("MULTIPOLYGON ( ((0 0, 100 0, 100 100, 0 100, 0 0), (1 1, 1 10, 10 10, 10 1, 1 1) ), ((200 200, 200 250, 250 250, 250 200, 200 200)) )");
  }

  public void testGeometryCollection()
      throws IOException, ParseException
  {
    runWKBTest("GEOMETRYCOLLECTION ( POINT ( 1 1), LINESTRING (0 0, 10 10), POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0)) )");
  }

  public void testNestedGeometryCollection()
      throws IOException, ParseException
  {
    runWKBTest("GEOMETRYCOLLECTION ( POINT (20 20), GEOMETRYCOLLECTION ( POINT ( 1 1), LINESTRING (0 0, 10 10), POLYGON ((0 0, 100 0, 100 100, 0 100, 0 0)) ) )");
  }
  public void testLineStringEmpty()
      throws IOException, ParseException
  {
    runWKBTest("LINESTRING EMPTY");
  }
  public void testGeometryCollectionContainingEmptyGeometries()
      throws IOException, ParseException
  {
    runWKBTest("GEOMETRYCOLLECTION (LINESTRING EMPTY, MULTIPOINT EMPTY)");
  }

  public void testBigPolygon()
      throws IOException, ParseException
  {
    GeometricShapeFactory shapeFactory = new GeometricShapeFactory(geomFactory);
    shapeFactory.setBase(new Coordinate(0,0));
    shapeFactory.setSize(1000);
    shapeFactory.setNumPoints(1000);
    Geometry geom = shapeFactory.createRectangle();
    runWKBTest(geom, 2, false);
  }

  public void testPolygonEmpty()
      throws IOException, ParseException
  {
    runWKBTest("POLYGON EMPTY");
  }
  public void testMultiPointEmpty()
      throws IOException, ParseException
  {
    runWKBTest("MULTIPOINT EMPTY");
  }
  public void testMultiLineStringEmpty()
      throws IOException, ParseException
  {
    runWKBTest("MULTILINESTRING EMPTY");
  }
  public void testMultiPolygonEmpty()
      throws IOException, ParseException
  {
    runWKBTest("MULTIPOLYGON EMPTY");
  }
  public void testGeometryCollectionEmpty()
      throws IOException, ParseException
  {
    runWKBTest("GEOMETRYCOLLECTION EMPTY");
  }

  /**
   * Tests if a previously written WKB with M-coordinates can be read as expected.
   */
  public void testWriteAndReadM() throws ParseException
  {
    String wkt = "MULTILINESTRING M((1 1 1, 2 2 2))";
    WKTReader wktReader = new WKTReader();
    Geometry geometryBefore = wktReader.read(wkt);

    WKBWriter wkbWriter = new WKBWriter(3);
    wkbWriter.setOutputOrdinates(EnumSet.of(Ordinate.X, Ordinate.Y, Ordinate.M));
    byte[] write = wkbWriter.write(geometryBefore);

    WKBReader wkbReader = new WKBReader();
    Geometry geometryAfter = wkbReader.read(write);
    
    assertEquals(1.0, geometryAfter.getCoordinates()[0].getX());
    assertEquals(1.0, geometryAfter.getCoordinates()[0].getY());
    assertEquals(Double.NaN, geometryAfter.getCoordinates()[0].getZ());
    assertEquals(1.0, geometryAfter.getCoordinates()[0].getM());
  }

  /**
   * Tests if a previously written WKB with Z-coordinates can be read as expected.
   */
  public void testWriteAndReadZ() throws ParseException
  {
    String wkt = "MULTILINESTRING ((1 1 1, 2 2 2))";
    WKTReader wktReader = new WKTReader();
    Geometry geometryBefore = wktReader.read(wkt);

    WKBWriter wkbWriter = new WKBWriter(3);
    byte[] write = wkbWriter.write(geometryBefore);

    WKBReader wkbReader = new WKBReader();
    Geometry geometryAfter = wkbReader.read(write);
    
    assertEquals(1.0, geometryAfter.getCoordinates()[0].getX());
    assertEquals(1.0, geometryAfter.getCoordinates()[0].getY());
    assertEquals(1.0, geometryAfter.getCoordinates()[0].getZ());
    assertEquals(Double.NaN, geometryAfter.getCoordinates()[0].getM());
  }

  private void runWKBTest(String wkt) throws IOException, ParseException 
  {
  	runWKBTestCoordinateArray(wkt);
  	runWKBTestPackedCoordinate(wkt);
	}

	private void runWKBTestPackedCoordinate(String wkt) throws IOException, ParseException {
		GeometryFactory geomFactory = new GeometryFactory(
				new PackedCoordinateSequenceFactory(PackedCoordinateSequenceFactory.DOUBLE));
	  WKTReader rdr = new WKTReader(geomFactory);
		Geometry g = rdr.read(wkt);
		
		// Since we are using a PCS of dim=2, only check 2-dimensional storage
		runWKBTest(g, 2, true);
		runWKBTest(g, 2, false);
	}

	private void runWKBTestCoordinateArray(String wkt) throws IOException, ParseException {
	  GeometryFactory geomFactory = new GeometryFactory();
	  WKTReader rdr = new WKTReader(geomFactory);
		Geometry g = rdr.read(wkt);
		
		// CoordinateArrays support dimension 3, so test both dimensions
		runWKBTest(g, 2, true);
		runWKBTest(g, 2, false);
		runWKBTest(g, 3, true);
		runWKBTest(g, 3, false);
	}

	private void runWKBTest(Geometry g, int dimension, boolean toHex)
	throws IOException, ParseException
	{
	  setZ(g);
    runWKBTest(g, dimension, ByteOrderValues.LITTLE_ENDIAN, toHex);
    runWKBTest(g, dimension, ByteOrderValues.BIG_ENDIAN, toHex);
	}
	
	private void runWKBTest(Geometry g, int dimension, int byteOrder, boolean toHex)
	throws IOException, ParseException
	{
    runGeometry(g, dimension, byteOrder, toHex, 100);
    runGeometry(g, dimension, byteOrder, toHex, 0);
    runGeometry(g, dimension, byteOrder, toHex, 101010);
	  runGeometry(g, dimension, byteOrder, toHex, -1);
	}

  private void setZ(Geometry g)
  {
    g.apply(new AverageZFilter());
  }

  //static Comparator comp2D = new Coordinate.DimensionalComparator();
  //static Comparator comp3D = new Coordinate.DimensionalComparator(3);

  static CoordinateSequenceComparator comp2 = new CoordinateSequenceComparator(2);
  static CoordinateSequenceComparator comp3 = new CoordinateSequenceComparator(3);

  /**
   * Use single WKB reader, to ensure it can be used for multiple input geometries
   */
  WKBReader wkbReader = new WKBReader(geomFactory);

  void runGeometry(Geometry g, int dimension, int byteOrder, boolean toHex, int srid)
      throws IOException, ParseException
  {
    boolean includeSRID = false;
    if (srid >= 0) {
      includeSRID = true;
      g.setSRID(srid);
    }
    
    WKBWriter wkbWriter = new WKBWriter(dimension, byteOrder, includeSRID);
    byte[] wkb = wkbWriter.write(g);
    String wkbHex = null;
    if (toHex)
      wkbHex = WKBWriter.toHex(wkb);

    if (toHex)
      wkb = WKBReader.hexToBytes(wkbHex);
    Geometry g2 = wkbReader.read(wkb);

    CoordinateSequenceComparator comp = (dimension == 2) ? comp2 : comp3;
    boolean isEqual = (g.compareTo(g2, comp) == 0);
    assertTrue(isEqual);
    
    if (includeSRID) {
      boolean isSRIDEqual = g.getSRID() == g2.getSRID();
      assertTrue(isSRIDEqual);
    }
  }
}

class AverageZFilter implements CoordinateFilter
{
  public void filter(Coordinate coord)
  {
    coord.setZ((coord.x + coord.y) / 2);
  }
}

