//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
// .NAME Definition.h - stores the definition of an attribute.
// .SECTION Description
// Stores all of the necessary information for a definition of a
// single attribute. Attributes should be created through
// System::createAttribute().
// .SECTION See Also

#ifndef __smtk_attribute_Definition_h
#define __smtk_attribute_Definition_h

#include "smtk/CoreExports.h"
#include "smtk/PublicPointerDefs.h"
#include "smtk/SharedFromThis.h" // For smtkTypeMacro.
#include "smtk/model/EntityTypeBits.h" // for BitFlags type
#include "smtk/model/EntityRef.h" //for EntityRef version of canBeAssociated

#include <map>
#include <string>
#include <set>
#include <vector>

namespace smtk
{
  namespace model
  {
    class Item;
  }

  namespace attribute
  {
    class Attribute;
    class ItemDefinition;
    class System;

    class SMTKCORE_EXPORT Definition
    {
    public:
      smtkTypeMacro(Definition);
      virtual ~Definition();

      // Description:
      // The type is the identifier that is used to access the
      // attribute definition through the System. It should never change.
      const std::string &type() const
      { return this->m_type;}

      smtk::attribute::System *system() const
      {return this->m_system;}

      // The label is what can be displayed in an application.  Unlike the type
      // which is constant w/r to the definition, an application can change the label
      const std::string &label() const
      { return this->m_label;}

      void setLabel(const std::string &newLabel)
      { this->m_label = newLabel;}

      smtk::attribute::DefinitionPtr baseDefinition() const
      { return this->m_baseDefinition;}

      bool isA(smtk::attribute::ConstDefinitionPtr def) const;

      int version() const
      {return this->m_version;}
      void setVersion(int myVersion)
      {this->m_version = myVersion;}

      bool isAbstract() const
      { return this->m_isAbstract;}

      void setIsAbstract(bool isAbstractValue)
      { this->m_isAbstract = isAbstractValue;}

      // The categories that the attribute applies to. Typically
      // a category will be a simulation type like heat transfer, fluid flow, etc.
      std::size_t numberOfCategories() const
      {return this->m_categories.size();}

      bool isMemberOf(const std::string &category) const
      { return (this->m_categories.find(category) != this->m_categories.end());}

      bool isMemberOf(const std::vector<std::string> &categories) const;

      const std::set<std::string> & categories() const
      {return this->m_categories;}

      // Description:
      // The attributes advance level. 0 is the simplest.
      int advanceLevel() const
      {return this->m_advanceLevel;}
      void setAdvanceLevel(int level)
      {this->m_advanceLevel = level;}

      // Indicates if a model entity can have multiple attributes of this
      // type associated with it
      bool isUnique() const
      {return this->m_isUnique;}
      // Be careful with setting isUnique to be false
      // in order to be consistant all definitions that this is
      // a descendant of should also have isUnique set to false!!
      // isUnique can be set to true without requiring its parent
      // class to also be true.
      void setIsUnique(bool isUniqueValue)
      {this->m_isUnique = isUniqueValue;}

      // Indicates if the attribute applies to the
      // nodes of the analysis mesh
      bool isNodal() const
      { return this->m_isNodal;}
      void setIsNodal(bool isNodalValue)
      {this->m_isNodal = isNodalValue;}

      //Color Specifications
      // Color in the case the attribute does not exist on the model entity
      // If the color has not been set and the def has no base definition it will
      // return s_notApplicableBaseColor
      const double * notApplicableColor() const;
      void setNotApplicableColor(double r, double g, double b, double alpha);
      void setNotApplicableColor(const double *color)
      {this->setNotApplicableColor(color[0], color[1], color[2], color[3]);}
      // By unsetting the color it is now inherited from the def's base definition
      void unsetNotApplicableColor()
      {this->m_isNotApplicableColorSet = false;}
      bool isNotApplicableColorSet() const
      {return this->m_isNotApplicableColorSet;}

      // Default Color for attributes created from this definition -
      // If the color has not been set and the def has no base definition it will
      // return s_defaultBaseColor
      const double * defaultColor() const;
      void setDefaultColor(double r, double g, double b, double alpha);
      void setDefaultColor(const double *color)
      {this->setDefaultColor(color[0], color[1], color[2], color[3]);}
      // By unsetting the color it is now inherited from the def's base definition
      void unsetDefaultColor()
      {this->m_isDefaultColorSet = false;}
      bool isDefaultColorSet() const
      {return this->m_isDefaultColorSet;}

      ModelEntityItemDefinitionPtr associationRule() const;
      virtual void setAssociationRule(ModelEntityItemDefinitionPtr);

      smtk::model::BitFlags associationMask() const;
      void setAssociationMask(smtk::model::BitFlags mask);

      bool associatesWithVertex() const;
      bool associatesWithEdge() const;
      bool associatesWithFace() const;
      bool associatesWithVolume() const;
      bool associatesWithModel() const;
      bool associatesWithGroup() const;

      bool canBeAssociated(smtk::model::BitFlags maskType) const;
      bool canBeAssociated(smtk::model::EntityRef entity,
        std::vector<smtk::attribute::Attribute *>*conflicts) const;

      bool conflicts(smtk::attribute::DefinitionPtr definition) const;

      std::size_t numberOfItemDefinitions() const
      {return this->m_itemDefs.size() + this->m_baseItemOffset;}

      smtk::attribute::ItemDefinitionPtr itemDefinition(int ith) const;

      // Description:
      // Item definitions are the definitions of what data is stored
      // in the attribute. For example, an IntItemDefinition would store
      // an integer value.
      bool addItemDefinition(smtk::attribute::ItemDefinitionPtr cdef);
      template<typename T>
        typename smtk::internal::shared_ptr_type<T>::SharedPointerType
        addItemDefinition(const std::string &name)
      {
        typedef smtk::internal::shared_ptr_type<T> SharedTypes;
        typename SharedTypes::SharedPointerType item;

        // First see if there is a item by the same name
        if (this->findItemPosition(name) < 0)
          {
          std::size_t n = this->m_itemDefs.size();
          item = SharedTypes::RawPointerType::New(name);
          this->m_itemDefs.push_back(item);
          this->m_itemDefPositions[name] = static_cast<int>(n);
          this->updateDerivedDefinitions();
          }
        return item;
      }

      int findItemPosition(const std::string &name) const;

      const std::string &detailedDescription() const
      {return this->m_detailedDescription;}
      void setDetailedDescription(const std::string &text)
        {this->m_detailedDescription = text;}

      const std::string &briefDescription() const
      {return this->m_briefDescription;}
      void setBriefDescription(const std::string &text)
        {this->m_briefDescription = text;}

      // Description:
      // Build an attribute corresponding to this definition. If the
      // attribute already has items, clear them out.
      void buildAttribute(smtk::attribute::Attribute *attribute) const;

      //This method resets the definition item offset - this is used by the
      // system when a definition is modified
      void resetItemOffset();
      std::size_t itemOffset() const
      {return this->m_baseItemOffset;}

      // Return the public pointer for this definition.
      smtk::attribute::DefinitionPtr pointer() const;
    protected:
      friend class smtk::attribute::System;
      // AttributeDefinitions can only be created by an attribute system
      Definition(const std::string &myType, smtk::attribute::DefinitionPtr myBaseDef,
                 smtk::attribute::System *mySystem);

      void clearSystem()
      { this->m_system = NULL;}

      void setCategories();

      // This method updates derived definitions when this
      // definition's items have been changed
      void updateDerivedDefinitions();

      smtk::attribute::System *m_system;
      int m_version;
      bool m_isAbstract;
      smtk::attribute::DefinitionPtr m_baseDefinition;
      std::string m_type;
      std::string m_label;
      bool m_isNodal;
      std::set<std::string> m_categories;
      int m_advanceLevel;
      std::vector<smtk::attribute::ItemDefinitionPtr> m_itemDefs;
      std::map<std::string, int> m_itemDefPositions;
//Is Unique indicates if more than one attribute of this type can be assigned to a
// model entity - NOTE This can be inherited meaning that if the definition's Super definition
// has isUnique = true it will also prevent an attribute from this definition being assigned if the
// targeted model entity has an attribute derived from the Super Definition
      bool m_isUnique;
      bool m_isRequired;
      bool m_isNotApplicableColorSet;
      bool m_isDefaultColorSet;
      smtk::attribute::ModelEntityItemDefinitionPtr m_associationRule;

      std::string m_detailedDescription;
      std::string m_briefDescription;
      // Used by the find method to calculate an item's position
      std::size_t m_baseItemOffset;
    private:

      // These colors are returned for base definitions w/o set colors
      //needs to be private for shiboken wrapping to work properly
      static double s_notApplicableBaseColor[4];
      static double s_defaultBaseColor[4];

      //needs to be private for shiboken wrapping to work properly
      double m_notApplicableColor[4];
      double m_defaultColor[4];

    };
//----------------------------------------------------------------------------
    inline void Definition::resetItemOffset()
    {
      if (this->m_baseDefinition)
        {
        this->m_baseItemOffset = this->m_baseDefinition->numberOfItemDefinitions();
        }
    }
//----------------------------------------------------------------------------
    inline const double * Definition::notApplicableColor() const
    {
      if (this->m_isNotApplicableColorSet)
        {
        return this->m_notApplicableColor;
        }
      else if (this->m_baseDefinition)
        {
        return this->m_baseDefinition->notApplicableColor();
        }
      return s_notApplicableBaseColor;
    }
//----------------------------------------------------------------------------
    inline void Definition::setNotApplicableColor(
      double r, double g, double b, double a)
    {
      this->m_isNotApplicableColorSet = true;
      this->m_notApplicableColor[0]= r;
      this->m_notApplicableColor[1]= g;
      this->m_notApplicableColor[2]= b;
      this->m_notApplicableColor[3]= a;
    }
//----------------------------------------------------------------------------
    inline const double * Definition::defaultColor() const
    {
      if (this->m_isDefaultColorSet)
        {
        return this->m_defaultColor;
        }
      else if (this->m_baseDefinition)
        {
        return this->m_baseDefinition->defaultColor();
        }
      return s_defaultBaseColor;
    }
//----------------------------------------------------------------------------
    inline void Definition::setDefaultColor(
      double r, double g, double b, double a)
    {
      this->m_isDefaultColorSet = true;
      this->m_defaultColor[0]= r;
      this->m_defaultColor[1]= g;
      this->m_defaultColor[2]= b;
      this->m_defaultColor[3]= a;
    }

  }
}
#endif /* __smtk_attribute_Definition_h */
