/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.lucene.document;

import org.apache.lucene.geo.Component2D;
import org.apache.lucene.geo.GeoUtils;
import org.apache.lucene.tests.util.LuceneTestCase;

/**
 * base shape encoding class for testing encoding of tessellated {@link
 * org.apache.lucene.document.XYShape} and {@link LatLonShape}
 */
public abstract class BaseShapeEncodingTestCase extends LuceneTestCase {

  protected abstract int encodeX(double x);

  protected abstract double decodeX(int x);

  protected abstract int encodeY(double y);

  protected abstract double decodeY(int y);

  protected abstract double nextX();

  protected abstract double nextY();

  protected abstract Object nextPolygon();

  protected abstract Component2D createPolygon2D(Object polygon);

  // One shared point with MBR -> MinY, MinX
  public void testPolygonEncodingMinLatMinLon() {
    double ay = 0.0;
    double ax = 0.0;
    double by = 1.0;
    double blon = 2.0;
    double cy = 2.0;
    double cx = 1.0;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(blon);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // One shared point with MBR -> MinLat, MaxLon
  public void testPolygonEncodingMinLatMaxLon() {
    double ay = 1.0;
    double ax = 0.0;
    double by = 0.0;
    double blon = 2.0;
    double cy = 2.0;
    double cx = 1.0;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(blon);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // One shared point with MBR -> MaxLat, MaxLon
  public void testPolygonEncodingMaxLatMaxLon() {
    double ay = 1.0;
    double ax = 0.0;
    double by = 2.0;
    double blon = 2.0;
    double cy = 0.0;
    double cx = 1.0;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(cy);
    int bxEnc = encodeX(cx);
    int cyEnc = encodeY(by);
    int cxEnc = encodeX(blon);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // One shared point with MBR -> MaxLat, MinLon
  public void testPolygonEncodingMaxLatMinLon() {
    double ay = 2.0;
    double ax = 0.0;
    double by = 1.0;
    double blon = 2.0;
    double cy = 0.0;
    double cx = 1.0;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(cy);
    int bxEnc = encodeX(cx);
    int cyEnc = encodeY(by);
    int cxEnc = encodeX(blon);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point below
  public void testPolygonEncodingMinLatMinLonMaxLatMaxLonBelow() {
    double ay = 0.0;
    double ax = 0.0;
    double by = 0.25;
    double blon = 0.75;
    double cy = 2.0;
    double cx = 2.0;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(blon);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // Two shared point with MBR -> [MinLat, MinLon], [MaxLat, MaxLon], third point above
  public void testPolygonEncodingMinLatMinLonMaxLatMaxLonAbove() {
    double ay = 0.0;
    double ax = 0.0;
    double by = 2.0;
    double bx = 2.0;
    double cy = 1.75;
    double cx = 1.25;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(bx);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point below
  public void testPolygonEncodingMinLatMaxLonMaxLatMinLonBelow() {
    double ay = 8.0;
    double ax = 6.0;
    double by = 6.25;
    double bx = 6.75;
    double cy = 6.0;
    double cx = 8.0;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(bx);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // Two shared point with MBR -> [MinLat, MaxLon], [MaxLat, MinLon], third point above
  public void testPolygonEncodingMinLatMaxLonMaxLatMinLonAbove() {
    double ay = 2.0;
    double ax = 0.0;
    double by = 0.0;
    double bx = 2.0;
    double cy = 1.75;
    double cx = 1.25;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(bx);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // all points shared with MBR
  public void testPolygonEncodingAllSharedAbove() {
    double ay = 0.0;
    double ax = 0.0;
    double by = 0.0;
    double bx = 2.0;
    double cy = 2.0;
    double cx = 2.0;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(bx);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    verifyEncodingPermutations(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // all points shared with MBR
  public void testPolygonEncodingAllSharedBelow() {
    double ay = 2.0;
    double ax = 0.0;
    double by = 0.0;
    double bx = 0.0;
    double cy = 2.0;
    double cx = 2.0;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(bx);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, cyEnc);
    assertEquals(encoded.cX, cxEnc);
  }

  // [a,b,c] == [c,a,b] == [b,c,a] == [c,b,a] == [b,a,c] == [a,c,b]
  public void verifyEncodingPermutations(
      int ayEnc, int axEnc, int byEnc, int bxEnc, int cyEnc, int cxEnc) {
    // this is only valid when points are not co-planar
    assertTrue(GeoUtils.orient(ayEnc, axEnc, byEnc, bxEnc, cyEnc, cxEnc) != 0);
    byte[] b = new byte[7 * ShapeField.BYTES];
    // [a,b,c]
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, false);
    ShapeField.DecodedTriangle encodedABC = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encodedABC);
    // [c,a,b]
    ShapeField.encodeTriangle(b, cyEnc, cxEnc, false, ayEnc, axEnc, true, byEnc, bxEnc, true);
    ShapeField.DecodedTriangle encodedCAB = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encodedCAB);
    assertEquals(encodedABC, encodedCAB);
    // [b,c,a]
    ShapeField.encodeTriangle(b, byEnc, bxEnc, true, cyEnc, cxEnc, false, ayEnc, axEnc, true);
    ShapeField.DecodedTriangle encodedBCA = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encodedBCA);
    assertEquals(encodedABC, encodedBCA);
    // [c,b,a]
    ShapeField.encodeTriangle(b, cyEnc, cxEnc, true, byEnc, bxEnc, true, ayEnc, axEnc, false);
    ShapeField.DecodedTriangle encodedCBA = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encodedCBA);
    assertEquals(encodedABC, encodedCBA);
    // [b,a,c]
    ShapeField.encodeTriangle(b, byEnc, bxEnc, true, ayEnc, axEnc, false, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encodedBAC = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encodedBAC);
    assertEquals(encodedABC, encodedBAC);
    // [a,c,b]
    ShapeField.encodeTriangle(b, ayEnc, axEnc, false, cyEnc, cxEnc, true, byEnc, bxEnc, true);
    ShapeField.DecodedTriangle encodedACB = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encodedACB);
    assertEquals(encodedABC, encodedACB);
  }

  public void testPointEncoding() {
    double lat = 45.0;
    double lon = 45.0;
    int latEnc = encodeY(lat);
    int lonEnc = encodeX(lon);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, latEnc, lonEnc, true, latEnc, lonEnc, true, latEnc, lonEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, latEnc);
    assertEquals(encoded.aX, lonEnc);
    assertEquals(encoded.bY, latEnc);
    assertEquals(encoded.bX, lonEnc);
    assertEquals(encoded.cY, latEnc);
    assertEquals(encoded.cX, lonEnc);
  }

  public void testLineEncodingSameLat() {
    double lat = 2.0;
    double ax = 0.0;
    double bx = 2.0;
    int latEnc = encodeY(lat);
    int axEnc = encodeX(ax);
    int bxEnc = encodeX(bx);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, latEnc, axEnc, true, latEnc, bxEnc, true, latEnc, axEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, latEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, latEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, latEnc);
    assertEquals(encoded.cX, axEnc);
    ShapeField.encodeTriangle(b, latEnc, axEnc, true, latEnc, axEnc, true, latEnc, bxEnc, true);
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, latEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, latEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, latEnc);
    assertEquals(encoded.cX, axEnc);
    ShapeField.encodeTriangle(b, latEnc, bxEnc, true, latEnc, axEnc, true, latEnc, axEnc, true);
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, latEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, latEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, latEnc);
    assertEquals(encoded.cX, axEnc);
  }

  public void testLineEncodingSameLon() {
    double ay = 0.0;
    double by = 2.0;
    double lon = 2.0;
    int ayEnc = encodeY(ay);
    int byEnc = encodeY(by);
    int lonEnc = encodeX(lon);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, lonEnc, true, byEnc, lonEnc, true, ayEnc, lonEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, lonEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, lonEnc);
    assertEquals(encoded.cY, ayEnc);
    assertEquals(encoded.cX, lonEnc);
    ShapeField.encodeTriangle(b, ayEnc, lonEnc, true, ayEnc, lonEnc, true, byEnc, lonEnc, true);
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, lonEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, lonEnc);
    assertEquals(encoded.cY, ayEnc);
    assertEquals(encoded.cX, lonEnc);
    ShapeField.encodeTriangle(b, byEnc, lonEnc, true, ayEnc, lonEnc, true, ayEnc, lonEnc, true);
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, lonEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, lonEnc);
    assertEquals(encoded.cY, ayEnc);
    assertEquals(encoded.cX, lonEnc);
  }

  public void testLineEncoding() {
    double ay = 0.0;
    double by = 2.0;
    double ax = 0.0;
    double bx = 2.0;
    int ayEnc = encodeY(ay);
    int byEnc = encodeY(by);
    int axEnc = encodeX(ax);
    int bxEnc = encodeX(bx);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, ayEnc, axEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, ayEnc);
    assertEquals(encoded.cX, axEnc);
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, ayEnc, axEnc, true, byEnc, bxEnc, true);
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, ayEnc);
    assertEquals(encoded.cX, axEnc);
    ShapeField.encodeTriangle(b, byEnc, bxEnc, true, ayEnc, axEnc, true, ayEnc, axEnc, true);
    ShapeField.decodeTriangle(b, encoded);
    assertEquals(encoded.aY, ayEnc);
    assertEquals(encoded.aX, axEnc);
    assertEquals(encoded.bY, byEnc);
    assertEquals(encoded.bX, bxEnc);
    assertEquals(encoded.cY, ayEnc);
    assertEquals(encoded.cX, axEnc);
  }

  public void testRandomPointEncoding() {
    double ay = nextY();
    double ax = nextX();
    verifyEncoding(ay, ax, ay, ax, ay, ax);
  }

  public void testRandomLineEncoding() {
    double ay = nextY();
    double ax = nextX();
    double by = nextY();
    double bx = nextX();
    verifyEncoding(ay, ax, by, bx, ay, ax);
  }

  public void testRandomPolygonEncoding() {
    double ay = nextY();
    double ax = nextX();
    double by = nextY();
    double bx = nextX();
    double cy = nextY();
    double cx = nextX();
    verifyEncoding(ay, ax, by, bx, cy, cx);
  }

  protected void verifyEncoding(double ay, double ax, double by, double bx, double cy, double cx) {
    // encode triangle
    int[] original =
        new int[] {encodeX(ax), encodeY(ay), encodeX(bx), encodeY(by), encodeX(cx), encodeY(cy)};
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(
        b,
        original[1],
        original[0],
        true,
        original[3],
        original[2],
        true,
        original[5],
        original[4],
        true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    // quantize decoded triangle
    double[] encodedQuantize =
        new double[] {
          decodeX(encoded.aX),
          decodeY(encoded.aY),
          decodeX(encoded.bX),
          decodeY(encoded.bY),
          decodeX(encoded.cX),
          decodeY(encoded.cY)
        };
    // quantize original and order it to reflect encoding
    double[] originalQuantize =
        orderTriangle(original[0], original[1], original[2], original[3], original[4], original[5]);
    // check spatial property
    for (int i = 0; i < 100; i++) {
      Component2D polygon2D = createPolygon2D(nextPolygon());
      boolean originalIntersects = false;
      boolean encodedIntersects = false;
      boolean originalContains = false;
      boolean encodedContains = false;
      switch (encoded.type) {
        case POINT:
          originalIntersects = polygon2D.contains(originalQuantize[0], originalQuantize[1]);
          encodedIntersects = polygon2D.contains(encodedQuantize[0], encodedQuantize[1]);
          originalContains = polygon2D.contains(originalQuantize[0], originalQuantize[1]);
          encodedContains = polygon2D.contains(encodedQuantize[0], encodedQuantize[1]);
          break;
        case LINE:
          originalIntersects =
              polygon2D.intersectsLine(
                  originalQuantize[0],
                  originalQuantize[1],
                  originalQuantize[2],
                  originalQuantize[3]);
          encodedIntersects =
              polygon2D.intersectsLine(
                  encodedQuantize[0], encodedQuantize[1], encodedQuantize[2], encodedQuantize[3]);
          originalContains =
              polygon2D.containsLine(
                  originalQuantize[0],
                  originalQuantize[1],
                  originalQuantize[2],
                  originalQuantize[3]);
          encodedContains =
              polygon2D.containsLine(
                  encodedQuantize[0], encodedQuantize[1], encodedQuantize[2], encodedQuantize[3]);
          break;
        case TRIANGLE:
          originalIntersects =
              polygon2D.intersectsTriangle(
                  originalQuantize[0],
                  originalQuantize[1],
                  originalQuantize[2],
                  originalQuantize[3],
                  originalQuantize[4],
                  originalQuantize[5]);
          encodedIntersects =
              polygon2D.intersectsTriangle(
                  originalQuantize[0],
                  originalQuantize[1],
                  originalQuantize[2],
                  originalQuantize[3],
                  originalQuantize[4],
                  originalQuantize[5]);
          originalContains =
              polygon2D.containsTriangle(
                  originalQuantize[0],
                  originalQuantize[1],
                  originalQuantize[2],
                  originalQuantize[3],
                  originalQuantize[4],
                  originalQuantize[5]);
          encodedContains =
              polygon2D.containsTriangle(
                  originalQuantize[0],
                  originalQuantize[1],
                  originalQuantize[2],
                  originalQuantize[3],
                  originalQuantize[4],
                  originalQuantize[5]);
          break;
      }
      assertTrue(originalIntersects == encodedIntersects);
      assertTrue(originalContains == encodedContains);
    }
  }

  private double[] orderTriangle(int aX, int aY, int bX, int bY, int cX, int cY) {
    // quantize original and order it to reflect encoding
    int orientation = GeoUtils.orient(aX, aY, bX, bY, cX, cY);

    if (orientation == -1) {
      // we need to change the orientation if CW for triangles
      return new double[] {
        decodeX(cX), decodeY(cY), decodeX(bX), decodeY(bY), decodeX(aX), decodeY(aY)
      };
    } else if (aX == bX && aY == bY) {
      if (aX != cX || aY != cY) { // not a point
        if (aX < cX) {
          return new double[] {
            decodeX(aX), decodeY(aY), decodeX(cX), decodeY(cY), decodeX(aX), decodeY(aY)
          };
        } else {
          return new double[] {
            decodeX(cX), decodeY(cY), decodeX(aX), decodeY(aY), decodeX(cX), decodeY(cY)
          };
        }
      }
    } else if ((aX == cX && aY == cY) || (bX == cX && bY == cY)) {
      if (aX < bX) {
        return new double[] {
          decodeX(aX), decodeY(aY), decodeX(bX), decodeY(bY), decodeX(aX), decodeY(aY)
        };
      } else {
        return new double[] {
          decodeX(bX), decodeY(bY), decodeX(aX), decodeY(aY), decodeX(bX), decodeY(bY)
        };
      }
    }
    return new double[] {
      decodeX(aX), decodeY(aY), decodeX(bX), decodeY(bY), decodeX(cX), decodeY(cY)
    };
  }

  public void testDegeneratedTriangle() {
    double ay = 1e-26d;
    double ax = 0.0d;
    double by = -1.0d;
    double bx = 0.0d;
    double cy = 1.0d;
    double cx = 0.0d;
    int ayEnc = encodeY(ay);
    int axEnc = encodeX(ax);
    int byEnc = encodeY(by);
    int bxEnc = encodeX(bx);
    int cyEnc = encodeY(cy);
    int cxEnc = encodeX(cx);
    byte[] b = new byte[7 * ShapeField.BYTES];
    ShapeField.encodeTriangle(b, ayEnc, axEnc, true, byEnc, bxEnc, true, cyEnc, cxEnc, true);
    ShapeField.DecodedTriangle encoded = new ShapeField.DecodedTriangle();
    ShapeField.decodeTriangle(b, encoded);
    assertTrue(encoded.aY == byEnc);
    assertTrue(encoded.aX == bxEnc);
    assertTrue(encoded.bY == cyEnc);
    assertTrue(encoded.bX == cxEnc);
    assertTrue(encoded.cY == ayEnc);
    assertTrue(encoded.cX == axEnc);
  }
}
