/*
Copyright 2003-2012 GanttProject Team

This file is part of GanttProject, an opensource project management tool.

GanttProject is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

GanttProject is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GanttProject.  If not, see <http://www.gnu.org/licenses/>.
*/
package biz.ganttproject.core.chart.scene.gantt;

import java.awt.Dimension;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import biz.ganttproject.core.chart.canvas.Canvas;
import biz.ganttproject.core.chart.canvas.Canvas.Line;
import biz.ganttproject.core.chart.canvas.Canvas.Line.Arrow;
import biz.ganttproject.core.chart.scene.BarChartActivity;
import biz.ganttproject.core.chart.scene.BarChartConnector;

/**
 * Renders dependency lines between tasks.
 *
 * @author Dmitry Barashev
 */
public class DependencySceneBuilder<T, D extends BarChartConnector<T, D>> {
  private final Canvas myTaskCanvas;
  private final Canvas myOutputCanvas;
  private final ChartApi myChartApi;
  private final TaskApi<T, D> myTaskApi;

  public static interface TaskApi<T, D> {
    boolean isMilestone(T task);
    Dimension getUnitVector(BarChartActivity<T> activity, D dependency);
    String getStyle(D dependency);
    Iterable<D> getConnectors(T task);
    List<T> getTasks();
  }

  public static interface ChartApi {
    int getBarHeight();
  }

  public DependencySceneBuilder(Canvas taskCanvas, Canvas outputCanvas, TaskApi<T, D> taskApi, ChartApi chartApi) {
    myTaskApi = taskApi;
    myChartApi = chartApi;
    //myVisibleTasks = visibleTasks;
    myTaskCanvas = taskCanvas;
    myOutputCanvas = outputCanvas;
  }

  public void build() {
    List<Connector> dependencyDrawData = prepareDependencyDrawData();
    drawDependencies(dependencyDrawData);
  }


  public void drawDependencies(Collection<Connector> connectors) {
    Canvas primitiveContainer = myOutputCanvas;
    for (Connector connector : connectors) {
      Connector.Vector dependantVector = connector.getEnd();
      Connector.Vector dependeeVector = connector.getStart();
      // Determine the line style (depending on type of dependency)
      String lineStyle = connector.getStyleName();

      if (dependeeVector.getHProjection().reaches(dependantVector.getHProjection().getPoint())) {
        // when dependee.end <= dependant.start && dependency.type is
        // any
        // or dependee.end <= dependant.end && dependency.type==FF
        // or dependee.start >= dependant.end && dependency.type==SF
        Point first = new Point(dependeeVector.getPoint().x, dependeeVector.getPoint().y);
        
        int xEntry = dependantVector.getPoint().x;
        int yEntry = dependantVector.getPoint().y;
        Point second = new Point(xEntry, dependeeVector.getPoint().y);
        Point third = new Point(xEntry, yEntry);
        
        primitiveContainer.createLine(first.x, first.y, second.x, second.y).setStyle(lineStyle);
        Line secondLine = primitiveContainer.createLine(second.x, second.y, third.x, third.y);
        secondLine.setStyle(lineStyle);
        secondLine.setArrow(Arrow.FINISH);
      } else if (dependantVector.getHProjection().reaches(dependeeVector.getHProjection().getPoint(3))) {
        Point first = dependeeVector.getPoint(3);
        Point second = new Point(first.x, dependantVector.getPoint().y);
        primitiveContainer.createLine(dependeeVector.getPoint().x, dependeeVector.getPoint().y, first.x,
            first.y).setStyle(lineStyle);
        primitiveContainer.createLine(first.x, first.y, second.x, second.y).setStyle(lineStyle);
        Line line = primitiveContainer.createLine(second.x, second.y, dependantVector.getPoint().x,
            dependantVector.getPoint().y);
        line.setStyle(lineStyle);
        line.setArrow(Line.Arrow.FINISH);
      } else {
        Point first = dependeeVector.getPoint(3);
        Point forth = dependantVector.getPoint(3);
        Point second = new Point(first.x, (first.y + forth.y) / 2);
        Point third = new Point(forth.x, (first.y + forth.y) / 2);
        primitiveContainer.createLine(dependeeVector.getPoint().x, dependeeVector.getPoint().y, first.x,
            first.y).setStyle(lineStyle);
        primitiveContainer.createLine(first.x, first.y, second.x, second.y).setStyle(lineStyle);
        primitiveContainer.createLine(second.x, second.y, third.x, third.y).setStyle(lineStyle);
        primitiveContainer.createLine(third.x, third.y, forth.x, forth.y).setStyle(lineStyle);
        primitiveContainer.createLine(forth.x, forth.y, dependantVector.getPoint().x,
            dependantVector.getPoint().y).setStyle(lineStyle);
      }
    }
  }

  private List<Connector> prepareDependencyDrawData() {
    List<Connector> result = new ArrayList<Connector>();
    for (T t : myTaskApi.getTasks()) {
      if (t != null) {
        for (D td : myTaskApi.getConnectors(t)) {
          prepareDependencyDrawData(td, result);
        }
      }
    }
    return result;
  }

  private void prepareDependencyDrawData(D connector, List<Connector> result) {
    BarChartActivity<T> dependant = connector.getEnd();
    BarChartActivity<T> dependee = connector.getStart();

    Canvas graphicPrimitiveContainer = myTaskCanvas;
    Canvas.Polygon dependantRectangle = (Canvas.Polygon) graphicPrimitiveContainer.getPrimitive(dependant);
    if (dependantRectangle == null) {
      return;
    }
    Canvas.Polygon dependeeRectangle = (Canvas.Polygon) graphicPrimitiveContainer.getPrimitive(dependee);
    if (dependeeRectangle == null) {
      return;
    }
    if (!dependantRectangle.isVisible() && !dependeeRectangle.isVisible()) {
      return;
    }    
    
    final int ysign = Integer.signum(dependeeRectangle.getMiddleY() - dependantRectangle.getMiddleY());
    
    final Dimension dependantDirection = myTaskApi.getUnitVector(dependant, connector.getImpl());
    final int yDantEntry = ysign > 0 ? dependantRectangle.getBottomY() : dependantRectangle.getTopY();
    int xDantEntry;
    if (myTaskApi.isMilestone(dependant.getOwner())) {
      xDantEntry = dependantRectangle.getMiddleX();
    } else if (dependantDirection == Connector.Vector.WEST) {
      xDantEntry = dependantRectangle.getLeftX() + 3;  
    } else if (dependantDirection == Connector.Vector.EAST) {
      xDantEntry = dependantRectangle.getRightX() - 3;
    } else {
      xDantEntry = dependantRectangle.getMiddleX();
    }
    Connector.Vector dependantVector = new Connector.Vector(new Point(xDantEntry, yDantEntry), dependantDirection);

    Dimension dependeeDirection = myTaskApi.getUnitVector(dependee, connector.getImpl());
    int xDeeExit;
    int yDeeExit;
    if (myTaskApi.isMilestone(dependee.getOwner()) && xDantEntry == dependeeRectangle.getMiddleX()) {
      xDeeExit = xDantEntry;
      yDeeExit = ysign > 0 ? dependeeRectangle.getTopY() : dependeeRectangle.getBottomY();
    } else {
      yDeeExit = dependeeRectangle.getMiddleY();
      if (dependeeDirection == Connector.Vector.WEST) {
        xDeeExit = dependeeRectangle.getLeftX();
      } else if (dependeeDirection == Connector.Vector.EAST) {
        xDeeExit = dependeeRectangle.getRightX();
      } else {
        xDeeExit = dependeeRectangle.getMiddleX();
      }
    }
    Connector.Vector dependeeVector = new Connector.Vector(new Point(xDeeExit, yDeeExit), dependeeDirection);
    
    result.add(new Connector(dependeeVector, dependantVector, myTaskApi.getStyle(connector.getImpl())));
  }
}
