/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * 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 
 * OpenSceneGraph Public License for more details.
*/
//osgIntrospection - Copyright (C) 2005 Marco Jez

#ifndef OSGINTROSPECTION_PROPERTYINFO_
#define OSGINTROSPECTION_PROPERTYINFO_

#include <osgIntrospection/Export>
#include <osgIntrospection/Type>
#include <osgIntrospection/MethodInfo>
#include <osgIntrospection/Attributes>

#include <string>
#include <typeinfo>
#include <iosfwd>
#include <vector>

namespace osgIntrospection
{

    /// This class keeps information about a class' property. A property is
    /// defined by a name and a set of methods that store and retrieve
    /// values. When the user wants to "get" the value of a property, the
    /// getter method will be invoked and its value returned. When the user
    /// wants to "set" the value of a property, the setter method will be
    /// called. There are three kinds of property: simple (get/set), indexed
    /// (get[i1, i2, ...]/set[i1, i2, ...]), and array (count/add/get[i]/
    /// set[i]).
    /// Objects of class PropertyInfo can't be modified once they have been
    /// created, but they can be queried without restrictions. You can either
    /// retrieve the accessor methods and invoke them manually, or you can
    /// call getValue() / setValue() etc. methods to perform direct operations
    /// on the property, given an instance of the declaring type to work on.
    /// The latter technique is preferred because it checks for custom 
    /// attributes associated to the PropertyInfo object and passes control 
    /// to them when needed.
    ///
    class OSGINTROSPECTION_EXPORT PropertyInfo: public CustomAttributeProvider
    {
    public:
        /// Direct initialization constructor for simple properties.
        /// You must pass the Type object associated to the class that
        /// declares the property, the Type object that describes the
        /// type of the property's value, the property name and the
        /// getter/setter methods. Either the setter or the getter can
        /// be null, meaning a restricted access. If both are null, the
        /// user is expected to add a custom accessor attribute to this
        /// PropertyInfo object.
        PropertyInfo(const Type& declaratiionType, const Type& ptype, const std::string& name, const MethodInfo* getm, const MethodInfo* setm, std::string briefHelp = std::string(), std::string detailedHelp = std::string())
        :   CustomAttributeProvider(),
            _declarationType(declaratiionType),
            _ptype(ptype),
            _name(name),
            _getm(getm),
            _setm(setm),
            _numm(0),
            _addm(0),
            _insm(0),
            _remm(0),
            _is_array(false),
            _briefHelp(briefHelp),
            _detailedHelp(detailedHelp)
        {
        }

        /// Direct initialization constructor for "array" properties.
        /// You must pass the Type object associated to the type that
        /// declares the property, the Type object that describes the
        /// type of the property's value, the property name and the
        /// getter/setter/counter/adder/insert/remover methods.
        PropertyInfo(const Type& declaratiionType, const Type& ptype, const std::string& name, const MethodInfo* getm, const MethodInfo* setm, const MethodInfo* numm, const MethodInfo* addm, const MethodInfo* insm, const MethodInfo* remm, std::string briefHelp = std::string(), std::string detailedHelp = std::string())
        :   CustomAttributeProvider(),
            _declarationType(declaratiionType),
            _ptype(ptype),
            _name(name),
            _getm(getm),
            _setm(setm),
            _numm(numm),
            _addm(addm),
            _insm(insm),
            _remm(remm),
            _is_array(true),
            _briefHelp(briefHelp),
            _detailedHelp(detailedHelp)
        {
        }
        /// Direct initialization constructor for indexed properties.
        /// You must pass the Type object associated to the class that
        /// declares the property, the Type object that describes the
        /// type of the property's value, the property name and the
        /// getter/setter methods. Either the setter or the getter can
        /// be null, meaning a restricted access. If both are null, the
        /// user is expected to add a custom accessor attribute to this
        /// PropertyInfo object.
        /// If the getter method has parameters, the property is considered
        /// to be indexed. The same is true if the getter is null and the
        /// setter has more than one parameter.
        PropertyInfo(const Type& declaratiionType, const Type& ptype, const std::string& name, const MethodInfo* getm, const MethodInfo* setm, const MethodInfo* remm, std::string briefHelp = std::string(), std::string detailedHelp = std::string())
        :   CustomAttributeProvider(),
            _declarationType(declaratiionType),
            _ptype(ptype),
            _name(name),
            _getm(getm),
            _setm(setm),
            _numm(0),
            _addm(0),
            _insm(0),
            _remm(remm),
            _is_array(false),
            _briefHelp(briefHelp),
            _detailedHelp(detailedHelp)
        {
            if (_getm)
            {
                for (ParameterInfoList::size_type i=0; i<_getm->getParameters().size(); ++i)
                    _indices.push_back(_getm->getParameters().at(i));
            }
            else
            {
                if (_setm)
                {
                    for (ParameterInfoList::size_type i=0; i<(_setm->getParameters().size()-1); ++i)
                        _indices.push_back(_setm->getParameters().at(i));
                }
            }
        }

        /// Returns the number of indices
        inline int getNumIndices() const
        {
            return static_cast<int>(getIndexParameters().size());
        }

        /// Returns the name of the property being described.
        inline virtual const std::string& getName() const
        {
            return _name;
        }

        /// Returns the brief help of the property being described.
        inline virtual const std::string& getBriefHelp() const
        {
            return _briefHelp;
        }
        /// Returns the detailed help of the property being described.
        inline virtual const std::string& getDetailedHelp() const
        {
            return _detailedHelp;
        }

        /// Returns the type that declares the property.
        inline virtual const Type& getDeclaringType() const
        {
            return _declarationType;
        }

        /// Returns the type of the reflected property.
        inline const Type& getPropertyType() const
        {
            const PropertyTypeAttribute *pta = getAttribute<PropertyTypeAttribute>(false);
            if (pta) return pta->getPropertyType();
            return _ptype;
        }

        /// Returns the getter method.
        inline const MethodInfo* getGetMethod() const
        {
            return _getm;
        }

        /// Returns the setter method.
        inline const MethodInfo* getSetMethod() const
        {
            return _setm;
        }

        /// Returns the counter method.
        inline const MethodInfo* getCountMethod() const
        {
            return _numm;
        }

        /// Returns the adder method.
        inline const MethodInfo* getAddMethod() const
        {
            return _addm;
        }

        /// Returns the insert method.
        inline const MethodInfo* getInsertMethod() const
        {
            return _insm;
        }

        /// Returns the remover method.
        inline const MethodInfo* getRemoveMethod() const
        {
            return _remm;
        }

        /// Returns whether the property's value can be retrieved.
        inline bool canGet() const
        {
            return (_getm != 0) || isDefined<CustomPropertyGetAttribute>(false);
        }

        /// Returns whether the property's value can be set.
        inline bool canSet() const
        {
            return _setm != 0 || isDefined<CustomPropertySetAttribute>(false);
        }

        /// Returns whether the property's array of values can be counted.
        inline bool canCount() const
        {
            return _numm != 0 || isDefined<CustomPropertyCountAttribute>(false);
        }

        /// Returns whether items can be added to the array property.
        inline bool canAdd() const
        {
            return _addm != 0 || isDefined<CustomPropertyAddAttribute>(false);
        }

        /// Returns whether items can be insert to the array property.
        inline bool canInsert() const
        {
            return _insm != 0 || isDefined<CustomPropertyInsertAttribute>(false);
        }

        /// Returns whether items can be removed from the array property.
        inline bool canRemove() const
        {
            return _remm != 0 || isDefined<CustomPropertyRemoveAttribute>(false);
        }


        /// Returns whether the property is simple.
        inline bool isSimple() const
        {
            return !isIndexed() && !isArray();
        }

        /// Returns whether the property is indexed.
        inline bool isIndexed() const
        {
            return getNumIndices() > 0;
        }

        /// Returns whether the property is an array.
        inline bool isArray() const
        {
            return _is_array;
        }

        /// Returns the list of index parameters.
        /// If the property is not indexed, this list is empty. If neither
        /// the get method nor the set method are defined, this list is
        /// empty unless a custom indexing attribute is defined.
        const ParameterInfoList& getIndexParameters() const;

        /// Returns a list of valid values that can be used for the specified
        /// index. If a custom indexing attribute is defined for this property,
        /// then it will be queried for the index set, otherwise the index
        /// will be treated as an enumeration and the set of enumeration 
        /// values will be returned.
        void getIndexValueSet(int whichindex, const Value& instance, ValueList& values) const;

        /// Invokes the getter method on the given const instance and
        /// returns the property's value. If a custom getter attribute
        /// is defined, it will be invoked instead.
        Value getValue(const Value& instance) const;

        /// Invokes the getter method on the given instance and
        /// returns the property's value. If a custom getter attribute
        /// is defined, it will be invoked instead.
        Value getValue(Value& instance) const;

        /// Invokes the setter method on the given instance and
        /// sets the property's value. If a custom setter attribute
        /// is defined, it will be invoked instead.
        void setValue(Value& instance, const Value& value) const;

        /// Invokes the getter method on the given const instance passing a
        /// list of indices and returns the indexed property's value. If a 
        /// custom getter attribute is defined, it will be invoked instead.
        Value getIndexedValue(Value& instance, ValueList& indices) const;

        /// Invokes the getter method on the given instance passing a list
        /// of indices and returns the indexed property's value. If a custom
        /// getter attribute is defined, it will be invoked instead.
        Value getIndexedValue(const Value& instance, ValueList& indices) const;

        /// Invokes the setter method on the given instance passing a list
        /// of indices and sets the indexed property's value. If a custom
        /// setter attribute is defined, it will be invoked instead.
        void setIndexedValue(Value& instance, ValueList& indices, const Value& value) const;

        /// Invokes the remover method on the given instance and removes
        /// an item from the indexed property. If a custom remover attribute
        /// is defined, it will be invoked instead.
        void removeIndexedItem(Value& instance, ValueList& indices) const;

        /// Invokes the counter method on the given instance and returns 
        /// the number of items of the array property. If a custom counter
        /// attribute is defined, it will be invoked instead.
        int getNumArrayItems(const Value& instance) const;

        /// Invokes the getter method on the given const instance and returns 
        /// the i-th item of the array property. If a custom getter attribute
        /// us defined, it will be invoked instead.
        Value getArrayItem(const Value& instance, int i) const;

        /// Invokes the getter method on the given instance and returns 
        /// the i-th item of the array property. If a custom getter attribute
        /// us defined, it will be invoked instead.
        Value getArrayItem(Value& instance, int i) const;

        /// Invokes the setter method on the given instance and sets
        /// the i-th item of the array property. If a custom setter attribute
        /// is defined, it will be invoked instead.
        void setArrayItem(Value& instance, int i, const Value& value) const;

        /// Invokes the adder method on the given instance and adds
        /// an item to the array property. If a custom adder attribute is
        /// defined, it will be invoked instead.
        void addArrayItem(Value& instance, const Value& value) const;

        /// Invokes the insert method on the given instance and inserts
        /// an item  to the array property after the i-th item of the array 
        /// property. If a custom adder attribute is defined,
        /// it will be invoked instead.
        void insertArrayItem(Value& instance, int i, const Value& value) const;

        /// Invokes the remover method on the given instance and removes
        /// an item from the array property. If a custom remover attribute
        /// is defined, it will be invoked instead.
        void removeArrayItem(Value& instance, int i) const;

        /// Returns the default value associated to the reflected property.
        /// If no default value has been specified, this method tries to
        /// create an instance of the property type and then returns its 
        /// value. There are some attributes that change the behavior of
        /// this method, for example NoDefaultValueAttribute.
        Value getDefaultValue() const;

        virtual ~PropertyInfo() {};
        
    protected:            
        virtual void getInheritedProviders(CustomAttributeProviderList& providers) const;

    private:
    
        PropertyInfo& operator = (const PropertyInfo&) { return *this; }

        const Type& _declarationType;
        const Type& _ptype;
        std::string _name;
        const MethodInfo* _getm;
        const MethodInfo* _setm;
        const MethodInfo* _numm;
        const MethodInfo* _addm;
        const MethodInfo* _insm;
        const MethodInfo* _remm;
        ParameterInfoList _indices;
        bool _is_array;

        std::string _briefHelp;
        std::string _detailedHelp; 
    };

}

#endif
