/*
 * file:       GanttMPXJOpen.java
 * author:     Jon Iles
 * copyright:  (c) Tapster Rock Limited 2005
 * date:       10/01/2005
 */

/*
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * This library 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 Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

package org.ganttproject.impex.msproject;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;

import javax.swing.tree.DefaultMutableTreeNode;

import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;

import net.sourceforge.ganttproject.GPLogger;
import net.sourceforge.ganttproject.GanttCalendar;
import net.sourceforge.ganttproject.GanttProject;
import net.sourceforge.ganttproject.GanttTask;
import net.sourceforge.ganttproject.GanttTaskRelationship;
import net.sourceforge.ganttproject.GanttTree2;
import net.sourceforge.ganttproject.gui.GanttDialogInfo;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.resource.HumanResource;
import net.sourceforge.ganttproject.resource.HumanResourceManager;
import net.sourceforge.ganttproject.resource.ProjectResource;
import net.sourceforge.ganttproject.task.ResourceAssignment;
import net.sourceforge.ganttproject.task.TaskManager;
import net.sourceforge.ganttproject.task.TaskNode;
import net.sourceforge.ganttproject.task.dependency.TaskDependency;
import net.sourceforge.ganttproject.task.dependency.TaskDependencyException;

import com.tapsterrock.mpx.MPXCalendar;
import com.tapsterrock.mpx.MPXDuration;
import com.tapsterrock.mpx.MPXException;
import com.tapsterrock.mpx.MPXFile;
import com.tapsterrock.mpx.Priority;
import com.tapsterrock.mpx.Relation;
import com.tapsterrock.mpx.RelationList;
import com.tapsterrock.mpx.RelationType;
import com.tapsterrock.mpx.Resource;
import com.tapsterrock.mpx.Task;
import com.tapsterrock.mpx.TimeUnit;

/**
 * This class implements the mechanism used to import Microsoft Project data
 * into the GanttProject application using MPXJ.
 */
public abstract class GanttMPXJOpen {
    /**
     * Constructor.
     */
    public GanttMPXJOpen(GanttTree2 tasks, GanttProject project, Locale mpxLocale) {
        m_tasks = tasks;
        m_project = project;
        m_mpxImportLocale = mpxLocale;
    }

    /**
     * Constructor.
     */
    public GanttMPXJOpen(GanttTree2 tasks, GanttProject project) {
        m_tasks = tasks;
        m_project = project;
    }

    /**
     * This method opens a file based on the supplied filename.
     * 
     * @param filename
     *            filename
     * @return boolean flag
     * @throws MPXException 
     */
    public boolean load(String filename) throws MPXException {
        return (load(new File(filename)));
    }

    /**
     * Load the contents of a file.
     * 
     * @param file
     *            input file
     * @return boolean flag
     * @throws MPXException 
     */
    public abstract boolean load(File file) throws MPXException;

    /**
     * Load the contents of a project from an input stream.
     * 
     * @param is
     *            input stream
     * @return boolean flag
     * @throws MPXException 
     */
    public abstract boolean load(InputStream is) throws MPXException;

    /**
     * This method loads project data from an input stream. The type of data
     * expected in the stream is indicated by the class passed as the first
     * argument to this method. The file argument is used purely for error
     * reporting, if it is not null then error messages generated by this method
     * can refer to the source file. Finally, if the input stream argument is
     * not null, the data is loaded from the supplied input stream. If it is
     * null, then a new input stream is created from the supplied file object.
     * 
     * @param mpxjClass
     *            MPXJ class representing the type of file to be imported
     * @param file
     *            file instance
     * @param is
     *            input stream instance
     * @return boolean flag
     * @throws Exception 
     */
    protected boolean load(MPXFile mpx) throws MPXException {
            processResources(mpx);
            try {
                processTasks(mpx);
            } catch (Exception e) {
                throw new MPXException(e.getMessage(), e);
            }
            processRelationships(mpx);
            processResourceAssignments(mpx);
            return true;
    }

    /**
     * This method extracts all of the resources from the MPXFile instance and
     * creates the appropriate GanttProject resource representation.
     * 
     * @param mpx
     *            Current MPXFile instance
     */
    private void processResources(MPXFile mpx) {
        HumanResourceManager hrm = (HumanResourceManager) m_project
                .getHumanResourceManager();
        LinkedList resources = mpx.getAllResources();
        Iterator iter = resources.iterator();
        Resource resource;
        HumanResource people;

        while (iter.hasNext() == true) {
            resource = (Resource) iter.next();
            if (resource.getName() != null) {
                people = hrm.newHumanResource();
                people.setName(resource.getName());
                people.setMail(resource.getEmailAddress());
                hrm.add(people);

                m_resourceMap
                        .put(resource.getID(), new Integer(people.getId()));
            }
        }
    }

    /**
     * This method is used to extract all tasks from an MPXFile instance, and
     * create the equivalent data structures in GanttProject.
     * 
     * @param mpx
     *            Current MPXFile instance
     * @throws Exception
     */
    private void processTasks(MPXFile mpx) throws Exception {
        TaskManager tm = m_project.getTaskManager();
        MPXCalendar cal = mpx.getBaseCalendar("Standard");
        LinkedList tasks = mpx.getChildTasks();
        Iterator iter = tasks.iterator();

        while (iter.hasNext() == true) {
            processTask(tm, cal, (Task) iter.next(), null);
        }
    }

    /**
     * This method is called recursively to process the task hierarchy and
     * create the equivalent data structure in GanttProject.
     * 
     * @param defaultCalendar
     *            BaseCalendar instance
     * @param task
     *            Parent task
     * @param node
     *            Parent node
     * @throws Exception
     */
    private void processTask(TaskManager tm, MPXCalendar defaultCalendar,
            Task task, DefaultMutableTreeTableNode node) throws Exception {
        //
        // Calculate the duration in days
        //
        MPXCalendar taskCalendar = task.getCalendar();
        MPXCalendar cal;
        if (taskCalendar != null) {
            cal = taskCalendar;
        } else {
            cal = defaultCalendar;
        }

        final MPXDuration duration;
        boolean milestone = task.getMilestoneValue();
        if (milestone == true) {
            duration = MILESTONE_DURATION;
        } else {
            Date taskStart = task.getStart();
            Date taskFinish = task.getFinish();
            if (taskStart != null && taskFinish != null) {
                //duration = cal.getDuration(taskStart, taskFinish);
                duration = task.getDuration();
            } else {
                duration = task.getDuration();
            }
        }

        //
        // Create the new task object
        //
        GanttTask gtask = tm.createTask();
        // gtask.setChecked();
        // gtask.setColor();
        gtask.setCompletionPercentage((int) task.getPercentageCompleteValue());
        // gtask.setExpand()
        // gtask.setLength();
        gtask.setMilestone(milestone);
        gtask.setName(task.getName() == null ? "-" : task.getName());
        gtask.setNotes(task.getNotes());
        Priority prio = task.getPriority();
        if (prio != null) {
            int priority = prio.getValue();
            int p;
            switch (priority) {
            case Priority.HIGHEST:
            case Priority.HIGHER:
            case Priority.VERY_HIGH:
                p = 2;
                break;
            case Priority.LOWEST:
            case Priority.LOWER:
            case Priority.VERY_LOW:
                p = 0;
                break;
            default:
                p = 1;
            }

            gtask.setPriority(p);
        }
        // gtask.setShape();
        // gtask.setStartFixed()
        // gtask.setTaskID()
        gtask.setWebLink(task.getHyperlink());
        Date taskStart = task.getStart();
        assert taskStart!=null : "Task="+task+" has null start";
        gtask.setStart(new GanttCalendar(taskStart));
//        gtask.setDuration(tm.createLength((long) duration.getDuration()));
        long longDuration = (long) Math.ceil(duration.convertUnits(TimeUnit.DAYS).getDuration());
        if (longDuration > 0) {
            gtask.setDuration(tm.createLength(longDuration));
        }
        else {
            System.err.println("Task "+task.getName()+" has duration="+duration+" which is 0 as long integer. This duration has been ignored, task has got the default duration");
        }
        // gtask.setEnd(new GanttCalendar(task.getFinish()));

        //
        // Add the task and process any child tasks
        //
        tm.registerTask(gtask);

        m_tasks.addObject(gtask, (TaskNode) node, -1);

        m_taskMap.put(task.getID(), new Integer(gtask.getTaskID()));

        LinkedList children = task.getChildTasks();

        if (children.size() != 0) {
            node = m_tasks.getNode(gtask.getTaskID());

            Iterator iter = children.iterator();

            while (iter.hasNext() == true) {
                processTask(tm, defaultCalendar, (Task) iter.next(), node);
            }
        }
    }

    /**
     * This method extracts all of the inter-task relationships from and MPXFile
     * instance, and creates the equivalent data structures in the GanttProject
     * application.
     * 
     * @param mpx
     *            Currenct MPXFile instance
     */
    private void processRelationships(MPXFile mpx) {
        TaskManager tm = m_project.getTaskManager();
        Iterator taskIter = mpx.getAllTasks().iterator();
        Task task;
        RelationList rels;
        Iterator relIter;
        Relation rel;

        GanttTask gTask1;
        int gTaskNumber1;
        GanttTask gTask2;
        int gTaskNumber2;
        TaskDependency gTaskDependency;
        int gConstraintType;
        while (taskIter.hasNext() == true) {
            task = (Task) taskIter.next();
            gTaskNumber1 = mapTaskNumber(task.getID());

            if (gTaskNumber1 == -1) {
                continue;
            }

            rels = (RelationList)task.getPredecessors();

            if (rels != null) {
                relIter = rels.iterator();

                while (relIter.hasNext() == true) {
                    rel = (Relation) relIter.next();

                    gTaskNumber2 = mapTaskNumber(new Integer(rel
                            .getTaskIDValue()));

                    if (gTaskNumber2 != -1) {
                        gTask1 = tm.getTask(gTaskNumber1);
                        gTask2 = tm.getTask(gTaskNumber2);

                        switch (rel.getType().getType()) {
                        case RelationType.FINISH_FINISH_VALUE: {
                            gConstraintType = GanttTaskRelationship.FF;
                            break;
                        }

                        case RelationType.START_FINISH_VALUE: {
                            gConstraintType = GanttTaskRelationship.SF;
                            break;
                        }

                        case RelationType.START_START_VALUE: {
                            gConstraintType = GanttTaskRelationship.SS;
                            break;
                        }

                        default:
                        case RelationType.FINISH_START_VALUE: {
                            gConstraintType = GanttTaskRelationship.FS;
                            break;
                        }
                        }

                        try {
                            gTaskDependency = tm
                                    .getDependencyCollection()
                                    .createDependency(
                                            gTask1,
                                            gTask2,
                                            tm
                                                    .createConstraint(gConstraintType));
                            gTaskDependency.setConstraint(tm
                                    .createConstraint(gConstraintType));
                        }

                        catch (TaskDependencyException e) {
                            if (!GPLogger.log(e)) {
                                e.printStackTrace(System.err);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * This method extracts all resource assignment data from an MPXFile
     * instance and creates the equivalent GanttProject data structures.
     * 
     * @param mpx
     *            Currenct MPXFile instance
     */
    private void processResourceAssignments(MPXFile mpx) {
        TaskManager tm = m_project.getTaskManager();
        HumanResourceManager hrm = (HumanResourceManager) m_project
                .getHumanResourceManager();
        LinkedList assignments = mpx.getAllResourceAssignments();
        Iterator iter = assignments.iterator();
        com.tapsterrock.mpx.ResourceAssignment assignment;
        int gTaskID;
        int gResourceID;
        GanttTask gTask;
        ProjectResource gResource;
        ResourceAssignment gAssignment;

        while (iter.hasNext() == true) {
            assignment = (com.tapsterrock.mpx.ResourceAssignment) iter.next();
            gTaskID = mapTaskNumber(assignment.getTask().getID());
            gResourceID = mapResourceNumber(assignment.getResource().getID());

            if ((gTaskID != -1) && (gResourceID != -1)) {
                gTask = tm.getTask(gTaskID);
                gResource = hrm.getById(gResourceID);

                gAssignment = gTask.getAssignmentCollection().addAssignment(
                        gResource);
                gAssignment.setLoad((float) assignment.getUnitsValue());
                gAssignment.setCoordinator(false);
                if (gResource instanceof HumanResource)
                    gAssignment
                            .setRoleForAssignment(((HumanResource) gResource)
                                    .getRole());
            }
        }
    }

    /**
     * Convenience function used to map an MPXFile task ID to a GanttProject
     * task number.
     * 
     * @param taskID
     *            MPXFile task ID
     * @return GanttProject task number
     */
    private int mapTaskNumber(Integer taskID) {
        int result = -1;

        Integer taskNumber = (Integer) m_taskMap.get(taskID);

        if (taskNumber != null) {
            result = taskNumber.intValue();
        }

        return (result);
    }

    /**
     * Convenience function used to map an MPXFile resource ID to a GanttProject
     * resource number.
     * 
     * @param taskID
     *            MPXFile task ID
     * @return GanttProject task number
     */
    private int mapResourceNumber(Integer resourceID) {
        int result = -1;

        Integer resourceNumber = (Integer) m_resourceMap.get(resourceID);

        if (resourceNumber != null) {
            result = resourceNumber.intValue();
        }

        return (result);
    }

    private GanttTree2 m_tasks;

    private GanttProject m_project;

    private HashMap m_taskMap = new HashMap();

    private HashMap m_resourceMap = new HashMap();

    private Locale m_mpxImportLocale = Locale.ENGLISH;

    private static final MPXDuration MILESTONE_DURATION = MPXDuration.getInstance(1,
            TimeUnit.DAYS);
}
