/***************************************************************************
    qgseditorwidgetwrapper.h
     --------------------------------------
    Date                 : 20.4.2013
    Copyright            : (C) 2013 Matthias Kuhn
    Email                : matthias at opengis dot ch
 ***************************************************************************
 *                                                                         *
 *   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 QGSEDITORWIDGETWRAPPER_H
#define QGSEDITORWIDGETWRAPPER_H

#include <QObject>
#include "qgis.h"
#include <QMap>
#include <QVariant>

class QgsVectorLayer;
class QgsField;

#include "qgswidgetwrapper.h"
#include "qgis_gui.h"

/**
 * \ingroup gui
 * Manages an editor widget
 * Widget and wrapper share the same parent
 *
 * A wrapper controls one attribute editor widget and is able to create a default
 * widget or use a pre-existent widget. It is able to set the widget to the value implied
 * by a field of a vector layer, or return the value it currently holds. Every time it is changed
 * it has to emit a valueChanged signal. If it fails to do so, there is no guarantee that the
 * changed status of the widget will be saved.
 *
 */
class GUI_EXPORT QgsEditorWidgetWrapper : public QgsWidgetWrapper
{
    Q_OBJECT

    Q_PROPERTY( bool constraintResultVisible READ constraintResultVisible WRITE setConstraintResultVisible NOTIFY constraintResultVisibleChanged )
    Q_PROPERTY( ConstraintResult constraintResult READ constraintResult NOTIFY constraintStatusChanged )

  public:

    /**
     * Result of constraint checks.
     * \since QGIS 3.0
     */
    enum ConstraintResult
    {
      ConstraintResultPass = 0, //!< Widget passed constraints successfully
      ConstraintResultFailHard, //!< Widget failed at least one hard (enforced) constraint
      ConstraintResultFailSoft, //!< Widget failed at least one soft (non-enforced) constraint
    };

    /**
     * Create a new widget wrapper
     *
     * \param vl        The layer on which the field is
     * \param fieldIdx  The field which will be controlled
     * \param editor    An editor widget. Can be NULL if one should be autogenerated.
     * \param parent    A parent widget for this widget wrapper and the created widget.
     */
    explicit QgsEditorWidgetWrapper( QgsVectorLayer *vl, int fieldIdx, QWidget *editor = nullptr, QWidget *parent SIP_TRANSFERTHIS = nullptr );

    /**
     * Will be used to access the widget's value. Read the value from the widget and
     * return it properly formatted to be saved in the attribute.
     *
     * If an invalid variant is returned this will be interpreted as no change.
     * Be sure to return a NULL QVariant if it should be set to NULL.
     *
     * \returns The current value the widget represents
     */
    virtual QVariant value() const = 0;

    /**
     * Access the field index.
     *
     * \returns The index of the field you are working on
     *
     * \see layer()
     */
    int fieldIdx() const;

    /**
     * Access the field.
     *
     * \returns The field you are working on
     *
     * \see layer()
     */
    QgsField field() const;

    /**
     * Access the default value of the field.
     *
     * \returns the default value of the field
     *
     * \see layer()
     */
    QVariant defaultValue() const;

    /**
     * Will return a wrapper for a given widget
     * \param widget The widget which was created by a wrapper
     * \returns The wrapper for the widget or NULL
     */
    static QgsEditorWidgetWrapper *fromWidget( QWidget *widget );

    /**
     * Check if the given widget or one of its parent is a QTableView.
     * \param parent the widget to check
     * \returns true if yes
     */
    static bool isInTable( const QWidget *parent );

    /**
     * Is used to enable or disable the edit functionality of the managed widget.
     * By default this will enable or disable the whole widget
     *
     * \param enabled  Enable or Disable?
     */
    void setEnabled( bool enabled ) override;

    /**
     * Sets the widget to display in an indeterminate "mixed value" state.
     * \since QGIS 2.16
     */
    virtual void showIndeterminateState() {}

    /**
     * Update constraint.
     * \param featureContext the feature to use to evaluate the constraint
     * \param constraintOrigin optional origin for constraints to check. This can be used to limit the constraints tested
     * to only provider or layer based constraints.
     * \since QGIS 2.16
     */
    void updateConstraint( const QgsFeature &featureContext, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet );

    /**
     * Update constraint on a feature coming from a specific layer.
     * \param layer The vector layer where the feature is defined
     * \param index The index of the field to check
     * \param feature The feature to use to evaluate the constraint
     * \param constraintOrigin Optional origin for constraints to check. This
     * can be used to limit the constraints tested to only provider or layer
     * based constraints.
     * \since QGIS 3.0
     */
    void updateConstraint( const QgsVectorLayer *layer, int index, const QgsFeature &feature, QgsFieldConstraints::ConstraintOrigin constraintOrigin = QgsFieldConstraints::ConstraintOriginNotSet );

    /**
     * Gets the current constraint status.
     * \returns true if the constraint is valid or if there's no constraint,
     * false otherwise
     * \see constraintFailureReason()
     * \see isBlockingCommit()
     * \since QGIS 2.16
     */
    bool isValidConstraint() const;

    /**
     * Returns true if the widget is preventing the feature from being committed. This may be true as a result
     * of attribute values failing enforced field constraints.
     * \see isValidConstraint()
     * \since QGIS 3.0
     */
    bool isBlockingCommit() const;

    /**
     * Returns the reason why a constraint check has failed (or an empty string
     * if constraint check was successful).
     * \see isValidConstraint()
     * \since QGIS 3.0
     */
    QString constraintFailureReason() const;

    /**
     * Add a hint text on the widget
     * \param hintText The hint text to display
     * \since QGIS 3.0
     */
    virtual void setHint( const QString &hintText );

    /**
     * Returns the constraint result, which is the current result of the constraint
     * on the widget influencing its visualization.
     *
     * \since QGIS 3.0
     */
    ConstraintResult constraintResult() const;

    /**
     * Returns whether the constraint result is visible.
     *
     * Returns true if the constraint result will be visualized on the widget (with color).
     * This will be disabled when the form is not editable.
     *
     * \since QGIS 3.0
     */
    bool constraintResultVisible() const;

    /**
     * Sets whether the constraint result is visible.
     *
     * Controls if the constraint result should be visualized on the widget (with color).
     * This will be disabled when the form is not editable.
     *
     * \param constraintResultVisible if constraintResult should be displayed (mostly editable status)
     * \since QGIS 3.0
     */
    void setConstraintResultVisible( bool constraintResultVisible );

  signals:

    /**
     * Emit this signal, whenever the value changed.
     *
     * \param value The new value
     */
    void valueChanged( const QVariant &value );

    /**
     * Emit this signal when the constraint status changed.
     * \brief constraintStatusChanged
     * \param constraint represented as a string
     * \param desc is the constraint description
     * \param err the error represented as a string. Empty if none.
     * \param status
     */
    void constraintStatusChanged( const QString &constraint, const QString &desc, const QString &err, QgsEditorWidgetWrapper::ConstraintResult status );

    /**
     * Emit this signal when the constraint result visibility changed.
     */
    void constraintResultVisibleChanged( bool visible );

  public slots:

    /**
     * Will be called when the feature changes
     *
     * Is forwarded to the slot setValue()
     *
     * \param feature The new feature
     */
    void setFeature( const QgsFeature &feature ) override;

    /**
     * Is called, when the value of the widget needs to be changed. Update the widget representation
     * to reflect the new value.
     *
     * \param value The new value of the attribute
     */
    virtual void setValue( const QVariant &value ) = 0;

    /**
     * Will call the value() method to determine the emitted value
     */
    void emitValueChanged();

  protected:

    /**
     * This should update the widget with a visual cue if a constraint status
     * changed.
     *
     * By default a stylesheet will be applied on the widget that changes the
     * background color to red.
     *
     * This can be overwritten in subclasses to allow individual widgets to
     * change the visual cue.
     *
     * \since QGIS 2.16
     */
    virtual void updateConstraintWidgetStatus();


    /**
     * The feature currently being edited, in its current state
     *
     * \return the feature currently being edited, in its current state
     * \since QGIS 3.2
     */
    QgsFeature formFeature() const { return mFormFeature; }

    /**
     * Set the feature currently being edited to \a feature
     *
     * \since QGIS 3.2
     */
    void setFormFeature( const QgsFeature &feature ) { mFormFeature = feature; }

    /**
     * Update the feature currently being edited by changing its
     * attribute \a attributeName to \a attributeValue
     *
     * \return bool true on success
     * \since QGIS 3.2
     */
    bool setFormFeatureAttribute( const QString &attributeName, const QVariant &attributeValue );


  private:

    /**
     * mFieldIdx the widget feature field id
     */
    int mFieldIdx = -1;

    /**
     * The feature currently being edited, in its current state
     */
    QgsFeature mFormFeature;

    /**
     * Boolean storing the current validity of the constraint for this widget.
     */
    bool mValidConstraint;

    //! True if widget is blocking feature commits
    bool mIsBlockingCommit;

    //! Contains the string explanation of why a constraint check failed
    QString mConstraintFailureReason;

    //! The current constraint result
    ConstraintResult mConstraintResult = ConstraintResultPass;

    //! The current constraint result
    bool mConstraintResultVisible = false;

    mutable QVariant mDefaultValue; // Cache default value, we don't want to retrieve different serial numbers if called repeatedly

};

// We'll use this class inside a QVariant in the widgets properties
Q_DECLARE_METATYPE( QgsEditorWidgetWrapper * )

#endif // QGSEDITORWIDGETWRAPPER_H
