/*
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.util.*;

import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import net.sourceforge.ganttproject.resource.AssignmentNode;
import net.sourceforge.ganttproject.resource.HumanResource;
import net.sourceforge.ganttproject.resource.HumanResourceManager;
import net.sourceforge.ganttproject.resource.ResourceNode;
import net.sourceforge.ganttproject.resource.ResourceTableNode;
import net.sourceforge.ganttproject.task.ResourceAssignment;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.event.TaskHierarchyEvent;
import net.sourceforge.ganttproject.task.event.TaskListenerAdapter;
import net.sourceforge.ganttproject.task.event.TaskScheduleEvent;

import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
import org.jdesktop.swingx.treetable.MutableTreeTableNode;

import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

public class ResourceTreeTableModel extends DefaultTreeTableModel {
  private static final int STANDARD_COLUMN_COUNT = ResourceDefaultColumn.values().length;
  /** Column indexer */
  private static int index = -1;

  private DefaultMutableTreeTableNode root = null;

  private final HumanResourceManager myResourceManager;

  private final TaskManager myTaskManager;

  private TreeSelectionModel mySelectionModel;

  private final CustomPropertyManager myCustomPropertyManager;

  public ResourceTreeTableModel(HumanResourceManager resMgr, TaskManager taskManager,
      CustomPropertyManager customPropertyManager) {
    super();
    myCustomPropertyManager = customPropertyManager;
    myResourceManager = resMgr;
    myTaskManager = taskManager;
    myTaskManager.addTaskListener(new TaskListenerAdapter() {
      @Override
      public void taskScheduleChanged(TaskScheduleEvent e) {
        Set<HumanResource> affected = Sets.newHashSet();
        List<Task> subtree = Lists.newArrayList(myTaskManager.getTaskHierarchy().getDeepNestedTasks(e.getTask()));
        subtree.add(e.getTask());
        for (Task t : subtree) {
          for (ResourceAssignment ra : t.getAssignments()) {
            affected.add(ra.getResource());
          }
        }
        for (HumanResource resource : affected) {
          resource.resetLoads();
        }
        resourceAssignmentsChanged(affected);
      }
    });
    root = buildTree();
    this.setRoot(root);
  }

  public int useNextIndex() {
    index++;
    return index;
  }

  public MutableTreeTableNode getNodeForAssigment(ResourceAssignment assignement) {
    for (MutableTreeTableNode an : ImmutableList.copyOf(Iterators.forEnumeration(getNodeForResource(
        assignement.getResource()).children()))) {
      if (assignement.equals(an.getUserObject())) {
        return an;
      }
    }
    return null;
  }

  private DefaultMutableTreeTableNode buildTree() {

    DefaultMutableTreeTableNode root = new DefaultMutableTreeTableNode();
    List<HumanResource> listResources = myResourceManager.getResources();
    Iterator<HumanResource> itRes = listResources.iterator();

    while (itRes.hasNext()) {
      HumanResource hr = itRes.next();
      ResourceNode rnRes = new ResourceNode(hr); // the first for the resource
      root.add(rnRes);
    }
    return root;
  }

  public void updateResources() {
    HumanResource[] listResources = myResourceManager.getResourcesArray();

    for (int idxResource = 0; idxResource < listResources.length; idxResource++) {
      HumanResource hr = listResources[idxResource];

      ResourceNode rnRes = getNodeForResource(hr);
      if (rnRes == null) {
        rnRes = new ResourceNode(hr);
      }
      buildAssignmentsSubtree(rnRes);
      // for (int i = 0; i < tra.length; i++) {
      // AssignmentNode an = exists(rnRes, tra[i]);
      // if (an == null) {
      // an = new AssignmentNode(tra[i]);
      // rnRes.add(an);
      // }
      // }
      if (getNodeForResource(hr) == null) {
        root.add(rnRes);
      }
    }
    // this.setRoot(root);

  }

  public void updateResources(List<HumanResource> sorted){
    myResourceManager.clear();
    sorted.forEach(hr -> myResourceManager.add(hr));
    updateResources();
  }

  public ResourceNode getNodeForResource(final HumanResource hr) {
    try {
      return (ResourceNode) Iterators.find(Iterators.forEnumeration(root.children()),
          new Predicate<MutableTreeTableNode>() {
            @Override
            public boolean apply(MutableTreeTableNode input) {
              return input.getUserObject().equals(hr);
            }
          });
    } catch (NoSuchElementException e) {
      return null;
    }
  }

  public void changePeople(List<HumanResource> people) {
    Iterator<HumanResource> it = people.iterator();
    while (it.hasNext()) {
      addResource(it.next());
    }
  }

  public DefaultMutableTreeTableNode addResource(HumanResource people) {
    DefaultMutableTreeTableNode result = new ResourceNode(people);
    insertNodeInto(result, root, root.getChildCount());
    myResourceManager.toString();
    return result;
  }

  public void deleteResources(HumanResource[] peoples) {
    for (int i = 0; i < peoples.length; i++) {
      deleteResource(peoples[i]);
    }
  }

  public void deleteResource(HumanResource people) {
    removeNodeFromParent(getNodeForResource(people));
    // myResourceManager.remove(people);
  }

  /** Move Up the selected resource */
  public boolean moveUp(HumanResource resource) {
    myResourceManager.up(resource);
    ResourceNode rn = getNodeForResource(resource);
    int index = TreeUtil.getPrevSibling(root, rn);
    if (index == -1) {
      return false;
    }
    removeNodeFromParent(rn);
    insertNodeInto(rn, root, index);
    return true;
  }

  public boolean moveDown(HumanResource resource) {
    myResourceManager.down(resource);
    ResourceNode rn = getNodeForResource(resource);
    int index = TreeUtil.getNextSibling(root, rn);
    if (index == -1) {
      return false;
    }
    removeNodeFromParent(rn);
    insertNodeInto(rn, root, index);
    return true;
  }

  public void reset() {
    myResourceManager.clear();
  }

  public List<HumanResource> getAllResouces() {
    return myResourceManager.getResources();
  }

  @Override
  public int getColumnCount() {
    return STANDARD_COLUMN_COUNT + myCustomPropertyManager.getDefinitions().size();
  }

  // public ArrayList<ResourceColumn> getColumns()
  // {
  // return new ArrayList<ResourceColumn>(columns.values());
  // }
  //
  // /** @return the ResourceColumn associated to the given index */
  // public ResourceColumn getColumn(int index) {
  // return columns.get(new Integer(index));
  // }

  private CustomPropertyDefinition getCustomProperty(int columnIndex) {
    return myCustomPropertyManager.getDefinitions().get(columnIndex - STANDARD_COLUMN_COUNT);
  }

  @Override
  public Class<?> getColumnClass(int column) {
    if (column < 0) {
      return null;
    }
    if (column >= 0 && column < STANDARD_COLUMN_COUNT) {
      return ResourceDefaultColumn.values()[column].getValueClass();
    }
    CustomPropertyDefinition customColumn = getCustomProperty(column);
    Class<?> result = customColumn == null ? String.class : customColumn.getType();
    return result;
  }

  @Override
  public String getColumnName(int column) {
    if (column < STANDARD_COLUMN_COUNT) {
      return ResourceDefaultColumn.values()[column].getName();
    }
    CustomPropertyDefinition customColumn = getCustomProperty(column);
    return customColumn.getName();
  }

  @Override
  public boolean isCellEditable(Object node, int column) {
    if (false == node instanceof ResourceTableNode) {
      return false;
    }
    if (column >= STANDARD_COLUMN_COUNT) {
      return true;
    }
    ResourceDefaultColumn standardColumn = ResourceDefaultColumn.values()[column];
    ResourceTableNode resourceNode = (ResourceTableNode) node;
    return resourceNode.isEditable(standardColumn);
  }

  @Override
  public Object getValueAt(Object obj, int column) {
    if (false == obj instanceof ResourceTableNode) {
      return "";
    }
    ResourceTableNode node = (ResourceTableNode)obj;
    if (column >= STANDARD_COLUMN_COUNT) {
      return node.getCustomField(getCustomProperty(column));
    }
    return node.getStandardField(ResourceDefaultColumn.values()[column]);
  }

  @Override
  public void setValueAt(Object value, Object obj, int column) {
    if (false == obj instanceof ResourceTableNode) {
      return;
    }
    ResourceTableNode node = (ResourceTableNode)obj;
    if (column >= STANDARD_COLUMN_COUNT) {
      node.setCustomField(getCustomProperty(column), value);
      return;
    }
    if (isCellEditable(node, column)) {
      node.setStandardField(ResourceDefaultColumn.values()[column], value);
    }
  }

  public void resourceChanged(HumanResource resource) {
    ResourceNode node = getNodeForResource(resource);
    if (node == null) {
      return;
    }
    modelSupport.firePathChanged(TreeUtil.createPath(node));
  }

  public void resourceAssignmentsChanged(Iterable<HumanResource> resources) {
    for (HumanResource resource : resources) {
      ResourceNode nextNode = getNodeForResource(resource);
      SelectionKeeper selectionKeeper = new SelectionKeeper(mySelectionModel, nextNode);
      buildAssignmentsSubtree(nextNode);
      selectionKeeper.restoreSelection();
    }
  }

  private void buildAssignmentsSubtree(ResourceNode resourceNode) {
    HumanResource resource = resourceNode.getResource();
    resourceNode.removeAllChildren();
    ResourceAssignment[] assignments = resource.getAssignments();
    int[] indices = new int[assignments.length];
    TreeNode[] children = new TreeNode[assignments.length];
    if (assignments.length > 0) {
      for (int i = 0; i < assignments.length; i++) {
        indices[i] = i;
        AssignmentNode an = new AssignmentNode(assignments[i]);
        children[i] = an;
        resourceNode.add(an);
      }
    }
    modelSupport.fireTreeStructureChanged(TreeUtil.createPath(resourceNode));
  }

  void decreaseCustomPropertyIndex(int i) {
    index -= i;
  }

  void setSelectionModel(TreeSelectionModel selectionModel) {
    mySelectionModel = selectionModel;
  }

  private class SelectionKeeper {
    private final DefaultMutableTreeTableNode myChangingSubtreeRoot;
    private final TreeSelectionModel mySelectionModel;
    private boolean hasWork = false;
    private Object myModelObject;

    SelectionKeeper(TreeSelectionModel selectionModel, DefaultMutableTreeTableNode changingSubtreeRoot) {
      mySelectionModel = selectionModel;
      myChangingSubtreeRoot = changingSubtreeRoot;
      TreePath selectionPath = mySelectionModel.getSelectionPath();
      if (selectionPath != null && TreeUtil.createPath(myChangingSubtreeRoot).isDescendant(selectionPath)) {
        hasWork = true;
        DefaultMutableTreeTableNode lastNode = (DefaultMutableTreeTableNode) selectionPath.getLastPathComponent();
        myModelObject = lastNode.getUserObject();
      }
    }

    void restoreSelection() {
      if (!hasWork) {
        return;
      }
      for (MutableTreeTableNode node : TreeUtil.collectSubtree(myChangingSubtreeRoot)) {
        if (node.getUserObject().equals(myModelObject)) {
          mySelectionModel.setSelectionPath(TreeUtil.createPath(node));
          break;
        }
      }
    }
  }
}