/***************************************************************************

 GanttGraphicArea.java  -  description

 -------------------

 begin                : dec 2002

 copyright            : (C) 2002 by Thomas Alexandre

 email                : alexthomas(at)ganttproject.org

 ***************************************************************************/

/***************************************************************************

 *                                                                         *

 *   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 2 of the License, or     *

 *   (at your option) any later version.                                   *

 *                                                                         *

 ***************************************************************************/

package net.sourceforge.ganttproject;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JPanel;
import javax.swing.table.JTableHeader;

import sun.awt.image.IntegerInterleavedRaster;

import net.sourceforge.ganttproject.action.GPAction;
import net.sourceforge.ganttproject.calendar.GPCalendar;
import net.sourceforge.ganttproject.chart.ChartModel;
import net.sourceforge.ganttproject.chart.ChartModelBase;
import net.sourceforge.ganttproject.chart.ChartModelImpl;
import net.sourceforge.ganttproject.chart.DependencyInteractionRenderer;
import net.sourceforge.ganttproject.chart.GanttChart;
import net.sourceforge.ganttproject.chart.PublicHolidayDialogAction;
import net.sourceforge.ganttproject.chart.RenderedChartImage;
import net.sourceforge.ganttproject.chart.RenderedGanttChartImage;
import net.sourceforge.ganttproject.chart.TaskInteractionHintRenderer;
import net.sourceforge.ganttproject.chart.VisibleNodesFilter;
import net.sourceforge.ganttproject.chart.item.ChartItem;
import net.sourceforge.ganttproject.chart.item.TaskBoundaryChartItem;
import net.sourceforge.ganttproject.chart.item.TaskProgressChartItem;
import net.sourceforge.ganttproject.chart.item.TaskRegularAreaChartItem;
import net.sourceforge.ganttproject.font.Fonts;
import net.sourceforge.ganttproject.gui.UIConfiguration;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.gui.options.model.ChangeValueDispatcher;
import net.sourceforge.ganttproject.gui.options.model.ChangeValueEvent;
import net.sourceforge.ganttproject.gui.options.model.ChangeValueListener;
import net.sourceforge.ganttproject.gui.options.model.GPOptionChangeListener;
import net.sourceforge.ganttproject.gui.scrolling.ScrollingManager;
import net.sourceforge.ganttproject.gui.zoom.ZoomListener;
import net.sourceforge.ganttproject.gui.zoom.ZoomManager;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.task.CustomColumEvent;
import net.sourceforge.ganttproject.task.CustomColumsListener;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskImpl;
import net.sourceforge.ganttproject.task.TaskLength;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.TaskMutator;
import net.sourceforge.ganttproject.task.TaskSelectionManager;
import net.sourceforge.ganttproject.task.algorithm.RecalculateTaskScheduleAlgorithm;
import net.sourceforge.ganttproject.task.dependency.TaskDependencyException;
import net.sourceforge.ganttproject.task.dependency.constraint.FinishStartConstraintImpl;
import net.sourceforge.ganttproject.task.event.TaskDependencyEvent;
import net.sourceforge.ganttproject.task.event.TaskListenerAdapter;
import net.sourceforge.ganttproject.task.event.TaskScheduleEvent;
import net.sourceforge.ganttproject.time.TimeUnit;
import net.sourceforge.ganttproject.time.gregorian.GregorianCalendar;
import net.sourceforge.ganttproject.undo.GPUndoManager;

/**
 * Class for the graphic part of the soft
 */
public class GanttGraphicArea extends ChartComponentBase implements GanttChart,
        CustomColumsListener, ProjectEventListener {

    static {
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        URL cursorResource = GanttGraphicArea.class.getClassLoader()
                .getResource("icons/cursorpercent.gif");
        Image image = toolkit.getImage(cursorResource);
        CHANGE_PROGRESS_CURSOR = toolkit.createCustomCursor(image, new Point(
                10, 5), "CursorPercent");
    }

    private static final Cursor W_RESIZE_CURSOR = new Cursor(
            Cursor.W_RESIZE_CURSOR);

    private static final Cursor E_RESIZE_CURSOR = new Cursor(
            Cursor.E_RESIZE_CURSOR);

    private static final Cursor CHANGE_PROGRESS_CURSOR;

    private static final int HEADER_OFFSET = 44;

    /** Begin of display. */

//    public GanttCalendar date;

    /** Reference to the GanttTree */

    public GanttTree2 tree;

    /** Default color for tasks */

    public static Color taskDefaultColor

    // = new Color( (float) 0.549, (float) 0.713, (float) 0.807);

    = new Color(140, 182, 206);

    /** This value is connected to the GanttTRee Scrollbar to move up or down */

    private int margY;

    /* ! The main application */

    private GanttProject appli;

    private UIConfiguration myUIConfiguration;

    private Color myProjectLevelTaskColor;

    private final ChartModelImpl myChartModel;

    private final TaskManager myTaskManager;

    private GPUndoManager myUndoManager;

    private JTableHeader myTableHeader = null;

    private MouseEvent myLastMouseEvent = null;

    //private List myItemsToConsider;

    private JPanel myPreviewPanel = new ChartOptionsPreviewPanel();

    private TaskTreeImageGenerator myTaskImageGenerator;
    /** Constructor */

    public GanttGraphicArea(GanttProject app, GanttTree2 ttree,
            TaskManager taskManager, ZoomManager zoomManager,
            GPUndoManager undoManager) {
        super((IGanttProject) app, (UIFacade) app, zoomManager);
        this.setBackground(Color.WHITE);
        myTaskManager = taskManager;
        myUndoManager = undoManager;
        //
        myChartModel = new ChartModelImpl(getTaskManager(), app
                .getTimeUnitStack(), app.getUIConfiguration());
        myChartModel.addOptionChangeListener(new GPOptionChangeListener() {
            public void optionsChanged() {
                repaint();
                
            }
        });
        getViewState().addStateListener(myChartModel);
        getViewState().setStartDate(GregorianCalendar.getInstance().getTime());

        myTaskManager.addTaskListener(new TaskListenerAdapter() {
            public void taskScheduleChanged(TaskScheduleEvent e) {
                adjustDependencies((Task) e.getSource());
            }

            public void dependencyAdded(TaskDependencyEvent e) {
                adjustDependencies(e.getDependency().getDependee());
                repaint();
            }

            public void dependencyRemoved(TaskDependencyEvent e) {
                repaint();
            }

            private void adjustDependencies(Task task) {
                RecalculateTaskScheduleAlgorithm alg = myTaskManager
                        .getAlgorithmCollection()
                        .getRecalculateTaskScheduleAlgorithm();
                if (!alg.isRunning()) {
                    try {
                        alg.run(task);
                    } catch (TaskDependencyException e1) {
                        e1.printStackTrace(); // To change body of catch
                        // statement use File | Settings
                        // | File Templates.
                    }
                    // appli.setQuickSave (true);
                }
            }
        });

//        date = new GanttCalendar();
//
//        date.setDay(1);

        this.tree = ttree;

        margY = 0;

        appli = app;

        // creation of the different color use to paint

        // arrayColor[0] = new Color((float)0.905,(float)0.905,(float)0.905);
        myTableHeader = tree.getTreeTable().getTable().getTableHeader();

        Mediator.getCustomColumnsManager().addCustomColumnsListener(this);
        myTaskImageGenerator = new TaskTreeImageGenerator(ttree, app.getUIConfiguration());
    }

    /** Return the color of the task */

    public Color getTaskColor() {

        return myProjectLevelTaskColor == null ? myUIConfiguration
                .getTaskColor() : myProjectLevelTaskColor;

    }

    /** Change the color of the task */

    public void setProjectLevelTaskColor(Color c) {

        myProjectLevelTaskColor = c;

    }

    /** The size of the panel. */

    public Dimension getPreferredSize() {

        return new Dimension(465, 600);

    }

    public void paintComponent(Graphics g) {
        myChartModel.setBounds(getSize());
        myChartComponentImpl.paintComponent(g);
    }

    public ChartModelImpl getMyChartModel() {
        return myChartModel;
    }

    public void drawGPVersion(Graphics g) {
        g.setColor(Color.black);
        g.setFont(Fonts.GP_VERSION_FONT);
        g.drawString("GanttProject (" + GanttProject.version + ")", 3,
                getHeight() + 18);
    }

    /** Change the velue connected to the JTree's Scrollbar */

    public void setScrollBar(int v) {

        margY = v;

    }

    /** Return the value of the JTree's Scrollbar */

    public int getScrollBar() {
        return margY;
    }

    public String getName() {
        return GanttLanguage.getInstance().getText("chart.gantt.name");
    }

    public Date getStartDate() {
        GanttCalendar pstart = new GanttCalendar(getTaskManager()
                .getProjectStart());
        GanttCalendar st = pstart.Clone();
        return st.getTime();
    }

    public Date getEndDate() {
        TaskLength projectLength = getTaskManager().getProjectLength();
        GanttCalendar pstart = new GanttCalendar(getTaskManager()
                .getProjectStart());
        pstart.add((int) projectLength.getLength());
        GanttCalendar end = pstart.Clone();
        return end.getTime();
    }

    /** Return an image with the gantt chart */
    // TODO: 1.11 take into account flags "render this and don't render that"

    public BufferedImage getChart(GanttExportSettings settings) {
    	RenderedChartImage renderedImage = (RenderedChartImage) getRenderedImage(settings);
    	BufferedImage result = renderedImage.getWholeImage();
        repaint();
        return result;
    }
    
    public RenderedImage getRenderedImage(GanttExportSettings settings) {
        Date dateStart = null;
        Date dateEnd = null;

        List myItemsToConsider = myTaskImageGenerator.getPrintableNodes(settings);
        TimeUnit unit = getViewState().getBottomTimeUnit();

        dateStart = settings.getStartDate() == null ? getStartDate() : settings
                .getStartDate();
        dateEnd = settings.getEndDate() == null ? getEndDate() : settings
                .getEndDate();
        // dateStart = new GregorianCalendar(2005,2,25).getTime();

        if (dateStart.after(dateEnd)) {
            Date tmp = (Date) dateStart.clone();
            dateStart = (Date) dateEnd.clone();
            dateEnd = tmp;
        }

        // System.out.println("GanttGraphicArea dateStart=" + dateStart);
        // System.out.println("GanttGraphicArea dateEnd=" + dateEnd);

        TaskLength printedLength = getTaskManager().createLength(unit,
                dateStart, dateEnd);
        BufferedImage taskImage = (BufferedImage) myTaskImageGenerator.createImage(myItemsToConsider);
        int chartWidth = (int) ((printedLength.getLength(getViewState()
                .getBottomTimeUnit()) + 1) * getViewState()
                .getBottomUnitWidth());
        if (chartWidth<getWidth()) {
            chartWidth = getWidth();
        }
        int chartHeight = taskImage.getHeight(null);
        return new RenderedGanttChartImage(myChartModel, myChartComponentImpl, GanttTree2.convertNodesListToItemList(myItemsToConsider), taskImage, chartWidth, chartHeight);
    }
    
    private GanttTree2 getTree() {

        return this.tree;

    }

    IGanttProject getProject() {
        return appli;
    }

    GPUndoManager getUndoManager() {
        return myUndoManager;
    }

    protected ChartModelBase getChartModel() {
        return myChartModel;
    }

    protected MouseListener getMouseListener() {
        return getChartImplementation().getMouseListener();
    }

    protected MouseMotionListener getMouseMotionListener() {
        return getChartImplementation().getMouseMotionListener();
    }

    private Action[] getPopupMenuActions() {
        return new Action[] { getOptionsDialogAction(),
                new PublicHolidayDialogAction(getProject(), getUIFacade()) };
        // actions.add(createMenuAction(GanttProject.correctLabel(language
        // .getText("editPublicHolidays")), "));
    }

    
    protected Component createPreviewComponent() {
        return myPreviewPanel;
    }

    public void repaint() {
        try {
            myChartModel.setHeaderHeight(myTableHeader.getHeight()
                    + HEADER_OFFSET);
        } catch (NullPointerException e) {
            // e.printStackTrace();
        }
        super.repaint();
    }

    class MouseSupport {
        protected Task findTaskUnderMousePointer(int xpos, int ypos) {
            // int taskID = detectPosition(xpos, ypos, false);
            // return taskID==-1 ? null : getTaskManager().getTask(taskID);
            ChartItem chartItem = myChartModel.getChartItemWithCoordinates(
                    xpos, ypos);
            return chartItem == null ? null : chartItem.getTask();
        }

        protected ChartItem getChartItemUnderMousePoint(int xpos, int ypos) {
            ChartItem result = myChartModel.getChartItemWithCoordinates(xpos,
                    ypos);
            return result;
        }
    }

    abstract class ChangeTaskBoundaryInteraction extends MouseInteractionBase {
        private TaskInteractionHintRenderer myLastNotes;

        private final Task myTask;

        private final float myInitialDuration;

        private GanttCalendar myInitialEnd;

        private GanttCalendar myInitialStart;

        protected ChangeTaskBoundaryInteraction(MouseEvent initiatingEvent,
                TaskBoundaryChartItem taskBoundary) {
            super(initiatingEvent);
            myTask = taskBoundary.getTask();
            myInitialDuration = myTask.getDuration().getLength(
                    getViewState().getBottomTimeUnit());
            myInitialEnd = getTask().getEnd();
            myInitialStart = getTask().getStart();
        }

        public void apply(MouseEvent e) {
            if (myLastNotes == null) {
                myLastNotes = new TaskInteractionHintRenderer("", e.getX(), e
                        .getY());
            }
            float diff = getLengthDiff(e);
            apply(diff);
            myLastNotes.setString(getNotesText());
            myLastNotes.setX(e.getX());
        }

        protected Task getTask() {
            return myTask;
        }

        protected float getInitialDuration() {
            return myInitialDuration;
        }

        public void finish(final TaskMutator mutator) {
            mutator.setIsolationLevel(TaskMutator.READ_COMMITED);

            // if
            // ((!myInitialEnd.equals(getTask().getEnd()))||(!myInitialStart.equals(getTask().getStart())))
            getUndoManager().undoableEdit("Task boundary changed",
                    new Runnable() {
                        public void run() {
                            doFinish(mutator);
                        }
                    });

        }

        private void doFinish(TaskMutator mutator) {
            mutator.commit();
            myLastNotes = null;
            try {
                getTaskManager().getAlgorithmCollection()
                        .getRecalculateTaskScheduleAlgorithm().run();
            } catch (TaskDependencyException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            GanttGraphicArea.this.repaint();
        }

        public void paint(Graphics g) {
            if (myLastNotes != null) {
                myLastNotes.paint(g);
            }
        }

        protected abstract void apply(float diff);

        protected abstract String getNotesText();
    }

    class ChangeTaskEndInteraction extends ChangeTaskBoundaryInteraction
            implements MouseInteraction {
        private TaskMutator myMutator;

        private GanttCalendar myInitialEnd;

        public ChangeTaskEndInteraction(MouseEvent initiatingEvent,
                TaskBoundaryChartItem taskBoundary) {
            super(initiatingEvent, taskBoundary);
            setCursor(E_RESIZE_CURSOR);
            myMutator = getTask().createMutator();
            myInitialEnd = getTask().getEnd();
        }

        protected void apply(float diff) {
            TaskLength newLength = getTaskManager().createLength(
                    getViewState().getBottomTimeUnit(),
                    getInitialDuration() + diff);
            TaskLength translated = getTask().translateDuration(newLength);
            if (translated.getLength() != 0) {
                myMutator.setDuration(translated);
            }
        }

        protected String getNotesText() {
            return getTask().getEnd().toString();
        }

        public void finish() {
            super.finish(myMutator);
        }
    }

    class ChangeTaskStartInteraction extends ChangeTaskBoundaryInteraction
            implements MouseInteraction {
        private TaskLength myInitialLength;

        private TaskMutator myMutator;

        private GanttCalendar myInitialStart;

        ChangeTaskStartInteraction(MouseEvent e,
                TaskBoundaryChartItem taskBoundary) {
            super(e, taskBoundary);
            setCursor(W_RESIZE_CURSOR);
            myInitialLength = getTask().getDuration();
            myMutator = getTask().createMutator();
            myInitialStart = getTask().getStart();
        }

        protected void apply(float diff) {
            TaskLength newLength = getTaskManager().createLength(
                    getViewState().getBottomTimeUnit(),
                    getInitialDuration() + diff);
            TaskLength translated = getTask().translateDuration(newLength);
            int dayDiff = (int) (translated.getValue() - myInitialLength
                    .getValue());
            // System.err.println("[ChangeTaskStart] dayDiff="+dayDiff+"
            // newLength="+newLength+" translated="+translated);
            if (dayDiff != 0) {
                // System.err.println("[ChangeTaskStartInteraction] apply():
                // oldStart="+getTask().getStart());
                GanttCalendar newStart = myInitialStart.newAdd(dayDiff);
                // System.err.println("newStart"+newStart);

                myMutator.setStart(newStart);
                if ((getTask().getThird() != null)
                        && (getTask().getThirdDateConstraint() == TaskImpl.EARLIESTBEGIN))
                    myMutator.setEnd(getTask().getEnd().Clone());
                getTask().applyThirdDateConstraint();

                // mutator.commit();
                // myInitialLength = getTask().getDuration();
            }
        }

        public void finish() {
            super.finish(myMutator);
            getTask().applyThirdDateConstraint();
        }

        protected String getNotesText() {
            return getTask().getStart().toString();
        }
    }

    class ChangeTaskProgressInteraction extends MouseInteractionBase implements
            MouseInteraction {
        private TaskProgressChartItem myTaskProgrssItem;

        private TaskMutator myMutator;

        private TaskInteractionHintRenderer myLastNotes;

        private int myProgressWas;

        private int myProgressIs;

        public ChangeTaskProgressInteraction(MouseEvent e,
                TaskProgressChartItem taskProgress) {
            super(e);
            Toolkit toolkit = Toolkit.getDefaultToolkit();
            try {
                setCursor(CHANGE_PROGRESS_CURSOR);
            } catch (Exception exept) {
                setCursor(E_RESIZE_CURSOR);
            }
            myTaskProgrssItem = taskProgress;
            myMutator = myTaskProgrssItem.getTask().createMutator();
            myProgressWas = myTaskProgrssItem.getTask()
                    .getCompletionPercentage();
        }

        public void apply(MouseEvent event) {
            // int deltaProgress =
            // (int)myTaskProgrssItem.getProgressDelta(event.getX());
            float deltaUnits = getLengthDiff(event);
            int deltaPercents = (int) (100 * deltaUnits / myTaskProgrssItem
                    .getTask().getDuration().getLength(
                            getViewState().getBottomTimeUnit()));
            int newProgress = myProgressWas + deltaPercents;
            if (newProgress > 100) {
                newProgress = 100;
            }
            if (newProgress < 0) {
                newProgress = 0;
            }
            myProgressIs = newProgress;
            myMutator.setCompletionPercentage(newProgress);
            myLastNotes = new TaskInteractionHintRenderer(newProgress + "%",
                    event.getX(), event.getY() - 30);
        }

        public void finish() {

            myMutator.setIsolationLevel(TaskMutator.READ_COMMITED);

            getUndoManager().undoableEdit("Task progress changed",
                    new Runnable() {
                        public void run() {
                            doFinish(myMutator);
                        }
                    });
            GanttGraphicArea.this.repaint();
        }

        private void doFinish(TaskMutator mutator) {
            mutator.commit();
            myLastNotes = null;
            try {
                getTaskManager().getAlgorithmCollection()
                        .getRecalculateTaskScheduleAlgorithm().run();
            } catch (TaskDependencyException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            if (myProgressIs == myProgressWas) {
                // getUndoManager ().die ();

                myMutator.commit();
                repaint();
                int myProgressIs = myTaskProgrssItem.getTask()
                        .getCompletionPercentage();
                if (myProgressIs != myProgressWas) {
                    // appli.setQuickSave(true);
                    // appli.quickSave ("Task progress changed");

                }

            }
        }

        public void paint(Graphics g) {
            if (myLastNotes != null) {
                myLastNotes.paint(g);
            }
        }
    }

    class DrawDependencyInteraction extends MouseInteractionBase implements
            MouseInteraction {

        private final Task myTask;

        private Point myStartPoint;

        private DependencyInteractionRenderer myArrow;

        private GanttGraphicArea.MouseSupport myMouseSupport;

        private Task myDependant;

        public DrawDependencyInteraction(MouseEvent initiatingEvent,
                TaskRegularAreaChartItem taskArea, MouseSupport mouseSupport) {
            super(initiatingEvent);
            myStartPoint = initiatingEvent.getPoint();
            myTask = taskArea.getTask();
            myArrow = new DependencyInteractionRenderer(myStartPoint.x,
                    myStartPoint.y, myStartPoint.x, myStartPoint.y);
            myMouseSupport = mouseSupport;
        }

        public void apply(MouseEvent event) {
            myArrow.changePoint2(event.getX(), event.getY());
            // myDependant = myMouseSupport.findTaskUnderMousePointer(
            // event.getX(), event.getY());
            myLastMouseEvent = event;
        }

        public void finish() {
            if (myLastMouseEvent != null) {
                myDependant = myMouseSupport.findTaskUnderMousePointer(
                        myLastMouseEvent.getX(), myLastMouseEvent.getY());
                final Task dependee = myTask;
                if (myDependant != null) {
                    if (getTaskManager().getDependencyCollection()
                            .canCreateDependency(myDependant, dependee)) {
                        getUndoManager().undoableEdit("Draw dependency",
                                new Runnable() {
                                    public void run() {
                                        try {
                                            getTaskManager()
                                                    .getDependencyCollection()
                                                    .createDependency(
                                                            myDependant,
                                                            dependee,
                                                            new FinishStartConstraintImpl());

                                        } catch (TaskDependencyException e1) {
                                            e1.printStackTrace();
                                        }
                                        appli.setAskForSave(true);
                                        // appli.setQuickSave (true);
                                        // appli.quickSave ("Draw dependency");
                                    }
                                });
                    }
                } else {
                    myArrow = new DependencyInteractionRenderer();
                    repaint();
                }
            }

        }

        public void paint(Graphics g) {
            myArrow.paint(g);
        }
    }

    class MoveTaskInteraction extends MouseInteractionBase implements
            MouseInteraction {
        private Task myTask;

        private TaskMutator myMutator;

        private GanttCalendar myInitialStart;

        MoveTaskInteraction(MouseEvent e, Task task) {
            super(e);
            myTask = task;
            myMutator = task.createMutator();
            myInitialStart = myTask.getStart();
        }

        public void apply(MouseEvent event) {
            float diff = getChartModel().calculateLengthNoWeekends(getStartX(),
                    event.getX());
            TaskLength bottomUnitLength = getTaskManager().createLength(
                    getViewState().getBottomTimeUnit(), diff);
            TaskLength taskLength = myTask.translateDuration(bottomUnitLength);
            int dayDiff = (int) (taskLength.getValue());
            // System.err.println("[MoveTaskInteraction] apply():
            // dayDiff="+dayDiff+" bottomUnitLength="+bottomUnitLength+"
            // translated="+taskLength);
            if (dayDiff != 0) {
                myMutator.shift(dayDiff);
            }
        }

        public void finish() {
            myMutator.setIsolationLevel(TaskMutator.READ_COMMITED);
            getUndoManager().undoableEdit("Task moved", new Runnable() {
                public void run() {
                    doFinish();
                }
            });

        }

        private void doFinish() {
            myMutator.commit();
            try {
                getTaskManager().getAlgorithmCollection()
                        .getRecalculateTaskScheduleAlgorithm().run();
            } catch (TaskDependencyException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            GanttGraphicArea.this.repaint();
        }

    }

    class MoveTaskInteractions extends MouseInteractionBase implements
            MouseInteraction {

        private List myTasks; // of Task

        private List myMutators; // of TaskMutator

        private List myInitialStarts; // of GanttCalendar

        MoveTaskInteractions(MouseEvent e, List tasks) {
            super(e);
            myTasks = tasks;
            myMutators = new ArrayList(myTasks.size());
            myInitialStarts = new ArrayList(myTasks.size());
            Iterator itTasks = myTasks.iterator();
            while (itTasks.hasNext()) {
                Task t = (Task) itTasks.next();
                myMutators.add(t.createMutator());
                myInitialStarts.add(t.getStart());
            }
        }

        public void apply(MouseEvent event) {
            float diff = getLengthDiff(event);
            TaskLength bottomUnitLength = getTaskManager().createLength(
                    getViewState().getBottomTimeUnit(), diff);

            for (int i = 0; i < myTasks.size(); i++) {
                Task task = (Task) myTasks.get(i);
                TaskLength taskLength = task
                        .translateDuration(bottomUnitLength);
                int dayDiff = (int) (taskLength.getValue());
                if (dayDiff != 0) {
                    ((TaskMutator) myMutators.get(i)).shift(dayDiff);
                }
            }
        }

        public void finish() {
            Iterator itMutators = myMutators.iterator();
            while (itMutators.hasNext())
                ((TaskMutator) itMutators.next())
                        .setIsolationLevel(TaskMutator.READ_COMMITED);

            getUndoManager().undoableEdit("Task moved", new Runnable() {
                public void run() {
                    doFinish();
                }
            });

        }

        private void doFinish() {
            Iterator itMutators = myMutators.iterator();
            while (itMutators.hasNext())
                ((TaskMutator) itMutators.next()).commit();

            try {
                getTaskManager().getAlgorithmCollection()
                        .getRecalculateTaskScheduleAlgorithm().run();
            } catch (TaskDependencyException e) {
                e.printStackTrace();
            }

            Iterator itTasks = myTasks.iterator();
            while (itTasks.hasNext()) {
                Task t = ((Task) itTasks.next());
                t.applyThirdDateConstraint();
            }

            GanttGraphicArea.this.repaint();
        }
    }

    public interface ChartImplementation extends ZoomListener {
        void paintComponent(Graphics g);

        void paintComponent(Graphics g, List/*<Task>*/ visibleTasks);

        MouseListener getMouseListener();

        MouseMotionListener getMouseMotionListener();

        void beginChangeTaskEndInteraction(MouseEvent initiatingEvent,
                TaskBoundaryChartItem taskBoundary);

        MouseInteraction getActiveInteraction();

        void beginChangeTaskStartInteraction(MouseEvent e,
                TaskBoundaryChartItem taskBoundary);

        MouseInteraction finishInteraction();

        void beginChangeTaskProgressInteraction(MouseEvent e,
                TaskProgressChartItem item);

        void beginDrawDependencyInteraction(MouseEvent initiatingEvent,
                TaskRegularAreaChartItem taskArea,
                GanttGraphicArea.MouseSupport mouseSupport);

        void beginMoveTaskInteraction(MouseEvent e, Task task);

        void beginMoveTaskInteractions(MouseEvent e, List tasks);

        void beginScrollViewInteraction(MouseEvent e);

    }

    private class ChartImplementationBase extends AbstractChartImplementation {
        public void beginChangeTaskEndInteraction(MouseEvent initiatingEvent,
                TaskBoundaryChartItem taskBoundary) {
            setActiveInteraction(new ChangeTaskEndInteraction(initiatingEvent,
                    taskBoundary));
        }

        public void beginChangeTaskStartInteraction(MouseEvent e,
                TaskBoundaryChartItem taskBoundary) {
            setActiveInteraction(new ChangeTaskStartInteraction(e, taskBoundary));
        }

        public void beginChangeTaskProgressInteraction(MouseEvent e,
                TaskProgressChartItem taskProgress) {
            setActiveInteraction(new ChangeTaskProgressInteraction(e,
                    taskProgress));
        }

        public void beginDrawDependencyInteraction(MouseEvent initiatingEvent,
                TaskRegularAreaChartItem taskArea,
                GanttGraphicArea.MouseSupport mouseSupport) {
            setActiveInteraction(new DrawDependencyInteraction(initiatingEvent,
                    taskArea, mouseSupport));
        }

        public void beginMoveTaskInteraction(MouseEvent e, Task task) {
            setActiveInteraction(new MoveTaskInteraction(e, task));
        }

        public void beginMoveTaskInteractions(MouseEvent e, List tasks) {
            setActiveInteraction(new MoveTaskInteractions(e, tasks));
        }
    }

    private class NewChartComponentImpl extends ChartImplementationBase
            implements ChartImplementation {

        public void paintComponent(Graphics g, List/*<Task>*/ visibleTasks) {
        	synchronized(myChartModel) {
	            GanttGraphicArea.super.paintComponent(g);
	            ChartModel model = myChartModel;
	            model.setTaskContainment(appli.getTaskContainment());
	            // model.setBounds(getSize());
	            // System.err.println("[NewChartComponentImpl] paintComponent. unit
	            // width="+getViewState().getBottomUnitWidth());
	            model.setBottomUnitWidth(getViewState().getBottomUnitWidth());
	            model.setRowHeight(((GanttTree2) tree).getTreeTable()
	                    .getRowHeight());
	            model.setTopTimeUnit(getViewState().getTopTimeUnit());
	            model.setBottomTimeUnit(getViewState().getBottomTimeUnit());
	            model.setVisibleTasks(visibleTasks);
	            model.paint(g);
	            if (getActiveInteraction() != null) {
	                getActiveInteraction().paint(g);
	            }
        	}
        }

        public void paintComponent(Graphics g) {
        	synchronized(ChartModelBase.STATIC_MUTEX) {
	            GanttGraphicArea.super.paintComponent(g);
	            ChartModel model = myChartModel;
	            model.setTaskContainment(appli.getTaskContainment());
	            // model.setBounds(getSize());
	            // System.err.println("[NewChartComponentImpl] paintComponent. unit
	            // width="+getViewState().getBottomUnitWidth());
	            model.setBottomUnitWidth(getViewState().getBottomUnitWidth());
	            model.setRowHeight(((GanttTree2) tree).getTreeTable()
	                    .getRowHeight());
	            model.setTopTimeUnit(getViewState().getTopTimeUnit());
	            model.setBottomTimeUnit(getViewState().getBottomTimeUnit());
	            VisibleNodesFilter visibleNodesFilter = new VisibleNodesFilter();
	            List visibleTasks = visibleNodesFilter.getVisibleNodes(tree
	                    .getJTree(), getScrollBar(), getHeight(), tree
	                    .getTreeTable().getRowHeight());
	            model.setVisibleTasks(visibleTasks);
                //myChartModel.setExplicitlyHiddenTasks(tree.getHiddenTasks());
                model.setVerticalOffset(getScrollBar() % tree.getTreeTable().getRowHeight());
	            model.paint(g);
	            if (getActiveInteraction() != null) {
	                getActiveInteraction().paint(g);
	            }
        	}
        }

        public MouseListener getMouseListener() {
            return myMouseListener;
        }

        public MouseMotionListener getMouseMotionListener() {
            return myMouseMotionListener;
        }

        private OldChartMouseListenerImpl myMouseListener = new OldChartMouseListenerImpl();

        private OldMouseMotionListenerImpl myMouseMotionListener = new OldMouseMotionListenerImpl();

    }

    protected AbstractChartImplementation getImplementation() {
        return (AbstractChartImplementation) getChartImplementation();
    }

    private ChartImplementation getChartImplementation() {
        if (myChartComponentImpl == null) {
            myChartComponentImpl = new NewChartComponentImpl();
        }
        return myChartComponentImpl;
    }

    public Action getScrollCenterAction(ScrollingManager scrollMgr,
            TaskSelectionManager taskSelMgr, String iconSize) {
        if (myScrollCenterAction == null)
            myScrollCenterAction = new ScrollGanttChartCenterAction(scrollMgr,
                    taskSelMgr, iconSize);
        return myScrollCenterAction;
    }

    public void setPreviousStateTasks(ArrayList tasks) {
        int rowHeight = myChartModel.setPreviousStateTasks(tasks);
        ((GanttTree2) appli.getTree()).getTable().setRowHeight(rowHeight);
    }


    private ChartImplementation myChartComponentImpl;

    private ScrollGanttChartCenterAction myScrollCenterAction;

    protected class ScrollGanttChartCenterAction extends GPAction {
        private final ScrollingManager myScrollingManager;

        private final TaskSelectionManager myTaskSelectionManager;

        public ScrollGanttChartCenterAction(ScrollingManager scrollingManager,
                TaskSelectionManager taskSelectionManager, String iconSize) {
            super("ScrollCenter", iconSize);
            myScrollingManager = scrollingManager;
            myTaskSelectionManager = taskSelectionManager;
        }

        public void actionPerformed(ActionEvent e) {
        	getUIFacade().setStatusText(GanttLanguage.getInstance().getText("centerOnSelectedTasks"));
            scroll();
        }

        private void scroll() {
            GanttCalendar min = null;
            GanttCalendar max = null;
            Date scrollDate = null;

            Iterator it = null;
            if (myTaskSelectionManager.getSelectedTasks().isEmpty()) {
                // scrollDate = getTaskManager().getProjectStart();
                it = Arrays.asList(getTaskManager().getTasks()).iterator();
            } else {
                it = myTaskSelectionManager.getSelectedTasks().iterator();
            }
            while (it.hasNext()) {
                Task t = (Task) it.next();
                GanttCalendar dStart = t.getStart();
                GanttCalendar dEnd = t.getEnd();

                min = min == null ? dStart.Clone()
                        : (min.compareTo(dStart) > 0 ? dStart.Clone() : min);
                max = max == null ? dEnd.Clone()
                        : (max.compareTo(dEnd) < 0 ? dEnd.Clone() : max);
            }

            //no tasks defined, nothing to do
            if(min == null || max == null)
            	return;
            
            TimeUnit defaultUnit = getTimeUnitStack().getDefaultTimeUnit();
            final TaskLength selectionLength = getTaskManager().createLength(
                    defaultUnit, min.getTime(), max.getTime());
            final TaskLength viewLength = getChartModel().getVisibleLength();
            float viewLengthInDefaultUnits = viewLength.getLength(defaultUnit);
            // if selection is shorter than view we'll scroll right,
            // otherwise we'll scroll left
            // delta is measured in the bottom line time units
            final float delta = (selectionLength.getValue() - viewLengthInDefaultUnits) / 2;
            scrollDate = GPCalendar.PLAIN.shiftDate(min.getTime(),
                    getTaskManager().createLength(defaultUnit, delta));

            myScrollingManager.scrollLeft(scrollDate);
        }

        /*
         * (non-Javadoc)
         * 
         * @see net.sourceforge.ganttproject.action.GPAction#getIconFilePrefix()
         */
        protected String getIconFilePrefix() {
            return "scrollcenter_";
        }

        protected String getLocalizedName() {
            return super.getLocalizedName();
        }
    }

    private class OldChartMouseListenerImpl extends MouseListenerBase implements
            MouseListener {
        private MouseSupport myMouseSupport = new MouseSupport();

        public void mouseClicked(MouseEvent e) {
            if (e.getButton() == MouseEvent.BUTTON1) {
                Task taskUnderPointer = myMouseSupport
                        .findTaskUnderMousePointer(e.getX(), e.getY());
                if (taskUnderPointer == null) {
                    tree.selectTreeRow(-1);
                }
            }
            if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
                if (!appli.isOnlyViewer)
                    appli.propertiesTask();
            }
        }

        protected Action[] getPopupMenuActions() {
            Action[] treeActions = tree.getPopupMenuActions();
            int sep = 0;
            if (treeActions.length != 0) {
                sep = 1;
            }
            Action[] chartActions = GanttGraphicArea.this.getPopupMenuActions();
            Action[] result = new Action[treeActions.length + sep
                    + chartActions.length];
            System.arraycopy(treeActions, 0, result, 0, treeActions.length);
            System.arraycopy(chartActions, 0, result, treeActions.length
                    + sep, chartActions.length);
            return result;
        }
        
        public void mousePressed(MouseEvent e) {
            tree.stopEditing();
            if (appli.isOnlyViewer)
                return;
            Task taskUnderPointer = myMouseSupport.findTaskUnderMousePointer(e
                    .getX(), e.getY());
            if (taskUnderPointer!=null && !Mediator.getTaskSelectionManager().isTaskSelected(
                    taskUnderPointer)) {
                boolean ctrl = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK;
                tree.selectTask(taskUnderPointer, ctrl);
            }
            super.mousePressed(e);
            if (taskUnderPointer == null) {
                return;
            }
            if (e.getButton() == MouseEvent.BUTTON1) {
                ChartItem itemUnderPoint = myMouseSupport
                        .getChartItemUnderMousePoint(e.getX(), e.getY());
                if (itemUnderPoint instanceof TaskBoundaryChartItem) {
                    TaskBoundaryChartItem taskBoundary = (TaskBoundaryChartItem) itemUnderPoint;
                    if (taskBoundary.isStartBoundary()) {
                        getChartImplementation()
                                .beginChangeTaskStartInteraction(e,
                                        taskBoundary);
                    } else {
                        getChartImplementation().beginChangeTaskEndInteraction(
                                e, taskBoundary);
                    }
                } else if (itemUnderPoint instanceof TaskProgressChartItem) {
                    getChartImplementation()
                            .beginChangeTaskProgressInteraction(e,
                                    (TaskProgressChartItem) itemUnderPoint);
                    // setCursor(new Cursor(Cursor.E_RESIZE_CURSOR));
                } else if (itemUnderPoint instanceof TaskRegularAreaChartItem) {
                    getChartImplementation().beginDrawDependencyInteraction(e,
                            (TaskRegularAreaChartItem) itemUnderPoint,
                            myMouseSupport);
                }
                repaint();
                appli.recalculateCriticalPath();
            } else if (e.getButton() == MouseEvent.BUTTON2) {
                if (!Mediator.getTaskSelectionManager().isTaskSelected(
                        taskUnderPointer))
                    tree.selectTask(taskUnderPointer, false);
                List l = Mediator.getTaskSelectionManager().getSelectedTasks();

                getChartImplementation().beginMoveTaskInteractions(e, l);
            }
        }

    }

    private class OldMouseMotionListenerImpl extends MouseMotionListenerBase {
        private MouseSupport myMouseSupport = new MouseSupport();

        public void mouseDragged(MouseEvent e) {
            if (appli.isOnlyViewer)
                return;
            super.mouseDragged(e);
            /*
             * Add the repaint in order to repaint the treetable when an action
             * occurs on the GraphicArea. here mousedragged because all actions
             * modifying task properties on the graphics are made through
             * mousedragged (I think !)
             */
            // Mediator.getGanttProjectSingleton().repaint();
            // getUIFacade().repaint2();
            if (myUIConfiguration.isCriticalPathOn()) {
                MouseInteraction mi = myChartComponentImpl
                        .getActiveInteraction();
                if ((mi instanceof ChangeTaskBoundaryInteraction)
                        || (mi instanceof MoveTaskInteraction)
                        || (mi instanceof MoveTaskInteractions))
                    appli.recalculateCriticalPath();
            }
            GanttGraphicArea.this.repaint();
            // avant

        }

        // Move the move on the area
        public void mouseMoved(MouseEvent e) {
            ChartItem itemUnderPoint = myMouseSupport
                    .getChartItemUnderMousePoint(e.getX(), e.getY());
            Task taskUnderPoint = itemUnderPoint == null ? null
                    : itemUnderPoint.getTask();
            // System.err.println("[OldMouseMotionListenerImpl] mouseMoved:
            // taskUnderPoint="+taskUnderPoint);
            if (taskUnderPoint == null) {
                setDefaultCursor();
            } else {
                if (itemUnderPoint instanceof TaskBoundaryChartItem) {
                    Cursor cursor = ((TaskBoundaryChartItem) itemUnderPoint)
                            .isStartBoundary() ? W_RESIZE_CURSOR
                            : E_RESIZE_CURSOR;
                    setCursor(cursor);
                }
                // special cursor
                else if (itemUnderPoint instanceof TaskProgressChartItem) {
                    setCursor(CHANGE_PROGRESS_CURSOR);
                } else {
                    setDefaultCursor();
                }
                // getUIFacade().repaint2();
                appli.repaint();
            }
        }
    }

    public void setTaskManager(TaskManager taskManager) {
        // TODO Auto-generated method stub

    }

    public void reset() {
        // TODO Auto-generated method stub

    }

    public Icon getIcon() {
        // TODO Auto-generated method stub
        return null;
    }

    public void customColumsChange(CustomColumEvent event) {
        repaint();
    }

    public void setUIConfiguration(UIConfiguration configuration) {
        myUIConfiguration = configuration;
    }

    private static class ChartOptionsPreviewPanel extends JPanel implements
            ChangeValueListener {
        Text upText, downText, leftText, rightText;

        TaskBar taskBar;

        public ChartOptionsPreviewPanel() {
            super();
            addToDispatchers();
            setBackground(Color.WHITE);
            setPreferredSize(new Dimension(450, 70));

            taskBar = new TaskBar();

            upText = new Text(Text.UP, taskBar);
            downText = new Text(Text.DOWN, taskBar);
            leftText = new Text(Text.LEFT, taskBar);
            rightText = new Text(Text.RIGHT, taskBar);
        }

        void refresh() {
            
        }
        private void addToDispatchers() {
            List dispatchers = Mediator.getChangeValueDispatchers();
            for (int i = 0; i < dispatchers.size(); i++) {
                ((ChangeValueDispatcher) dispatchers.get(i))
                        .addChangeValueListener(this);
            }
        }

        public void paint(Graphics g) {
            super.paint(g);
            taskBar.paintMe(g);
            upText.paintMe(g);
            downText.paintMe(g);
            leftText.paintMe(g);
            rightText.paintMe(g);
        }

        public void changeValue(ChangeValueEvent event) {
            Object id = event.getID();
            if (id.equals("up")) {
                upText.text = getI18n(event.getNewValue().toString());
            } else if (id.equals("down")) {
                downText.text = getI18n(event.getNewValue().toString());
            } else if (id.equals("left")) {
                leftText.text = getI18n(event.getNewValue().toString());
            } else if (id.equals("right")) {
                rightText.text = getI18n(event.getNewValue().toString());
            }
            repaint();
        }

        static String getI18n(String id) {
            String res = GanttLanguage.getInstance().getText(
                    "optionValue." + id + ".label");
            if (res.startsWith(GanttLanguage.MISSING_RESOURCE)) {
                res = id;
            }
            return res;
        }

        class TaskBar {
            int width, height, x, y;

            Color color;

            TaskBar() {
                width = 100;
                height = 12;
                x = (int) (ChartOptionsPreviewPanel.this.getPreferredSize()
                        .getWidth() / 2 - width / 2);
                y = (int) (ChartOptionsPreviewPanel.this.getPreferredSize()
                        .getHeight() / 2 - height / 2);
                color = new Color(140, 182, 206);
            }

            void paintMe(Graphics g) {
                g.setColor(color);
                g.fillRect(x, y, width, height);
                g.setColor(Color.BLACK);
                g.drawRect(x, y, width, height);
            }

        }

        private static class Text {
            static final Font FONT = Fonts.PREVIEW_BAR_FONT;

            static final int LEFT = 0;

            static final int RIGHT = 1;

            static final int UP = 2;

            static final int DOWN = 3;

            static final int MARGIN = 3;

            String text = "";

            int position;

            private int x, y;

            TaskBar taskBar;

            Text(int position, TaskBar refBar) {
                this.position = position;
                this.taskBar = refBar;
            }

            void paintMe(Graphics g) {
                calculateCoordinates(g);
                g.setFont(FONT);
                g.drawString(text, x, y);
            }

            private void calculateCoordinates(Graphics g) {

                int textHeight = g.getFontMetrics(FONT).getHeight();
                int textWidth = g.getFontMetrics(FONT).stringWidth(text);

                switch (position) {
                case UP:
                    y = taskBar.y - MARGIN;
                    x = taskBar.x + taskBar.width / 2 - textWidth / 2;
                    break;
                case DOWN:
                    x = taskBar.x + taskBar.width / 2 - textWidth / 2;
                    y = taskBar.y + taskBar.height + textHeight - MARGIN;
                    break;
                case LEFT:
                    y = taskBar.y + taskBar.height / 2 + textHeight / 2
                            - MARGIN;
                    x = taskBar.x - MARGIN - textWidth;
                    break;
                case RIGHT:
                    y = taskBar.y + taskBar.height / 2 + textHeight / 2
                            - MARGIN;
                    x = taskBar.x + taskBar.width + MARGIN;
                    break;
                }
            }

        }
    }

    public void appendBlankRow() {
        tree.addBlankLine(null, -1);
    }

    public void projectModified() {
        // TODO Auto-generated method stub
        
    }

    public void projectSaved() {
        // TODO Auto-generated method stub
        
    }

    public void projectClosed() {
        repaint();
        setProjectLevelTaskColor(null);
        setPreviousStateTasks(null);
    }

}

