/*
   * https://osgeo-org.atlassian.net/browse/GEOT-5287
   */
  @Test
  public void testEmptyGeometryRendering() throws Exception {

    MapContent mc = new MapContent();

    /*
     * We simulate reading empty geometries with this properties and mocking the capability to
     * filter, so that no filter layer is installed over our data and the empty geometry reaches
     * rendering code. These geometries are in EPSG:32717 because the 0,0 coordinate is in the
     * pole.
     */
    File dir = new File(TestData.getResource(this, "empty-geom-rendering.properties").toURI());
    PropertyDataStore dataStore =
        new PropertyDataStore(dir.getParentFile()) {
          @Override
          protected ContentFeatureSource createFeatureSource(ContentEntry entry)
              throws IOException {
            return new PropertyFeatureSource(entry, Query.ALL) {
              @Override
              protected boolean canFilter() {
                return true;
              }
            };
          }
        };
    /*
     * Set up the rendering of previous empty geometry
     */
    StyleBuilder sb = new StyleBuilder();
    Style style = sb.createStyle(sb.createPolygonSymbolizer());
    Layer layer = new FeatureLayer(dataStore.getFeatureSource("empty-geom-rendering"), style);
    mc.addLayer(layer);
    StreamingRenderer sr = new StreamingRenderer();
    sr.setMapContent(mc);
    BufferedImage img = new BufferedImage(40, 40, BufferedImage.TYPE_INT_ARGB);
    Graphics2D graphics = img.createGraphics();
    Rectangle paintArea = new Rectangle(40, 40);
    // An EPSG:8357 extent on the EPSG:32717 area of application.
    double minx = -8929252.1;
    double maxx = -8708634.6;
    double miny = -491855.7;
    double maxy = -271204.3;
    ReferencedEnvelope referencedEnvelope =
        new ReferencedEnvelope(
            new Rectangle2D.Double(minx, miny, maxx - minx, maxy - miny), CRS.decode("EPSG:3857"));
    sr.addRenderListener(
        new RenderListener() {
          public void featureRenderer(SimpleFeature feature) {}

          public void errorOccurred(Exception e) {
            errors++;
          }
        });
    errors = 0;

    sr.paint(graphics, paintArea, referencedEnvelope);

    assertTrue(errors == 0);
  }
  @Test
  public void testDrawIntepolation() throws Exception {

    MapContent mc = new MapContent();
    ReferencedEnvelope reWgs =
        new ReferencedEnvelope(new Envelope(-180, 180, -90, 90), DefaultGeographicCRS.WGS84);

    BufferedImage testImage = new BufferedImage(200, 200, BufferedImage.TYPE_4BYTE_ABGR);

    GridCoverage2D testCoverage = new GridCoverageFactory().create("test", testImage, reWgs);
    GridCoverage2D coverage = new GridCoverage2D("test", testCoverage);

    // mocking a GridCoverageReader to wrap the testing coverage
    GridCoverage2DReader gridCoverageReader = Mockito.mock(GridCoverage2DReader.class);
    Mockito.when(gridCoverageReader.getOriginalEnvelope()).thenReturn(new GeneralEnvelope(reWgs));
    Mockito.when(gridCoverageReader.getCoordinateReferenceSystem())
        .thenReturn(DefaultGeographicCRS.WGS84);
    Mockito.when(gridCoverageReader.read(Mockito.any(GeneralParameterValue[].class)))
        .thenReturn(coverage);

    Layer layer =
        new FeatureLayer(
            FeatureUtilities.wrapGridCoverageReader(
                gridCoverageReader, new GeneralParameterValue[] {}),
            createRasterStyle());
    layer
        .getUserData()
        .put(
            StreamingRenderer.BYLAYER_INTERPOLATION,
            Interpolation.getInstance(Interpolation.INTERP_BICUBIC));
    mc.addLayer(layer);

    BufferedImage image = new BufferedImage(200, 200, BufferedImage.TYPE_4BYTE_ABGR);

    StreamingRenderer sr = new StreamingRenderer();
    sr.setMapContent(mc);

    Graphics2D graphics = (Graphics2D) image.getGraphics();

    sr.paint(graphics, new Rectangle(200, 200), reWgs);
    // test right interpolation hint is set on Graphics2D
    assertEquals(
        graphics.getRenderingHint(JAI.KEY_INTERPOLATION),
        Interpolation.getInstance(Interpolation.INTERP_BICUBIC));

    layer
        .getUserData()
        .put(
            StreamingRenderer.BYLAYER_INTERPOLATION,
            Interpolation.getInstance(Interpolation.INTERP_NEAREST));

    sr.paint(graphics, new Rectangle(200, 200), reWgs);
    // test right interpolation hint is set on Graphics2D
    assertEquals(
        graphics.getRenderingHint(JAI.KEY_INTERPOLATION),
        Interpolation.getInstance(Interpolation.INTERP_NEAREST));
  }
  @Test
  public void testEventAfterDrawing() throws Exception {
    // build map context
    MapContent mc = new MapContent();
    mc.addLayer(new FeatureLayer(createLineCollection(), createLineStyle()));

    // build projected envelope to work with (small one around the area of
    // validity of utm zone 1, which being a Gauss projection is a vertical
    // slice parallel to the central meridian, -177°)
    ReferencedEnvelope reWgs =
        new ReferencedEnvelope(new Envelope(-180, -170, 20, 40), DefaultGeographicCRS.WGS84);
    CoordinateReferenceSystem utm1N = CRS.decode("EPSG:32601");
    ReferencedEnvelope reUtm = reWgs.transform(utm1N, true);

    BufferedImage image = new BufferedImage(200, 200, BufferedImage.TYPE_4BYTE_ABGR);

    // setup the renderer and listen for errors
    final AtomicInteger commandsCount = new AtomicInteger(0);
    final BlockingQueue<RenderingRequest> queue =
        new ArrayBlockingQueue<RenderingRequest>(10) {
          @Override
          public void put(RenderingRequest e) throws InterruptedException {
            commandsCount.incrementAndGet();
            super.put(e);
          }
        };
    StreamingRenderer sr =
        new StreamingRenderer() {
          @Override
          protected BlockingQueue<RenderingRequest> getRequestsQueue() {
            return queue;
          }
        };
    sr.setMapContent(mc);
    sr.addRenderListener(
        new RenderListener() {
          public void featureRenderer(SimpleFeature feature) {
            assertTrue(commandsCount.get() > 0);
            features++;
          }

          public void errorOccurred(Exception e) {
            errors++;
          }
        });
    errors = 0;
    features = 0;
    sr.paint((Graphics2D) image.getGraphics(), new Rectangle(200, 200), reUtm);

    // we should get errors since there are two features that cannot be
    // projected but the renderer itself should not throw exceptions
    assertTrue(errors > 0);
  }
  /**
   * Test that we don't have the geometry added twice by StreamingRenderer#findStyleAttributes when
   * geofence is filtering a layer.
   *
   * @throws Exception
   */
  @Test
  public void testFindLineStyleAttributeWithAddedFilter() throws Exception {
    final List<Filter> filters = new ArrayList<Filter>();

    SimpleFeatureSource testSource =
        new CollectionFeatureSource(createLineCollection()) {
          @Override
          public SimpleFeatureCollection getFeatures(Query query) {
            filters.add(query.getFilter());
            return super.getFeatures(query);
          }
        };

    Style style = createPointStyle();
    MapContent mc = new MapContent();
    FeatureLayer layer = new FeatureLayer(testSource, style);
    mc.addLayer(layer);

    StreamingRenderer sr = new StreamingRenderer();
    sr.setMapContent(mc);

    ReferencedEnvelope envelope =
        new ReferencedEnvelope(0, 100, 0, 100, DefaultGeographicCRS.WGS84);

    // simulate geofence adding a bbox
    BBOX bbox = StreamingRenderer.filterFactory.bbox("", 30, 60, 30, 60, "WGS84");
    StyleFactoryImpl sf = new StyleFactoryImpl();
    Rule bboxRule =
        sf.createRule(
            new Symbolizer[0], new DescriptionImpl(), new Graphic[0], "bbox", bbox, false, 1e12, 0);
    style.featureTypeStyles().get(0).rules().add(bboxRule);

    BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR);
    Graphics2D graphics = bi.createGraphics();
    try {
      sr.paint(graphics, new Rectangle(5, 5, 7, 7), envelope);
    } finally {
      graphics.dispose();
    }

    // must have only one bbox, not two
    assertEquals(1, filters.size());
    assertEquals(FastBBOX.class, filters.get(0).getClass());
  }
  @Test
  public void testRepeatedEnvelopeExpansion() throws Exception {
    final List<Filter> filters = new ArrayList<Filter>();

    SimpleFeatureSource testSource =
        new CollectionFeatureSource(createLineCollection()) {
          @Override
          public SimpleFeatureCollection getFeatures(Query query) {
            filters.add(query.getFilter());
            return super.getFeatures(query);
          }
        };

    StyleBuilder sb = new StyleBuilder();
    Style style20 = sb.createStyle(sb.createLineSymbolizer(20));
    Style style10 = sb.createStyle(sb.createLineSymbolizer(10));

    MapContent mc = new MapContent();
    mc.addLayer(new FeatureLayer(testSource, style20));
    mc.addLayer(new FeatureLayer(testSource, style10));

    StreamingRenderer sr = new StreamingRenderer();
    sr.setMapContent(mc);
    BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR);
    Graphics2D graphics = bi.createGraphics();
    sr.paint(
        graphics,
        new Rectangle(0, 0, 100, 100),
        new ReferencedEnvelope(0, 100, 0, 100, DefaultGeographicCRS.WGS84));
    graphics.dispose();

    System.out.println(filters);
    assertEquals(2, filters.size());
    Filter f1 = filters.get(0);
    assertTrue(f1 instanceof BBOX);
    BoundingBox bbox1 = ((BBOX) f1).getBounds();
    ReferencedEnvelope expected =
        new ReferencedEnvelope(-11, 111, -11, 111, DefaultGeographicCRS.WGS84);
    assertEquals(expected, bbox1);
    Filter f2 = filters.get(1);
    assertTrue(f2 instanceof BBOX);
    BoundingBox bbox2 = ((BBOX) f2).getBounds();
    assertEquals(new ReferencedEnvelope(-6, 106, -6, 106, DefaultGeographicCRS.WGS84), bbox2);
  }
  private void testTransformWithQuery(boolean invert)
      throws IOException, URISyntaxException, CQLException, NoSuchAuthorityCodeException,
          FactoryException, Exception {
    // grab the style
    Style style =
        RendererBaseTest.loadStyle(
            this, invert ? "attributeRename.sld" : "attributeRenameNoInvert.sld");
    // grab the data
    File property = new File(TestData.getResource(this, "point.properties").toURI());
    PropertyDataStore ds = new PropertyDataStore(property.getParentFile());
    FeatureSource fs = ds.getFeatureSource("point");

    // prepare a feature layer with a query and the rendering tx
    FeatureLayer layer = new FeatureLayer(fs, style);
    layer.setQuery(new Query(null, CQL.toFilter("id > 5")));

    // render it
    MapContent mc = new MapContent();
    mc.addLayer(layer);
    StreamingRenderer renderer = new StreamingRenderer();
    final AtomicInteger counter = new AtomicInteger();
    renderer.addRenderListener(
        new RenderListener() {

          @Override
          public void featureRenderer(SimpleFeature feature) {
            counter.incrementAndGet();
          }

          @Override
          public void errorOccurred(Exception e) {}
        });
    renderer.setMapContent(mc);
    ReferencedEnvelope re = new ReferencedEnvelope(0, 12, 0, 12, CRS.decode("EPSG:4326"));
    BufferedImage image =
        RendererBaseTest.showRender("Lines with circle stroke", renderer, TIME, re);

    // if everything went fine we'll have a single red dot in the middle, and we rendered
    // just one feature
    assertEquals(1, counter.get());
    assertEquals(Color.RED, getPixelColor(image, image.getWidth() / 2, image.getHeight() / 2));
  }
  @Test
  public void testTransformReproject() throws Exception {
    // grab the style
    Style style = RendererBaseTest.loadStyle(this, "reproject-rt.sld");
    // grab the data
    File property = new File(TestData.getResource(this, "point.properties").toURI());
    PropertyDataStore ds = new PropertyDataStore(property.getParentFile());
    FeatureSource fs = ds.getFeatureSource("point");

    // prepare a feature layer with a query and the rendering tx
    FeatureLayer layer = new FeatureLayer(fs, style);

    // prepare a bbox in UTM-32N
    ReferencedEnvelope reWgs84 = new ReferencedEnvelope(0, 12, 0, 12, CRS.decode("EPSG:4326"));
    ReferencedEnvelope reUTM32N = reWgs84.transform(CRS.decode("EPSG:3857"), true);

    // render it
    MapContent mc = new MapContent();
    mc.addLayer(layer);
    StreamingRenderer renderer = new StreamingRenderer();
    final AtomicInteger counter = new AtomicInteger();
    renderer.addRenderListener(
        new RenderListener() {

          @Override
          public void featureRenderer(SimpleFeature feature) {
            counter.incrementAndGet();
          }

          @Override
          public void errorOccurred(Exception e) {}
        });
    renderer.setMapContent(mc);
    BufferedImage image =
        RendererBaseTest.showRender("Lines with circle stroke", renderer, TIME, reUTM32N);

    // if everything went fine we rendered all the features
    assertEquals(10, counter.get());
    assertEquals(Color.RED, getPixelColor(image, image.getWidth() / 2, image.getHeight() / 2));
  }
  @Test
  public void testTransformNullCoverage() throws Exception {
    Style style = RendererBaseTest.loadStyle(this, "coverageCenter.sld");

    GridCoverage2DReader reader =
        new AbstractGridCoverage2DReader() {

          @Override
          public Format getFormat() {
            return null;
          }

          @Override
          public GridCoverage2D read(GeneralParameterValue[] parameters)
              throws IllegalArgumentException, IOException {
            // we return null on purpose, simulating a reader queried outside of its area, or
            // on a dimension value it does not have
            return null;
          }
        };

    MapContent mc = new MapContent();
    mc.addLayer(new GridReaderLayer(reader, style));

    StreamingRenderer renderer = new StreamingRenderer();
    renderer.setMapContent(mc);

    ReferencedEnvelope re = new ReferencedEnvelope(-70, 70, -160, 160, CRS.decode("EPSG:4326"));

    BufferedImage image =
        RendererBaseTest.showRender("Transformation with null input", renderer, TIME, re);
    // full white, no NPE
    double[] minimums = new ImageWorker(image).getMinimums();
    assertEquals(255, minimums[0], 0d);
    assertEquals(255, minimums[1], 0d);
    assertEquals(255, minimums[2], 0d);
    assertEquals(255, minimums[3], 0d);
  }
  @Test
  public void testTransformReprojectedGridCoverage() throws Exception {
    Style style = RendererBaseTest.loadStyle(this, "coverageCenter.sld");

    GeoTiffReader reader = new GeoTiffReader(TestData.copy(this, "geotiff/world.tiff"));

    MapContent mc = new MapContent();
    mc.addLayer(new GridCoverageLayer(reader.read(null), style));

    StreamingRenderer renderer = new StreamingRenderer();
    renderer.setMapContent(mc);

    ReferencedEnvelope reWgs84 =
        new ReferencedEnvelope(-70, 70, -160, 160, CRS.decode("EPSG:4326"));
    ReferencedEnvelope re = reWgs84.transform(CRS.decode("EPSG:3857"), true);

    BufferedImage image =
        RendererBaseTest.showRender("Lines with circle stroke", renderer, TIME, re);
    // if everything worked we are going to have a red dot in the middle of the map
    assertEquals(Color.RED, getPixelColor(image, image.getWidth() / 2, image.getHeight() / 2));
    assertEquals(Color.WHITE, getPixelColor(image, image.getWidth() / 4, image.getHeight() / 2));
    assertEquals(Color.WHITE, getPixelColor(image, image.getWidth() / 2, image.getHeight() / 4));
    assertEquals(Color.WHITE, getPixelColor(image, image.getWidth() / 4, image.getHeight() / 4));
  }
  @Test
  public void testScreenMapMemory() {
    // build a feature source with two zig-zag line occupying the same position
    LiteCoordinateSequence cs =
        new LiteCoordinateSequence(new double[] {0, 0, 1, 1, 2, 0, 3, 1, 4, 0});
    SimpleFeature zigzag1 =
        SimpleFeatureBuilder.build(
            testLineFeatureType, new Object[] {gf.createLineString(cs)}, "zz1");
    SimpleFeature zigzag2 =
        SimpleFeatureBuilder.build(
            testLineFeatureType, new Object[] {gf.createLineString(cs)}, "zz2");
    DefaultFeatureCollection fc = new DefaultFeatureCollection();
    fc.add(zigzag1);
    fc.add(zigzag2);
    SimpleFeatureSource zzSource = new CollectionFeatureSource(fc);

    // prepare the map
    MapContent mc = new MapContent();
    StyleBuilder sb = new StyleBuilder();
    mc.addLayer(new FeatureLayer(zzSource, sb.createStyle(sb.createLineSymbolizer())));
    StreamingRenderer sr = new StreamingRenderer();
    sr.setMapContent(mc);

    // collect rendered features
    final List<SimpleFeature> features = new ArrayList<SimpleFeature>();
    RenderListener renderedFeaturesCollector =
        new RenderListener() {

          @Override
          public void featureRenderer(SimpleFeature feature) {
            features.add(feature);
          }

          @Override
          public void errorOccurred(Exception e) {
            // nothing to do
          }
        };
    sr.addRenderListener(renderedFeaturesCollector);
    BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR);
    Graphics2D graphics = bi.createGraphics();
    // have the lines be smaller than a 1/3 of a pixel
    sr.paint(
        graphics,
        new Rectangle(0, 0, 1, 1),
        new ReferencedEnvelope(0, 8, 0, 8, DefaultGeographicCRS.WGS84));

    // check we only rendered one feature
    assertEquals(1, features.size());
    assertEquals("zz1", features.get(0).getID());

    // now have the lines be big enough to be painted instead
    features.clear();
    sr.paint(
        graphics,
        new Rectangle(0, 0, 1, 1),
        new ReferencedEnvelope(0, 1, 0, 1, DefaultGeographicCRS.WGS84));
    assertEquals(2, features.size());
    assertEquals("zz1", features.get(0).getID());
    assertEquals("zz2", features.get(1).getID());

    graphics.dispose();
  }
  @Test
  public void testDeadlockOnException() throws Exception {

    ReferencedEnvelope reWgs =
        new ReferencedEnvelope(new Envelope(-180, 180, -90, 90), DefaultGeographicCRS.WGS84);

    // create the grid coverage that throws a OOM
    BufferedImage testImage = new BufferedImage(200, 200, BufferedImage.TYPE_4BYTE_ABGR);
    GridCoverage2D testCoverage = new GridCoverageFactory().create("test", testImage, reWgs);
    GridCoverage2D oomCoverage =
        new GridCoverage2D("test", testCoverage) {

          @Override
          public RenderedImage getRenderedImage() {
            throw new OutOfMemoryError("Boom!");
          }
        };

    // also have a collections of features to create the deadlock once the painter
    // thread is dead
    SimpleFeatureCollection lines = createLineCollection();

    Style rasterStyle = createRasterStyle();
    Style lineStyle = createLineStyle();

    MapContent mapContent = new MapContent();
    mapContent.addLayer(new GridCoverageLayer(oomCoverage, rasterStyle));
    mapContent.addLayer(new FeatureLayer(lines, lineStyle));

    final StreamingRenderer sr =
        new StreamingRenderer() {

          // makes it easy to reproduce the deadlock, just two features are sufficient
          protected BlockingQueue<RenderingRequest> getRequestsQueue() {
            return new RenderingBlockingQueue(1);
          }
        };
    sr.setMapContent(mapContent);
    final List<Exception> exceptions = new ArrayList<Exception>();
    sr.addRenderListener(
        new RenderListener() {
          public void featureRenderer(SimpleFeature feature) {
            features++;
          }

          public void errorOccurred(Exception e) {
            errors++;
            exceptions.add(e);
          }
        });
    errors = 0;
    features = 0;
    BufferedImage image = new BufferedImage(200, 200, BufferedImage.TYPE_4BYTE_ABGR);
    sr.paint((Graphics2D) image.getGraphics(), new Rectangle(200, 200), reWgs);

    // all the lines should have been painted, the coverage reports as painted too
    // since the reporting happens in the main thread that does not error
    assertEquals(4, features);
    assertEquals(1, errors);
    assertTrue(exceptions.get(0).getCause() instanceof OutOfMemoryError);
  }