/*
 * This code is provided under the terms of GPL version 2.
 * Please see LICENSE file for details
 * (C) Dmitry Barashev, GanttProject team, 2004-2008
 */
package net.sourceforge.ganttproject.chart;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import net.sourceforge.ganttproject.chart.ChartModelBase.Offset;
import net.sourceforge.ganttproject.chart.GraphicPrimitiveContainer.Rectangle;
import net.sourceforge.ganttproject.resource.LoadDistribution;
import net.sourceforge.ganttproject.resource.ProjectResource;
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 ResourceChart myResourcechart;

    public ResourceLoadRenderer(ChartModelResource model,
            ResourceChart resourceChart) {
        super(model);
        myResourcechart = resourceChart;
    }

    /**
     * Renders load distribution one by one, from top of the chart downwards
     * If some resource is expanded, calls rendering of the load details 
     */
    public void render() {
       beforeProcessingTimeFrames();
       int ypos = 0;
       for (LoadDistribution nextDistribution: myDistributions) {
           List<Load> loads = nextDistribution.getLoads();
           renderLoads(nextDistribution.getDaysOff(), ypos);
           renderLoads(loads, ypos);
           if (myResourcechart.isExpanded(nextDistribution.getResource())) {
               renderLoadDetails(nextDistribution, ypos);
               ypos += calculateGap(nextDistribution.getResource());
           }
           ypos += getConfig().getRowHeight();
       }
    }
    
    /**
     * 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();

        for (ResourceAssignment assignment : distribution.getResource().getAssignments()) {
            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;
        Queue<Offset> offsets = getOffsets();
        String suffix = "";
        for (int curIndex=1; curIndex<loads.size() && offsets.peek()!=null; 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, Queue<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));
    }

    /**
     * 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) {
        Queue<Offset> offsets = getOffsets();
        Iterator<Load> loads = partition.iterator();
        while (loads.hasNext() && offsets.peek()!=null) {
            final Load nextLoad = loads.next();
            final Date nextStart = nextLoad.startDate;
            final Date nextEnd = nextLoad.endDate;
                  
            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));
        }
    }
    
    private Rectangle createRectangle(Queue<Offset> offsets, Date start, Date end, int ypos) {
        if (start.after(getChartEndDate()) || end.compareTo(getChartStartDate())<=0) {
            return null;
        }
                
        Offset offsetBefore = null;
        Offset offsetAfter = null;
        
        while (offsets.peek()!=null) {
            Offset offset = offsets.peek();
            if (offset.getOffsetEnd().compareTo(start)<=0) {
                offsetBefore = offset;
            }
            if (offsets.peek().getOffsetEnd().after(end)) {
                offsetAfter = offsets.peek();
                break;
            }
            if (offset.getOffsetEnd().equals(end)) {
                offsetAfter = offset;
                break;
            }            
            offsets.poll();
        }
        
        int rectStart;
        int rectEnd;
        if (offsetAfter==null) {
            rectEnd = getChartModel().getBounds().width;
        }
        else if (offsetAfter.getOffsetEnd().equals(end)) {
            rectEnd = offsetAfter.getOffsetPixels();
        }
        else {
            rectEnd = -1;
        }
        if (offsetBefore == null) {
            rectStart = 0;
        }
        else if (offsetBefore.getOffsetEnd().equals(start)) {
            rectStart = offsetBefore.getOffsetPixels();
        }
        else {
            rectStart = -1;
        }
        if (rectStart==-1 || rectEnd==-1) {
            return createRectangle(getDefaultOffsetsInRange(offsetBefore, offsetAfter), start, end, ypos);
        }
        Rectangle nextRect = getPrimitiveContainer().createRectangle(
                rectStart, ypos, rectEnd-rectStart, getConfig().getRowHeight());
        return nextRect;
    }
    
    private Queue<Offset> getDefaultOffsetsInRange(Offset offsetBefore, Offset offsetAfter) {
        LinkedList<Offset> result = new LinkedList<Offset>(
                getChartModel().getDefaultUnitOffsetsInRange(offsetBefore, offsetAfter));
        if (offsetBefore!=null) {
            result.addFirst(offsetBefore);
        }
        return result;
    }

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

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

    private Queue<Offset> getOffsets() {
        return new LinkedList<Offset>(getChartModel().getBottomUnitOffsets());
    }
    
    public void beforeProcessingTimeFrames() {
        myDistributions = new ArrayList<LoadDistribution>();
        getPrimitiveContainer().clear();
        getPrimitiveContainer().setOffset(0, getChartModel().getChartUIConfiguration().getHeaderHeight());
        ProjectResource[] resources = ((ChartModelResource) getChartModel())
                .getVisibleResources();
        for (int i = 0; i < resources.length; i++) {
            ProjectResource nextResource = resources[i];
            LoadDistribution nextDistribution = nextResource.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;
        }

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

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