//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Job/JobparQModel.cpp
//! @brief     Implements class JobparQModel.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/Job/JobparQModel.h"
#include "GUI/Model/Job/BatchInfo.h"
#include "GUI/Model/Job/JobItem.h"
#include "GUI/Model/Job/JobStatus.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/Model/Sim/InstrumentItems.h"

namespace {

namespace Column {
enum Columns { Name, Value };
} // namespace Column
const QString ColumnNames[] = {"Name", "Value"};
const int NumColumns = (int)std::size(ColumnNames);

namespace Row {
enum Rows { Name, Sample, Instrument, Status, Begin, End, Duration };
} // namespace Row
const QString RowNames[] = {"Name", "Sample", "Instrument", "Status", "Begin", "End", "Duration"};
const int NumRows = (int)std::size(RowNames);

const QString ModelDateShortFormat = "yyyy.MM.dd hh:mm:ss";

} // namespace

JobparQModel::JobparQModel(QObject* parent)
    : QAbstractTableModel(parent)
    , m_job_item(nullptr)
{
}

JobparQModel::~JobparQModel()
{
    if (m_job_item)
        disconnect(m_job_item, nullptr, this, nullptr);
}

int JobparQModel::rowCount(const QModelIndex& parent) const
{
    if (!parent.isValid() && m_job_item)
        return NumRows;
    return 0;
}

int JobparQModel::columnCount(const QModelIndex& parent) const
{
    if (!parent.isValid() && m_job_item)
        return NumColumns;
    return 0;
}

QVariant JobparQModel::data(const QModelIndex& index, int role) const
{
    if ((role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::ToolTipRole)
        || index.column() < 0 || index.column() >= NumColumns || index.row() < 0
        || index.row() >= NumRows || !m_job_item)
        return {};

    switch (index.column()) {
    case Column::Name:
        return RowNames[index.row()];
    case Column::Value: {
        switch (index.row()) {
        case Row::Name:
            return m_job_item->batchInfo()->jobName();
        case Row::Sample:
            return m_job_item->sampleItem()->name();
        case Row::Instrument:
            return m_job_item->instrumentItem()->name();
        case Row::Status:
            return jobStatusToString(m_job_item->batchInfo()->status());
        case Row::Begin:
            if (role == Qt::ToolTipRole)
                return QLocale().toString(m_job_item->batchInfo()->beginTime(),
                                          QLocale::LongFormat);
            return m_job_item->batchInfo()->beginTime().toString(ModelDateShortFormat);
        case Row::End:
            if (role == Qt::ToolTipRole)
                return QLocale().toString(m_job_item->batchInfo()->endTime(), QLocale::LongFormat);
            return m_job_item->batchInfo()->endTime().toString(ModelDateShortFormat);
        case Row::Duration: {
            std::optional<size_t> duration = m_job_item->batchInfo()->duration();
            if (duration)
                return QString("%1 s").arg(duration.value() / 1000., 0, 'f', 3);
            return {};
        }
        default:
            return {};
        }
    }
    default:
        return {};
    }
}

QVariant JobparQModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section >= 0
        && section < NumColumns)
        return ColumnNames[section];
    return {};
}

Qt::ItemFlags JobparQModel::flags(const QModelIndex& index) const
{
    Qt::ItemFlags f = QAbstractTableModel::flags(index);
    if (index.column() == Column::Value && index.row() == Row::Name && m_job_item)
        f.setFlag(Qt::ItemIsEditable);
    return f;
}

bool JobparQModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (role != Qt::EditRole || index.column() != Column::Value || index.row() != Row::Name
        || !m_job_item)
        return false;
    m_job_item->batchInfo()->setJobName(value.toString());
    return true;
}

void JobparQModel::setJobItem(JobItem* jobItem)
{
    beginResetModel();
    if (m_job_item)
        disconnect(m_job_item, nullptr, this, nullptr);
    m_job_item = jobItem;
    if (m_job_item)
        notifyJobPropertyChange();
    endResetModel();
}

void JobparQModel::notifyJobPropertyChange()
{
    // name
    connect(m_job_item->batchInfo(), &BatchInfo::jobNameChanged, [this](const QString&) {
        emit dataChanged(index(Row::Name, Column::Value), index(Row::Name, Column::Value),
                         {Qt::DisplayRole, Qt::EditRole});
    });

    // status
    connect(m_job_item->batchInfo(), &BatchInfo::jobStatusChanged, [this](const JobStatus) {
        emit dataChanged(index(Row::Status, Column::Value), index(Row::Status, Column::Value),
                         {Qt::DisplayRole, Qt::EditRole});
    });

    // begin time
    connect(m_job_item->batchInfo(), &BatchInfo::jobBeginTimeChanged, [this](const QDateTime&) {
        emit dataChanged(index(Row::Begin, Column::Value), index(Row::Begin, Column::Value),
                         {Qt::DisplayRole, Qt::EditRole});
    });

    // end time and duration
    connect(m_job_item->batchInfo(), &BatchInfo::jobEndTimeChanged, [this](const QDateTime&) {
        emit dataChanged(index(Row::End, Column::Value), index(Row::Duration, Column::Value),
                         {Qt::DisplayRole, Qt::EditRole});
    });
}
