/*
GanttProject is an opensource project management tool. License: GPL3
Copyright (C) 2010-2011 Dmitry Barashev, GanttProject Team

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package net.sourceforge.ganttproject.chart;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import biz.ganttproject.core.chart.canvas.Canvas;
import biz.ganttproject.core.chart.canvas.TextMetrics;
import biz.ganttproject.core.chart.canvas.TextSelector;
import biz.ganttproject.core.chart.canvas.Canvas.HAlignment;
import biz.ganttproject.core.chart.canvas.Canvas.Label;
import biz.ganttproject.core.chart.canvas.Canvas.Rectangle;
import biz.ganttproject.core.chart.canvas.Canvas.Text;
import biz.ganttproject.core.chart.canvas.Canvas.VAlignment;
import biz.ganttproject.core.chart.grid.Offset;
import biz.ganttproject.core.chart.grid.OffsetLookup;

import net.sourceforge.ganttproject.resource.LoadDistribution;
import net.sourceforge.ganttproject.resource.HumanResource;
import net.sourceforge.ganttproject.resource.LoadDistribution.Load;
import net.sourceforge.ganttproject.task.ResourceAssignment;
import net.sourceforge.ganttproject.task.Task;

/**
 * Renders resource load chart
 */
class ResourceLoadRenderer extends ChartRendererBase {

  private List<LoadDistribution> myDistributions;

  private final ResourceChart myResourcechart;

  private final ChartModelResource myModel;

  private final Canvas myTextCanvas;

  public ResourceLoadRenderer(ChartModelResource model, ResourceChart resourceChart) {
    super(model);
    myResourcechart = resourceChart;
    myModel = model;
    myTextCanvas = getPrimitiveContainer().newLayer();
  }

  /**
   * Renders load distribution one by one, from top of the chart downwards If
   * some resource is expanded, calls rendering of the load details
   */
  @Override
  public void render() {
    getPrimitiveContainer().setOffset(0, getConfig().getHeaderHeight() - myModel.getVerticalOffset());
    beforeProcessingTimeFrames();
    int ypos = 0;
    for (LoadDistribution distribution : myDistributions) {
      List<Load> loads = distribution.getLoads();
      renderLoads(distribution.getDaysOff(), ypos);
      renderLoads(loads, ypos);
      if (myResourcechart.isExpanded(distribution.getResource())) {
        renderLoadDetails(distribution, ypos);
        ypos += calculateGap(distribution.getResource());
      }
      ypos += getConfig().getRowHeight();
      Canvas.Line nextLine = getPrimitiveContainer().createLine(0, ypos,
          (int) getChartModel().getBounds().getWidth(), ypos);
      nextLine.setForegroundColor(Color.GRAY);
    }
  }

  /**
   * Renders resource load details, that is, tasks where the resource is
   * assigned to, with that resource load percentage
   */
  private void renderLoadDetails(LoadDistribution distribution, int ypos) {
    int yPos2 = ypos;
    Map<Task, List<Load>> task2loads = distribution.getSeparatedTaskLoads();

    ResourceAssignment[] assignments = distribution.getResource().getAssignments();
    for (int i = 0; i < assignments.length; i++) {
      ResourceAssignment assignment = assignments[i];
      List<Load> nextLoads = task2loads.get(assignment.getTask());
      yPos2 += getConfig().getRowHeight();
      if (nextLoads == null) {
        continue;
      }
      buildTasksLoadsRectangles(nextLoads, yPos2);
    }
  }

  /**
   * Renders the list of loads in a single chart row Preconditions: loads come
   * from the same distribution and are ordered by their time offsets
   */
  private void renderLoads(List<Load> loads, int ypos) {
    Load prevLoad = null;
    Load curLoad = null;
    List<Offset> offsets = getDefaultOffsets();
    String suffix = "";
    for (int curIndex = 1; curIndex < loads.size(); curIndex++) {
      curLoad = loads.get(curIndex);
      prevLoad = loads.get(curIndex - 1);
      if (prevLoad.load != 0) {
        renderLoads(prevLoad, curLoad, offsets, ypos, suffix);
        suffix = "";
      } else if (curLoad.load > 0) {
        suffix = ".first";
      }
    }
  }

  /**
   * Renders prevLoad, with curLoad serving as a load right border marker and
   * style hint
   */
  private void renderLoads(Load prevLoad, Load curLoad, List<Offset> offsets, int ypos, String suffix) {
    final Date prevEnd = curLoad.startDate;
    final Date prevStart = prevLoad.startDate;

    Rectangle nextRect = createRectangle(offsets, prevStart, prevEnd, ypos);
    if (nextRect == null) {
      return;
    }
    String style;
    if (prevLoad.isResourceUnavailable()) {
      style = "dayoff";
    } else {
      suffix += curLoad.load == 0 ? ".last" : "";
      if (prevLoad.load < 100f) {
        style = "load.underload";
      } else if (prevLoad.load > 100f) {
        style = "load.overload";
      } else {
        style = "load.normal";
      }
      style += suffix;
    }
    nextRect.setStyle(style);
    nextRect.setModelObject(new ResourceLoad(prevLoad.load));
    createLoadText(nextRect, prevLoad);
  }

  /**
   * Renders a list of loads in a single chart row Precondition: loads belong to
   * the same pair (resource,task) and are ordered by their time values
   */
  private void buildTasksLoadsRectangles(List<Load> partition, int ypos) {
    List<Offset> offsets = getDefaultOffsets();
    Iterator<Load> loads = partition.iterator();
    while (loads.hasNext()) {
      final Load nextLoad = loads.next();
      final Date nextStart = nextLoad.startDate;
      final Date nextEnd = nextLoad.endDate;

      final Rectangle nextRect = createRectangle(offsets, nextStart, nextEnd, ypos);
      if (nextRect == null) {
        continue;
      }
      String style;
      if (nextLoad.load < 100f) {
        style = "load.underload";
      } else if (nextLoad.load > 100f) {
        style = "load.overload";
      } else {
        style = "load.normal";
      }
      style += ".first.last";
      nextRect.setStyle(style);
      nextRect.setModelObject(new ResourceLoad(nextLoad.load));
      createLoadText(nextRect, nextLoad);
    }
  }

  private void createLoadText(final Rectangle rect, final Load load) {
    final Text loadLabel = myTextCanvas.createText(rect.getMiddleX(), rect.getTopY(), "");
    loadLabel.setSelector(new TextSelector() {
      @Override
      public Label[] getLabels(TextMetrics textLengthCalculator) {
        int loadInt = Math.round(load.load);
        String loadStr = loadInt + "%";
        int emsLength = textLengthCalculator.getTextLength(loadStr);
        boolean displayLoad = (loadInt != 100 && emsLength <= rect.getWidth());
        return displayLoad ? new Label[] {loadLabel.createLabel(loadStr, rect.getWidth())} : new Label[0];
      }
    });
    loadLabel.setAlignment(HAlignment.CENTER, VAlignment.TOP);
    loadLabel.setStyle("text.resource.load");
  }

  private Rectangle createRectangle(List<Offset> offsets, Date start, Date end, int ypos) {
    if (start.after(getChartEndDate()) || end.compareTo(getChartStartDate()) <= 0) {
      return null;
    }
    OffsetLookup offsetLookup = new OffsetLookup();
    int[] bounds = offsetLookup.getBounds(start, end, offsets);
    return getPrimitiveContainer().createRectangle(bounds[0], ypos, bounds[1] - bounds[0], getConfig().getRowHeight());
  }

  private Date getChartStartDate() {
    return getChartModel().getStartDate();
  }

  private Date getChartEndDate() {
    return getChartModel().getBottomUnitOffsets().get(getChartModel().getBottomUnitOffsets().size() - 1).getOffsetEnd();
  }

  private List<Offset> getDefaultOffsets() {
    return getChartModel().getDefaultUnitOffsets();
  }

  public void beforeProcessingTimeFrames() {
    myDistributions = new ArrayList<LoadDistribution>();
    getPrimitiveContainer().clear();
    HumanResource[] resources = ((ChartModelResource) getChartModel()).getVisibleResources();
    for (HumanResource resource : resources) {
      LoadDistribution nextDistribution = resource.getLoadDistribution();
      myDistributions.add(nextDistribution);
    }
  }

  /**
   * Class to use as Model object to display the load percentage in the
   * rectangle.
   *
   * @author bbaranne
   */
  static class ResourceLoad {
    private float load;

    ResourceLoad(float load) {
      this.load = load;
    }

    public float getLoad() {
      return load;
    }

    @Override
    public String toString() {
      return Float.toString(load);
    }
  }

  private int calculateGap(HumanResource resource) {
    return resource.getAssignments().length * getConfig().getRowHeight();
  }
}
