//----------------------------------------------------------------------------
///
/// \file   periphery.cpp
///
/// \brief  foo
///
/// \date   20180215
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//----------------------------------------------------------------------------


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


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


//---General--------------------------

#include <QSharedPointer>
#include <QDebug>

//---Own------------------------------

#include "periphery.h"


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


void CPeriphery::addAlternateFunctions(const QMap<QString, QString> &alternateFunctions)
{
   // QMap::insert(const QMap&) Qt 5.15
   for( auto key: alternateFunctions.keys())
   {
      QStringList parts=key.split(u'@');
      m_alternateFunctions[ parts[0] ] = alternateFunctions[key];
      m_alternateFunctionGroup[ parts[0] ]= ( parts.size() > 1 ) ? parts[1] : "";
   }
   return;
}


QStringList CPeriphery::getAlternateFunctionGroups() const
{
   return( m_alternateFunctionGroup.values() );
}


void CPeriphery::dump() const
{
    qWarning("Dumping Periphery:");
    CPeripheryTrivia::dump();
    qWarning("   used: %d", (int)isUsed());
    qWarning("   periphery: %s", qPrintable(getPeripheryName()) );
    qWarning("   allowShared: %d", (int)getShared() );
}

bool CPeriphery::matches(const CReference &reference, EMatchMode eMatchMode
                         , const CReference &rootReference
                         , const QString &group)
{
   bool result=false;

   switch(eMatchMode)
   {
      case EMatchMode::peripheryType:
      {
         result=matchesType(reference.getPeripheryType());
         break;
      }
      case EMatchMode::predefined:
      {
         //result=( getPeripheryName() == rootReference.getPredefined(predefinedPos) );
         qFatal("Predefined preiphery must use seperate resolver");
         break;
      }
      case EMatchMode::peripheryName:
      {
         result=( getPeripheryName() == reference.getPeripheryName() );
         break;
      }
      case EMatchMode::alternateFunction:
      {
         result = m_alternateFunctions.contains( reference.getAlternateFunction() );
         if( result )
         {
            result = m_alternateFunctionGroup[ reference.getAlternateFunction() ] == group ;
         }
         break;
      }
      default:
      {
         qFatal("Unknown match mode %d", (int)eMatchMode);
         break;
      }
   }

   #if 0
   qWarning() << "Required Attribute: " << rootReference.getRequiredAttribute();
   qWarning() << "   RootName: " << rootReference.getName() << "/ Type " << rootReference.getPeripheryType();
   qWarning() << "   Attribute: " << getAttribute( rootReference.getRequiredAttribute() );
   qWarning() << "   Result: " << result;
   qWarning() << "   Type: " << getPeripheryType() << "/" << getPeripheryName();
   #endif

   // Does the periphery
   if ( result && ( getPeripheryType() == "GPIO" ) )
   {
      QString requiredAttribute;
      requiredAttribute=reference.getRequiredAttribute();
      #if 0
      qWarning() << "###" << reference.getPeripheryType()
                 << "/" << rootReference.getPeripheryType()
                 << "/" << rootReference.getRequiredAttribute()
                 << "/" << reference.getParent()->getPeripheryType() << "!";
      #endif


      if( requiredAttribute.isEmpty() )
      {
         requiredAttribute=rootReference.getRequiredAttribute();
      }
      // The 'required' attribute in the root application file can contain a ':'.
      // This means the required field is only needed for gpios in a periphery
      // of a given periphery type. Example: "uart:uartBootloader".
      requiredAttribute=rootReference.getRequiredAttribute();
      QStringList list = requiredAttribute.split(":");
      if( list.count() == 2 )
      {
         if ( list[0].toUpper() == reference.getParent()->getPeripheryType() )
         {
            requiredAttribute=list[1];
         }
         else
         {
            requiredAttribute.clear();
         }
      }

      if ( ! requiredAttribute.isEmpty() )
      {
           result = attributeSet( requiredAttribute );
      }
   }

   return(result);
}

/// \brief  Calculate the order for initialization
///
///         Iterate over all consumed periphery, find the max, increase it by 1
int CPeriphery::calculateOrder()
{
   if(m_order)
   {
      return(m_order);
   }

   for(const auto &reference: m_consumes)
   {
      if(!reference.getPeripheryLink())
      {
         reference.dump();
         qFatal("Used reference is not resolved! Id:%d", reference.getId());
      }
      CPeriphery *pPeriphery=reference.getPeripheryLink().data();
      if( ! pPeriphery )
      {
         qFatal("Used reference is not resolved! Id:%d", reference.getId());
      }
      else
      {
         m_order=qMax(m_order, pPeriphery->calculateOrder());
      }
   }
   m_order++;
   return(m_order);
}


/// \brief  If the periphery has relation 'fabricate', create a new empty copy
///
///         The current periphery will stay valid. THe returned one can be
///         added to the peripheral list as is ready for use.
///         The current periphery will also get relation "ERelation::unshared"
///         so its not available any more.
QSharedPointer<CPeriphery> CPeriphery::fabricate(  )
{
   static int counter=0;
   if( getRelation() != ERelation::fabricate )
   {
      qFatal("Wont fabricate an periphery thats not fabricable");
   }

   QSharedPointer<CPeriphery> peripheryCopy( new CPeriphery( *this ) );

   peripheryCopy->setReferenceLink(nullptr);
   for( CReference &reference:peripheryCopy->getConsumesList() )
   {
      reference.setPeripheryLink(nullptr);
      reference.updateId();
   }

   // Its automatically added as copy, but QSharedPointer is needed anyhow
   peripheryCopy->updateId();

   setRelation( ERelation::unshared );

   // postfixing string confuses
   setPeripheryName( getPeripheryName() + "" + QString("[%1]").arg( counter++ ) );

   return( peripheryCopy );
}

void cutFabricateMarker( QString &peripheryName)
{
   int index;

   // Cut off possible fabricated markings
   if( ( index= peripheryName.indexOf("[")) != -1 )
   {
      peripheryName.truncate(index);
   }

   return;
}


void CPeriphery::replaceLabels(QString& string) const
{
   string.replace("<PeripheryNumber>", getPeripheryNumber() );
   string.replace("<PeripheryIndex>", getPeripheryNumber() );
   string.replace("<PeripheryBlock>", getPeripheryBlock() );
   if( getReferenceLink() )
   {
      string.replace("<ReferenceName>", getReferenceLink()->getName());
   }
   else
   {
      string.replace("<ReferenceName>", "NoReference");
   }
   string.replace("<PeripheryName>", getPeripheryNameForCode() );
   
   /*
    * R:PWM ->P:PWM -> R:TIMER -> P:TIMER
    */
   QString rootName;
   CReferencePtr rootReference=getRootReference();
   if( ! rootReference->getName().isEmpty() )
   {
      rootName = rootReference->getName();
   }
   else
   {
      rootName = getName();
   }
   string.replace("<RootName>", rootName );
}


CReferencePtr CPeriphery::getRootReference() const
{
   CReferencePtr reference=getReferenceLink();
   if(!reference)
   {
      qFatal("No parent.");
   }
   while( reference->getParent() )
   {
      if(!reference->getParent()->getReferenceLink())
      {
         qWarning("Parent has no reference: Parent: %s. Reference: %s. This: %s/%d",
                qPrintable( reference->getParent()->getName() ),
                qPrintable( reference->getName() ),
                qPrintable( getName() ), getId() );

         return(reference);
      }
      reference=reference->getParent()->getReferenceLink();
   }
   
   return( reference );
}


//---fin----------------------------------------------------------------------
