/******************************************************************************
 * Copyright (C) 2018 Kitsune Ral <kitsune-ral@users.sf.net>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 */

#pragma once

#include "events/stateevent.h"

#include <utility>

namespace Quotient {
class StateEventBase;

class EventStatus {
    Q_GADGET
public:
    /** Special marks an event can assume
     *
     * This is used to hint at a special status of some events in UI.
     * All values except Redacted and Hidden are mutually exclusive.
     */
    enum Code {
        Normal = 0x0, //< No special designation
        Submitted = 0x01, //< The event has just been submitted for sending
        FileUploaded = 0x02, //< The file attached to the event has been
                             // uploaded to the server
        Departed = 0x03, //< The event has left the client
        ReachedServer = 0x04, //< The server has received the event
        SendingFailed = 0x05, //< The server could not receive the event
        Redacted = 0x08, //< The event has been redacted
        Hidden = 0x10, //< The event should not be shown in the timeline
    };
    Q_DECLARE_FLAGS(Status, Code)
    Q_FLAG(Status)
};

class EventItemBase {
public:
    explicit EventItemBase(RoomEventPtr&& e) : evt(std::move(e))
    {
        Q_ASSERT(evt);
    }

    const RoomEvent* event() const { return rawPtr(evt); }
    const RoomEvent* get() const { return event(); }
    template <typename EventT>
    const EventT* viewAs() const
    {
        return eventCast<const EventT>(evt);
    }
    const RoomEventPtr& operator->() const { return evt; }
    const RoomEvent& operator*() const { return *evt; }

    // Used for event redaction
    RoomEventPtr replaceEvent(RoomEventPtr&& other)
    {
        return std::exchange(evt, move(other));
    }

protected:
    template <typename EventT>
    EventT* getAs()
    {
        return eventCast<EventT>(evt);
    }

private:
    RoomEventPtr evt;
};

class TimelineItem : public EventItemBase {
public:
    // For compatibility with Qt containers, even though we use
    // a std:: container now for the room timeline
    using index_t = int;

    TimelineItem(RoomEventPtr&& e, index_t number)
        : EventItemBase(std::move(e)), idx(number)
    {}

    index_t index() const { return idx; }

private:
    index_t idx;
};

template <>
inline const StateEventBase* EventItemBase::viewAs<StateEventBase>() const
{
    return evt->isStateEvent() ? weakPtrCast<const StateEventBase>(evt)
                               : nullptr;
}

template <>
inline const CallEventBase* EventItemBase::viewAs<CallEventBase>() const
{
    return evt->isCallEvent() ? weakPtrCast<const CallEventBase>(evt) : nullptr;
}

class PendingEventItem : public EventItemBase {
    Q_GADGET
public:
    using EventItemBase::EventItemBase;

    EventStatus::Code deliveryStatus() const { return _status; }
    QDateTime lastUpdated() const { return _lastUpdated; }
    QString annotation() const { return _annotation; }

    void setDeparted() { setStatus(EventStatus::Departed); }
    void setFileUploaded(const QUrl& remoteUrl);
    void setReachedServer(const QString& eventId)
    {
        setStatus(EventStatus::ReachedServer);
        (*this)->addId(eventId);
    }
    void setSendingFailed(QString errorText)
    {
        setStatus(EventStatus::SendingFailed);
        _annotation = std::move(errorText);
    }
    void resetStatus() { setStatus(EventStatus::Submitted); }

private:
    EventStatus::Code _status = EventStatus::Submitted;
    QDateTime _lastUpdated = QDateTime::currentDateTimeUtc();
    QString _annotation;

    void setStatus(EventStatus::Code status)
    {
        _status = status;
        _lastUpdated = QDateTime::currentDateTimeUtc();
        _annotation.clear();
    }
};

inline QDebug& operator<<(QDebug& d, const TimelineItem& ti)
{
    QDebugStateSaver dss(d);
    d.nospace() << "(" << ti.index() << "|" << ti->id() << ")";
    return d;
}
} // namespace Quotient
Q_DECLARE_METATYPE(Quotient::EventStatus)