/***************************************************************************
 *   Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#ifndef GUI_DOCUMENT_H
#define GUI_DOCUMENT_H

#include <list>
#include <map>
#include <string>
#include <boost/signals2.hpp>
#include <QString>

#include <Base/Persistence.h>
#include <Gui/TreeItemMode.h>

class SoNode;
class SoPath;

namespace Base
{
class Matrix4D;
}

namespace App
{
class Document;
class DocumentObject;
class DocumentObjectGroup;
class Property;
class Transaction;
}  // namespace App

namespace Gui
{

class BaseView;
class MDIView;
class View3DInventor;
class ViewProvider;
class ViewProviderDocumentObject;
class Application;
class DocumentPy;
class TransactionViewProvider;

enum class CreateViewMode
{
    Normal,
    Clone
};

/** The Gui Document
 *  This is the document on GUI level. Its main responsibility is keeping
 *  track off open windows for a document and warning on unsaved closes.
 *  All handled views on the document must inherit from MDIView
 *  @see App::Document
 *  @see MDIView
 *  @author Jürgen Riegel
 */
class GuiExport Document: public Base::Persistence
{
public:
    Document(App::Document* pcDocument, Application* app);
    ~Document() override;

protected:
    /** @name I/O of the document */
    //@{
    /// This slot is connected to the App::Document::signalNewObject(...)
    void slotNewObject(const App::DocumentObject&);
    void slotDeletedObject(const App::DocumentObject&);
    void slotChangedObject(const App::DocumentObject&, const App::Property&);
    void slotRelabelObject(const App::DocumentObject&);
    void slotTransactionAppend(const App::DocumentObject&, App::Transaction*);
    void slotTransactionRemove(const App::DocumentObject&, App::Transaction*);
    void slotActivatedObject(const App::DocumentObject&);
    void slotStartRestoreDocument(const App::Document&);
    void slotFinishRestoreDocument(const App::Document&);
    void slotUndoDocument(const App::Document&);
    void slotRedoDocument(const App::Document&);
    void slotShowHidden(const App::Document&);
    void slotFinishImportObjects(const std::vector<App::DocumentObject*>&);
    void slotFinishRestoreObject(const App::DocumentObject& obj);
    void slotRecomputed(const App::Document&);
    void slotSkipRecompute(const App::Document& doc, const std::vector<App::DocumentObject*>& objs);
    void slotTouchedObject(const App::DocumentObject&);
    void slotChangePropertyEditor(const App::Document&, const App::Property&);
    void callSignalBeforeRecompute();
    //@}

public:
    void addViewProvider(Gui::ViewProviderDocumentObject*);

    /** @name Signals of the document */
    //@{
    /// signal on new Object
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&)> signalNewObject;
    /// signal on deleted Object
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&)> signalDeletedObject;
    /** signal on changed Object, the 2nd argument is the changed property
        of the referenced document object, not of the view provider */
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&, const App::Property&)>
        signalChangedObject;
    /// signal on renamed Object
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&)> signalRelabelObject;
    /// signal on activated Object
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&)> signalActivatedObject;
    /// signal on activated Object in the tree (bold item)
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject*, const char*)>
        signalActivatedViewProvider;
    /// signal on entering in edit mode
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&)> signalInEdit;
    /// signal on leaving edit mode
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&)> signalResetEdit;
    /// signal on changed Object, the 2nd argument is the highlite mode to use
    mutable boost::signals2::signal<void(
        const Gui::ViewProviderDocumentObject&,
        const Gui::HighlightMode&,
        bool,
        App::DocumentObject* parent,
        const char* subname
    )>
        signalHighlightObject;
    /// signal on changed Object, the 2nd argument is the highlite mode to use
    mutable boost::signals2::signal<void(
        const Gui::ViewProviderDocumentObject&,
        const Gui::TreeItemMode&,
        App::DocumentObject* parent,
        const char* subname
    )>
        signalExpandObject;
    /// signal on changed ShowInTree property in view provider
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&)> signalShowItem;
    /// signal on scrolling to an object
    mutable boost::signals2::signal<void(const Gui::ViewProviderDocumentObject&)> signalScrollToObject;
    /// signal on undo Document
    mutable boost::signals2::signal<void(const Gui::Document& doc)> signalUndoDocument;
    /// signal on redo Document
    mutable boost::signals2::signal<void(const Gui::Document& doc)> signalRedoDocument;
    /// signal on deleting Document
    mutable boost::signals2::signal<void(const Gui::Document& doc)> signalDeleteDocument;
    //@}

    /** @name I/O of the document */
    //@{
    unsigned int getMemSize() const override;
    /// Save the document
    bool save();
    /// Save the document under a new file name
    bool saveAs();
    /// Save a copy of the document under a new file name
    bool saveCopy();
    /// Save all open document
    static void saveAll();
    /// This method is used to save properties or very small amounts of data to an XML document.
    void Save(Base::Writer& writer) const override;
    /// This method is used to restore properties from an XML document.
    void Restore(Base::XMLReader& reader) override;
    /// This method is used to save large amounts of data to a binary file.
    void SaveDocFile(Base::Writer& writer) const override;
    /// This method is used to restore large amounts of data from a binary file.
    void RestoreDocFile(Base::Reader& reader) override;
    void exportObjects(const std::vector<App::DocumentObject*>&, Base::Writer&);
    void importObjects(
        const std::vector<App::DocumentObject*>&,
        Base::Reader&,
        const std::map<std::string, std::string>& nameMapping
    );
    /// Add all root objects of the given array to a group
    void addRootObjectsToGroup(const std::vector<App::DocumentObject*>&, App::DocumentObjectGroup*);
    //@}

    /// Observer message from the App doc
    void setModified(bool);
    bool isModified() const;

    /// Returns true if the document is about to be closed, false otherwise
    bool isAboutToClose() const;

    /// Getter for the App Document
    App::Document* getDocument() const;

    /** @name methods for View handling */
    //@{
    /// Getter for the active view
    Gui::MDIView* getActiveView() const;
    void setActiveWindow(Gui::MDIView* view);
    Gui::MDIView* getEditingViewOfViewProvider(Gui::ViewProvider*) const;
    Gui::MDIView* getViewOfViewProvider(const Gui::ViewProvider*) const;
    Gui::MDIView* getViewOfNode(SoNode*) const;
    /// Create a new view
    MDIView* createView(const Base::Type& typeId, CreateViewMode mode = CreateViewMode::Normal);
    /** send messages to the active view
     * Send a specific massage to the active view and is able to receive a
     * return message
     */
    /// send Messages to all views
    bool sendMsgToViews(const char* pMsg);
    /** Sends the message \a pMsg to the views of type \a typeid and stops with
     * the first view that supports the message and returns \a ppReturn. The very
     * first checked view is the current active view.
     * If a view supports the message true is returned and false otherwise.
     */
    bool sendMsgToFirstView(const Base::Type& typeId, const char* pMsg, const char** ppReturn);
    /// Attach a view (get called by the MDIView constructor)
    void attachView(Gui::BaseView* pcView, bool bPassiv = false);
    /// Detach a view (get called by the MDIView destructor)
    void detachView(Gui::BaseView* pcView, bool bPassiv = false);
    /// helper for selection
    ViewProviderDocumentObject* getViewProviderByPathFromTail(SoPath* path) const;
    /// helper for selection
    ViewProviderDocumentObject* getViewProviderByPathFromHead(SoPath* path) const;
    /// Get all view providers along the path and the corresponding node index in the path
    std::vector<std::pair<ViewProviderDocumentObject*, int>> getViewProvidersByPath(SoPath* path) const;
    /// call update on all attached views
    void onUpdate();
    /// call relabel to all attached views
    void onRelabel();
    /// returns a list of all attached MDI views
    std::list<MDIView*> getMDIViews() const;
    /// returns a list of all MDI views of a certain type
    std::list<MDIView*> getMDIViewsOfType(const Base::Type& typeId) const;
    MDIView* setActiveView(
        const ViewProviderDocumentObject* vp = nullptr,
        Base::Type typeId = Base::Type()
    );
    View3DInventor* openEditingView3D(const ViewProviderDocumentObject* vp);
    View3DInventor* openEditingView3D(const App::DocumentObject* obj);
    //@}

    /** @name View provider handling  */
    //@{
    /// Get the view provider for that object
    ViewProvider* getViewProvider(const App::DocumentObject*) const;
    ViewProviderDocumentObject* getViewProvider(SoNode* node) const;
    /// set an annotation view provider
    void setAnnotationViewProvider(const char* name, ViewProvider* pcProvider);
    /// get an annotation view provider
    ViewProvider* getAnnotationViewProvider(const char* name) const;
    /// return true if the view provider is added as an annotation view provider
    bool isAnnotationViewProvider(const ViewProvider* vp) const;
    /// remove an annotation view provider from the document and return it
    ViewProvider* takeAnnotationViewProvider(const char* name);
    /// remove and delete an annotation view provider
    void removeAnnotationViewProvider(const char* name);
    /// test if the feature is in show
    bool isShow(const char* name);
    /// put the feature in show
    void setShow(const char* name);
    /// set the feature in Noshow
    void setHide(const char* name);
    /// set the feature transformation (only viewing)
    void setPos(const char* name, const Base::Matrix4D& rclMtrx);
    std::vector<ViewProvider*> getViewProvidersOfType(const Base::Type& typeId) const;
    ViewProvider* getViewProviderByName(const char* name) const;
    /// set the ViewProvider in special edit mode
    bool setEdit(Gui::ViewProvider* p, int ModNum = 0, const char* subname = nullptr);
    const Base::Matrix4D& getEditingTransform() const;
    void setEditingTransform(const Base::Matrix4D& mat);
    /// reset from edit mode, this cause all document to reset edit
    void resetEdit();
    /// reset edit of this document
    void _resetEdit();
    /// set if the edit asks for restore or not.
    void setEditRestore(bool val);
    /// get the in edit ViewProvider or NULL
    ViewProvider* getInEdit(
        ViewProviderDocumentObject** parentVp = nullptr,
        std::string* subname = nullptr,
        int* mode = nullptr,
        std::string* subElement = nullptr
    ) const;
    /// set the in edit ViewProvider subname reference
    void setInEdit(ViewProviderDocumentObject* parentVp, const char* subname);
    /** Add or remove view provider from scene graphs of all views
     *
     * It calls ViewProvider::canAddToSceneGraph() to decide whether to add the
     * view provider or remove it
     */
    void toggleInSceneGraph(ViewProvider* vp);
    //@}

    /** @name methods for the UNDO REDO handling */
    //@{
    /// Open a new Undo transaction on the document
    void openCommand(const char* sName = nullptr);
    /// Commit the Undo transaction on the document
    void commitCommand();
    /// Abort the Undo transaction on the document
    void abortCommand();
    /// Check if an Undo transaction is open
    bool hasPendingCommand() const;
    /// Get an Undo string vector with the Undo names
    std::vector<std::string> getUndoVector() const;
    /// Get an Redo string vector with the Redo names
    std::vector<std::string> getRedoVector() const;
    /// Will UNDO one or more steps
    void undo(int iSteps);
    /// Will REDO one or more steps
    void redo(int iSteps);
    /** Check if the document is performing undo/redo transaction
     *
     * Unlike App::Document::isPerformingTransaction(), Gui::Document will
     * report transacting when triggering grouped undo/redo in other documents
     */
    bool isPerformingTransaction() const;
    //@}

    /// handles the application close event
    bool canClose(bool checkModify = true, bool checkLink = false);
    bool isLastView();

    /// called by Application before being deleted
    void beforeDelete();

    PyObject* getPyObject() override;

    const char* getCameraSettings() const;
    bool saveCameraSettings(const char*) const;

    /// get all tree root objects (objects that are at the root of the object tree)
    std::vector<App::DocumentObject*> getTreeRootObjects() const;

protected:
    // pointer to the python class
    Gui::DocumentPy* _pcDocPy;

private:
    bool trySetEdit(Gui::ViewProvider* p, int ModNum, const char* subname);
    void resetIfEditing();
    // handles the scene graph nodes to correctly group child and parents
    void handleChildren3D(ViewProvider* viewProvider, bool deleting = false);

    /// Check other documents for the same transaction ID
    bool checkTransactionID(bool undo, int iSteps);
    /// Ask for user interaction if saving has failed
    bool askIfSavingFailed(const QString&);

    struct DocumentP* d;
    static int _iDocCount;

    mutable std::string cameraSettings;

    /** @name attributes for the UNDO REDO facility
     */
    //@{
    /// undo names list
    std::list<std::string> listUndoNames;
    /// redo names list
    std::list<std::string> listRedoNames;
    //@}

    friend class TransactionViewProvider;
};

}  // namespace Gui


#endif  // GUI_DOCUMENT_H
