/***************************************************************************

    source::worx xtree
    Copyright © 2024-2025 c.holzheuer
    christoph.holzheuer@gmail.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

***************************************************************************/


#include <xqitemfactory.h>
#include <xqexception.h>
#include <xqdocumentstore.h>
#include <xqviewmodel.h>
#include <xqitemtype.h>

#include <znode_factory.h>


void XQItemFactory::initItemFactory( const QString& modelSheetFileName )
{
  auto configureItemType = [&, this](XQItemType* itemType, const XQNodePtr& sheetNode )
  {
    // über alle attribute
    for( const auto& [key,value] : sheetNode->attributes() )
    {
      //qDebug() << " --- conf item Type: " << key << " : " << value;
      setItemTypeDataFromString( *itemType, key, value );
    }
  };

  // schritt #1: modelbeschreibung laden
  XQNodeFactory treeLoader;
  _modelSheet = treeLoader.load_tree( qPrintable(modelSheetFileName) );

  // schritt #2: model root testen
  if (!_modelSheet)
    throw XQException("modelSheet load failed. ", modelSheetFileName);

  // schritt #3: itemtype beschreibungen laden ...
  _typesSheet =  _modelSheet->child( "ItemTypes" );
  // ... und testen
  if( !_typesSheet )
    throw XQException( "initItemFactory <ItemTypes> is null" );

  // alle itemtype vorlagen erzeugen
  for( const XQNodePtr& typeSheetNode : _typesSheet->children())
  {
    XQItemType* itemType = new XQItemType;
    const QString& typeName = typeSheetNode->tag_name();
    configureItemType(itemType, typeSheetNode);

    itemType->setText( typeName);
    s_ItemTypeTemplates[typeName] = itemType;
  }

}


bool XQItemFactory::isValid()
{
  return _modelSheet && _typesSheet;
}


//! Es reicht nicht, einen itemType aus den itemType-templates zu
//! holen: möglicherweise muss der noch mit zusätzlichen attributen
//! ergänzt werden.
//! Vom aufwand ist es auch egal, ob ich daten aus dem XML in das
//! item oder den itemtyp schreiben muss.

XQItemType* XQItemFactory::makeItemType(const XQNodePtr& sheetEntry )
{
  QString typeKey = sheetEntry->attribute( c_ItemType );
  XQItemType* itemType = findItemTypeTemplate(typeKey);

  // wir prüfen, ob im sheetEntry noch zusätzliche attribute vorhanden
  // sind, die wir in dem itemType müssen

  //qDebug() << " --- makeItemType: " << sheetEntry->to_string();

   // über alle attribute
  for (const auto& attrEntry : sheetEntry->attributes())
  {
    // prüfen, ob der itemType des attribute schon hat
    int role = itemType->roleForAttributeKey( attrEntry.first );
    // wenn ja, überschreiben
    if( role != XQItem::NoRole )
    {
      QVariant newValue = XQItem::makeVariant( itemType, role, attrEntry.second );
      itemType = itemType->replaceAttribute( newValue, role );
    }

  }
  return itemType;
}


//! sucht einen item typ aus der map mit 'vorgefertigen' itemtypen.

XQItemType* XQItemFactory::findItemTypeTemplate(const QString& key ) const
{
  if( !key.isEmpty() && s_ItemTypeTemplates.contains(key))
    return s_ItemTypeTemplates[key];  
  throw XQException( "itemfactory: findItemTypeTemplate: not found:", key );
}


//! sucht eine model-beschreibung

XQNodePtr XQItemFactory::findModelSheet( const QString& modelName ) const
{
  XQNodePtr modelSheet = _modelSheet->child( modelName );
  if( !modelSheet )
    throw XQException( "model sheet not found: ", modelName );

  return modelSheet;
}


//! erzeugt eine QVariant aus dem gegebenen string und setzt diese dann via role im item.

void XQItemFactory::setItemTypeDataFromString( XQItem& item, const QString& roleKey, const QString& source ) const
{
  int dataRole = XQItem::fetchItemDataRole( roleKey );
  if( dataRole != XQItem::NoRole)
  {
    QVariant variant = XQItem::makeVariant( &item, dataRole, source );
    if( !variant.isNull() && variant.isValid() )
      item.setData( variant, dataRole );
  }
}


//! erzeugt eine item-row.

XQItemList XQItemFactory::makeRow(const XQNodePtr& sheetNode, const XQNodePtr& contentNode )
{

  XQItemList list;

  // - Gehe über alle Einträge der Typbeschreibung:
  //
  // <Battery>
  //     <Voltage .../>
  //  -> <Capacity ../>
  //
  // - Nimm das dazugehörige  Attribut aus dem contentNode
  //  value = contentNode->attributes["Capacity"];
  //

  // __fix! Obacht! das setzt das vorhandensein des Contents voraus!

  for( const auto& sheetEntry : sheetNode->children() )
  {
    list.append( makeItem( sheetEntry, contentNode ) );
  }

  Q_ASSERT(!list.empty());

  // wir merken uns den original content node auch, aber
  // im ersten Item. Kann null sein, macht aber erstmal nix.
  dynamic_cast<XQItem*>(list[0])->setContentNode(contentNode);

  return list;
}


//! Erzeugt kind-items zu einem section-eintrag.

XQItemList XQItemFactory::makeChildRow( QStandardItem* parent, const XQNodePtr& sheetNode, const XQNodePtr& contentNode )
{
  XQItemList list;
  for (const auto& contentChild : contentNode->children())
  {
    const QString& contentKey = contentChild->tag_name();
    // wir brauchen ein beschreibenden sheetnode für diesen content-child knoten
    if( sheetNode->has_child( contentKey ))
    {
      const XQNodePtr& sheetChild = sheetNode->child(contentKey);
      list = makeRow( sheetChild, contentChild );
      // als kinder einfügen
      //insertRow( parent->row()+1, list );
      parent->appendRow( list );

    }
  }
  return list;
}

//! Erzeugt ein XQItem aus einer typ-beschreibung ('sheetNode') und einem daten-knoten ('contentNode').
//! Wenn der content node nicht gesetzt ist, wird stattdess das attribut 'Caption' aus der typ-beschreibung
//! verwendet: es handelt sich dann um ein header item

XQItem* XQItemFactory::makeItem(const XQNodePtr& sheetNode, const XQNodePtr& contentNode )
{
  // den itemtype des neuen items rausfinden
  XQItemType* itemType = makeItemType(sheetNode); // throws

  const QString* contentPtr{};

  // das ist Unterschied vom HeaderItem zum normalen Item: Der Titel kommt aus der Modelbeschreibung,
  // sonst wird der content indirekt über den tag-name des sheetnode geholt

  if( !contentNode )
    contentPtr = sheetNode->attribute_ptr(c_Caption);
  else
    contentPtr = contentNode->attribute_ptr( sheetNode->tag_name() );

  XQItem* newItem = new XQItem( itemType, contentPtr);

  // __fixme!
  if( newItem->isCheckable() )
  {
    newItem->setCheckState( Qt::Checked );
  }

  return newItem;
}

//! Erzeugt ein Item _ohne_ internen content node, sondern
XQItem* XQItemFactory::makeSingleItem( const XQNodePtr& sheetNode, const QString& caption )
{
  // den itemtype des neuen items rausfinden
  XQItemType* itemType = makeItemType(sheetNode); // throws
  XQItem* newItem = new XQItem( itemType, caption);
  // __fixme!
  if( newItem->isCheckable() )
  {
    newItem->setCheckState( Qt::Checked );
  }

  return newItem;
}
