
/*
   SPDX-FileCopyrightText: 2003-2007 Clarence Dang <dang@kde.org>

   SPDX-License-Identifier: BSD-2-Clause
*/

#ifndef KP_DOCUMENT_H
#define KP_DOCUMENT_H

#include <QObject>
#include <QString>
#include <QUrl>

#include "imagelib/kpImage.h"
#include "pixmapfx/kpPixmapFX.h"
#undef environ

class QImage;
class QIODevice;
class QPoint;
class QRect;
class QSize;

class kpColor;
class kpDocumentEnvironment;
class kpDocumentSaveOptions;
class kpDocumentMetaInfo;
class kpAbstractImageSelection;
class kpAbstractSelection;
class kpTextSelection;

// REFACTOR: rearrange method order to make sense and reflect kpDocument_*.cpp split.
class kpDocument : public QObject
{
    Q_OBJECT

public:
    // REFACTOR: Hide constructor and have 2 factory methods:
    //
    //           Method 1. Creates a blank document with dimensions <w>x<h>.
    //
    //           Method 2. Calls open().  <w> and <h> (aka constructorWidth()
    //              and constructorHeight()) need not be specified.
    //
    //           ?
    kpDocument(int w, int h, kpDocumentEnvironment *environ);
    ~kpDocument() override;

    kpDocumentEnvironment *environ() const;
    void setEnviron(kpDocumentEnvironment *environ);

    //
    // File I/O - Open
    //

    static QImage getPixmapFromFile(const QUrl &url,
                                    bool suppressDoesntExistDialog,
                                    QWidget *parent,
                                    kpDocumentSaveOptions *saveOptions = nullptr,
                                    kpDocumentMetaInfo *metaInfo = nullptr);
    // REFACTOR: fix: open*() should only be called once.
    //                Create a new kpDocument() if you want to open again.
    void openNew(const QUrl &url);
    bool open(const QUrl &url, bool newDocSameNameIfNotExist = false);

    static void getDataFromImage(const QImage &image, kpDocumentSaveOptions &saveOptions, kpDocumentMetaInfo &metaInfo);

    //
    // File I/O - Save
    //

    static bool lossyPromptContinue(const QImage &pixmap, const kpDocumentSaveOptions &saveOptions, QWidget *parent);
    static bool savePixmapToDevice(const QImage &pixmap,
                                   QIODevice *device,
                                   const kpDocumentSaveOptions &saveOptions,
                                   const kpDocumentMetaInfo &metaInfo,
                                   bool lossyPrompt,
                                   QWidget *parent,
                                   bool *userCancelled = nullptr);
    static bool savePixmapToFile(const QImage &pixmap,
                                 const QUrl &url,
                                 const kpDocumentSaveOptions &saveOptions,
                                 const kpDocumentMetaInfo &metaInfo,
                                 bool lossyPrompt,
                                 QWidget *parent);
    bool save(bool lossyPrompt = false);
    bool saveAs(const QUrl &url, const kpDocumentSaveOptions &saveOptions, bool lossyPrompt = true);

    // Returns whether save() or saveAs() have ever been called and returned true
    bool savedAtLeastOnceBefore() const;

    QUrl url() const;
    void setURL(const QUrl &url, bool isFromExistingURL);

    // Returns whether the document's image was successfully opened from
    // or saved to the URL returned by url().  This is not true for a
    // new kpDocument and in the case of open() being passed
    // "newDocSameNameIfNotExist = true" when the URL doesn't exist.
    //
    // If this returns true and the kpDocument hasn't been modified,
    // this gives a pretty good indication that the image stored at url()
    // is equal to image() (unless the something has happened to that url
    // outside of KolourPaint).
    //
    // e.g. If the user types "kolourpaint doesnotexist.png" to start
    //      KolourPaint, this method will return false.
    bool isFromExistingURL() const;

    // Checks whether @p url still exists
    bool urlExists(const QUrl &url) const;

    // (will convert: empty Url --> "Untitled")
    QString prettyUrl() const;

    // (will convert: empty Url --> "Untitled")
    QString prettyFilename() const;

    // (guaranteed to return valid pointer)

    const kpDocumentSaveOptions *saveOptions() const;
    void setSaveOptions(const kpDocumentSaveOptions &saveOptions);

    const kpDocumentMetaInfo *metaInfo() const;
    void setMetaInfo(const kpDocumentMetaInfo &metaInfo);

    /*
     * Properties (modified, width, height, color depth...)
     */

    void setModified(bool yes = true);
    bool isModified() const;
    bool isEmpty() const;

    // REFACTOR: Rename to originalWidth()?
    int constructorWidth() const; // as passed to the constructor
    int width(bool ofSelection = false) const;
    int oldWidth() const; // only valid in a slot connected to sizeChanged()
    void setWidth(int w, const kpColor &backgroundColor);

    // REFACTOR: Rename to originalHeight()?
    int constructorHeight() const; // as passed to the constructor
    int height(bool ofSelection = false) const;
    int oldHeight() const; // only valid in a slot connected to sizeChanged()
    void setHeight(int h, const kpColor &backgroundColor);

    QRect rect(bool ofSelection = false) const;

    //
    // Image access
    //

    // Returns a copy of part of the document's image (not including the
    // selection).
    kpImage getImageAt(const QRect &rect) const;

    void setImageAt(const kpImage &image, const QPoint &at);

    // "image(false)" returns a copy of the document's image, ignoring any
    // floating selection.
    //
    // "image(true)" returns a copy of a floating image selection's base
    // image (i.e. before selection transparency is applied), which may be
    // null if the image selection is a just a border.
    //
    // ASSUMPTION: For <ofSelection> == true only, an image selection exists.
    kpImage image(bool ofSelection = false) const;
    kpImage *imagePointer() const;

    void setImage(const kpImage &image);
    // ASSUMPTION: If setting the selection's image, the selection must be
    //             an image selection.
    void setImage(bool ofSelection, const kpImage &image);

    //
    // Selections
    //

public:
    kpAbstractSelection *selection() const;
    kpAbstractImageSelection *imageSelection() const;
    kpTextSelection *textSelection() const;

    // Sets the document's selection to the given one and changes to the
    // matching selection tool.  Tool changes occur in the following situations:
    //
    // 1. Setting a <selection> when a selection tool is not active.
    //
    // 2. Setting an image <selection> when the text tool is active.
    //    ASSUMPTION: There is no text selection active when calling this
    //                method (push it onto the document before calling this,
    //                to avoid this problem).
    //
    // 3. Setting a text <selection> when an image selection tool is active.
    //    ASSUMPTION: There is no image selection active when calling this
    //                method (push it onto the document before calling this,
    //                to avoid this problem).
    //
    // The justification for the above assumptions are to reduce the complexity
    // of this method's implementation -- changing from an image selection tool
    // to a text selection tool, or vice-versa, calls the end() method of the
    // current tool, which pushes any active selection onto the document.  Since
    // this method sets the selection, losing the old selection in the middle of
    // the method would be tricky to work around.
    //
    // WARNING: Before calling this, you must ensure that the UI (kpMainWindow)
    //          has the <selection>'s selection transparency or
    //          for a text selection, its text style, selected.
    // TODO: Why can't we change it for them, if we change tool automatically for them already?
    void setSelection(const kpAbstractSelection &selection);

    // Returns the base image of the current image selection.  If this is
    // null (because the selection is still a border), it extracts the
    // pixels of the document marked out by the border of the selection.
    //
    // ASSUMPTION: There is an imageSelection().
    //
    // TODO: this always returns base image - need ver that applies selection
    //       transparency.
    kpImage getSelectedBaseImage() const;

    // Sets the base image of the current image selection to the pixels
    // of the document marked out by the border of the selection.
    //
    // ASSUMPTION: There is an imageSelection() that is just a border
    //             (no base image).
    void imageSelectionPullFromDocument(const kpColor &backgroundColor);

    // Deletes the current selection, if there is a selection(), else NOP
    void selectionDelete();

    // Stamps a copy of the selection onto the document.
    //
    // For image selections, <applySelTransparency> set to true, means that
    // the transparent image of the selection is used.  If set to false,
    // the base image of the selection is used.  This argument is ignored
    // for non-image selections.
    //
    // ASSUMPTION: There is a selection() with content, else NOP
    void selectionCopyOntoDocument(bool applySelTransparency = true);

    // Same as selectionCopyOntoDocument() but deletes the selection
    // afterwards.
    void selectionPushOntoDocument(bool applySelTransparency = true);

    //
    // Same as image() but returns a _copy_ of the document image
    // + any (even non-image) selection pasted on top.
    //
    // Even if the selection has no content, it is still pasted:
    //
    // 1. For an image selection, this makes no difference.
    //
    // 2. For a text selection:
    //
    //    a) with an opaque background: the background rectangle is
    //      included -- this is necessary since the rectangle is visually
    //      there after all, and the intention of this method is to report
    //      everything.
    //
    //    b) with a transparent background: this makes no difference.
    //
    kpImage imageWithSelection() const;

    /*
     * Transformations
     * (convenience only - you could achieve the same effect (and more) with
     *  kpPixmapFX: these functions do not affect the selection)
     */

    void fill(const kpColor &color);
    void resize(int w, int h, const kpColor &backgroundColor);

public Q_SLOTS:
    // these will emit signals!
    void slotContentsChanged(const QRect &rect);
    void slotSizeChanged(const QSize &newSize);

Q_SIGNALS:
    void documentOpened();
    void documentSaved();

    // Emitted whenever the isModified() flag changes from false to true.
    // This is the _only_ signal that may be emitted in addition to the others.
    void documentModified();

    void contentsChanged(const QRect &rect);
    void sizeChanged(int newWidth, int newHeight); // see oldWidth(), oldHeight()
    void sizeChanged(const QSize &newSize);

    void selectionEnabled(bool on);

    // Emitted when setSelection() is given a selection such that we change
    // from a non-text-selection tool to the text selection tool or vice-versa.
    // <isText> reports whether the new selection is text (and therefore,
    // whether we've switched to the text tool).
    void selectionIsTextChanged(bool isText);

private:
    int m_constructorWidth, m_constructorHeight;
    kpImage *m_image;

    QUrl m_url;
    bool m_isFromExistingURL;
    bool m_savedAtLeastOnceBefore;

    kpDocumentSaveOptions *m_saveOptions;
    kpDocumentMetaInfo *m_metaInfo;

    bool m_modified;

    kpAbstractSelection *m_selection;

    int m_oldWidth, m_oldHeight;

    // There is no need to maintain binary compatibility at this stage.
    // The d-pointer is just so that you can experiment without recompiling
    // the kitchen sink.
    struct kpDocumentPrivate *d;
};

#endif // KP_DOCUMENT_H
