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

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.List;

import org.locationtech.jts.awt.GeometryCollectionShape;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.util.AffineTransformation;
import org.locationtech.jtstest.testbuilder.AppCursors;
import org.locationtech.jtstest.testbuilder.geom.GeometryComponentTransformer;
import org.locationtech.jtstest.testbuilder.geom.GeometryLocation;


/**
 * @version 1.7
 */
public class MoveTool 
extends IndicatorTool 
{
  private static MoveTool instance = null;

  private Point2D startIndicatorLoc = null;
  private Coordinate currentVertexLoc = null;
  private Geometry targetComp;
  
  public static MoveTool getInstance() {
    if (instance == null)
      instance = new MoveTool();
    return instance;
  }

  private MoveTool() {
    super(AppCursors.EDIT_VERTEX);
  }

  private Geometry getComponent(Coordinate pt, double tolerance) {
    List<GeometryLocation> geoms = geomModel().getComponents(pt, tolerance);
    if (geoms.size() <= 0) return null;
    return geoms.get(0).getComponent();
  }
  
  public void mousePressed(MouseEvent e) {
    startIndicatorLoc = null;
    //TODO: only start move if cursor is over geometry
    Coordinate mousePtModel = toModelCoordinate(e.getPoint());
    double tolModel = getModelSnapTolerance();
    Geometry comp = getComponent(mousePtModel, tolModel);
    if (comp == null) {
      return;
    }
    //-- Ctl-Drag -> use component, otherwise use entire geom
    targetComp = e.isControlDown() ? comp : null;
    
    //-- over a geom - start gesture
    startIndicatorLoc = e.getPoint();
  	currentVertexLoc = null;
    
    // initiate move
  	currentVertexLoc = toModelCoordinate(e.getPoint());
    redrawIndicator();
  }

  public void mouseReleased(MouseEvent e) {
    clearIndicator();
    // execute the move
    if (startIndicatorLoc != null) {
      Coordinate startLoc = toModelCoordinate((Point) startIndicatorLoc);
      Coordinate newLoc = toModelSnapped(e.getPoint());
      execute(startLoc, newLoc, e.isControlDown());
    }
    startIndicatorLoc = null;
  }

  private void execute(Coordinate fromLoc, Coordinate toLoc, boolean isComponentMoved) {
    double dx = toLoc.getX() - fromLoc.getX();
    double dy = toLoc.getY() - fromLoc.getY();
    AffineTransformation trans = AffineTransformation.translationInstance(dx, dy);
    Geometry geomTrans = null;
    if (isComponentMoved) {
      geomTrans = GeometryComponentTransformer.transform(geomModel().getGeometry(), targetComp, trans);
    }
    else {
      geomTrans = GeometryComponentTransformer.transform(geomModel().getGeometry(), trans);
    }
    geomModel().setGeometry(geomTrans);
  }

  public void mouseDragged(MouseEvent e) {
  	currentVertexLoc = toModelSnapped(e.getPoint());
    if (startIndicatorLoc != null)
      redrawIndicator();
  }

  protected Shape getShape() 
  {
    Point2D currentIndicatorLoc = toView(currentVertexLoc);
    GeneralPath line = new GeneralPath();
    line.moveTo((float) currentIndicatorLoc.getX(), (float) currentIndicatorLoc.getY());
    Point2D pt = startIndicatorLoc;
    line.lineTo((float) pt.getX(), (float) pt.getY());
    
    GeometryCollectionShape ind = new GeometryCollectionShape();
    ind.add(line);
    
    int dx = (int) (currentIndicatorLoc.getX() - startIndicatorLoc.getX());
    int dy = (int) (currentIndicatorLoc.getY() - startIndicatorLoc.getY());
    Rectangle rect = boxTarget(dx, dy);
    if (rect != null) {
      ind.add(rect);
    }

    return ind;
  }

  private Rectangle boxTarget(int dx, int dy) {
    Envelope env = null;
    if (targetComp != null) {
      env = targetComp.getEnvelopeInternal();
    }
    else if (geomModel().getGeometry() != null) {
      env = geomModel().getGeometry().getEnvelopeInternal();
    }
    if (env == null) return null;
    return box(env, dx, dy);
  }

  
  private Rectangle box(Envelope env, int dx, int dy) {
    Coordinate envLL = new Coordinate(env.getMinX(), env.getMinY());
    Point2D boxLL = toView(envLL);
    Coordinate envUR = new Coordinate(env.getMaxX(), env.getMaxY());
    Point2D boxUR = toView(envUR);
    int width = (int) Math.abs(boxUR.getX() - boxLL.getX());
    int height = (int) Math.abs(boxUR.getY() - boxLL.getY());
    return new Rectangle((int) boxLL.getX() + dx, (int) boxUR.getY() + dy, width, height);
  }
}
