/*
 *  eventlistmodel.cpp  -  model class for lists of alarms or templates
 *  Program:  kalarm
 *  Copyright © 2007-2012 by David Jarvie <djarvie@kde.org>
 *
 *  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 2 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.
 */

#include "kalarm.h"

#include "resources/alarmresource.h"
#include "resources/alarmresources.h"
#include "alarmcalendar.h"
#include "preferences.h"
#include "synchtimer.h"
#include "eventlistmodel.moc"

#include <kalarmcal/alarmtext.h>

#include <klocale.h>
#include <kiconloader.h>
#include <kdebug.h>

#include <QApplication>
#include <QPixmap>

/*=============================================================================
= Class: EventListModel
= Contains all active/archived alarms, or all alarm templates, unsorted.
=============================================================================*/

EventListModel* EventListModel::mAlarmInstance = 0;
EventListModel* EventListModel::mTemplateInstance = 0;
QPixmap* EventListModel::mTextIcon = 0;
QPixmap* EventListModel::mFileIcon = 0;
QPixmap* EventListModel::mCommandIcon = 0;
QPixmap* EventListModel::mEmailIcon = 0;
QPixmap* EventListModel::mAudioIcon = 0;
QSize    EventListModel::mIconSize;
int      EventListModel::mTimeHourPos = -2;


EventListModel* EventListModel::alarms()
{
    if (!mAlarmInstance)
    {
        mAlarmInstance = new EventListModel(CalEvent::ACTIVE | CalEvent::ARCHIVED);
        Preferences::connect(SIGNAL(archivedColourChanged(QColor)), mAlarmInstance, SLOT(slotUpdateArchivedColour(QColor)));
        Preferences::connect(SIGNAL(disabledColourChanged(QColor)), mAlarmInstance, SLOT(slotUpdateDisabledColour(QColor)));
        Preferences::connect(SIGNAL(holidaysChanged(KHolidays::HolidayRegion)), mAlarmInstance, SLOT(slotUpdateHolidays()));
        Preferences::connect(SIGNAL(workTimeChanged(QTime,QTime,QBitArray)), mAlarmInstance, SLOT(slotUpdateWorkingHours()));
    }
    return mAlarmInstance;
}

EventListModel* EventListModel::templates()
{
    if (!mTemplateInstance)
        mTemplateInstance = new EventListModel(CalEvent::TEMPLATE);
    return mTemplateInstance;
}

EventListModel::~EventListModel()
{
    if (this == mAlarmInstance)
        mAlarmInstance = 0;
    else if (this == mTemplateInstance)
        mTemplateInstance = 0;
}

EventListModel::EventListModel(CalEvent::Types status, QObject* parent)
    : QAbstractTableModel(parent),
      mStatus(status)
{
    // Load the current list of alarms.
    // The list will be updated whenever a signal is received notifying changes.
    // We need to store the list so that when deletions occur, the deleted alarm's
    // position in the list can be determined.
    mEvents = AlarmCalendar::resources()->events(mStatus);
    mHaveEvents = !mEvents.isEmpty();
//for(int x=0; x<mEvents.count(); ++x)kDebug(0)<<"Resource"<<(void*)mEvents[x]->resource()<<"Event"<<(void*)mEvents[x];
    if (!mTextIcon)
    {
        mTextIcon    = new QPixmap(SmallIcon("dialog-information"));
        mFileIcon    = new QPixmap(SmallIcon("document-open"));
        mCommandIcon = new QPixmap(SmallIcon("system-run"));
        mEmailIcon   = new QPixmap(SmallIcon("mail-message-unread"));
        mAudioIcon   = new QPixmap(SmallIcon("audio-x-generic"));
        mIconSize = mTextIcon->size().expandedTo(mFileIcon->size()).expandedTo(mCommandIcon->size()).expandedTo(mEmailIcon->size()).expandedTo(mAudioIcon->size());
    }
    MinuteTimer::connect(this, SLOT(slotUpdateTimeTo()));
    AlarmResources* resources = AlarmResources::instance();
    connect(resources, SIGNAL(resourceStatusChanged(AlarmResource*,AlarmResources::Change)),
                       SLOT(slotResourceStatusChanged(AlarmResource*,AlarmResources::Change)));
    connect(resources, SIGNAL(resourceLoaded(AlarmResource*,bool)),
                       SLOT(slotResourceLoaded(AlarmResource*,bool)));
}

int EventListModel::rowCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    return mEvents.count();
}

int EventListModel::columnCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    return ColumnCount;
}

QModelIndex EventListModel::index(int row, int column, const QModelIndex& parent) const
{
    if (parent.isValid()  ||  row >= mEvents.count())
        return QModelIndex();
    return createIndex(row, column, mEvents[row]);
}

QVariant EventListModel::data(const QModelIndex& index, int role) const
{
    int column = index.column();
    if (role == Qt::WhatsThisRole)
        return whatsThisText(column);
    KAEvent* event = static_cast<KAEvent*>(index.internalPointer());
    if (!event)
        return QVariant();
    bool resourceColour = false;
    switch (column)
    {
        case TimeColumn:
            switch (role)
            {
                case Qt::BackgroundRole:
                    resourceColour = true;
                    break;
                case Qt::DisplayRole:
                {
                    DateTime due = event->expired() ? event->startDateTime() : event->nextTrigger(KAEvent::DISPLAY_TRIGGER);
                    return alarmTimeText(due);
                }
                case SortRole:
                {
                    DateTime due = event->expired() ? event->startDateTime() : event->nextTrigger(KAEvent::DISPLAY_TRIGGER);
                    return due.isValid() ? due.effectiveKDateTime().toUtc().dateTime()
                                         : QDateTime(QDate(9999,12,31), QTime(0,0,0));
                }
                default:
                    break;
            }
            break;
        case TimeToColumn:
            switch (role)
            {
                case Qt::BackgroundRole:
                    resourceColour = true;
                    break;
                case Qt::DisplayRole:
                    if (event->expired())
                        return QString();
                    return timeToAlarmText(event->nextTrigger(KAEvent::DISPLAY_TRIGGER));
                case SortRole:
                {
                    if (event->expired())
                        return -1;
                    KDateTime now = KDateTime::currentUtcDateTime();
                    DateTime due = event->nextTrigger(KAEvent::DISPLAY_TRIGGER);
                    if (due.isDateOnly())
                        return now.date().daysTo(due.date()) * 1440;
                    return (now.secsTo(due.effectiveKDateTime()) + 59) / 60;
                }
            }
            break;
        case RepeatColumn:
            switch (role)
            {
                case Qt::BackgroundRole:
                    resourceColour = true;
                    break;
                case Qt::DisplayRole:
                    return repeatText(event);
                case Qt::TextAlignmentRole:
                    return Qt::AlignHCenter;
                case SortRole:
                    return repeatOrder(event);
            }
            break;
        case ColourColumn:
            switch (role)
            {
                case Qt::BackgroundRole:
                    switch (event->actionTypes())
                    {
                        case KAEvent::ACT_DISPLAY_COMMAND:
                        case KAEvent::ACT_DISPLAY:
                            return event->bgColour();
                        case KAEvent::ACT_COMMAND:
                            if (event->commandError() != KAEvent::CMD_NO_ERROR)
                                return Qt::red;
                            break;
                        default:
                            break;
                    }
                    break;
                case Qt::ForegroundRole:
                    if (event->commandError() != KAEvent::CMD_NO_ERROR)
                    {
                        if (event->actionTypes() == KAEvent::ACT_COMMAND)
                            return Qt::white;
                        QColor colour = Qt::red;
                        int r, g, b;
                        event->bgColour().getRgb(&r, &g, &b);
                        if (r > 128  &&  g <= 128  &&  b <= 128)
                            colour = Qt::white;
                        return colour;
                    }
                    break;
                case Qt::DisplayRole:
                    if (event->commandError() != KAEvent::CMD_NO_ERROR)
                        return QString::fromLatin1("!");
                    break;
                case SortRole:
                {
                    unsigned i = (event->actionTypes() == KAEvent::ACT_DISPLAY)
                               ? event->bgColour().rgb() : 0;
                    return QString("%1").arg(i, 6, 10, QLatin1Char('0'));
                }
                default:
                    break;
            }
            break;
        case TypeColumn:
            switch (role)
            {
                case Qt::DecorationRole:
                {
                    QVariant v;
                    v.setValue(*eventIcon(event));
                    return v;
                }
                case Qt::TextAlignmentRole:
                    return Qt::AlignHCenter;
                case Qt::SizeHintRole:
                    return mIconSize;
                case Qt::AccessibleTextRole:
#ifdef __GNUC__
#warning Implement this
#endif
                    return QString();
                case ValueRole:
                    return static_cast<int>(event->actionSubType());
                case SortRole:
                    return QString("%1").arg(event->actionSubType(), 2, 10, QLatin1Char('0'));
            }
            break;
        case TextColumn:
            switch (role)
            {
                case Qt::BackgroundRole:
                    resourceColour = true;
                    break;
                case Qt::DisplayRole:
                case SortRole:
                    return AlarmText::summary(*event, 1);
                case Qt::ToolTipRole:
                    return AlarmText::summary(*event, 10);
                default:
                    break;
            }
            break;
        case TemplateNameColumn:
            switch (role)
            {
                case Qt::BackgroundRole:
                    resourceColour = true;
                    break;
                case Qt::DisplayRole:
                    return event->templateName();
                case SortRole:
                    return event->templateName().toUpper();
            }
            break;
        default:
            break;
    }

    switch (role)
    {
        case Qt::ForegroundRole:
            if (!event->enabled())
                   return Preferences::disabledColour();
            if (event->expired())
                   return Preferences::archivedColour();
            break;   // use the default for normal active alarms
        case Qt::ToolTipRole:
            // Show the last command execution error message
            switch (event->commandError())
            {
                case KAEvent::CMD_ERROR:
                    return i18nc("@info:tooltip", "Command execution failed");
                case KAEvent::CMD_ERROR_PRE:
                    return i18nc("@info:tooltip", "Pre-alarm action execution failed");
                case KAEvent::CMD_ERROR_POST:
                    return i18nc("@info:tooltip", "Post-alarm action execution failed");
                case KAEvent::CMD_ERROR_PRE_POST:
                    return i18nc("@info:tooltip", "Pre- and post-alarm action execution failed");
                default:
                case KAEvent::CMD_NO_ERROR:
                    break;
            }
            break;
        case StatusRole:
            return event->category();
        case EnabledRole:
            return event->enabled();
        default:
            break;
    }

    if (resourceColour)
    {
        AlarmResource* resource = AlarmResources::instance()->resourceForIncidence(event->id());
        if (resource  &&  resource->colour().isValid())
            return resource->colour();
    }
    return QVariant();
}

bool EventListModel::setData(const QModelIndex& ix, const QVariant&, int role)
{
    if (ix.isValid()  &&  role == Qt::EditRole)
    {
        int row = ix.row();
        emit dataChanged(index(row, 0), index(row, ColumnCount - 1));
        return true;
    }
    return false;
}

QVariant EventListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal)
    {
        if (role == Qt::DisplayRole)
        {
            switch (section)
            {
                case TimeColumn:
                    return i18nc("@title:column", "Time");
                case TimeToColumn:
                    return i18nc("@title:column", "Time To");
                case RepeatColumn:
                    return i18nc("@title:column", "Repeat");
                case ColourColumn:
                    return QString();
                case TypeColumn:
                    return QString();
                case TextColumn:
                    return i18nc("@title:column", "Message, File or Command");
                case TemplateNameColumn:
                    return i18nc("@title:column Template name", "Name");
            }
        }
        else if (role == Qt::WhatsThisRole)
            return whatsThisText(section);
    }
    return QVariant();
}

Qt::ItemFlags EventListModel::flags(const QModelIndex& index) const
{
    if (!index.isValid())
        return Qt::ItemIsEnabled;
    return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
}

/******************************************************************************
* Signal every minute that the time-to-alarm values have changed.
*/
void EventListModel::slotUpdateTimeTo()
{
    int n = mEvents.count();
    if (n > 0)
        emit dataChanged(index(0, TimeToColumn), index(n - 1, TimeToColumn));
}

/******************************************************************************
* Called when the colour used to display archived alarms has changed.
*/
void EventListModel::slotUpdateArchivedColour(const QColor&)
{
    kDebug();
    int firstRow = -1;
    for (int row = 0, end = mEvents.count();  row < end;  ++row)
    {
        if (mEvents[row]->category() == CalEvent::ARCHIVED)
        {
            // For efficiency, emit a single signal for each group
            // of consecutive archived alarms, rather than a separate
            // signal for each alarm.
            if (firstRow < 0)
                firstRow = row;
        }
        else if (firstRow >= 0)
        {
            emit dataChanged(index(firstRow, 0), index(row - 1, ColumnCount - 1));
            firstRow = -1;
        }
    }
    if (firstRow >= 0)
        emit dataChanged(index(firstRow, 0), index(mEvents.count() - 1, ColumnCount - 1));
}

/******************************************************************************
* Called when the colour used to display disabled alarms has changed.
*/
void EventListModel::slotUpdateDisabledColour(const QColor&)
{
    kDebug();
    int firstRow = -1;
    for (int row = 0, end = mEvents.count();  row < end;  ++row)
    {
        if (!mEvents[row]->enabled())
        {
            // For efficiency, emit a single signal for each group
            // of consecutive disabled alarms, rather than a separate
            // signal for each alarm.
            if (firstRow < 0)
                firstRow = row;
        }
        else if (firstRow >= 0)
        {
            emit dataChanged(index(firstRow, 0), index(row - 1, ColumnCount - 1));
            firstRow = -1;
        }
    }
    if (firstRow >= 0)
        emit dataChanged(index(firstRow, 0), index(mEvents.count() - 1, ColumnCount - 1));
}

/******************************************************************************
* Called when the definition of holidays has changed.
* Update the next trigger time for all alarms which are set to recur only on
* non-holidays.
*/
void EventListModel::slotUpdateHolidays()
{
    kDebug();
    int firstRow = -1;
    for (int row = 0, end = mEvents.count();  row < end;  ++row)
    {
        if (mEvents[row]->holidaysExcluded())
        {
            // For efficiency, emit a single signal for each group
            // of consecutive alarms to update, rather than a separate
            // signal for each alarm.
            if (firstRow < 0)
                firstRow = row;
        }
        else if (firstRow >= 0)
        {
            emit dataChanged(index(firstRow, TimeColumn), index(row - 1, TimeColumn));
            emit dataChanged(index(firstRow, TimeToColumn), index(row - 1, TimeToColumn));
            firstRow = -1;
        }
    }
    if (firstRow >= 0)
    {
        emit dataChanged(index(firstRow, TimeColumn), index(mEvents.count() - 1, TimeColumn));
        emit dataChanged(index(firstRow, TimeToColumn), index(mEvents.count() - 1, TimeToColumn));
    }
}

/******************************************************************************
* Called when the definition of working hours has changed.
* Update the next trigger time for all alarms which are set to recur only
* during working hours.
*/
void EventListModel::slotUpdateWorkingHours()
{
    kDebug();
    int firstRow = -1;
    for (int row = 0, end = mEvents.count();  row < end;  ++row)
    {
        if (mEvents[row]->workTimeOnly())
        {
            // For efficiency, emit a single signal for each group
            // of consecutive alarms to update, rather than a separate
            // signal for each alarm.
            if (firstRow < 0)
                firstRow = row;
        }
        else if (firstRow >= 0)
        {
            emit dataChanged(index(firstRow, TimeColumn), index(row - 1, TimeColumn));
            emit dataChanged(index(firstRow, TimeToColumn), index(row - 1, TimeToColumn));
            firstRow = -1;
        }
    }
    if (firstRow >= 0)
    {
        emit dataChanged(index(firstRow, TimeColumn), index(mEvents.count() - 1, TimeColumn));
        emit dataChanged(index(firstRow, TimeToColumn), index(mEvents.count() - 1, TimeToColumn));
    }
}

/******************************************************************************
* Called when the command error status of an alarm has changed.
* Update the visual command error indication.
*/
void EventListModel::updateCommandError(const QString& eventId)
{
    int row = findEvent(eventId);
    if (row >= 0)
    {
        QModelIndex ix = index(row, ColourColumn);
        emit dataChanged(ix, ix);
    }
}

/******************************************************************************
* Called when loading of a resource is complete.
*/
void EventListModel::slotResourceLoaded(AlarmResource* resource, bool active)
{
    if (active)
        slotResourceStatusChanged(resource, AlarmResources::Added);
}

/******************************************************************************
* Static method called when a resource status has changed.
*/
void EventListModel::resourceStatusChanged(AlarmResource* resource, AlarmResources::Change change)
{
    if (mAlarmInstance)
        mAlarmInstance->slotResourceStatusChanged(resource, change);
    if (mTemplateInstance)
        mTemplateInstance->slotResourceStatusChanged(resource, change);
}

/******************************************************************************
* Called when a resource status has changed.
*/
void EventListModel::slotResourceStatusChanged(AlarmResource* resource, AlarmResources::Change change)
{
    bool added = false;
    switch (change)
    {
        case AlarmResources::Added:
            kDebug() << resource->resourceName() << "Added";
            added = true;
            break;
        case AlarmResources::Deleted:
            kDebug() << resource->resourceName() << "Deleted";
            removeResource(resource);
            return;
        case AlarmResources::Invalidated:
            kDebug() << resource->resourceName() << "Invalidated";
            removeResource(resource);
            return;
        case AlarmResources::Location:
            kDebug() << resource->resourceName() << "Location";
            removeResource(resource);
            added = true;
            break;
        case AlarmResources::Enabled:
            if (resource->isActive())
                added = true;
            else
                removeResource(resource);
            break;
        case AlarmResources::Colour:
        {
            kDebug() << "Colour";
            int firstRow = -1;
            for (int row = 0, end = mEvents.count();  row < end;  ++row)
            {
                if (mEvents[row]->resource() == resource)
                {
                    // For efficiency, emit a single signal for each group
                    // of consecutive alarms for the resource, rather than a separate
                    // signal for each alarm.
                    if (firstRow < 0)
                        firstRow = row;
                }
                else if (firstRow >= 0)
                {
                    emit dataChanged(index(firstRow, 0), index(row - 1, ColumnCount - 1));
                    firstRow = -1;
                }
            }
            if (firstRow >= 0)
                emit dataChanged(index(firstRow, 0), index(mEvents.count() - 1, ColumnCount - 1));
            return;
        }
        case AlarmResources::ReadOnly:
        case AlarmResources::WrongType:
            return;
    }

    if (added)
    {
        KAEvent::List list = AlarmCalendar::resources()->events(resource, mStatus);
        for (int i = list.count();  --i >= 0;  )
        {
            if (mEvents.indexOf(list[i]) >= 0)
                list.remove(i);    // avoid creating duplicate entries
        }
        if (!list.isEmpty())
        {
            int row = mEvents.count();
            beginInsertRows(QModelIndex(), row, row + list.count() - 1);
            mEvents += list;
            endInsertRows();
            if (!mHaveEvents)
                updateHaveEvents(true);
        }
    }
}

/******************************************************************************
* Remove a resource's events from the list.
* This has to be called before the resource is actually deleted or reloaded. If
* not, timer based updates can occur between the resource being deleted and
* slotResourceStatusChanged(Deleted) being triggered, leading to crashes when
* data from the resource's events is fetched.
*/
void EventListModel::removeResource(AlarmResource* resource)
{
    kDebug();
    int lastRow = -1;
    for (int row = mEvents.count();  --row >= 0; )
    {
        AlarmResource* r = mEvents[row]->resource();
        if (!r  ||  r == resource)
        {
            // For efficiency, delete each group of consecutive
            // alarms for the resource, rather than deleting each
            // alarm separately.
            if (lastRow < 0)
                lastRow = row;
        }
        else if (lastRow >= 0)
        {
            beginRemoveRows(QModelIndex(), row + 1, lastRow);
            while (lastRow > row)
                mEvents.remove(lastRow--);
            endRemoveRows();
            lastRow = -1;
        }
    }
    if (lastRow >= 0)
    {
        beginRemoveRows(QModelIndex(), 0, lastRow);
        while (lastRow >= 0)
            mEvents.remove(lastRow--);
        endRemoveRows();
    }
    if (mHaveEvents  &&  mEvents.isEmpty())
        updateHaveEvents(false);
}

/******************************************************************************
* Reload the event list.
*/
void EventListModel::reload()
{
    // This would be better done by a reset(), but the signals are private to QAbstractItemModel
    if (!mEvents.isEmpty())
    {
        beginRemoveRows(QModelIndex(), 0, mEvents.count() - 1);
        mEvents.clear();
        endRemoveRows();
    }
    KAEvent::List list = AlarmCalendar::resources()->events(mStatus);
    if (!list.isEmpty())
    {
        beginInsertRows(QModelIndex(), 0, list.count() - 1);
        mEvents = list;
        endInsertRows();
    }
    if (mHaveEvents == mEvents.isEmpty())
        updateHaveEvents(!mHaveEvents);
}

/******************************************************************************
* Return the index to a specified event.
*/
QModelIndex EventListModel::eventIndex(const QString& eventId) const
{
    for (int row = 0, end = mEvents.count();  row < end;  ++row)
    {
        if (mEvents[row]->id() == eventId)
            return createIndex(row, 0, mEvents[row]);
    }
    return QModelIndex();
}

/******************************************************************************
* Return the index to a specified event.
*/
QModelIndex EventListModel::eventIndex(const KAEvent* event) const
{
    int row = mEvents.indexOf(const_cast<KAEvent*>(event));
    if (row < 0)
        return QModelIndex();
    return createIndex(row, 0, mEvents[row]);
}

/******************************************************************************
* Add an event to the list.
*/
void EventListModel::addEvent(KAEvent* event)
{
    if (!(event->category() & mStatus)  ||  mEvents.contains(event))
        return;
    int row = mEvents.count();
    beginInsertRows(QModelIndex(), row, row);
    mEvents += event;
    endInsertRows();
    if (!mHaveEvents)
        updateHaveEvents(true);
}

/******************************************************************************
* Add an event to the list.
*/
void EventListModel::addEvents(const KAEvent::List& events)
{
    KAEvent::List evs;
    for (int i = 0, count = events.count();  i < count;  ++i)
        if (events[i]->category() & mStatus)
            evs += events[i];
    int row = mEvents.count();
    beginInsertRows(QModelIndex(), row, row + evs.count() - 1);
    mEvents += evs;
    endInsertRows();
    if (!mHaveEvents)
        updateHaveEvents(true);
}

/******************************************************************************
* Remove an event from the list.
*/
void EventListModel::removeEvent(int row)
{
    if (row < 0)
        return;
    beginRemoveRows(QModelIndex(), row, row);
    mEvents.remove(row);
    endRemoveRows();
    if (mHaveEvents  &&  mEvents.isEmpty())
        updateHaveEvents(false);
}

/******************************************************************************
* Notify that an event in the list has been updated.
*/
bool EventListModel::updateEvent(int row)
{
    if (row < 0)
        return false;
    emit dataChanged(index(row, 0), index(row, ColumnCount - 1));
    return true;
}

bool EventListModel::updateEvent(const QString& oldId, KAEvent* newEvent)
{
    int row = findEvent(oldId);
    if (row < 0)
        return false;
    mEvents[row] = newEvent;
    emit dataChanged(index(row, 0), index(row, ColumnCount - 1));
    return true;
}

/******************************************************************************
* Find the row of an event in the list, given its unique ID.
*/
int EventListModel::findEvent(const QString& eventId) const
{
    for (int row = 0, end = mEvents.count();  row < end;  ++row)
    {
        if (mEvents[row]->id() == eventId)
            return row;
    }
    return -1;
}

/******************************************************************************
* Return the event for a given row.
*/
KAEvent* EventListModel::event(int row) const
{
    if (row < 0  ||  row >= mEvents.count())
        return 0;
    return mEvents[row];
}

/******************************************************************************
* Return the event referred to by an index.
*/
KAEvent* EventListModel::event(const QModelIndex& index)
{
    if (!index.isValid())
        return 0;
    return static_cast<KAEvent*>(index.internalPointer());
}

/******************************************************************************
* Return the alarm time text in the form "date time".
*/
QString EventListModel::alarmTimeText(const DateTime& dateTime) const
{
    if (!dateTime.isValid())
        return i18nc("@info/plain Alarm never occurs", "Never");
    KLocale* locale = KGlobal::locale();
    KDateTime kdt = dateTime.effectiveKDateTime().toTimeSpec(Preferences::timeZone());
    QString dateTimeText = locale->formatDate(kdt.date(), KLocale::ShortDate);
    if (!dateTime.isDateOnly()
    ||  (!dateTime.isClockTime()  &&  kdt.utcOffset() != dateTime.utcOffset()))
    {
        // Display the time of day if it's a date/time value, or if it's
        // a date-only value but it's in a different time zone
        dateTimeText += QLatin1Char(' ');
        QString time = locale->formatTime(kdt.time());
        if (mTimeHourPos == -2)
        {
            // Initialise the position of the hour within the time string, if leading
            // zeroes are omitted, so that displayed times can be aligned with each other.
            mTimeHourPos = -1;     // default = alignment isn't possible/sensible
            if (QApplication::isLeftToRight())    // don't try to align right-to-left languages
            {
                QString fmt = locale->timeFormat();
                int i = fmt.indexOf(QRegExp("%[kl]"));   // check if leading zeroes are omitted
                if (i >= 0  &&  i == fmt.indexOf(QLatin1Char('%')))   // and whether the hour is first
                    mTimeHourPos = i;             // yes, so need to align
            }
        }
        if (mTimeHourPos >= 0  &&  (int)time.length() > mTimeHourPos + 1
        &&  time[mTimeHourPos].isDigit()  &&  !time[mTimeHourPos + 1].isDigit())
            dateTimeText += QLatin1Char('~');     // improve alignment of times with no leading zeroes
        dateTimeText += time;
    }
    return dateTimeText + QLatin1Char(' ');
}

/******************************************************************************
* Return the time-to-alarm text.
*/
QString EventListModel::timeToAlarmText(const DateTime& dateTime) const
{
    if (!dateTime.isValid())
        return i18nc("@info/plain Alarm never occurs", "Never");
    KDateTime now = KDateTime::currentUtcDateTime();
    if (dateTime.isDateOnly())
    {
        int days = now.date().daysTo(dateTime.date());
        // xgettext: no-c-format
        return i18nc("@info/plain n days", "%1d", days);
    }
    int mins = (now.secsTo(dateTime.effectiveKDateTime()) + 59) / 60;
    if (mins < 0)
        return QString();
    char minutes[3] = "00";
    minutes[0] = (mins%60) / 10 + '0';
    minutes[1] = (mins%60) % 10 + '0';
    if (mins < 24*60)
        return i18nc("@info/plain hours:minutes", "%1:%2", mins/60, minutes);
    int days = mins / (24*60);
    mins = mins % (24*60);
    return i18nc("@info/plain days hours:minutes", "%1d %2:%3", days, mins/60, minutes);
}

/******************************************************************************
* Return the repetition text.
*/
QString EventListModel::repeatText(const KAEvent* event) const
{
    QString repeatText = event->recurrenceText(true);
    if (repeatText.isEmpty())
        repeatText = event->repetitionText(true);
    return repeatText;
}

/******************************************************************************
* Return a string for sorting the repetition column.
*/
QString EventListModel::repeatOrder(const KAEvent* event) const
{
    int repeatOrder = 0;
    int repeatInterval = 0;
    if (event->repeatAtLogin())
        repeatOrder = 1;
    else
    {
        repeatInterval = event->recurInterval();
        switch (event->recurType())
        {
            case KARecurrence::MINUTELY:
                repeatOrder = 2;
                break;
            case KARecurrence::DAILY:
                repeatOrder = 3;
                break;
            case KARecurrence::WEEKLY:
                repeatOrder = 4;
                break;
            case KARecurrence::MONTHLY_DAY:
            case KARecurrence::MONTHLY_POS:
                repeatOrder = 5;
                break;
            case KARecurrence::ANNUAL_DATE:
            case KARecurrence::ANNUAL_POS:
                repeatOrder = 6;
                break;
            case KARecurrence::NO_RECUR:
            default:
                break;
        }
    }
    return QString("%1%2").arg(static_cast<char>('0' + repeatOrder)).arg(repeatInterval, 8, 10, QLatin1Char('0'));
}

/******************************************************************************
* Return the icon associated with the event's action.
*/
QPixmap* EventListModel::eventIcon(const KAEvent* event) const
{
    switch (event->actionTypes())
    {
        case KAEvent::ACT_EMAIL:
            return mEmailIcon;
        case KAEvent::ACT_AUDIO:
            return mAudioIcon;
        case KAEvent::ACT_COMMAND:
            return mCommandIcon;
        case KAEvent::ACT_DISPLAY:
            if (event->actionSubType() == KAEvent::FILE)
                return mFileIcon;
            // fall through to ACT_DISPLAY_COMMAND
        case KAEvent::ACT_DISPLAY_COMMAND:
        default:
            return mTextIcon;
    }
}

/******************************************************************************
* Returns the QWhatsThis text for a specified column.
*/
QString EventListModel::whatsThisText(int column) const
{
    switch (column)
    {
        case TimeColumn:
            return i18nc("@info:whatsthis", "Next scheduled date and time of the alarm");
        case TimeToColumn:
            return i18nc("@info:whatsthis", "How long until the next scheduled trigger of the alarm");
        case RepeatColumn:
            return i18nc("@info:whatsthis", "How often the alarm recurs");
        case ColourColumn:
            return i18nc("@info:whatsthis", "Background color of alarm message");
        case TypeColumn:
            return i18nc("@info:whatsthis", "Alarm type (message, file, command or email)");
        case TextColumn:
            return i18nc("@info:whatsthis", "Alarm message text, URL of text file to display, command to execute, or email subject line");
        case TemplateNameColumn:
            return i18nc("@info:whatsthis", "Name of the alarm template");
        default:
            return QString();
    }
}


/*=============================================================================
= Class: EventListFilterModel
= Base class for all filters on EventListModel.
=============================================================================*/

EventListFilterModel::EventListFilterModel(EventListModel* baseModel, QObject* parent)
    : QSortFilterProxyModel(parent)
{
    setSourceModel(baseModel);
    setSortRole(EventListModel::SortRole);
    setDynamicSortFilter(true);
}

/******************************************************************************
* Return the event referred to by an index.
*/
KAEvent* EventListFilterModel::event(const QModelIndex& index) const
{
    return static_cast<EventListModel*>(sourceModel())->event(mapToSource(index));
}

KAEvent* EventListFilterModel::event(int row) const
{
    return static_cast<EventListModel*>(sourceModel())->event(mapToSource(index(row, 0)));
}

/******************************************************************************
* Return the index to a specified event.
*/
QModelIndex EventListFilterModel::eventIndex(const QString& eventId) const
{
    return mapFromSource(static_cast<EventListModel*>(sourceModel())->eventIndex(eventId));
}

/******************************************************************************
* Return the index to a specified event.
*/
QModelIndex EventListFilterModel::eventIndex(const KAEvent* event) const
{
    return mapFromSource(static_cast<EventListModel*>(sourceModel())->eventIndex(event));
}

void EventListFilterModel::slotDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
    emit dataChanged(mapFromSource(topLeft), mapFromSource(bottomRight));
}

// vim: et sw=4:
