//----------------------------------------------------------------------------
///
/// \file   board.cpp
///
/// \brief  class describing the periphery of an MCU board
///
/// \date   20180129
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//----------------------------------------------------------------------------


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


//---Defines------------------------------------------------------------------

#define  MAKRO_IDENT    35

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


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

#include <QJsonDocument>
#include <QFile>
#include <QDir>
#include <QDebug>

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

#include "board.h"
#include "app.h"
#include "pinmux.h"
#include "resolverPredefined.h"
#include "resolverDefault.h"


//--- Implementation ----------------------------------------------------------


void CPinmux::reset()
{
   for (auto &reference: m_pApp->getReferenceList())
   {
      reference.setPeripheryLink(nullptr);
   }

   for ( const auto &periphery: m_pBoard->list())
   {
      periphery->setReferenceLink(nullptr);
      for( auto &consumes: periphery->getConsumesList())
      {
         if ( consumes.getPeripheryLink() )
         {
            consumes.getPeripheryLink()->setReferenceLink( nullptr );
         }
         consumes.setPeripheryLink(nullptr);
      }
   }
}

bool CPinmux::checkFinished(CReferencePtr *ptr)
{
   for (CReference &reference: m_pApp->getReferenceList())
   {
      if( reference.isFiltered() )
      {
         continue;
      }
      if(!reference.getPeripheryLink())
      {
         if(ptr)
            *ptr=CReferencePtr(&reference);
         return(false);
      }
   }
   return(true);
}

void CPinmux::resolvePrefered()
{
   for (CReference &reference: m_pApp->getReferenceList())
   {
      if( reference.isFiltered() )
      {
         continue;
      }
      
      if(!reference.getPredefined().isEmpty())
      {
         CResolverPredefined resolver(getApp(), getBoard());
         //CPeripheryPtr periphery=resolveReference(reference, reference, preferedPos);
         CPeripheryPtr periphery=resolver.resolveReference( reference );
         if(!periphery)
         {
            QString err=QString("Could not resolve periphery '%1' by prefered, type '%2', prefered: '%3'")
                  .arg(reference.getName()).arg(reference.getPeripheryType()).arg(reference.getPredefined());
            errorExit( qPrintable(err) );
         }
      }
   }
}

void CPinmux::resolveByRandom()
{
   bool finished=false;
   bool doBreak;
   QString problems;
   CReference *ref=nullptr;
   // Don't use the systems random generator. We want something reproducable
   QRandomGenerator rnd(123);
   CResolverDefault resolver( getApp(), getBoard() );

   // 260 Loops have been observed
   do {
      //qWarning("Loop %d", m_loops);
      reset();
      resolvePrefered();
      doBreak=false;
      int innerLoops=0;

      do{
         CReference &reference=m_pApp->getReferenceList()[ rnd.generate() % m_pApp->getReferenceList().size()];
         
         if( reference.isFiltered() )
         {
            finished=checkFinished();
            continue;
         }
         
         if(reference.getPeripheryLink())
         {
            // It can occure somehow that there are no references left.
            // Must not stuck in infinite loop.
            finished=checkFinished();
            continue;
         }
         CPeripheryPtr periphery=resolver.resolveReference(reference, reference);

         if(!periphery)
         {
            ref=&reference;
            if(  (reference.getPeripheryType()=="LED")
              || (reference.getPeripheryType()=="OUTPUT")
              || (reference.getPeripheryType()=="GPIO") )
            {
               errorExit( QString("%1: error: An %2 could not be solved? Thats nearly impossible. (Required attribute: '%3')")
                          .arg( getBoard()->getMainFile() )
                          .arg(reference.getPeripheryType() )
                          .arg(reference.getRequiredAttribute() ));
            }
            doBreak=true;
         }

         if( (!periphery) && (innerLoops==0) )
         {
            qWarning () << "Last unresolvable:" << resolver.getLastUnresolvable();
            resolveErrorExit( reference );
         }

         if(!doBreak)
         {
            finished=checkFinished();
         }
         innerLoops++;
         if(innerLoops>200)
         {
            errorExit("Inner loop ran more than 200 times. Something inlinked my periphery");
         }
      } while( (!doBreak) && (!finished) );

      m_loops++;

   } while( (!finished) && ( m_loops<600) );

   if(!finished)
   {
      {
         qWarning("%s: error: Could not resolve application; %d loops"
                        , qPrintable( m_pApp->getMainFile() ), m_loops);
         qWarning("Problems: %s", qPrintable(problems));
         if(ref)
         {
            qWarning("Causing reference: %s, type: %s", qPrintable(ref->getName())
                     , qPrintable(ref->getPeripheryType())
                     );
         }
         else
         {
            ;
         }
         dump("random.dump");
         reset();
         dump("random_reset.dump");
         resolvePrefered();
         dump("random_prefered.dump");
         throw(QString("could not resolve app"));
      }
   }
   else
   {
      qWarning("Successfully resolved application after %d loops.", m_loops);
   }
}

void CPinmux::resolveLinear()
{
   CResolverDefault resolver( getApp(), getBoard() );

   for (CReference &reference: m_pApp->getReferenceList())
   {
      if( reference.isFiltered() )
      {
         continue;
      }

      if(reference.getPredefined().isEmpty())
      {
         CPeripheryPtr periphery=resolver.resolveReference(reference, reference);
         if(!periphery)
         {
            errorExit( QString("Could not find periphery '%1' linear, type '%2'")
                       .arg(reference.getName()).arg(reference.getPeripheryType())
                       );
            throw(QString("could not resolve app: %d"));
         }
      }
   }
}

void CPinmux::resolveByShuffle()
{
   bool resolved=false;
   int loops=0;

   for(loops=0; (loops<100) && (!resolved); loops++)
   {
      reset();
      resolvePrefered();

      try
      {
         m_pBoard->shuffle();
         resolveLinear();
         resolved=true;
      }
      catch( ... )
      {

      }
   }

   if(!resolved)
   {
      qWarning("Could not resolve board");
      throw(QString("could not resolve app: %d"));
   }

}

void CPinmux::resolveApp( EMethod eMethod /*=EMethod::random*/ )
{
   qWarning("Resolving Application with board: "
         "   %s -> %s", qPrintable( m_pApp->getName() ), qPrintable( m_pBoard->getName() ) );
   qWarning("   Mode: %d", eMethod );

   switch ( eMethod )
   {
      case EMethod::random:
         resolveByRandom();
         break;
      case EMethod::shuffle:
         resolveByShuffle();
         break;
      case EMethod::linear:
         resolveLinear();
         break;
      default:
         qFatal("unknown method");
   }

   checkPlausibility();
#if 0
   for (const auto &periphery: m_pBoard->listConst())
   {
      const CConstructor *constructor=m_constructorPool.getConstructor( CPeripheryConstPtr(&*periphery) );
      if(constructor==nullptr)
         continue;
   }
#endif
   reOrder();
   generateIrqList();
}


void CPinmux::dump(QString fileName)
{
   QFile file( fileName );
   if(file.open(QIODevice::WriteOnly | QIODevice::Text))
   {
       // We're going to streaming text to the file
       QTextStream stream(&file);
       m_pBoard->dump(stream);
   }
}

void CPinmux::generateIrqList()
{
   irqHandlers.clear();
   for ( const auto &periphery: m_pBoard->list())
   {
      CReferencePtr pReference=periphery->getReferenceLink();
      if( periphery->isUsed() )
      {
         if( pReference && pReference->getUseIrq())
         {
            QStringList irqs;
            if( periphery->getIrqs().size() )
            {
               irqHandlers+=periphery->getIrqs();
            }
            for ( const auto &consumesReference: periphery->getConsumesListConst() )
            {
               if( consumesReference.getPeripheryLink()->getIrqs().size() )
               {
                  irqHandlers+=consumesReference.getPeripheryLink()->getIrqs();
               }
            }
         }
      }
   }
}

void CPinmux::reOrder()
{

   for(auto periphery:m_pBoard->list())
   {
      if(periphery->isUsed())
      {
         // qWarning() << "Reordering " << periphery->getPeripheryName();
         periphery->calculateOrder();
      }
   }
   std::sort(m_pBoard->list().begin(),
             m_pBoard->list().end() ,
             [](auto &a, auto &b) {
                        return (a->getOrder() < b->getOrder());
                     }
             );

   return;
}


void CPinmux::checkPlausibility()
{
   for(auto periphery:m_pBoard->list())
   {
      if(periphery->isUsed())
      {
         if( periphery->getReferenceLink() )
         {
            if( periphery->getReferenceLink()->getPeripheryLink() != periphery.data() )
            {
               qWarning("Error: Not plausible: %d", periphery->getId());
            }
         }
      }
   }
}


void CPinmux::errorExit(QString message) const
{
   qCritical( "%s", qPrintable(message) );
   throw(message);
}


void CPinmux::resolveErrorExit( CReference& reference ) const
{
   int count=0;
   int usedCount=0;
   QSharedPointer <CPeriphery> p=m_pBoard->getPeripheryTypeCount( reference.getPeripheryType(), count, usedCount );
   
   qCritical()
      << QString("%1: error: Could not resolve reference even on  the"
         " first try (after predefined peripheries). ").arg( getBoard()->getMainFile() );
   
   qCritical()
      << QString(
                     "There is probably an problem in your board files. Periphery type: %1. Reuired attribute: %2."
                     " The periphery type is available %3 times, %4 of them used.")
                 .arg(reference.getPeripheryType() )
                 .arg( reference.getRequiredAttribute().isEmpty()
                          ? QString("None")
                          : reference.getRequiredAttribute()
                      )
                 .arg( count )
                 .arg( usedCount );
   qCritical()
       << "Name of free:" << p->getPeripheryName();
   errorExit( "fin." );
}


void CPinmux::filterSubsetVariant( const QString& variant )
{
   m_pApp->setVariant(variant);
#if 0
   for(auto periphery:m_pBoard->list())
   {
      periphery->setFilterOut(false);
      if(periphery->isUsed())
      {
         CVariant referenceVariant=periphery->getReferenceLink()->getAttribute("variant");
         //CVariant referenceVariant=periphery->getRootReference()->getAttribute("variant");
         if( ! referenceVariant.matches( m_pApp->getVariant() ) )
         {
            periphery->setFilterOut(true);
            /*
            CPeripheryPtr parent=periphery->getReferenceLink()->getParent();
            if(parent)
            {
               parent->setFilterOut(true);
            }
            */
            for(auto consumed: periphery->getConsumesList())
            {
               consumed.getPeripheryLink()->setFilterOut(true);
            }
         }
      }
   }
#endif
   int cnt=0;
   
   for(auto& reference:m_pApp->getReferenceList())
   {
      //reference.setFilterOut(false);
      qWarning("Check %s: %s", qPrintable( reference.getName() ), qPrintable( reference.getAttribute("variant") ) );
      CVariant referenceVariant=reference.getAttribute("variant");
      if( ! referenceVariant.matches( m_pApp->getVariant() ) )
      {
         reference.filterOut();
         cnt++;
      }
   }

   #if 0
   if(!cnt)
   {
      qFatal("Subset variant did not filter anything");
   }
   #endif
   
   return;
}


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