//----------------------------------------------------------------------------
///
/// \file   resolverPredefined.cpp
///
/// \brief  Resolver class for references with predefined periphery path
///
/// \date   20230329
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//----------------------------------------------------------------------------


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


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


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

#include <QDebug>

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

#include "resolverDefault.h"


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


CResolverDefault::CResolverDefault(CApp *app, CBoard *board)
   :m_pApp(app)
   ,m_pBoard(board)
{
}


CPeripheryPtr CResolverDefault::resolveReference(CReference &reference, const CReference &rootReference)
{
   return( resolveReference(reference, rootReference, "") );
}


CPeripheryPtr CResolverDefault::resolveReference(CReference &reference, const CReference &rootReference, const QString &group)
{
   CPeripheryPtr foundPeriphery;
   EMatchMode eMatchMode=m_pBoard->getMatchMode( reference, rootReference );
   bool foundAny=false;
   static int nestingCount=0;
   int peripheryCounter=0;

   // Using priorities has problems at the moment; the periphery list
   // which is iterated may expand due to "fabricated" peripheries.
	bool usePriority = true;

   nestingCount++;
   if(nestingCount>10)
   {
      qWarning("Looks like you created an circle");
      throw("Looks like you created an circle");
   }

   if(reference.getPeripheryLink())
   {
      QString str=QString("Reference is already linked: %1. Root reference: %2")
               .arg( reference.getId() ).arg(rootReference.getId() );
      //qFatal( qPrintable(str) );
      errorExit( str );
   }

   if( m_pBoard->list().count() == 0 )
   {
      qFatal("Board is empty. You did not load anything?");
   }

   // inflate the list. Trying to resolve references may add fabricated
   // periphery. Reallocation during iterating over the list will crash.
	m_pBoard->list().reserve( RESERVE_PERIPHERY );

   // Iterate over all peripheries and check if it matches the reference.
   // Find the reference with the highest priority.
   for( QSharedPointer <CPeriphery> periphery : m_pBoard->list() )
   {
      QSharedPointer<CPeriphery> ptr=periphery;
      peripheryCounter++;
		// Check if the periphery is under 5% to the limit
		if( peripheryCounter > RESERVE_PERIPHERY - ( RESERVE_PERIPHERY / 20 ) )
      {
			qWarning() << "Periphery list too big: " << peripheryCounter;
         qWarning() << "RESERVE_PERIPHERY: " << RESERVE_PERIPHERY;
         errorExit( "Increase variabel 'RESERVE_PERIPHERY'." );
      }

      ERelation shareMode=reference.getRelation();

      if( rootReference.getRelation() == ERelation::placeholder )
      {
         shareMode=ERelation::placeholder;
      }

      if( rootReference.isForcedExclusive() )
      {
         shareMode=ERelation::unshared;
      }

      if( ! periphery->matches(reference, eMatchMode, rootReference, group) )
      {
         continue;
      }

      foundAny=true;

      if( periphery->isAvailable(shareMode, rootReference) )
      {
         // Shared periphery is handled in resolveConsumes()
         if( !resolveConsumes(*periphery, rootReference))
         {
            // I Could not resolve the consumes of the periphery
            continue;
         }

          // I keep searching and check for the highest priority if enabled
         if(!foundPeriphery)
         {
            foundPeriphery=periphery.data();
         }
         else
         {
            if( periphery->getPriority() < foundPeriphery->getPriority() )
            {
               foundPeriphery=periphery.data();
            }
         }

         if( !usePriority )
         {
            break;
         }

         // Dont unlink an periphery thats already used by someone else / shared
         if ( !periphery->isUsed() )
         {
            periphery->unlink();
         }
      }
   }

   if( (!foundAny) && (eMatchMode == EMatchMode::peripheryType) )
   {
      reference.dump();
      QString requiredAttributeText;
      if( ! rootReference.getRequiredAttribute().isEmpty() )
      {
         requiredAttributeText = QString("(required attribute: %1)")
               .arg( rootReference.getRequiredAttribute() );
      }
      qCritical("%s: error: Periphery type '%s' does not exist at all %s"
                , qPrintable( m_pBoard->getMainFile() )
                , qPrintable( reference.getPeripheryType() )
                , qPrintable( requiredAttributeText ) );
      qCritical("MatchMode: %d", (int)eMatchMode);
      throw("Error");
   }

   if(foundPeriphery)
   {
      if( usePriority )
      {
         if(!resolveConsumes(*foundPeriphery, reference) )
         {
            foundPeriphery->dump();
            qWarning("Could not resolve consumes of peripheral for a second time.");
	    qWarning("This can happen when a given 'required'-attribute is not available for the periphery.");
	    qFatal("Unable to resolve application");
         }
      }
   }

   if(foundPeriphery)
   {
      checkFabricate( *foundPeriphery );
      //foundPeriphery = checkFabricateList( foundPeriphery );
   }

   reference.link(foundPeriphery);

   nestingCount--;

   return(foundPeriphery);
}


bool CResolverDefault::resolveConsumes(CPeriphery &periphery, const CReference &rootReference)
{
   if( !m_pBoard->getAlternateFunctionGroups().size() )
   {
      return( resolveConsumes(periphery, rootReference, "") );
   }
   QMap<QString, bool> map;
   CPeripheryPtr peripheryPtr;
   for ( QString group :m_pBoard->getAlternateFunctionGroups() )
   {
      if( map.contains(group) )
      {
         continue;
      }
      bool ret=resolveConsumes(periphery, rootReference, group);
      if(ret)
      {
         return(ret);
      }
      map[group]=true;

   }
   return(false);
}


bool CResolverDefault::resolveConsumes(CPeriphery &periphery, const CReference &rootReference, const QString &group)
{
   if(skipConsume(periphery, rootReference))
   {
      //predefinedPos+=periphery.getConsumesListConst().count();
      return(true);
   }

   for(CReference & reference : periphery.getConsumesList())
   {

      CPeripheryPtr pPeriphery=resolveReference(reference, rootReference, group );
      reference.setPeripheryLink( pPeriphery );
      if(pPeriphery)
      {
         // If the periphery is already used other ways (shared) we should
         // not set the link to the reference.
         if( !pPeriphery->isUsed() )
         {
            pPeriphery->setReferenceLink(&reference);
         }
      }

      if(!pPeriphery)
      {
         m_lastUnresolvable = reference.getName();
         periphery.unlink();
         return(false);
      }
      reference.setPeripheryLink(pPeriphery);
   }

   return(true);
}


/// \brief  If the periphery has relation 'fabricate', create a copy
void CResolverDefault::checkFabricate( CPeriphery &periphery )
{
   if( periphery.getRelation() == ERelation::fabricate )
   {
      QSharedPointer<CPeriphery> peripheryCopy=periphery.fabricate();
      m_pBoard->addPeriphery( peripheryCopy );
   }
}


/// \brief  Check if consume references have to be resolved
///
///         When the periphery and the reference are both shared, further
///         consume references do not be resolved.
bool CResolverDefault::skipConsume(const CPeriphery &periphery, const CReference &reference)
{
   if( periphery.getReferenceLink() )
   {
       if( periphery.getReferenceLink()->getId() != reference.getId() )
       {
         if( ( reference.getRelation() != ERelation::shared )
         &&  ( periphery.getRelation() != ERelation::shared )
         &&  ( periphery.getRelation() != ERelation::fabricate )
            )
         {
            //reference.dump();
            //periphery.dump();
            //periphery.getReferenceLink()->dump();
         }
         else
         {
            return(true);
         }
      }
   }
   return(false);
}


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