/*
GanttProject is an opensource project management tool.
Copyright (C) 2011 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.task;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import net.sourceforge.ganttproject.resource.HumanResource;
import net.sourceforge.ganttproject.resource.HumanResourceManager;
import net.sourceforge.ganttproject.roles.Role;

class ResourceAssignmentCollectionImpl implements ResourceAssignmentCollection {
  private final Map<HumanResource, ResourceAssignment> myAssignments = new LinkedHashMap<HumanResource, ResourceAssignment>();

  private final TaskImpl myTask;

  private HumanResourceManager myResourceManager;

  public ResourceAssignmentCollectionImpl(TaskImpl task, HumanResourceManager resourceManager) {
    myTask = task;
    myResourceManager = resourceManager;
  }

  private ResourceAssignmentCollectionImpl(ResourceAssignmentCollectionImpl collection) {
    myTask = collection.myTask;
    ResourceAssignment[] assignments = collection.getAssignments();
    for (int i = 0; i < assignments.length; i++) {
      ResourceAssignment next = assignments[i];
      ResourceAssignment copy = new ResourceAssignmentImpl(next.getResource());
      copy.setLoad(next.getLoad());
      copy.setCoordinator(next.isCoordinator());
      copy.setRoleForAssignment(next.getRoleForAssignment());
      addAssignment(copy);
    }
  }

  @Override
  public void clear() {
    ResourceAssignment[] assignments = getAssignments();
    for (int i = 0; i < assignments.length; i++) {
      assignments[i].delete();
    }
  }

  @Override
  public ResourceAssignment[] getAssignments() {
    return myAssignments.values().toArray(new ResourceAssignment[myAssignments.size()]);
  }

  @Override
  public ResourceAssignment getAssignment(HumanResource resource) {
    return myAssignments.get(resource);
  }

  @Override
  public ResourceAssignmentMutator createMutator() {
    return new ResourceAssignmentMutatorImpl();
  }

  ResourceAssignmentCollectionImpl copy() {
    return new ResourceAssignmentCollectionImpl(this);
  }

  @Override
  public ResourceAssignment addAssignment(HumanResource resource) {
    return auxAddAssignment(resource);
  }

  @Override
  public void deleteAssignment(HumanResource resource) {
    myAssignments.remove(resource);
  }

  private ResourceAssignment auxAddAssignment(HumanResource resource) {
    final ResourceAssignment result = new ResourceAssignmentImpl(resource);
    addAssignment(result);
    return result;
  }

  private void addAssignment(ResourceAssignment assignment) {
    myAssignments.put(assignment.getResource(), assignment);
  }

  /**
   * Removes the assignments related to the given resource.
   * 
   * @param resource
   *          Assigned resource
   */
  public void removeAssignment(HumanResource resource) {
    new ResourceAssignmentImpl(resource).delete();
  }

  private Task getTask() {
    return myTask;
  }

  private class ResourceAssignmentImpl implements ResourceAssignment {
    private ResourceAssignment myAssignmentToResource;

    public ResourceAssignmentImpl(HumanResource resource) {
      myAssignmentToResource = resource.createAssignment(this);
      // resource.setAssignmentCollection(ResourceAssignmentCollectionImpl.this);
    }

    @Override
    public Task getTask() {
      return ResourceAssignmentCollectionImpl.this.getTask();
    }

    @Override
    public HumanResource getResource() {
      return myAssignmentToResource.getResource();
    }

    @Override
    public float getLoad() {
      return myAssignmentToResource.getLoad();
    }

    // TODO transaction
    @Override
    public void setLoad(float load) {
      myAssignmentToResource.setLoad(load);
    }

    /**
     * Deletes all the assignments and all the related assignments
     */
    @Override
    public void delete() {
      ResourceAssignmentCollectionImpl.this.deleteAssignment(getResource());
      myAssignmentToResource.delete();
    }

    @Override
    public void setCoordinator(boolean responsible) {
      myAssignmentToResource.setCoordinator(responsible);
    }

    @Override
    public boolean isCoordinator() {
      return myAssignmentToResource.isCoordinator();
    }

    @Override
    public Role getRoleForAssignment() {
      return myAssignmentToResource.getRoleForAssignment();
    }

    @Override
    public void setRoleForAssignment(Role role) {
      myAssignmentToResource.setRoleForAssignment(role);

    }

    @Override
    public String toString() {
      return this.getResource().getName() + " -> " + this.getTask().getName();
    }
  }

  private class ResourceAssignmentStub implements ResourceAssignment {
    private final HumanResource myResource;

    private float myLoad;

    private boolean myCoordinator;

    private Role myRoleForAssignment;

    public ResourceAssignmentStub(HumanResource resource) {
      myResource = resource;
    }

    @Override
    public Task getTask() {
      return ResourceAssignmentCollectionImpl.this.getTask();
    }

    @Override
    public HumanResource getResource() {
      return myResource;
    }

    @Override
    public float getLoad() {
      return myLoad;
    }

    @Override
    public void setLoad(float load) {
      myLoad = load;
    }

    @Override
    public void delete() {
    }

    @Override
    public void setCoordinator(boolean responsible) {
      myCoordinator = responsible;
    }

    @Override
    public boolean isCoordinator() {
      return myCoordinator;
    }

    @Override
    public Role getRoleForAssignment() {

      return myRoleForAssignment;
    }

    @Override
    public void setRoleForAssignment(Role role) {
      myRoleForAssignment = role;

    }

    @Override
    public String toString() {
      return this.getResource().getName() + " -> " + this.getTask().getName();
    }
  }

  private class ResourceAssignmentMutatorImpl implements ResourceAssignmentMutator {
    private Map<HumanResource, MutationInfo> myQueue = new HashMap<HumanResource, MutationInfo>();

    @Override
    public ResourceAssignment addAssignment(HumanResource resource) {
      ResourceAssignment result = new ResourceAssignmentStub(resource);
      myQueue.put(resource, new MutationInfo(result, MutationInfo.ADD));
      return result;
    }

    @Override
    public void deleteAssignment(HumanResource resource) {
      MutationInfo info = myQueue.get(resource);
      if (info == null) {
        myQueue.put(resource, new MutationInfo(resource, MutationInfo.DELETE));
      } else if (info.myOperation == MutationInfo.ADD) {
        myQueue.remove(resource);
      }
    }

    @Override
    public void commit() {
      List<MutationInfo> mutations = new ArrayList<MutationInfo>(myQueue.values());
      Collections.sort(mutations);
      for (int i = 0; i < mutations.size(); i++) {
        MutationInfo next = mutations.get(i);
        switch (next.myOperation) {
        case MutationInfo.DELETE: {
          myAssignments.remove(next.myResource);
          break;
        }
        case MutationInfo.ADD: {
          ResourceAssignment result = auxAddAssignment(next.myResource);
          result.setLoad(next.myAssignment.getLoad());
          result.setCoordinator(next.myAssignment.isCoordinator());
          result.setRoleForAssignment(next.myAssignment.getRoleForAssignment());
        }
        default:
          break;
        }
      }
    }

  }

  private static class MutationInfo implements Comparable<MutationInfo> {
    static final int ADD = 0;

    static final int DELETE = 1;

    private final ResourceAssignment myAssignment;

    private final int myOrder;

    private static int ourOrder;

    private int myOperation;

    private final HumanResource myResource;

    public MutationInfo(ResourceAssignment assignment, int operation) {
      myAssignment = assignment;
      myOrder = ourOrder++;
      myOperation = operation;
      myResource = assignment.getResource();
    }

    public MutationInfo(HumanResource resource, int operation) {
      this.myAssignment = null;
      this.myOrder = ourOrder++;
      this.myOperation = operation;
      this.myResource = resource;
    }

    @Override
    public boolean equals(Object o) {
      boolean result = o instanceof MutationInfo;
      if (result) {
        result = myAssignment.getResource().equals(((MutationInfo) o).myAssignment.getResource());
      }
      return result;
    }

    @Override
    public int compareTo(MutationInfo o) {
      if (!(o instanceof MutationInfo)) {
        throw new IllegalArgumentException();
      }
      return myOrder - o.myOrder;
    }
  }

  public void importData(ResourceAssignmentCollection assignmentCollection) {
    if (myTask.isUnplugged()) {
      ResourceAssignment[] assignments = assignmentCollection.getAssignments();
      for (int i = 0; i < assignments.length; i++) {
        ResourceAssignment next = assignments[i];
        addAssignment(next);
      }
    } else {
      ResourceAssignment[] assignments = assignmentCollection.getAssignments();
      for (int i = 0; i < assignments.length; i++) {
        ResourceAssignment next = assignments[i];
        HumanResource nextResource = next.getResource();
        HumanResource nextImportedResource = myResourceManager.getById(nextResource.getId());
        if (nextImportedResource != null) {
          ResourceAssignment copy = new ResourceAssignmentImpl(nextImportedResource);
          copy.setLoad(next.getLoad());
          copy.setCoordinator(next.isCoordinator());
          copy.setRoleForAssignment(next.getRoleForAssignment());
          addAssignment(copy);
        }
      }
    }
  }

  @Override
  public HumanResource getCoordinator() {
    for (Iterator<ResourceAssignment> assignments = myAssignments.values().iterator(); assignments.hasNext();) {
      ResourceAssignment next = assignments.next();
      if (next.isCoordinator()) {
        return next.getResource();
      }
    }
    return null;
  }
}
