/*
Copyright 2003-2012 Dmitry Barashev, 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 net.sourceforge.ganttproject;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.Date;
import java.util.List;

import javax.swing.Action;

import com.google.common.collect.Lists;

import net.sourceforge.ganttproject.chart.ChartModelBase;
import net.sourceforge.ganttproject.chart.ChartModelImpl;
import net.sourceforge.ganttproject.chart.ChartOptionGroup;
import net.sourceforge.ganttproject.chart.ChartViewState;
import net.sourceforge.ganttproject.chart.GanttChart;
import net.sourceforge.ganttproject.chart.ProjectCalendarDialogAction;
import net.sourceforge.ganttproject.chart.export.RenderedChartImage;
import net.sourceforge.ganttproject.chart.gantt.GanttChartController;
import net.sourceforge.ganttproject.chart.item.CalendarChartItem;
import net.sourceforge.ganttproject.chart.item.ChartItem;
import net.sourceforge.ganttproject.font.Fonts;
import net.sourceforge.ganttproject.gui.UIConfiguration;
import net.sourceforge.ganttproject.gui.zoom.ZoomManager;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.task.CustomPropertyEvent;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.algorithm.RecalculateTaskScheduleAlgorithm;
import net.sourceforge.ganttproject.task.dependency.TaskDependencyException;
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.undo.GPUndoManager;
import biz.ganttproject.core.calendar.CalendarEvent;
import biz.ganttproject.core.calendar.GPCalendar;
import biz.ganttproject.core.option.ColorOption;
import biz.ganttproject.core.option.DefaultColorOption;
import biz.ganttproject.core.option.GPOption;
import biz.ganttproject.core.option.GPOptionChangeListener;
import biz.ganttproject.core.option.GPOptionGroup;
import biz.ganttproject.core.time.CalendarFactory;

/**
 * Class for the graphic part of the soft
 */
public class GanttGraphicArea extends ChartComponentBase implements GanttChart, CustomPropertyListener,
    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");
  }

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

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

  public static final Cursor CHANGE_PROGRESS_CURSOR;

  private GanttChartController myChartComponentImpl;

  private final GanttTree2 tree;

  private final ChartModelImpl myChartModel;

  private final TaskManager myTaskManager;

  private GPUndoManager myUndoManager;

  private ChartViewState myViewState;

  private GanttPreviousState myBaseline;

  private final ProjectCalendarDialogAction myPublicHolidayDialogAction;

  private final ChartOptionGroup myStateDiffOptions;

  public GanttGraphicArea(GanttProject app, GanttTree2 ttree, TaskManager taskManager, ZoomManager zoomManager,
      GPUndoManager undoManager) {
    super(app.getProject(), app.getUIFacade(), zoomManager);
    this.setBackground(Color.WHITE);
    myTaskManager = taskManager;
    myUndoManager = undoManager;

    myChartModel = new ChartModelImpl(getTaskManager(), app.getTimeUnitStack(), app.getUIConfiguration());
    myChartModel.addOptionChangeListener(new GPOptionChangeListener() {
      @Override
      public void optionsChanged() {
        repaint();
      }
    });
    myStateDiffOptions = createBaselineColorOptions(myChartModel, app.getUIConfiguration());
    this.tree = ttree;
    myViewState = new ChartViewState(this, app.getUIFacade());
    app.getUIFacade().getZoomManager().addZoomListener(myViewState);

    super.setStartDate(CalendarFactory.newCalendar().getTime());
    myTaskManager.addTaskListener(new TaskListenerAdapter() {
      @Override
      public void taskScheduleChanged(TaskScheduleEvent e) {
        adjustDependencies((Task) e.getSource());
      }

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

      @Override
      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();
          }
        }
      }
    });
    myPublicHolidayDialogAction = new ProjectCalendarDialogAction(getProject(), getUIFacade());
    getProject().getTaskCustomColumnManager().addListener(this);
    initMouseListeners();
  }

  @Override
  public GPOptionGroup getBaselineColorOptions() {
    return myStateDiffOptions;
  }

  @Override
  public ColorOption getTaskDefaultColorOption() {
    return myChartModel.getTaskDefaultColorOption();
  }

  @Override
  public GPOptionGroup getTaskLabelOptions() {
    return myChartModel.getTaskLabelOptions();
  }

  /** @return the preferred size of the panel. */
  @Override
  public Dimension getPreferredSize() {
    return new Dimension(465, 600);
  }

  public ChartModelImpl getMyChartModel() {
    return myChartModel;
  }

  @Override
  public String getName() {
    return GanttLanguage.getInstance().getText("gantt");
  }

  private int getHeaderHeight() {
    return getImplementation().getHeaderHeight(tree, tree.getTreeTable().getScrollPane().getViewport());
  }

  /** @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);
    int width = renderedImage.getWidth();
    int height = renderedImage.getHeight();
    BufferedImage result = renderedImage.getWholeImage();
    repaint();
    return result;
  }

  @Override
  protected GPTreeTableBase getTreeTable() {
    return tree.getTreeTable();
  }

  GPUndoManager getUndoManager() {
    return myUndoManager;
  }

  @Override
  protected ChartModelBase getChartModel() {
    return myChartModel;
  }

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

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

  @Override
  public Action[] getPopupMenuActions(MouseEvent e) {
    List<Action> actions = Lists.newArrayList(getOptionsDialogAction(), myPublicHolidayDialogAction);
    actions.addAll(createToggleHolidayAction(e.getX()));
    return actions.toArray(new Action[actions.size()]);
  }

  private List<Action> createToggleHolidayAction(int x) {
    List<Action> result = Lists.newArrayList();
    ChartItem chartItem = myChartModel.getChartItemWithCoordinates(x, 0);
    if (chartItem instanceof CalendarChartItem) {
      Date date = ((CalendarChartItem) chartItem).getDate();
      CalendarEvent event = getProject().getActiveCalendar().getEvent(date);
      int dayMask = getProject().getActiveCalendar().getDayMask(date);
      if ((dayMask & GPCalendar.DayMask.WEEKEND) != 0) {
        switch (dayMask & GPCalendar.DayMask.WORKING) {
        case 0:
          if (event == null) {
            result.add(CalendarEventAction.addException(getProject().getActiveCalendar(), date));
          }
          break;
        case GPCalendar.DayMask.WORKING:
          result.add(CalendarEventAction.removeException(getProject().getActiveCalendar(), date));
          break;
        }
      }
      if ((dayMask & GPCalendar.DayMask.HOLIDAY) != 0) {
        result.add(CalendarEventAction.removeHoliday(getProject().getActiveCalendar(), date));
      } else {
        if (event == null) {
          result.add(CalendarEventAction.addHoliday(getProject().getActiveCalendar(), date));
        }
      }
    }
    return result;
  }

  @Override
  public void repaint() {
    if (myChartModel != null && isShowing()) {
      myChartModel.setHeaderHeight(getHeaderHeight());
    }
    super.repaint();
  }

  @Override
  public void setBaseline(GanttPreviousState baseline) {
    if (baseline == null) {
      setPreviousStateTasks(null);
    } else {
      setPreviousStateTasks(baseline.load());
    }
    myBaseline = baseline;
  }

  @Override
  public GanttPreviousState getBaseline() {
    return myBaseline;
  }

  static class MouseSupport {
    private final ChartModelImpl myChartModel;

    MouseSupport(ChartModelImpl chartModel) {
      myChartModel = chartModel;
    }

    protected Task findTaskUnderMousePointer(int xpos, int ypos) {
      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;
    }
  }

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

  GanttChartController getChartImplementation() {
    if (myChartComponentImpl == null) {
      myChartComponentImpl = new GanttChartController(getProject(), getUIFacade(), myChartModel, this, tree,
          getViewState());
    }
    return myChartComponentImpl;
  }

  public void setPreviousStateTasks(List<GanttPreviousStateTask> tasks) {
    int rowHeight = myChartModel.setBaseline(tasks);
    tree.getTable().setRowHeight(rowHeight);
  }


  @Override
  public void customPropertyChange(CustomPropertyEvent event) {
    repaint();
  }

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

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

  @Override
  public void projectClosed() {
    repaint();
    setPreviousStateTasks(null);
  }

  @Override
  public void projectOpened() {
  }

  @Override
  public void projectCreated() {
    // TODO Auto-generated method stub

  }

  @Override
  public ChartViewState getViewState() {
    return myViewState;
  }

  private static ChartOptionGroup createBaselineColorOptions(ChartModelImpl chartModel, final UIConfiguration projectConfig) {
    final ColorOption myTaskAheadOfScheduleColor = new DefaultColorOption("ganttChartStateDiffColors.taskAheadOfScheduleColor", new Color(50, 229, 50));
    myTaskAheadOfScheduleColor.addPropertyChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent evt) {
        projectConfig.setEarlierPreviousTaskColor(myTaskAheadOfScheduleColor.getValue());
      }
    });
    //
    final ColorOption myTaskBehindScheduleColor = new DefaultColorOption("ganttChartStateDiffColors.taskBehindScheduleColor", new Color(229, 50, 50));
    myTaskBehindScheduleColor.addPropertyChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent evt) {
        projectConfig.setLaterPreviousTaskColor(myTaskBehindScheduleColor.getValue());
      }
    });
    //
    final ColorOption myTaskOnScheduleColor = new DefaultColorOption("ganttChartStateDiffColors.taskOnScheduleColor", Color.LIGHT_GRAY);
    myTaskOnScheduleColor.addPropertyChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent evt) {
        projectConfig.setPreviousTaskColor(myTaskOnScheduleColor.getValue());
      }
    });
    //
    return new ChartOptionGroup("ganttChartStateDiffColors", new GPOption[] { myTaskOnScheduleColor,
        myTaskAheadOfScheduleColor, myTaskBehindScheduleColor }, chartModel.getOptionEventDispatcher());
  }
}
