/***************************************************************************
 *   Copyright (C) 2007 by Pino Toscano <pino@kde.org>                     *
 *   Copyright (C) 2017    Klarälvdalens Datakonsult AB, a KDAB Group      *
 *                         company, info@kdab.com. Work sponsored by the   *
 *                         LiMux project of the city of Munich             *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#ifndef _OKULAR_FORMWIDGETS_H_
#define _OKULAR_FORMWIDGETS_H_

#include "core/area.h"
#include "core/form.h"

#include <qcheckbox.h>
#include <qcombobox.h>
#include <qlineedit.h>
#include <qlistwidget.h>
#include <qpushbutton.h>
#include <qradiobutton.h>
#include <ktextedit.h>
#include <kurlrequester.h>

class ComboEdit;
class QMenu;
class QButtonGroup;
class FormWidgetIface;
class PageViewItem;
class RadioButtonEdit;
class QEvent;

namespace Okular {
class Action;
class FormField;
class FormFieldButton;
class FormFieldChoice;
class FormFieldText;
class FormFieldSignature;
class Document;
}

struct RadioData
{
    RadioData() {}

    QList< int > ids;
    QButtonGroup *group;
};

class FormWidgetsController : public QObject
{
    Q_OBJECT

    public:
        explicit FormWidgetsController( Okular::Document *doc );
        ~FormWidgetsController() override;

        void signalAction( Okular::Action *action );

        void processScriptAction( Okular::Action *a, Okular::FormField * field, Okular::Annotation::AdditionalActionType type );

        void registerRadioButton( FormWidgetIface *fwButton, Okular::FormFieldButton *formButton );
        void dropRadioButtons();
        bool canUndo();
        bool canRedo();

        static bool shouldFormWidgetBeShown( Okular::FormField *form );

    Q_SIGNALS:
        void changed( int pageNumber );
        void requestUndo();
        void requestRedo();
        void canUndoChanged( bool undoAvailable );
        void canRedoChanged( bool redoAvailable);
        void formTextChangedByWidget( int pageNumber,
                                      Okular::FormFieldText *form,
                                      const QString & newContents,
                                      int newCursorPos,
                                      int prevCursorPos,
                                      int prevAnchorPos );

        void formTextChangedByUndoRedo( int pageNumber,
                                        Okular::FormFieldText *form,
                                        const QString & contents,
                                        int cursorPos,
                                        int anchorPos );

        void formListChangedByWidget( int pageNumber,
                                      Okular::FormFieldChoice *form,
                                      const QList< int > & newChoices );

        void formListChangedByUndoRedo( int pageNumber,
                                        Okular::FormFieldChoice *form,
                                        const QList< int > & choices );

        void formComboChangedByWidget( int pageNumber,
                                       Okular::FormFieldChoice *form,
                                       const QString & newText,
                                       int newCursorPos,
                                       int prevCursorPos,
                                       int prevAnchorPos
                                     );

        void formComboChangedByUndoRedo( int pageNumber,
                                         Okular::FormFieldChoice *form,
                                         const QString & text,
                                         int cursorPos,
                                         int anchorPos
                                       );

        void formButtonsChangedByWidget( int pageNumber,
                                         const QList< Okular::FormFieldButton* > & formButtons,
                                         const QList< bool > & newButtonStates );


        void action( Okular::Action *action );

        void focusAction( const Okular::Action *action, Okular::FormFieldText *ff );

        void formatAction( const Okular::Action *action, Okular::FormFieldText *ff );

        void keystrokeAction( const Okular::Action *action, Okular::FormFieldText *ff, bool &ok );

        void validateAction( const Okular::Action *action, Okular::FormFieldText *ff, bool &ok );

        void refreshFormWidget( Okular::FormField * form );

    private Q_SLOTS:
        void slotButtonClicked( QAbstractButton *button );
        void slotFormButtonsChangedByUndoRedo( int pageNumber,
                                               const QList< Okular::FormFieldButton* > & formButtons );

    private:
        friend class TextAreaEdit;
        friend class FormLineEdit;
        friend class FileEdit;
        friend class ListEdit;
        friend class ComboEdit;
        friend class SignatureEdit;

        QList< RadioData > m_radios;
        QHash< int, QAbstractButton* > m_buttons;
        Okular::Document* m_doc;
};


class FormWidgetFactory
{
    public:
        static FormWidgetIface * createWidget( Okular::FormField * ff, QWidget * parent = nullptr );
};


class FormWidgetIface
{
    public:
        FormWidgetIface( QWidget * w, Okular::FormField * ff );
        virtual ~FormWidgetIface();

        FormWidgetIface(const FormWidgetIface &) = delete;
        FormWidgetIface &operator=(const FormWidgetIface &) = delete;

        Okular::NormalizedRect rect() const;
        void setWidthHeight( int w, int h );
        void moveTo( int x, int y );
        bool setVisibility( bool visible );
        void setCanBeFilled( bool fill );

        void setPageItem( PageViewItem *pageItem );
        PageViewItem* pageItem() const;
        void setFormField( Okular::FormField *field );
        Okular::FormField* formField() const;

        virtual void setFormWidgetsController( FormWidgetsController *controller );

    protected:
        virtual void slotRefresh( Okular::FormField *form );

        FormWidgetsController * m_controller;
        Okular::FormField * m_ff;

    private:
        QWidget * m_widget;
        PageViewItem * m_pageItem;
};

#define DECLARE_ADDITIONAL_ACTIONS \
    protected: \
    virtual void mousePressEvent( QMouseEvent *event ) override; \
    virtual void mouseReleaseEvent( QMouseEvent *event ) override; \
    virtual void focusInEvent( QFocusEvent *event ) override; \
    virtual void focusOutEvent( QFocusEvent *event ) override; \
    virtual void leaveEvent( QEvent *event ) override; \
    virtual void enterEvent( QEvent *event ) override;

class PushButtonEdit : public QPushButton, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit PushButtonEdit( Okular::FormFieldButton * button, QWidget * parent = nullptr );

    DECLARE_ADDITIONAL_ACTIONS
};

class CheckBoxEdit : public QCheckBox, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit CheckBoxEdit( Okular::FormFieldButton * button, QWidget * parent = nullptr );

        // reimplemented from FormWidgetIface
        void setFormWidgetsController( FormWidgetsController *controller ) override;

        void doActivateAction();

    protected:
        void slotRefresh( Okular::FormField *form ) override;
    DECLARE_ADDITIONAL_ACTIONS
};

class RadioButtonEdit : public QRadioButton, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit RadioButtonEdit( Okular::FormFieldButton * button, QWidget * parent = nullptr );

        // reimplemented from FormWidgetIface
        void setFormWidgetsController( FormWidgetsController *controller ) override;
    DECLARE_ADDITIONAL_ACTIONS
};

class FormLineEdit : public QLineEdit, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit FormLineEdit( Okular::FormFieldText * text, QWidget * parent = nullptr );
        void setFormWidgetsController( FormWidgetsController *controller ) override;
        bool event ( QEvent * e ) override;
        void contextMenuEvent( QContextMenuEvent* event ) override;


    public Q_SLOTS:
        void slotHandleTextChangedByUndoRedo( int pageNumber,
                                              Okular::FormFieldText* textForm,
                                              const QString & contents,
                                              int cursorPos,
                                              int anchorPos );
    private Q_SLOTS:
        void slotChanged();

    protected:
        void slotRefresh( Okular::FormField* form ) override;

    private:
        int m_prevCursorPos;
        int m_prevAnchorPos;
        bool m_editing;
    DECLARE_ADDITIONAL_ACTIONS
};

class TextAreaEdit : public KTextEdit, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit TextAreaEdit( Okular::FormFieldText * text, QWidget * parent = nullptr );
        ~TextAreaEdit() override;
        void setFormWidgetsController( FormWidgetsController *controller ) override;
        bool event ( QEvent * e ) override;


    public Q_SLOTS:
        void slotHandleTextChangedByUndoRedo( int pageNumber,
                                              Okular::FormFieldText * textForm,
                                              const QString & contents,
                                              int cursorPos,
                                              int anchorPos );
        void slotUpdateUndoAndRedoInContextMenu( QMenu* menu );

    private Q_SLOTS:
        void slotChanged();

    protected:
        void slotRefresh( Okular::FormField* form ) override;

    private:
        int m_prevCursorPos;
        int m_prevAnchorPos;
        bool m_editing;
    DECLARE_ADDITIONAL_ACTIONS
};


class FileEdit : public KUrlRequester, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit FileEdit( Okular::FormFieldText * text, QWidget * parent = nullptr );
        void setFormWidgetsController( FormWidgetsController *controller ) override;

    protected:
        bool eventFilter( QObject *obj, QEvent *event ) override;


    private Q_SLOTS:
        void slotChanged();
        void slotHandleFileChangedByUndoRedo( int pageNumber,
                                              Okular::FormFieldText * form,
                                              const QString & contents,
                                              int cursorPos,
                                              int anchorPos );
    private:
        int m_prevCursorPos;
        int m_prevAnchorPos;
    DECLARE_ADDITIONAL_ACTIONS
};


class ListEdit : public QListWidget, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit ListEdit( Okular::FormFieldChoice * choice, QWidget * parent = nullptr );
        void setFormWidgetsController( FormWidgetsController *controller ) override;

    private Q_SLOTS:
        void slotSelectionChanged();
        void slotHandleFormListChangedByUndoRedo( int pageNumber,
                                                  Okular::FormFieldChoice * listForm,
                                                  const QList< int > & choices );
    DECLARE_ADDITIONAL_ACTIONS
};


class ComboEdit : public QComboBox, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit ComboEdit( Okular::FormFieldChoice * choice, QWidget * parent = nullptr );
        void setFormWidgetsController( FormWidgetsController *controller ) override;
        bool event ( QEvent * e ) override;
        void contextMenuEvent( QContextMenuEvent* event ) override;

    private Q_SLOTS:
        void slotValueChanged();
        void slotHandleFormComboChangedByUndoRedo( int pageNumber,
                                                   Okular::FormFieldChoice * comboForm,
                                                   const QString & text,
                                                   int cursorPos,
                                                   int anchorPos
                                                 );

    private:
        int m_prevCursorPos;
        int m_prevAnchorPos;
    DECLARE_ADDITIONAL_ACTIONS
};

class SignatureEdit : public QAbstractButton, public FormWidgetIface
{
    Q_OBJECT

    public:
        explicit SignatureEdit( Okular::FormFieldSignature * signature, QWidget * parent = nullptr );

        // This will be called when an item in signature panel is clicked. Calling it changes the
        // widget state. If this widget was visible prior to calling this then background
        // color will change and borders will remain otherwise visibility of this widget will change.
        // During the change all interactions will be disabled.
        void setDummyMode( bool set );

    protected:
        bool event( QEvent * e ) override;
        void contextMenuEvent( QContextMenuEvent * event ) override;
        void paintEvent( QPaintEvent * event ) override;

    private Q_SLOTS:
        void slotViewProperties();

    private:
        bool m_widgetPressed;
        bool m_dummyMode;
        bool m_wasVisible; // this will help in deciding whether or not to paint border for this widget

    DECLARE_ADDITIONAL_ACTIONS
};

#undef DECLARE_ADDITIONAL_ACTIONS

#endif
