/******************************************************************************
 * QSkinny - Copyright (C) The authors
 *           SPDX-License-Identifier: BSD-3-Clause
 *****************************************************************************/

#ifndef QSK_OBJECT_TREE_H
#define QSK_OBJECT_TREE_H

#include "QskControl.h"
#include "QskWindow.h"

#include <qvariant.h>

namespace QskObjectTree
{
    class Visitor
    {
      public:
        Visitor() = default;
        virtual ~Visitor() = default;

        virtual bool visitDown( QObject* object ) = 0;
        virtual bool visitUp( const QObject* object ) = 0;

      private:
        Q_DISABLE_COPY( Visitor )
    };

    QSK_EXPORT QObjectList childNodes( const QObject* );
    QSK_EXPORT QObject* parentNode( const QObject* );
    QSK_EXPORT bool isRoot( const QObject* );

    void traverseDown( QObject* object, Visitor& visitor );
    void traverseUp( QObject* object, Visitor& visitor );

    template< class T >
    class ResolveVisitor : public Visitor
    {
      public:
        ResolveVisitor( const char* propertyName )
            : m_propertyName( propertyName )
        {
        }

        inline const T& resolveValue() const
        {
            return m_value;
        }

        void setResolveValue( const T& value )
        {
            m_value = value;
        }

        bool visitDown( QObject* object ) override final
        {
            if ( auto control = qobject_cast< QskControl* >( object ) )
                return setImplicitValue( control, m_value );

            if ( auto window = qobject_cast< QskWindow* >( object ) )
                return setImplicitValue( window, m_value );

            return !setProperty( object, m_propertyName.constData(), m_value );
        }

        bool visitUp( const QObject* object ) override final
        {
            if ( isRoot( object ) )
                return true;

            if ( auto control = qobject_cast< const QskControl* >( object ) )
            {
                m_value = value( control );
                return true;
            }

            if ( auto window = qobject_cast< const QskWindow* >( object ) )
            {
                m_value = value( window );
                return true;
            }

            return getProperty( object, m_propertyName, m_value );
        }

      private:
        inline bool getProperty( const QObject* object,
            const char* name, T& value ) const
        {
            if ( !m_propertyName.isEmpty() )
            {
                const QVariant v = object->property( name );
                if ( v.canConvert< T >() )
                {
                    value = qvariant_cast< T >( v );
                    return true;
                }
            }

            return false;
        }

        inline bool setProperty( QObject* object,
            const char* name, const T& value ) const
        {
            T oldValue;
            if ( !getProperty( object, name, oldValue ) || oldValue == value )
                return false;

            object->setProperty( name, value );
            return true;
        }

        virtual bool setImplicitValue( QskControl*, const T& ) = 0;
        virtual bool setImplicitValue( QskWindow*, const T& ) = 0;

        virtual T value( const QskControl* ) const = 0;
        virtual T value( const QskWindow* ) const = 0;

      private:
        const QByteArray m_propertyName;
        T m_value;
    };
}

#endif
