#ifndef ARENA_PINMUX_PERIPHERY_H
#define ARENA_PINMUX_PERIPHERY_H
//----------------------------------------------------------------------------
///
/// \file   periphery.h
///
/// \brief  See implementation for further details
///
/// \date   20180129
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//----------------------------------------------------------------------------


//---Documentation------------------------------------------------------------


//---Includes -----------------------------------------------------------------


#include "peripheryTrivia.h"
#include "pointer.h"
#include "reference.h"


//---Declaration --------------------------------------------------------------


class CPeriphery: public CPeripheryTrivia
{
private:
   int m_priority;
   QList <CReference> m_consumes;
   CReferencePtr m_pReferenceLink;
   QList <QString> m_irqs;
   int m_order;
   int m_sortId;
   bool m_enabled;
   bool m_filterOut=false;
   /*
   struct SAlternateFunction
   {
      QString code;
      QString group;
   };
   */

   QMap<QString, QString> m_alternateFunctions;
   QMap<QString, QString> m_alternateFunctionGroup;
   QString m_overlapCode;

public:

   enum class EBinding
   {
      direct,
      byReference
   };

   CPeriphery(const QString &_name, const  QString &_type
                     , const  QString &_peripheryName)
      :CPeripheryTrivia(_name, _type, _peripheryName )
      ,m_priority( 5 )
      ,m_pReferenceLink(nullptr)
      ,m_order(0)
      ,m_sortId(0)
      ,m_enabled(true)
   {
   }
   virtual ~CPeriphery()
   {

   }
   // Need to be able to copy "fabricate" periphery
   // CPeriphery(const CPeriphery&) = delete;

   int getPriority() const
   {
      return(m_priority);
   }
   int setPriority( int priority )
   {
      return( (m_priority=priority) );
   }
   const QStringList getIrqs() const
   {
      if( ! getAttribute("irqs").isEmpty() )
      {
         return( getAttribute("irqs").split("\n") ) ;
      }
      return( QStringList{ } );
   }
   void addAlternateFunctions(const QMap<QString, QString> &alternateFunctions);
   QStringList getAlternateFunctionGroups() const ;
   /*
   const QMap<QString, QString> &getAlternateFunctions()
   {
      return(m_alternateFunctions);
   }
   */
   const QMap<QString, QString> &getConstAlternateFunctions() const
   {
      return(m_alternateFunctions);
   }
   bool isUsed() const
   {
      if(m_filterOut)
      {
         return(false);
      }
      return( getReferenceLink() );
   }
   bool doInstantiateImplementation( bool describe = false ) const
   {
      if( !isUsed() )
      {
         return(false);
      }
      if( attributeSet("pseudo") )
      {
         if( ! ( describe && attributeSet( "describe" ) ) )
         {
            return(false);
         }
      }
      return(true);
   }
   bool doInstantiate( bool describe = false ) const
   {
      if(!doInstantiateImplementation(describe))
      {
         return(false);
      }
      // GPIOs/INPUTs etc. in the root application have to be instanciated
      if( ! getReferenceLink()->getParent() )
      {
         return(true);
      }
      return( getBinding() != EBinding::direct );
   }
   /// \brief  enable or disable a periphery
   ///
   ///         This is needed for overloading the GPIO. All GPIOs are declared
   ///         in MCU-family-file but disabled. In the MCU-Package-file the
   ///         available pins can be enabled.
   void setEnabled(bool enabled)
   {
      m_enabled=enabled;
   }
   bool getEnabled() const
   {
      return(m_enabled);
   }

   bool isExclusive() const
   {
      CReferencePtr reference=getReferenceLink();
      bool exclusive=false;
      if(reference)
      {
         exclusive=reference->isExclusive();
      }
      return( (getRelation()==ERelation::exclusive) || exclusive );
   }

   /** \brief check if periphery could be used due to "shared" constellation
    **/
   bool isAvailableAsShared( ERelation relation, const CReference &rootReference )
   {
      if( (getRelation()== ERelation::shared) && ( relation == ERelation::shared ) )
      {
         if(getReferenceLink())
         {
            if( ( rootReference.getRelation() == ERelation::exclusive ) || isExclusive() )
            {
               return(false);
            }
            if( getReferenceLink()->getRelation() == ERelation::shared )
            {
               return(true);
            }
         }
         if( isExclusive() )
         {
            qFatal("An unlinked periphery is exlusive? That must not be!");
         }
         return(true);
      }
      return(false);
   }

   bool isAvailableAsPlaceholder( ERelation Relation, const CReference &rootReference )
   {
      if( Relation != ERelation::placeholder )
      {
         return(false);
      }
      if( ! getReferenceLink())
      {
         return(true);
      }

      // TODO: check real rootreferences 'getAttributes()["overlap"]'
      return(true);
   }

   /** \brief  check if periphery is basically available
    *
    *          Checks relation constellations. It does not check the availability
    *          of the consumed periphery.
    **/
   bool isAvailable( ERelation Relation, const CReference &rootReference )
   {
      if(!m_enabled)
      {
         return(false);
      }

      if(getRelation()== ERelation::fabricate)
      {
         return(true);
      }

      if( ! getReferenceLink() )
      {
         if( isExclusive() )
         {
            qFatal("An unlinked periphery is exlusive? That must not be!");
         }
         return(true);
      }

      if(    isAvailableAsShared( Relation, rootReference)
          || isAvailableAsPlaceholder( Relation, rootReference) )
      {
         return(true);
      }

      return(false);
   }
   CReference &addConsumes(const CReference &reference)
   {
      m_consumes+=reference;
      m_consumes.last().setParent( CPeripheryPtr(this) );
      return(m_consumes.last());
   }
   QList <CReference> &getConsumesList()
   {
      return(m_consumes);
   }
   const QList <CReference> &getConsumesListConst() const
   {
      return(m_consumes);
   }
   QString toPreferedString()
   {
      QString prefered;
      for(const CReference &consumed :m_consumes)
      {
         if(!prefered.isEmpty())
            prefered+=" ";
         if(!consumed.getPeripheryLink())
         {
            qFatal("Periphery nt resolved cmpletely");
         }
         prefered+=consumed.getPeripheryLink()->getPeripheryName();
      }
      return(prefered);
   }

   virtual QString printable() const
   {
      QString str;
      str+=QString("Periphery: '%1'%2, type: '%3', name: '%4', id: %5, used: %6, relation: %7; priority: %8")
                  .arg(getPeripheryName())
                  .arg( getEnabled() ? "" : " DISABLED")
                  .arg(getPeripheryType()).arg(getName()).arg(getId())
                  .arg(isUsed())
                  .arg( (int)getShared() )
                  .arg(getPriority());
      str+=QString(", mode: '%1'").arg((int)getRelation());
      str+=QString(" -> %1")
            .arg(getReferenceLink()?QString("%1").arg(getReferenceLink()->getId()):"unlinked");
      return(str);
   }
   void setReferenceLink(CReferencePtr link)
   {
      m_pReferenceLink=link;
   }
   CReferencePtr getReferenceLink() const
   {
      return(m_pReferenceLink);
   }
   virtual void dump() const override;
   void setOrder(int order)
   {
      m_order=order;
   }
   int getOrder() const
   {
      return(m_order);
   }
   bool operator <(const CPeriphery &ref) const
   {
      return(m_order<ref.getOrder());
   }
   int calculateOrder();
   int getSortId()
   {
      return(m_sortId);
   };
   void calculateSortId()
   {
      m_sortId=QRandomGenerator::global()->generate();
   };
   void setOverlapCode(const QString &overlapCode)
   {
      m_overlapCode=overlapCode;
   }
   const QString &getOverlapCode() const
   {
      return(m_overlapCode);
   }
   /** \brief  Check if periphery is instanciated directly or as pointer
    *
    *          An periphery is only (yet) instanziated by pointer when the reference
    *          is declared as "placeholder".
    **/
   const bool instanciateByPointer() const
   {
      if( getReferenceLink() )
      {
         return( getReferenceLink()->getRelation()==ERelation::placeholder );
      }
      return(false);
   }
   bool matches(const CReference &reference, EMatchMode eMatchMode
                , const CReference &rootReference
                , const QString &group);

   /** \brief  unlink the complete periphery
    *
    *          unlink all the consumed sub periphery (except the periphery is
    *          not the owner) and the link to its own reference.
    **/
   void unlink()
   {
      for(CReference & consumed: getConsumesList())
      {
         CReferencePtr consumedPtr(&consumed);
         if( consumed.getPeripheryLink() )
         {
            CPeripheryPtr consumedPeriphery=consumed.getPeripheryLink();
            if(consumedPeriphery->getReferenceLink() == consumedPtr)
            {
               if( consumed.getId() != consumedPeriphery->getReferenceLink()->getId() )
               {
                  qFatal("Pointers match but IDs dont?");
               }
               consumed.getPeripheryLink()->unlink();
            }
         }
         consumed.setPeripheryLink(CPeripheryPtr(nullptr));
      }
      if(getReferenceLink())
      {
         getReferenceLink()->setPeripheryLink(CPeripheryPtr(nullptr));
      }
      setReferenceLink(CReferencePtr(nullptr));
   }

   EBinding getBinding() const
   {
      if( ( getPeripheryType() == "GPIO")
       || ( getPeripheryType() == "INPUT")
       || ( getPeripheryType() == "OUTPUT")
       || ( getPeripheryType() == "EXTI")
      )
      {
         return( EBinding::direct );
      }
      return( EBinding::byReference );
   }
   /* \brief creates an clean copy of the periphery
    *
    */
   QSharedPointer<CPeriphery> fabricate( );
   void setFilterOut(bool filter)
   {
      m_filterOut=filter;
   }
   void replaceLabels(QString& string) const;
   CReferencePtr getRootReference() const;
};

void cutFabricateMarker( QString &peripheryName);

//---fin----------------------------------------------------------------------
#endif // ? ! ARENA_PINMUX_PERIPHERY_H
