/*
 * 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.testbuilder.ui.render;

//import java.awt.*;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;

import org.locationtech.jts.awt.PointShapeFactory;
import org.locationtech.jts.awt.ShapeWriter;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jtstest.*;
import org.locationtech.jtstest.testbuilder.AppConstants;
import org.locationtech.jtstest.testbuilder.ui.Viewport;
import org.locationtech.jtstest.testbuilder.ui.style.Style;



public class GeometryPainter 
{
	private static BasicStroke GEOMETRY_STROKE = new BasicStroke();
	private static BasicStroke POINT_STROKE = new BasicStroke(AppConstants.POINT_SIZE);
	
  public static void paint(Graphics2D g, Viewport viewport, Geometry geometry, Style style)
  throws Exception
  {
    if (geometry == null)
      return;

    // cull non-visible geometries
    if (! viewport.intersectsInModel(geometry.getEnvelopeInternal())) 
      return;

    if (geometry instanceof GeometryCollection) {
      GeometryCollection gc = (GeometryCollection) geometry;
      /**
       * Render each element separately.
       * Otherwise it is not possible to render both filled and non-filled
       * (1D) elements correctly
       */
      for (int i = 0; i < gc.getNumGeometries(); i++) {
        paint(g, viewport, gc.getGeometryN(i), style);
      }
      return;
    }
    
    style.paint(geometry, viewport, g);
  }

  private static void paintGeometryCollection(Graphics2D g, Viewport viewport, 
      GeometryCollection gc,
      Style style
      ) 
  throws Exception
  {
  }

  static Viewport viewportCache;
  static ShapeWriter converterCache;
  
  // TODO: does not work, has a race condition 
  public static ShapeWriter BADgetConverter(Viewport viewport)
  {
    if (viewportCache != viewport) {
      viewportCache = viewport;
      converterCache = new ShapeWriter(viewport, new PointShapeFactory.Point());
    }
    return converterCache;
  }
  
  /**
   * Choose a fairly conservative decimation distance to avoid visual artifacts
   */
  private static final double DECIMATION_DISTANCE = 1.3;
  
  // TODO: is this a performance problem?
  // probably not - only called once for each geom painted
  public static ShapeWriter getConverter(Viewport viewport)
  {
    ShapeWriter sw = new ShapeWriter(viewport, new PointShapeFactory.Point());
    //sw.setRemoveDuplicatePoints(true);
    sw.setDecimation(viewport.toModel(DECIMATION_DISTANCE));
    return sw;
  }
  
  /**
   * Paints a geometry onto a graphics context,
   * using a given Viewport.
   * 
   * @param geometry shape to paint
   * @param viewport
   * @param g the graphics context
   * @param lineColor line color (null if none)
   * @param fillColor fill color (null if none)
   */
  public static void paint(Geometry geometry, Viewport viewport, 
      Graphics2D g,
      Color lineColor, Color fillColor) 
  {
    paint(geometry, viewport, g, lineColor, fillColor, null);
  }
  
  public static void paint(Geometry geometry, Viewport viewport, 
      Graphics2D g,
      Color lineColor, Color fillColor, Stroke stroke) 
  {
    ShapeWriter converter = getConverter(viewport);
    //ShapeWriter converter = new ShapeWriter(viewport);
    paint(geometry, converter, g, lineColor, fillColor, stroke);
  }
  
  private static void paint(Geometry geometry, ShapeWriter converter, Graphics2D g,
      Color lineColor, Color fillColor) 
  {
    paint(geometry, converter, g, lineColor, fillColor, null);
  }
  
  private static void paint(Geometry geometry, ShapeWriter converter, Graphics2D g,
      Color lineColor, Color fillColor, Stroke stroke) 
  {
    if (geometry == null)
			return;

    if (geometry instanceof GeometryCollection) {
      GeometryCollection gc = (GeometryCollection) geometry;
      /**
       * Render each element separately.
       * Otherwise it is not possible to render both filled and non-filled
       * (1D) elements correctly
       */
      for (int i = 0; i < gc.getNumGeometries(); i++) {
        paint(gc.getGeometryN(i), converter, g, lineColor, fillColor, stroke);
      }
      return;
    }

		Shape shape = converter.toShape(geometry);
    
		// handle points in a special way for appearance and speed
		if (geometry instanceof Point) {
		  BasicStroke ptStroke = createPointStroke(stroke);
			g.setStroke(ptStroke);
		  g.setColor(lineColor);
	    g.draw(shape);
			return;
		}

		if (stroke == null)
		  g.setStroke(GEOMETRY_STROKE);
		else
		  g.setStroke(stroke);
		
    // Test for a polygonal shape and fill it if required
		if (geometry instanceof Polygon && fillColor != null) {
		  // if (!(shape instanceof GeneralPath) && fillColor != null) {
			g.setPaint(fillColor);
			g.fill(shape);
		}
		
		if (lineColor != null) {
		  g.setColor(lineColor);
		  try {
		    g.draw(shape);
		    
				// draw polygon boundaries twice, to discriminate them
		    // MD - this isn't very obvious.  Perhaps a dashed line instead?
		    /*
				if (geometry instanceof Polygon) {
					Shape polyShell = converter.toShape( ((Polygon)geometry).getExteriorRing());
					g.setStroke(new BasicStroke(2));
					g.draw(polyShell);
				}
*/
		  } 
		  catch (Throwable ex) {
		    System.out.println(ex);
		    // eat it!
		  }
		}
	}

  private static BasicStroke createPointStroke(Stroke stroke) {
    if (stroke == null) 
      return POINT_STROKE;
    BasicStroke bs = (BasicStroke) stroke;
    BasicStroke ptStroke = new BasicStroke(AppConstants.POINT_SIZE - 1 + bs.getLineWidth());
    return ptStroke;
  }



}
