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


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


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


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

#include <QString>
#include <QStringList>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <QFile>
#include <QDir>

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

#include "board.h"
#include "app.h"
#include "helpers.h"


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

int CBoard::parseJson(const QJsonObject &jsonObject, QStringList &includes){
   QJsonArray jsonArray = jsonObject["peripheries"].toArray();

   if(name.isEmpty())
   {
      name=jsonObject["name"].toString();
   }

   if(id.isEmpty())
      id=jsonObject["id"].toString();
   description=jsonObject["description"].toString();
   includes=jsonArrayToStringList( jsonObject["include"].toArray() );
   alias=jsonObject["alias"].toString();

   for(const auto &value: jsonArray)
   {
      QJsonArray subArray;
      QJsonObject object=value.toObject();
      QString name = object["name"].toString();
      QString peripheryName = object["periphery"].toString();
      CPeripheryPtr periphery;
      bool overloaded=false;
      bool doAddPeriphery=false;
      if(peripheryName.isEmpty())
         peripheryName=name;

      // Is there already periphery with the same priyphery name?
      // Then i will overload it
      if( (!peripheryName.isEmpty()) && (peripheryName!="1") && (peripheryName!="inherit") )
      {
         periphery=findPeripheryByPeripheryName(peripheryName);
         if( periphery )
         {
            if( object["type"].toString() != periphery->getPeripheryType() )
            {
               qCritical("Overloading periphery with non-matching type: %s", qPrintable(peripheryName) );
               qFatal("'%s' vs '%s'", qPrintable(object["type"].toString())
                     , qPrintable( periphery->getPeripheryType() ) );
            }
         }
      }

      if(periphery==nullptr)
      {
         periphery=CPeripheryPtr( new CPeriphery( name, object["type"].toString(), peripheryName) );
         doAddPeriphery=true;
      }
      else
      {
         overloaded=true;
      }

      if( object.contains("priority") )
         periphery->setPriority(object["priority"].toString().toInt());

      if(!object["relation"].isUndefined())
         periphery->setRelation(object["relation"].toString());

      // If no status is given, also set it to enabled. Compatible with all constelations
      if( (!overloaded) || (periphery->getEnabled() == false ) )
      {
         periphery->setEnabled( ( object["status"].toString()=="disabled")?false:true);
      }

      QJsonArray entries=object["consumes"].toArray();
      for(int i2=0; i2<entries.count(); i2++)
      {
         CReference reference(
                                 entries[i2].toObject()["name"].toString(),
                                 entries[i2].toObject()["type"].toString(),
                                 entries[i2].toObject()["periphery"].toString() );
         reference.parseJSON( entries[i2].toObject(), getId() );
         periphery->addConsumes(reference);
      }

      periphery->addJsonObject( object );
      periphery->addJsonObject( object["attributes"].toObject() );
      //periphery->setAttributes( jsonObjectToStringMap(object["attributes"].toObject() ) );
      if(object["alternateFunctions"].isString())
      {
         qFatal("Its a string");
      }
      else
      {
         periphery->addAlternateFunctions( jsonObjectToStringMap(object["alternateFunctions"].toObject() ) );
         m_alternateFunctionGroups += periphery->getAlternateFunctionGroups();
      }
      if(doAddPeriphery)
      {
         addPeriphery( QSharedPointer<CPeriphery>(periphery.data()) );
      }
   }

   loadProperties(jsonObject);
   loadOverloadingAttributes(jsonObject);

   if(m_final)
   {
      QString final=jsonObject["final"].toString();
      if( (final != "yes" ) && (final != "true" ) )
      {
         qFatal("Used board is not final. You probably want to use an inheriting board. Board: '%s'", qPrintable(name));
      }
   }
   m_final=false;
   return(0);
}


void CBoard::loadProperties(const QJsonObject &jsonObject)
{
   QVariantMap map = jsonObject["properties"].toObject().toVariantMap();
   // QMap::insert(const QMap&) Qt 5.15
   for( auto key: map.keys())
   {
     m_properties[key]=map[key];
   }

   return;
}

void CBoard::loadOverloadingAttributes(const QJsonObject &jsonObject)
{
   QVariantList list = jsonObject["overloadingAttributes"].toArray().toVariantList();
   m_overloadingAttributes.append(list);

   return;
}


bool CBoard::loadFile(const QString &fileName)
{
   QFile file;
   bool sta=true;

   addPath( QFileInfo( fileName ).dir().path() );
   m_loadedFiles+=fileName;

   file.setFileName(fileName);
   if (! file.open(QIODevice::ReadOnly | QIODevice::Text) )
   {
      qFatal("Could not open file: %s (%d)", qPrintable(fileName), (int)file.error());
   }

   sta=loadByteArray( file.readAll(), fileName );
   if( !sta )
   {
      qDebug() << "Parse Error in file " << fileName << ": " ;
   }
   file.close();

   return(sta);
}


bool CBoard::loadByteArray(const QByteArray &array, const QString &fileName)
{
    QJsonDocument settdoc;
    QJsonObject jsonObject;
    QJsonParseError error;
    QStringList inherits;
    bool sta=true;

    settdoc=QJsonDocument::fromJson(array, &error);
    if(error.error != QJsonParseError::ParseError::NoError )
    {
       qDebug() << "Parse Error in file " << fileName << ": " ;
       qDebug() << error.errorString() << " at offset " << error.offset;
       if(error.offset>40)
       {
          QString line=array.mid(error.offset-40, 80);
          qDebug() << "Line: " << line;
       }
       return(false);
    }
    jsonObject = settdoc.object();

    parseJson(jsonObject, inherits);

    //if( inherits.count() )
    for(const auto &inherit: inherits )
    {
       if( ! inherit.isEmpty() )
       {
          QDir dir;
          QString includeFileName=findPathFile( inherit );
          if( !includeFileName.isEmpty() )
          {
            sta=loadFile( includeFileName );
          }
           else
          {
                qFatal("%s: error: Can not inherit unknown board:%s", qPrintable(fileName), qPrintable(inherit));
          }
       }
    }
    return(sta);
}


bool peripheryLessThan(const QSharedPointer<CPeriphery> &v1, const QSharedPointer<CPeriphery> &v2)
{
    return v1->getSortId() < v2->getSortId();
}


void CBoard::shuffle()
{
   std::sort(list().begin(), list().end(), peripheryLessThan);
}


bool CBoard::isOverloadingAttribute(const QString &attribute) const
{
   return(m_overloadingAttributes.contains(attribute));
}

QString CBoard::getOverloadingAttributes() const
{
   QString str;
   for(auto const &e :m_overloadingAttributes)
   {
      str+=str.isEmpty()?"":",";
      str+=e.toString();
   }
   return(str);
}


void CBoard::cleanupMissingOptions(  )
{
   // iterate over the peripheries
   for(auto &periphery :list() )
   {
      int i1=0;
      int toBeRemoved=-1;

      do
      {
         toBeRemoved=-1;
         i1=0;
         for(auto &consume :periphery->getConsumesList() )
         {
            if( !consume.getAttribute( "optional" ).isEmpty() )
            {
               // keep it only when the apps options alsa have this option set
               if( ! m_options.contains( consume.getAttribute( "optional" ) ) )
               {
                  toBeRemoved = i1;
                  qWarning() << "Removing"
                             << consume.getName() << "due to 'optional'";
                  break;
               }
            }
            i1++;
            if( toBeRemoved >= 0 )
            {
               break;
            }
         }

         if( toBeRemoved >= 0 )
         {
            qWarning() << "Removing index " << toBeRemoved;
                       //<< periphery->getConsumesList()[toBeRemoved].getName();
            periphery->getConsumesList().removeAt( toBeRemoved );

         }

      } while ( toBeRemoved >= 0 );
   }

}

/*QString CBoard::findBoardFile( QString boardId )
{
   for( auto dir: m_path)
   {
      QString fileName=QString("%1/%2.json").arg( dir ).arg( boardId );
      QFile file( fileName );
      if( file.exists() )
      {
         qWarning() << "### Exist: " << fileName;
         return( fileName );
      }
   }
   return { };
}
*/

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