//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "TaskEditDlg.hpp"

#include <Dir.hpp>
#include <Forall.hpp>
#include <Information.hpp>
#include <MorphoGraphX.hpp>
#include <Parms.hpp>
#include <TasksView.hpp>

#include <algorithm>
#include <functional>
#include <QFileDialog>
#include <QFileInfo>
#include <QInputDialog>
#include <QList>
#include <QMessageBox>
#include <QMimeData>
#include <QRegExp>
#include <QSettings>
#include <QTextStream>
#include <QTreeWidgetItem>
#include <QUrl>
#include <stdio.h>

enum TaskEditRoles 
{
  ParmsRole = Qt::UserRole + 1,
  ParmNamesRole = Qt::UserRole + 2,
};

TasksModel::TasksModel(const tasks_t& ts, QObject* parent)
  : QAbstractItemModel(parent), tasks(ts)
{
  task_names = tasks.keys();
  task_names.sort();
  forall(const QString& type, task_names)
    task_numbers << tasks[type].size();
}

int TasksModel::rowCount(const QModelIndex& parent) const
{
  if(!parent.isValid())
    return task_names.size();
  else if(isTask(parent) and parent.row() < task_names.size())
    return task_numbers[parent.row()];
  return 0;
}

int TasksModel::columnCount(const QModelIndex&) const {
  return 1;
}

bool TasksModel::isTask(const QModelIndex& idx) const
{
  if(!idx.isValid())
    return false;
  return (idx.internalId() >> 16) == 0;
}

QVariant TasksModel::data(const QModelIndex& index, int role) const
{
  if(!index.isValid())
    return QVariant();
  if(isTask(index)) {
    if(index.row() >= task_names.size())
      return QVariant();
    if(role == Qt::DisplayRole and index.column() == 0) {
      return task_names[index.row()];
    }
  } else {
    int task_id = index.parent().row();
    QString task_name = task_names[task_id];
    if(task_name.isEmpty())
      return QVariant();
    if(index.row() >= tasks[task_name].size())
      return QVariant();
    const mgx::ProcessDefinition& def = tasks[task_name][index.row()];
    if(index.column() == 0) {
      switch(role) {
      case Qt::DisplayRole:
        return def.name;
      case Qt::DecorationRole:
        return def.icon;
      case Qt::ToolTipRole:
        return def.description;
      case ParmsRole:
        return def.parms;
      case ParmNamesRole:
        return def.parmNames;
      default:
        return QVariant();
      }
    } 
  }
  return QVariant();
}

QVariant TasksModel::headerData(int section, Qt::Orientation orientation, int role) const
{
  if(orientation == Qt::Horizontal and role == Qt::DisplayRole) {
    if(section == 0)
      return "Process";
  }
  return QVariant();
}

Qt::ItemFlags TasksModel::flags(const QModelIndex& index) const
{
  Qt::ItemFlags flags = Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
  if(!isTask(index)) {
    return flags | Qt::ItemIsDragEnabled;
  }
  return flags;
}

bool TasksModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
  if(!index.isValid())
    return false;
  if(isTask(index)) {
    if(index.column() == 0 and role == Qt::DisplayRole) {
      if(index.row() < task_names.size()) {
        QString newName = value.toString();
        if(newName.isEmpty() or task_names.contains(newName))
          return false;
        QString oldName = task_names[index.row()];
        if(!oldName.isEmpty())
          tasks[newName] = tasks[oldName];
        else
          tasks[newName] = QList<mgx::ProcessDefinition>();
        tasks.remove(oldName);
        task_names[index.row()] = newName;
        emit dataChanged(index, index);
      }
    }
  } else {
    int task_id = index.parent().row();
    int proc_num = index.row();
    if(proc_num < task_numbers[task_id] and index.column() < 1) {
      const QString& task = task_names[task_id];
      mgx::ProcessDefinition& def = tasks[task][proc_num];
      if(index.column() == 1) {
        if(role == Qt::DisplayRole) {
          //def.type = value.toString();
          emit dataChanged(index, index);
          return true;
        }
      } else {
        switch(role) {
        case Qt::DisplayRole:
          def.name = value.toString();
          emit dataChanged(index, index);
          return true;
        case Qt::DecorationRole:
          if(value.canConvert<QIcon>()) {
            def.icon = value.value<QIcon>();
            emit dataChanged(index, index);
            return true;
          }
          return false;
        case Qt::ToolTipRole:
          def.description = value.toString();
          emit dataChanged(index, index);
          return true;
        case ParmsRole:
          if(value.canConvert<QStringList>()) {
            mgx::Information::out << "Set strings role" << endl;
            def.parms = value.toStringList();
            return true;
          }
          return false;
        case ParmNamesRole:
          def.parmNames = value.toStringList();
          return true;
        default:
          return false;
        }
      }
    }
  }
  return false;
}

QMap<int, QVariant> TasksModel::itemData(const QModelIndex& index) const
{
  QMap<int, QVariant> result;
  if(index.isValid()) {
    if(!isTask(index)) {
      int task_id = index.parent().row();
      if(task_id < task_names.size() and index.row() < task_numbers[task_id]) {
        const mgx::ProcessDefinition& def = tasks[task_names[task_id]][index.row()];
        if(index.column() == 0) {
          result[Qt::DisplayRole] = def.name;
          result[ParmNamesRole] = def.parmNames;
          result[Qt::DecorationRole] = def.icon;
          result[Qt::ToolTipRole] = def.description;
          result[ParmsRole] = def.parms;
        } else if(index.column() == 1) {
          ; //result[Qt::DisplayRole] = def.type;
        }
      }
    } else if(index.row() < task_names.size()) {
      result[Qt::DisplayRole] = task_names[index.row()];
    }
  }
  return result;
}

bool TasksModel::setItemData(const QModelIndex& index, const QMap<int, QVariant>& roles)
{
  if(index.isValid()) {
    if(!isTask(index)) {
      int task_id = index.parent().row();
      if(task_id < task_names.size() and index.row() < task_numbers[task_id] and index.column() < 1) {
        mgx::ProcessDefinition& def = tasks[task_names[task_id]][index.row()];
        for(QMap<int, QVariant>::const_iterator it = roles.begin(); it != roles.end(); ++it) {
          if(index.column() == 1) {
            if(it.key() == Qt::DisplayRole)
              ;//def.type = it.value().toString();
          } else {
            switch(it.key()) {
            case Qt::DisplayRole:
              def.name = it.value().toString();
              break;
            case Qt::DecorationRole:
              if(it.value().canConvert<QIcon>())
                def.icon = it.value().value<QIcon>();
              break;
            case Qt::ToolTipRole:
              def.description = it.value().toString();
              break;
            case ParmsRole:
              if(it.value().canConvert<QStringList>())
                def.parms = it.value().toStringList();
              break;
            case ParmNamesRole:
              def.parmNames = it.value().toStringList();
              break;
            default:
              break;
            }
          }
        }
        emit dataChanged(index, this->index(index.row(), 1, index.parent()));
        return true;
      }
    } else {
      int task_id = index.row();
      if(task_id < task_names.size() and index.column() == 0) {
        for(QMap<int, QVariant>::const_iterator it = roles.begin(); it != roles.end(); ++it) {
          if(it.key() == Qt::DisplayRole) {
            setData(index, it.value(), it.key());
          }
        }
      }
      return true;
    }
  }
  return false;
}

Qt::DropActions TasksModel::supportedDropActions() const {
  return Qt::CopyAction | Qt::MoveAction;
}

QStringList TasksModel::mimeTypes() const
{
  QStringList lst;
  lst << itemlist_format << internal_format;
  return lst;
}

QMimeData* TasksModel::mimeData(const QModelIndexList& indexes) const
{
  QModelIndexList tosend;
  std::vector<QList<int> > lists(task_names.size(), QList<int>());
  forall(const QModelIndex& idx, indexes) {
    if(idx.isValid() and !isTask(idx)) {
      int task_id = idx.parent().row();
      if(not lists[task_id].contains(idx.row())) {
        tosend << index(idx.row(), 0, idx.parent());
        lists[task_id] << idx.row();
      }
    }
  }
  return QAbstractItemModel::mimeData(tosend);
}

bool TasksModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int, const QModelIndex& parent)
{
  const QStringList& lst = data->formats();
  if(lst.contains(itemlist_format)) {
    if(isTask(parent)) {
      return QAbstractItemModel::dropMimeData(data, action, row, 0, index(parent.row(), 0, parent.parent()));
    } else if(parent.isValid()) {
      int row = parent.row();
      return QAbstractItemModel::dropMimeData(data, action, row, 0, parent.parent());
    }
  }
  return false;
}

QModelIndex TasksModel::parent(const QModelIndex& index) const
{
  if(index.isValid() and !isTask(index)) {
    qint32 id = index.internalId();
    int r = id >> 16;
    --r;
    return createIndex(r, 0, r);
  }
  return QModelIndex();
}

QModelIndex TasksModel::index(int row, int column, const QModelIndex& parent) const
{
  if(!parent.isValid()) {
    if(row < task_names.size() and column < 1) {
      return createIndex(row, column, row);
    }
  } else if(isTask(parent)) {
    qint32 id = parent.row();
    if(id < task_names.size() and row < task_numbers[id]) {
      ++id;
      id <<= 16;
      id += row;
      return createIndex(row, column, id);
    }
  }
  return QModelIndex();
}

bool TasksModel::insertRows(int position, int rows, const QModelIndex& parent)
{
  if(!parent.isValid()) {
    beginInsertRows(parent, position, position + rows - 1);
    if(position >= task_names.size()) {
      for(int i = 0; i < rows; ++i) {
        task_names << "";
        task_numbers << 0;
      }
    } else {
      for(int i = 0; i < rows; ++i) {
        task_names.insert(position, "");
        task_numbers.insert(position, 0);
      }
    }
    endInsertRows();
    return true;
  } else if(isTask(parent)) {
    int task_id = parent.row();
    QList<mgx::ProcessDefinition>& defs = tasks[task_names[task_id]];
    beginInsertRows(parent, position, position + rows - 1);
    if(position >= task_numbers[task_id]) {
      for(int i = 0; i < rows; ++i)
        defs << mgx::ProcessDefinition();
    } else {
      for(int i = 0; i < rows; ++i)
        defs.insert(position, mgx::ProcessDefinition());
    }
    task_numbers[task_id] += rows;
    endInsertRows();
  }
  return false;
}

bool TasksModel::removeRows(int position, int rows, const QModelIndex& parent)
{
  if(!parent.isValid()) {
    beginRemoveRows(parent, position, position + rows - 1);
    for(int i = 0; i < rows; ++i) {
      QString str = task_names[i];
      tasks.remove(str);
      task_names.removeAt(position);
      task_numbers.removeAt(position);
    }
    endRemoveRows();
    return true;
  } else if(isTask(parent)) {
    int task_id = parent.row();
    if(task_id < task_names.size() and position + rows - 1 < task_numbers[task_id]) {
      QString task_name = task_names[task_id];
      beginRemoveRows(parent, position, position + rows - 1);
      for(int i = 0; i < rows; ++i) {
        task_numbers[task_id]--;
        tasks[task_name].removeAt(position);
      }
      endRemoveRows();
      return true;
    }
  }
  return false;
}

bool TasksModel::addTask(const QString& name)
{
  if(task_names.contains(name))
    return false;
  QStringList new_ts = task_names;
  new_ts << name;
  new_ts.sort();
  int i = new_ts.indexOf(name);
  insertRows(i, 1, QModelIndex());
  setData(index(i, 0, QModelIndex()), name, Qt::DisplayRole);
  return true;
}

bool TasksModel::addProcess(const QString& task_name, const mgx::ProcessDefinition& def)
{
  if(!task_names.contains(task_name))
    return false;
  int task_id = task_names.indexOf(task_name);
  const QModelIndex& parent = taskIndex(task_id);
  int position = task_numbers[task_id];
  beginInsertRows(parent, position, 1);
  ++task_numbers[task_id];
  tasks[task_name] << def;
  endInsertRows();
  return true;
}

bool TasksModel::deleteTask(const QString& name)
{
  if(!task_names.contains(name))
    return false;
  int idx = task_names.indexOf(name);
  beginRemoveRows(QModelIndex(), idx, idx);
  task_names.removeAt(idx);
  task_numbers.removeAt(idx);
  tasks.remove(name);
  endRemoveRows();
  return true;
}

bool TasksModel::copyTask(const QString& oldName, const QString& newName)
{
  if(!task_names.contains(oldName))
    return false;
  if(task_names.contains(newName))
    return false;
  QModelIndex root;
  QStringList new_names = task_names;
  new_names << newName;
  new_names.sort();
  int task_id = new_names.indexOf(newName);
  beginInsertRows(root, task_id, task_id);
  tasks[newName] = tasks[oldName];
  task_names.insert(task_id, newName);
  task_numbers.insert(task_id, tasks[newName].size());
  endInsertRows();
  return true;
}

bool TasksModel::renameTask(const QString& oldName, const QString& newName)
{
  if(!task_names.contains(oldName))
    return false;
  if(task_names.contains(newName))
    return false;
  int task_id = task_names.indexOf(oldName);
  QModelIndex root;
  QStringList new_names = task_names;
  new_names[task_id] = newName;
  new_names.sort();
  int new_task_id = new_names.indexOf(newName);
  if(task_id == new_task_id)
    setData(taskIndex(task_id), newName, Qt::DisplayRole);
  else {
    int dest_row = new_task_id;
    if(new_task_id > task_id)
      ++dest_row;
    beginMoveRows(root, task_id, task_id, root, dest_row);
    tasks[newName] = tasks[oldName];
    tasks.remove(oldName);
    int nb = task_numbers[task_id];
    task_numbers.removeAt(task_id);
    task_numbers.insert(new_task_id, nb);
    task_names = new_names;
    endMoveRows();
  }
  return true;
}

void TasksModel::deleteItems(QList<QList<int> > to_delete)
{
  if(to_delete.size() != task_names.size())
    return;
  for(int task_id = 0; task_id < to_delete.size(); ++task_id) {
    if(to_delete[task_id].empty())
      continue;
    QList<int>& lst = to_delete[task_id];
    std::sort(lst.begin(), lst.end(), std::greater<int>());
    forall(int i, lst)
      removeRows(i, 1, index(task_id, 0));
  }
}

void TasksModel::deleteItems(const QModelIndexList& lst)
{
  QList<QList<int> > to_delete;
  for(int i = 0; i < task_names.size(); ++i)
    to_delete << QList<int>();

  forall(const QModelIndex& idx, lst) {
    if(!isTask(idx)) {
      int task_id = idx.parent().row();
      if(!to_delete[task_id].contains(idx.row()))
        to_delete[task_id] << idx.row();
    }
  }

  deleteItems(to_delete);
}

TaskEditDlg::TaskEditDlg(const tasks_t& ts, QMap<QString, mgx::ProcessDefinition>& _procs,
                                                          QWidget* parent, Qt::WindowFlags f)
  : QDialog(parent, f), procs(_procs)
{
  ui.setupUi(this);

  model = new TasksModel(ts, this);

  ui.tasks->setModel(model);

  for(int i = 0; i < ts.size(); ++i)
    ui.tasks->setExpanded(model->taskIndex(i), true);

  setProcesses(ui.Processes);
}

// Get the top level (task) index for the selected row.
int TaskEditDlg::getSelectedRow(int default_row)
{
  QItemSelectionModel* sel = ui.tasks->selectionModel();
  QModelIndexList rows = sel->selectedRows();
  int selection = default_row;
  if(!rows.empty()) {
    const QModelIndex& row = rows[0];
    if(model->isTask(row))
      selection = row.row();
    else
      selection = row.parent().row();
  }
  return selection;
}

// Get a task name from the user
QString TaskEditDlg::getSelectedTaskName(const QString& op, const QString& msg)
{
  if(model->tasks.empty())
    return QString();

  bool ok;
  QStringList ts = model->tasks.keys();
  ts.sort();
  QString name = QInputDialog::getItem(this, op, msg, ts, getSelectedRow(), false, &ok);
  if(!ok)
    return QString();
  return name;
}

QString TaskEditDlg::getCurrentTaskName()
{
  if(model->tasks.empty())
    return QString();
  int s = getSelectedRow(-1);
  if(s < 0)
    return QString();
  return model->task_names[s];
}

void TaskEditDlg::on_exportTask_clicked()
{
  if(model->tasks.empty())
    return;
  QItemSelectionModel* sel = ui.tasks->selectionModel();
  QModelIndexList rows = sel->selectedRows();
  if(rows.empty()) {
    QMessageBox::warning(this, "Task selection", "You must select some tasks to export");
    return;
  }

  QString filename = QFileDialog::getSaveFileName(
      this, "Export Tasks", mgx::currentPath(), "Task Files (*.task);;All files (*.*)");
  if(filename.isEmpty())
    return;

  if(!filename.endsWith(".task"))
    filename.append(".task");
  QFile file(filename);
  if(!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
    mgx::Information::out << "Unable to open tasks export file: " << filename << endl;
    return;
  }
  QTextStream pout(&file);
  pout << "[Tasks]" << endl;
  for(int i = 0; i < rows.size(); ++i)
    if(model->isTask(rows[i])) {
      QString name = model->task_names[rows[i].row()];
      const QList<mgx::ProcessDefinition>& defs = model->tasks[name];
      if(name.endsWith("*"))
        name.chop(1);
      writeTask(pout, name, defs);
    }
}

void TaskEditDlg::on_importTask_clicked()
{
  QString filename = QFileDialog::getOpenFileName(
      this, "Import Task", mgx::currentPath(), "Task Files (*.task);;All files (*.*)");
  if(!filename.isEmpty()) {
    tasks_t newTasks;
    readTasks(filename, procs, newTasks);

    int addedCount = 0;
    for(tasks_t::iterator task = newTasks.begin(); task != newTasks.end(); ++task)
      if(model->addTask(task.key())) {
        addedCount++;

        for(task_t::iterator def = task.value().begin(); def != task.value().end(); ++def)
          model->addProcess(task.key(), *def);
        ui.tasks->setExpanded(model->taskIndex(task.key()), true);
      }
    mgx::Information::out << QString("Added %1 of %2 tasks.").arg(addedCount).arg(newTasks.size()) << endl;
  }
}

// Add a task to the shared task list in QSettings
void TaskEditDlg::on_makeSharedTask_clicked()
{
  QString name = getCurrentTaskName();
  if(name.isEmpty())
    return;

  if(not name.endsWith("*")) {
    if(!model->renameTask(name, name + "*"))
      QMessageBox::critical(this, "Error renaming task",
                            QString("The task '%1' cannot be made shared.\n"
                                    "Check if a shared task with the same name already exists.").arg(name));
  }
}

// Delete a task from the shared task list in QSettings
void TaskEditDlg::on_copyTaskToLocal_clicked()
{
  QString name = getCurrentTaskName();
  if(name.isEmpty())
    return;

  if(name.endsWith("*")) {
    QString new_name = name;
    new_name.chop(1);
    if(!model->copyTask(name, new_name))
      QMessageBox::critical(this, "Error copying task",
                            QString("The task '%1' cannot be copied as local.\n"
                                    "Check if a local task with the same name already exists.").arg(name));
  }
}

void TaskEditDlg::on_copyTask_clicked()
{
  QString name = getCurrentTaskName();
  if(name.isEmpty())
    return;

  bool ok;
  QString newName = QInputDialog::getText(this, QString("Copy task"),
                                          QString("Enter name for task's copy '%1': ").arg(name), QLineEdit::Normal, name, &ok);
  if(ok) {
    if(!model->copyTask(name, newName))
      QMessageBox::critical(
        this, "Error copying task", QString("The task '%1' cannot be copied into '%2'.\n"
                                            "Check if a task with the same name already exists.")
        .arg(name)
        .arg(newName));
  }
}

void TaskEditDlg::on_renameTask_clicked()
{
  QString name = getCurrentTaskName();
  if(name.isEmpty())
    return;

  bool ok;
  QString newName = QInputDialog::getText(this, QString("Renaming task"),
                                          QString("Enter new name for task '%1': ").arg(name), QLineEdit::Normal, name, &ok);
  if(ok) {
    if(!model->renameTask(name, newName))
      QMessageBox::critical(
        this, "Error renaming task", QString("The task '%1' cannot be renamed into '%2'.\n"
                                             "Check if a task with the same name already exists.")
        .arg(name)
        .arg(newName));
  }
}

void TaskEditDlg::setProcesses(QTreeWidget* widget)
{
  QStringList procs = mgx::listProcesses();
  forall(const QString& name, procs) {
    mgx::ProcessDefinition* def = mgx::getProcessDefinition(name);
    QStringList desc;
    desc << def->name;
    QTreeWidgetItem* item = new QTreeWidgetItem(desc);
    item->setData(0, ParmsRole, def->parms);
    item->setData(0, ParmNamesRole, def->parmNames);
    item->setIcon(0, def->icon);
    widget->addTopLevelItem(item);
  }
  widget->sortItems(0, Qt::AscendingOrder);
  widget->resizeColumnToContents(0);
}

void TaskEditDlg::on_newTask_clicked()
{
  QString name = QInputDialog::getText(this, "Adding task", "New task name");
  if(!name.isEmpty())
    if(!model->addTask(name))
      QMessageBox::warning(this, "Error adding task", "The task couldn't be added. "
         "There is probably already a task with the same name.");
}

void TaskEditDlg::on_deleteTask_clicked()
{
  QString name = getCurrentTaskName();
  if(name.isEmpty())
    return;

  if(QMessageBox::question(this, "Deleting task",
      QString("You are about to delete the task '%1'. Are you sure?").arg(name),
      QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) {
    if(!model->deleteTask(name))
      QMessageBox::warning(this, "Error deleting task", "This task couldn't be deleted.\n"
                                           "This is probably a bug, please report it.");
  }
}

void TaskEditDlg::on_tasks_deleteItems(const QModelIndexList& lst)
{
  if(lst.empty())
    return;
  bool all_same_row = true;
  int row = lst[0].row();
  for(int i = 1; i < lst.size(); ++i)
    if(lst[i].row() != row) {
      all_same_row = false;
      break;
    }
  if(all_same_row)
    on_deleteTask_clicked();
  else
    model->deleteItems(lst);
}

void TaskEditDlg::on_tasks_activated(const QModelIndex& index)
{
  if(index.isValid() and !model->isTask(index)) {
    QMap<int, QVariant> map = model->itemData(index);
    mgx::Information::out << QString("Selected process index (%1,%2), parent of %3")
                  .arg(index.row()).arg(index.column()).arg(index.parent().row()) << endl;
    QList<int> keys = map.keys();
    forall(int i, keys) {
      switch(i) {
      case Qt::DisplayRole:
        mgx::Information::out << "DisplayRole: " << map[i].toString() << endl;
        break;
      case Qt::ToolTipRole:
        mgx::Information::out << "ToolTipRole: " << map[i].toString() << endl;
        break;
      case Qt::DecorationRole:
        mgx::Information::out << "DecorationRole: **icon**" << endl;
        break;
      case ParmsRole:
        mgx::Information::out << "Parms: " << map[i].toStringList().join(", ") << endl;
        break;
      case ParmNamesRole:
        mgx::Information::out << "ParmNames: " << map[i].toStringList().join(", ") << endl;
        break;
      default:
        mgx::Information::out << "Unknown" << endl;
        break;
      }
    }
  }
}

void TaskEditDlg::readTasks(const QString& filename, 
                      QMap<QString, mgx::ProcessDefinition>& procs, tasks_t& newTasks)
{
  mgx::Parms parms(filename);

  std::vector<QString> taskNames;
  std::vector<uint> numProcs;
  std::vector<QString> procNames;
  std::vector<QString> procTypes;
  std::vector<uint> numParms;
  std::vector<QString> parmNames;
  std::vector<QString> parmStrings;

  parms.all("Tasks", "Task", taskNames);
  parms.all("Tasks", "NumProcs", numProcs);
  parms.all("Tasks", "ProcessName", procNames);
  parms.all("Tasks", "ProcessType", procTypes);
  parms.all("Tasks", "NumParms", numParms);
  parms.all("Tasks", "ParmName", parmNames);
  parms.all("Tasks", "ParmString", parmStrings);

  if(taskNames.size() == 0)
    return;

  if(taskNames.size() != numProcs.size()) {
    mgx::Information::out << "Error reading tasks. Name count (" << taskNames.size() 
      << ") does not match task definition count (" << numProcs.size() << ")" << endl;
    return;
  }

  // If there were task types, it must be old mgx
  bool oldTasks = false;
  if(procTypes.size() > 0)
    oldTasks = true;

  // Get number of process entries and check
  size_t sum = 0;
  forall(uint i, numProcs)
    sum += i;
  if((oldTasks and sum != procTypes.size()) or sum != procNames.size()) {
    mgx::Information::out << "Error reading tasks. Process name/type count ("
      << procNames.size() << "/" << procTypes.size() 
      << ") does not match process definition count (" << sum << ")" << endl;
    return;
  }
  // Get number of parameter entries and check
  sum = 0;
  forall(uint i, numParms)
    sum += i;
  if(sum != parmStrings.size() or sum != parmStrings.size()) {
    mgx::Information::out << "Error reading tasks. Parameter name/string count (" << parmNames.size() 
     << "/" << parmStrings.size() << ") does not match parameter definition count (" << sum << ")" << endl;
    return;
  }

  // Now read in the tasks
  size_t procIdx = 0;
  size_t parmIdx = 0;
  for(size_t i = 0; i < taskNames.size(); i++) {
    // Check process definition
    if(i >= numProcs.size()) {
      mgx::Information::out << "Error reading Task definition for task " << taskNames[i] << endl;
      return;
    }
    QList<mgx::ProcessDefinition> defs;
    for(size_t j = 0; j < numProcs[i]; ++j) {
      if(procIdx >= procNames.size() or (oldTasks and procIdx >= procTypes.size())
                                                             or procIdx >= numParms.size()) {
        mgx::Information::out << "Error reading Parameter definitions for task " 
                                                                  << taskNames[i] << endl;
        return;
      }

      // Get parm names and their string values and put in a map
      std::map<QString, QString> parmMap;
      for(size_t k = 0; k < numParms[procIdx]; ++k) {
        if(parmIdx + k >= parmNames.size() or parmIdx + k >= parmStrings.size()) {
          mgx::Information::out << "Error reading Parameters for task " << procNames[i] << endl;
          return;
        }
        parmMap[parmNames[parmIdx + k]] = parmStrings[parmIdx + k];
      }

      QString name = procNames[procIdx];
      mgx::ProcessDefinition *real_def = 0;
      // For old tasks grab the full name
      if(oldTasks) {
        QMap<QString, mgx::ProcessDefinition>::const_iterator it = procs.constBegin();
        while (it != procs.constEnd()) {
          if(it.key().endsWith(name)) {
            name = it.key();
            break;
          }
          ++it;
        }
      }
      real_def = &procs[name];

      if(real_def) {
        mgx::ProcessDefinition def;
        def.name = name;

        def.parmNames = real_def->parmNames;
        def.parmDescs = real_def->parmDescs;
        def.parms = real_def->parms;
        for(int k = 0; k < def.parmNames.size(); ++k)
          if(parmMap.count(def.parmNames[k]) == 1)
            def.parms[k] = parmMap[def.parmNames[k]];
          else
            mgx::Information::out << "Parameter '" << def.parmNames[k] 
                            << "' not found for process '" << name << "'"
                            << "', task '" << taskNames[i] << "'" << endl;
        // Now add to definitions.
        defs << def;
      } else
        mgx::Information::out << "Process '" << name << "' not found for task '" 
                              << taskNames[i] << "'" << endl;
      parmIdx += numParms[procIdx];
      procIdx++;
    }
    newTasks[taskNames[i]] = defs;
  }
  mgx::Information::out << "Tasks read from file:" << filename << ": " << taskNames.size() << endl;
}

void TaskEditDlg::writeTask(QTextStream& pout, const QString& name, const task_t& task)
{
  pout << "Task: " << name << endl;
  pout << "NumProcs: " << task.size() << endl;
  for(int i = 0; i < task.size(); ++i) {
    const mgx::ProcessDefinition& def = task[i];
    MorphoGraphX::writeProcessParms(def, pout);
  }
}

void TaskEditDlg::writeTasks(QTextStream& pout, const tasks_t& tasks)
{
  pout << endl << "[Tasks]" << endl;
  for(TaskEditDlg::tasks_t::const_iterator it = tasks.begin(); it != tasks.end(); ++it) {
    if(it.key().endsWith("*"))
      continue;
    writeTask(pout, it.key(), it.value());
  }
}
