/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2020 CERN
 * Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 * @author Maciej Suminski <maciej.suminski@cern.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 3
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef PROPERTY_H
#define PROPERTY_H

#include <core/wx_stl_compat.h>
#include <origin_transforms.h>
#include <properties/color4d_variant.h>
#include <properties/eda_angle_variant.h>
#include <properties/property_validator.h>

#include <wx/any.h>
#include <wx/string.h>
#include <wx/bitmap.h>
#include <wx/font.h>        // required for propgrid
#include <wx/validate.h>    // required for propgrid
#include <wx/propgrid/property.h>

#include <functional>
#include <unordered_map>
#include <memory>
#include <typeindex>
#include <type_traits>
#include "std_optional_variants.h"

class wxPGProperty;
class INSPECTABLE;
class PROPERTY_BASE;

template<typename T>
class ENUM_MAP;

///< Common property types
enum PROPERTY_DISPLAY
{
    PT_DEFAULT,    ///< Default property for a given type
    PT_SIZE,       ///< Size expressed in distance units (mm/inch)
    PT_AREA,       ///< Area expressed in distance units-squared (mm/inch)
    PT_COORD,      ///< Coordinate expressed in distance units (mm/inch)
    PT_DEGREE,     ///< Angle expressed in degrees
    PT_DECIDEGREE, ///< Angle expressed in decidegrees
    PT_RATIO
};

///< Macro to generate unique identifier for a type
#define TYPE_HASH( x ) typeid( x ).hash_code()
#define TYPE_NAME( x ) typeid( x ).name()
//#define TYPE_HASH( x ) typeid( std::decay<x>::type ).hash_code()

template<typename Owner, typename T>
class GETTER_BASE
{
public:
    virtual ~GETTER_BASE() {}

    virtual T operator()( const Owner* aOwner ) const = 0;
};

template<typename Owner, typename T, typename FuncType>
class GETTER : public GETTER_BASE<Owner, T>
{
public:
    GETTER( FuncType aFunc )
        : m_func( aFunc )
    {
        wxCHECK( m_func, /*void*/ );
    }

    T operator()( const Owner* aOwner ) const override
    {
        return ( aOwner->*m_func )();
    }

private:
    FuncType m_func;
};

template<typename Owner, typename T>
class SETTER_BASE
{
public:
    virtual ~SETTER_BASE() {}

    virtual void operator()( Owner* aOwner, T aValue ) = 0;
};

template<typename Owner, typename T, typename FuncType>
class SETTER : public SETTER_BASE<Owner, T>
{
public:
    SETTER( FuncType aFunc )
        : m_func( aFunc )
    {
        wxCHECK( m_func, /*void*/ );
    }

    void operator()( Owner* aOwner, T aValue ) override
    {
        ( aOwner->*m_func )( aValue );
    }

private:
    FuncType m_func;
};

#if defined( _MSC_VER )
#pragma warning( push )
#pragma warning( disable : 5266 ) // 'const' qualifier on return type has no effect
#endif

template<typename Owner, typename T, typename Base = Owner>
class METHOD
{
public:
    static GETTER_BASE<Owner, T>* Wrap( T (Base::*aFunc)() )
    {
        return new GETTER<Owner, T, T (Base::*)()>( aFunc );
    }

    constexpr static GETTER_BASE<Owner, T>* Wrap( const T (Base::*aFunc)() )
    {
        return new GETTER<Owner, T, T (Base::*)()>( aFunc );
    }

    constexpr static GETTER_BASE<Owner, T>* Wrap( const T& (Base::*aFunc)() )
    {
        return new GETTER<Owner, T, const T& (Base::*)()>( aFunc );
    }

    constexpr static GETTER_BASE<Owner, T>* Wrap( T (Base::*aFunc)() const )
    {
        return new GETTER<Owner, T, T (Base::*)() const>( aFunc );
    }

    constexpr static GETTER_BASE<Owner, T>* Wrap( const T (Base::*aFunc)() const )
    {
        return new GETTER<Owner, T, T (Base::*)() const>( aFunc );
    }

    constexpr static GETTER_BASE<Owner, T>* Wrap( const T& (Base::*aFunc)() const )
    {
        return new GETTER<Owner, T, const T& (Base::*)() const>( aFunc );
    }

    constexpr static SETTER_BASE<Owner, T>* Wrap( void (Base::*aFunc)( T ) )
    {
        return aFunc ? new SETTER<Owner, T, void (Base::*)( T )>( aFunc ) : nullptr;
    }

    constexpr static SETTER_BASE<Owner, T>* Wrap( void (Base::*aFunc)( T& ) )
    {
        return aFunc ? new SETTER<Owner, T, void (Base::*)( T& )>( aFunc ) : nullptr;
    }

    constexpr static SETTER_BASE<Owner, T>* Wrap( void (Base::*aFunc)( const T& ) )
    {
        return aFunc ? new SETTER<Owner, T, void (Base::*)( const T& )>( aFunc ) : nullptr;
    }

    METHOD() = delete;
};

#if defined( _MSC_VER )
#pragma warning( pop )
#endif

class PROPERTY_BASE
{
private:
    ///< Used to generate unique IDs.  Must come up front so it's initialized before ctor.

public:
PROPERTY_BASE( const wxString& aName, PROPERTY_DISPLAY aDisplay = PT_DEFAULT,
               ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType = ORIGIN_TRANSFORMS::NOT_A_COORD ) :
        m_name( aName ),
        m_display( aDisplay ),
        m_coordType( aCoordType ),
        m_hideFromPropertiesManager( false ),
        m_hideFromRulesEditor( false ),
        m_hideFromLibraryEditors( false ),
        m_availFunc( [](INSPECTABLE*)->bool { return true; } ),
        m_writeableFunc( [](INSPECTABLE*)->bool { return true; } ),
        m_validator( NullValidator )
    {
    }

    virtual ~PROPERTY_BASE()
    {
    }

    const wxString& Name() const { return m_name; }

    /**
     * Return a limited set of possible values (e.g. enum). Check with HasChoices() if a particular
     * PROPERTY provides such set.
     */
    virtual const wxPGChoices& Choices() const
    {
        static wxPGChoices empty;
        return empty;
    }

    /**
     * Set the possible values for for the property.
     */
    virtual void SetChoices( const wxPGChoices& aChoices )
    {
        wxFAIL; // only possible for PROPERTY_ENUM
    }

    /**
     * Return true if this PROPERTY has a limited set of possible values.
     * @see PROPERTY_BASE::Choices()
     */
    virtual bool HasChoices() const
    {
        return false;
    }

    /**
     * Return true if aObject offers this PROPERTY.
     */
    bool Available( INSPECTABLE* aObject ) const
    {
        return m_availFunc( aObject );
    }

    /**
     * Set a callback function to determine whether an object provides this property.
     */
    PROPERTY_BASE& SetAvailableFunc( std::function<bool(INSPECTABLE*)> aFunc )
    {
        m_availFunc = std::move( aFunc );
        return *this;
    }

    virtual bool Writeable( INSPECTABLE* aObject ) const
    {
        return m_writeableFunc( aObject );
    }

    PROPERTY_BASE& SetWriteableFunc( std::function<bool(INSPECTABLE*)> aFunc )
    {
        m_writeableFunc = std::move( aFunc );
        return *this;
    }

    /**
     * Return type-id of the Owner class.
     */
    virtual size_t OwnerHash() const = 0;

    /**
     * Return type-id of the Base class.
     */
    virtual size_t BaseHash() const = 0;

    /**
     * Return type-id of the property type.
     */
    virtual size_t TypeHash() const = 0;

    PROPERTY_DISPLAY Display() const { return m_display; }
    PROPERTY_BASE& SetDisplay( PROPERTY_DISPLAY aDisplay ) { m_display = aDisplay; return *this; }

    ORIGIN_TRANSFORMS::COORD_TYPES_T CoordType() const { return m_coordType; }
    PROPERTY_BASE& SetCoordType( ORIGIN_TRANSFORMS::COORD_TYPES_T aType )
    {
        m_coordType = aType;
        return *this;
    }

    bool IsHiddenFromPropertiesManager() const { return m_hideFromPropertiesManager; }
    PROPERTY_BASE& SetIsHiddenFromPropertiesManager( bool aHide = true )
    {
        m_hideFromPropertiesManager = aHide;
        return *this;
    }

    bool IsHiddenFromRulesEditor() const { return m_hideFromRulesEditor; }
    PROPERTY_BASE& SetIsHiddenFromRulesEditor( bool aHide = true )
    {
        m_hideFromRulesEditor = aHide;
        return *this;
    }

    bool IsHiddenFromLibraryEditors() const { return m_hideFromLibraryEditors; }
    PROPERTY_BASE& SetIsHiddenFromLibraryEditors( bool aIsHidden = true )
    {
        m_hideFromLibraryEditors = aIsHidden;
        return *this;
    }

    wxString Group() const { return m_group; }
    PROPERTY_BASE& SetGroup( const wxString& aGroup ) { m_group = aGroup; return *this; }

    PROPERTY_BASE& SetValidator( PROPERTY_VALIDATOR_FN&& aValidator )
    {
        m_validator = aValidator;
        return *this;
    }

    VALIDATOR_RESULT Validate( const wxAny&& aValue, EDA_ITEM* aItem )
    {
        return m_validator( std::move( aValue ), aItem );
    }

    static VALIDATOR_RESULT NullValidator( const wxAny&& aValue, EDA_ITEM* aItem )
    {
        return std::nullopt;
    }

protected:
    template<typename T>
    void set( void* aObject, T aValue )
    {
        wxAny a = aValue;

        // wxVariant will be type "long" even if the property is supposed to be
        // unsigned.  Let's trust that we're coming from the property grid where
        // we used a UInt editor.
        if( std::is_same<T, wxVariant>::value )
        {
            wxVariant var = static_cast<wxVariant>( aValue );
            wxAny pv = getter( aObject );

            if( pv.CheckType<unsigned>() )
            {
                a = static_cast<unsigned>( var.GetLong() );
            }
            else if( pv.CheckType<std::optional<int>>() )
            {
                auto* data = static_cast<STD_OPTIONAL_INT_VARIANT_DATA*>( var.GetData() );
                a = data->Value();
            }
            else if( pv.CheckType<std::optional<double>>() )
            {
                auto* data = static_cast<STD_OPTIONAL_DOUBLE_VARIANT_DATA*>( var.GetData() );
                a = data->Value();
            }
            else if( pv.CheckType<EDA_ANGLE>() )
            {
                EDA_ANGLE_VARIANT_DATA* ad = static_cast<EDA_ANGLE_VARIANT_DATA*>( var.GetData() );
                a = ad->Angle();
            }
            else if( pv.CheckType<KIGFX::COLOR4D>() )
            {
                COLOR4D_VARIANT_DATA* cd = static_cast<COLOR4D_VARIANT_DATA*>( var.GetData() );
                a = cd->Color();
            }
        }

        setter( aObject, a );
    }

    template<typename T>
    T get( const void* aObject ) const
    {
        wxAny a = getter( aObject );

        // We don't currently have a bool type, so change it to a numeric
        if( a.CheckType<bool>() )
            a = a.RawAs<bool>() ? 1 : 0;

        if ( !( std::is_enum<T>::value && a.CheckType<int>() ) && !a.CheckType<T>() )
            throw std::invalid_argument( "Invalid requested type" );

        return wxANY_AS( a, T );
    }

private:
    virtual void setter( void* aObject, wxAny& aValue ) = 0;
    virtual wxAny getter( const void* aObject ) const = 0;

private:
    /**
     * Permanent identifier for this property.  Property names are an API contract; changing them
     * after release will impact the Custom DRC Rules system as well as the automatic API binding
     * system.  Never rename properties; instead deprecate them and hide them from the GUI.
     */
    const wxString m_name;

    /// The display style controls how properties are edited in the properties manager GUI
    PROPERTY_DISPLAY m_display;

    /// The coordinate type controls how distances are mapped to the user coordinate system
    ORIGIN_TRANSFORMS::COORD_TYPES_T m_coordType;

    /// Some properties should not be shown in the Properties Manager GUI
    bool m_hideFromPropertiesManager;

    /// Some properties should not be shown in the Custom Rules editor autocomplete
    bool m_hideFromRulesEditor;

    /// This property should only be shown in the design editor, not the library editor
    bool m_hideFromLibraryEditors;

    /// Optional group identifier
    wxString m_group;

    std::function<bool(INSPECTABLE*)> m_availFunc;   ///< Eval to determine if prop is available

    std::function<bool(INSPECTABLE*)> m_writeableFunc;   ///< Eval to determine if prop is read-only

    PROPERTY_VALIDATOR_FN m_validator;

    friend class INSPECTABLE;
};


template<typename Owner, typename T, typename Base = Owner>
class PROPERTY : public PROPERTY_BASE
{
public:
    using BASE_TYPE = typename std::decay<T>::type;

    template<typename SetType, typename GetType>
    PROPERTY( const wxString& aName,
            void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )(),
            PROPERTY_DISPLAY aDisplay = PT_DEFAULT,
            ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType = ORIGIN_TRANSFORMS::NOT_A_COORD )
        : PROPERTY( aName, METHOD<Owner, T, Base>::Wrap( aSetter ),
                           METHOD<Owner, T, Base>::Wrap( aGetter ), aDisplay, aCoordType )
    {
    }

    template<typename SetType, typename GetType>
    PROPERTY( const wxString& aName,
            void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )() const,
            PROPERTY_DISPLAY aDisplay = PT_DEFAULT,
            ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType = ORIGIN_TRANSFORMS::NOT_A_COORD )
        : PROPERTY( aName, METHOD<Owner, T, Base>::Wrap( aSetter ),
                           METHOD<Owner, T, Base>::Wrap( aGetter ), aDisplay, aCoordType )
    {
    }

    size_t OwnerHash() const override
    {
        return m_ownerHash;
    }

    size_t BaseHash() const override
    {
        return m_baseHash;
    }

    size_t TypeHash() const override
    {
        return m_typeHash;
    }

    bool Writeable( INSPECTABLE* aObject ) const override
    {
        return m_setter && PROPERTY_BASE::Writeable( aObject );
    }

protected:
    PROPERTY( const wxString& aName, SETTER_BASE<Owner, T>* s, GETTER_BASE<Owner, T>* g,
              PROPERTY_DISPLAY aDisplay, ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType )
        : PROPERTY_BASE( aName, aDisplay, aCoordType ), m_setter( s ), m_getter( g ),
                m_ownerHash( TYPE_HASH( Owner ) ), m_baseHash( TYPE_HASH( Base ) ),
                m_typeHash( TYPE_HASH( BASE_TYPE ) )
    {
    }

    virtual ~PROPERTY() {}

    virtual void setter( void* obj, wxAny& v ) override
    {
        wxCHECK( m_setter, /*void*/ );

        if( !v.CheckType<T>() )
            throw std::invalid_argument( "Invalid type requested" );

        Owner* o = reinterpret_cast<Owner*>( obj );
        BASE_TYPE value = wxANY_AS(v, BASE_TYPE);
        (*m_setter)( o, value );
    }

    virtual wxAny getter( const void* obj ) const override
    {
        const Owner* o = reinterpret_cast<const Owner*>( obj );
        wxAny res = (*m_getter)( o );
        return res;
    }

    ///< Set method
    std::unique_ptr<SETTER_BASE<Owner, T>> m_setter;

    ///< Get method
    std::unique_ptr<GETTER_BASE<Owner, T>> m_getter;

    ///< Owner class type-id
    const size_t m_ownerHash;

    ///< Base class type-id
    const size_t m_baseHash;

    ///< Property value type-id
    const size_t m_typeHash;
};


template<typename Owner, typename T, typename Base = Owner>
class PROPERTY_ENUM : public PROPERTY<Owner, T, Base>
{
public:
    template<typename SetType, typename GetType>
    PROPERTY_ENUM( const wxString& aName,
            void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )(),
            PROPERTY_DISPLAY aDisplay = PT_DEFAULT )
        : PROPERTY<Owner, T, Base>( aName, METHOD<Owner, T, Base>::Wrap( aSetter ),
                                     METHOD<Owner, T, Base>::Wrap( aGetter ), aDisplay )
    {
        if ( std::is_enum<T>::value )
        {
            m_choices = ENUM_MAP<T>::Instance().Choices();
            wxASSERT_MSG( m_choices.GetCount() > 0, wxT( "No enum choices defined" ) );
        }
    }

    template<typename SetType, typename GetType>
    PROPERTY_ENUM( const wxString& aName,
            void ( Base::*aSetter )( SetType ), GetType( Base::*aGetter )() const,
            PROPERTY_DISPLAY aDisplay = PT_DEFAULT,
            ORIGIN_TRANSFORMS::COORD_TYPES_T aCoordType = ORIGIN_TRANSFORMS::NOT_A_COORD )
        : PROPERTY<Owner, T, Base>( aName, METHOD<Owner, T, Base>::Wrap( aSetter ),
                                     METHOD<Owner, T, Base>::Wrap( aGetter ), aDisplay, aCoordType )
    {
        if ( std::is_enum<T>::value )
        {
            m_choices = ENUM_MAP<T>::Instance().Choices();
            wxASSERT_MSG( m_choices.GetCount() > 0, wxT( "No enum choices defined" ) );
        }
    }

    void setter( void* obj, wxAny& v ) override
    {
        wxCHECK( ( PROPERTY<Owner, T, Base>::m_setter ), /*void*/ );
        Owner* o = reinterpret_cast<Owner*>( obj );

        if( v.CheckType<T>() )
        {
            T value = wxANY_AS(v, T);
            (*PROPERTY<Owner, T, Base>::m_setter)( o, value );
        }
        else if (v.CheckType<int>() )
        {
            int value = wxANY_AS(v, int);
            (*PROPERTY<Owner, T, Base>::m_setter)( o, static_cast<T>( value ) );
        }
        else
        {
            throw std::invalid_argument( "Invalid type requested" );
        }
    }

    wxAny getter( const void* obj ) const override
    {
        const Owner* o = reinterpret_cast<const Owner*>( obj );
        wxAny res = static_cast<T>( (*PROPERTY<Owner, T, Base>::m_getter)( o ) );
        return res;
    }

    const wxPGChoices& Choices() const override
    {
        return m_choices.GetCount() > 0 ? m_choices : ENUM_MAP<T>::Instance().Choices();
    }

    void SetChoices( const wxPGChoices& aChoices ) override
    {
        m_choices = aChoices;
    }

    bool HasChoices() const override
    {
        return Choices().GetCount() > 0;
    }

protected:
    wxPGChoices m_choices;
};


class TYPE_CAST_BASE
{
public:
    virtual ~TYPE_CAST_BASE() {}
    virtual void* operator()( void* aPointer ) const = 0;
    virtual const void* operator()( const void* aPointer ) const = 0;
    virtual size_t BaseHash() const = 0;
    virtual size_t DerivedHash() const = 0;
};


template<typename Base, typename Derived>
class TYPE_CAST : public TYPE_CAST_BASE
{
public:
    TYPE_CAST()
    {
    }

    void* operator()( void* aPointer ) const override
    {
        Base* base = reinterpret_cast<Base*>( aPointer );
        return static_cast<Derived*>( base );
    }

    const void* operator()( const void* aPointer ) const override
    {
        const Base* base = reinterpret_cast<const Base*>( aPointer );
        return static_cast<const Derived*>( base );
    }

    size_t BaseHash() const override
    {
        return TYPE_HASH( Base );
    }

    size_t DerivedHash() const override
    {
        return TYPE_HASH( Derived );
    }
};


template<typename T>
class ENUM_MAP
{
public:
    static ENUM_MAP<T>& Instance()
    {
        static ENUM_MAP<T> inst;
        return inst;
    }

    ENUM_MAP& Map( T aValue, const wxString& aName )
    {
        m_choices.Add( aName, static_cast<int>( aValue ) );
        m_reverseMap[ aName ] = aValue;
        return *this;
    }

    ENUM_MAP& Undefined( T aValue )
    {
        m_undefined = aValue;
        return *this;
    }

    const wxString& ToString( T value ) const
    {
        static const wxString s_undef = "UNDEFINED";

        int idx = m_choices.Index( static_cast<int>( value ) );

        if( idx >= 0 && idx < (int) m_choices.GetCount() )
            return m_choices.GetLabel( static_cast<int>( idx ) );
        else
            return s_undef;
    }

    bool IsValueDefined( T value ) const
    {
        int idx = m_choices.Index( static_cast<int>( value ) );

        if( idx >= 0 && idx < (int) m_choices.GetCount() )
            return true;

        return false;
    }

    T ToEnum( const wxString value )
    {
        if( m_reverseMap.count( value ) )
            return m_reverseMap[ value ];
        else
            return m_undefined;
    }

    wxPGChoices& Choices()
    {
        return m_choices;
    }

private:
    wxPGChoices                     m_choices;
    std::unordered_map<wxString, T> m_reverseMap;
    T                               m_undefined;      // Returned if the string is not recognized

    ENUM_MAP<T>()
    {
    }
};


// Helper macros to handle enum types
#define DECLARE_ENUM_TO_WXANY( type )                                                       \
    template <>                                                                             \
    class wxAnyValueTypeImpl<type> : public wxAnyValueTypeImplBase<type>                    \
    {                                                                                       \
        WX_DECLARE_ANY_VALUE_TYPE( wxAnyValueTypeImpl<type> )                               \
    public:                                                                                 \
        wxAnyValueTypeImpl() : wxAnyValueTypeImplBase<type>() {}                            \
        virtual ~wxAnyValueTypeImpl() {}                                                    \
        virtual bool ConvertValue( const wxAnyValueBuffer& src, wxAnyValueType* dstType,    \
                                   wxAnyValueBuffer& dst ) const override                   \
        {                                                                                   \
            type            value = GetValue( src );                                        \
            ENUM_MAP<type>& conv = ENUM_MAP<type>::Instance();                              \
            if( ! conv.IsValueDefined( value ) )                                            \
            {                                                                               \
                return false;                                                               \
            }                                                                               \
            if( dstType->CheckType<wxString>() )                                            \
            {                                                                               \
                wxAnyValueTypeImpl<wxString>::SetValue( conv.ToString( value ), dst );      \
                return true;                                                                \
            }                                                                               \
            if( dstType->CheckType<int>() )                                                 \
            {                                                                               \
                wxAnyValueTypeImpl<int>::SetValue( static_cast<int>( value ), dst );        \
                return true;                                                                \
            }                                                                               \
            else                                                                            \
            {                                                                               \
                return false;                                                               \
            }                                                                               \
        }                                                                                   \
    };

#define IMPLEMENT_ENUM_TO_WXANY( type ) WX_IMPLEMENT_ANY_VALUE_TYPE( wxAnyValueTypeImpl<type> )

#define ENUM_TO_WXANY( type )                                                               \
    DECLARE_ENUM_TO_WXANY( type )                                                           \
    IMPLEMENT_ENUM_TO_WXANY( type )

///< Macro to define read-only fields (no setter method available)
#define NO_SETTER( owner, type ) ( ( void ( owner::* )( type ) ) nullptr )

/*
#define DECLARE_PROPERTY(owner,type,name,getter,setter) \
namespace anonymous {\
static PROPERTY<owner,type> prop##_owner##_name_( "##name#", setter, getter );\
};
*/
#endif /* PROPERTY_H */
