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

    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 <xqitem.h>

#include <xqviewmodel.h>
#include <xqmaptor.h>
#include <QDateTime>
#include <xqitemtype.h>

#include <QDebug>


XQItem::XQItemFlagMap XQItem::s_ItemFlagMap
{
  { "NoItemFlags",           Qt::NoItemFlags },
  { "IsSelectable",          Qt::ItemIsSelectable },
  { "IsEditable",            Qt::ItemIsEditable },
  { "IsDragEnabled",         Qt::ItemIsDragEnabled },
  { "IsDropEnabled",         Qt::ItemIsDropEnabled },
  { "IsUserCheckable",       Qt::ItemIsUserCheckable },
  { "IsEnabled",             Qt::ItemIsEnabled },
  { "IsAutoTristate",        Qt::ItemIsAutoTristate },
  { "ItemNeverHasChildren",  Qt::ItemNeverHasChildren },
  { "IsUserTristate",        Qt::ItemIsUserTristate }
};

XQItem::XQItemDataRoleMap XQItem::s_ItemDataRoleMap
{
  {"ItemType", ItemTypeRole},
  {"Content",  ContentRole},
  {"RenderStyle", RenderStyleRole},
  {"EditorType", EditorTypeRole},
  {"ItemFlags", FlagsRole},
  {"UnitType", UnitTypeRole},
  {"ContentFormat", ContentFormatRole},
  {"FlagsRole", FlagsRole},
  {"Icon", IconRole},
  {"FixedChoices", FixedChoicesRole},
  {"DataNode", ContentNodeRole},
  {"SheetNode", SheetNodeRole}
};

// No bi-map needed here, qmap.key() is sufficient for the job
XQItem::XQRenderStyleMap XQItem::s_RenderStyleMap
{
  { "NoRenderStyle",     NoRenderStyle },
  { "HiddenStyle",       HiddenStyle },
  { "HeaderStyle",       HeaderStyle },
  { "PlainStyle",        PlainStyle },
  { "CheckBoxStyle",     CheckBoxStyle },
  { "ComboBoxStyle",     ComboBoxStyle },
  { "TreeHeaderStyle",   TreeHeaderStyle },
  { "CustomRenderStyle", CustomRenderStyle },
  { "PickerStyle",       PickerStyle },
  { "SpinBoxStyle",      SpinBoxStyle },
  { "ColorBarStyle",  ColorBarStyle},
  { "FormattedStyle",    FormattedStyle},
};

XQItem::XQEditorTypeMap XQItem::s_EditorTypeMap
{
  { "NoEditorType",     NoEditorType },
  { "LineEditType",     LineEditType },
  { "ComboBoxType",     ComboBoxType },
  { "PickerType",       PickerType },
  { "ColorBarType",  ColorBarType },
  { "SpinBoxType",      SpinBoxType},
  { "CustomEditorType", CustomEditorType}
};

XQItem::XQUnitTypeMap XQItem::s_UnitTypeMap
{
  { NoUnitType, "NoUnitType" },
  { Ampere,     "A" },
  { Volt,       "V" },
  { Ohm,        "Ohm" },
  { Farad,      "C" },
  { Watt,       "W" },
  { WattPeak,   "Wp" },
  { WattHour,   "Wh" },
  { Second,     "s" },
  { Percent,    "%" },
  { Hertz,      "Hz" },
  { Meter,      "m" },
  { Kg,         "kg" },
  { ISODate,    "ISODate" }, // fixme: ISO-Date is present, but has no Unit ?!?
};

XQItem::XQPrefixExponentMap XQItem::s_PrefixExponentMap
{
  { "p", -12 }, // pico
  { "n", -9 }, // nano
  { "µ", -6 }, // micro
  { "m", -3 }, // Milli
  { "" , 0 },  // No prefix means multiplier of 1
  //{ " ", 0 },  // No prefix means multiplier of 1
  { "k", 3 },  // Kilo
  { "M", 6 },  // Mega
  { "G", 9 },  // Giga
  { "T", 12 }, // Tera
  { "P", 15 }, // Peta
  { "E", 18 }, // Exa
  { "Z", 21 }, // Zetta
  { "Y", 24}, // Yotta
};


//! Default konstruktor, setzt einen ungültigen (dummy)
//! itemType.

XQItem::XQItem()
  : XQItem{XQItemType::staticItemType()}
{

}


//! Default konstruktor mit einem vorhandenen itemType.

XQItem::XQItem( XQItemType* itemType )
    : QStandardItem{}
{
  setItemType( itemType );
}


//! konstruiert ein daten-item mit zeiger auf 'unser' attribut
//! im übergeordneten content-node.

XQItem::XQItem(XQItemType* itemType, const QString* content )
  : XQItem{ itemType }
{
  // hier setzen wir direkt ohne umwege den string pointer
  QStandardItem::setData( QVariant::fromValue<const QString*>(content), XQItem::ContentRole );
}

XQItem::XQItem( XQItemType* itemType, const QString& content )
  : XQItem{ itemType }
{
   setText(content);
}
//! ruft den copy-konstruktor auf.

XQItem* XQItem::clone() const
{
  return new XQItem( *this );
}


//! false für ein ungültiges item. 'ungültig' heisst hier, dass nur ein
//! mockup-itemtype gesetzt ist.

bool XQItem::isValidX() const
{
  XQItemType* dummyType = XQItemType::staticItemType();
  return QStandardItem::data( XQItem::ItemTypeRole ).value<XQItemType*>() != dummyType;
}


bool XQItem::hasContentNode() const
{
  if( column() == 0)
  {
    QVariant value = QStandardItem::data( XQItem::ContentNodeRole );
    return !value.isNull();
  }

  // sonst: delegieren an den node-Besitzer
  QModelIndex pIndex = model()->index( row(), 0  );
  if( pIndex.isValid() )
  {
    XQItem& firstItem =  xqItemFromIndex( pIndex );
    return firstItem.hasContentNode();
  }
  return false;
}

//! gibt den content-node zurück.

XQNodePtr XQItem::contentNode() const
{
  return data( ContentNodeRole ).value<XQNodePtr>();
}


//! setzt den content node.

void XQItem::setContentNode( const XQNodePtr& contentNode )
{
  QStandardItem::setData( QVariant::fromValue(contentNode), ContentNodeRole);
}


//! gibt den sheet-node zurück.

XQNodePtr XQItem::sheetNode() const
{
  return data( SheetNodeRole ).value<XQNodePtr>();
}

//! setzt den sheet-node

void XQItem::setSheetNode(const XQNodePtr& sheetNode )
{
  QStandardItem::setData( QVariant::fromValue(sheetNode), SheetNodeRole);
}


//! gibt eine referenz auf den itemType dieses items zurück.

XQItemType& XQItem::itemType() const
{
  // __fix: wir gehen hier davon aus, das der itemType immer existiert,
  //  nur weil er in jeden konstruktor gesetzt wird, das muss aber nicht
  // so sein!
  XQItemType* itemTypePtr = QStandardItem::data( XQItem::ItemTypeRole ).value<XQItemType*>();
  return *itemTypePtr;
  // should_throw

}


//! speichert einen neuen itemType.

void XQItem::setItemType( XQItemType* itemTypePtr )
{
  // der ItemType wird direkt hier gespeichert
  QStandardItem::setData( QVariant::fromValue(itemTypePtr), XQItem::ItemTypeRole );
}


//! set ein einzelnes itemFlag.

void XQItem::addFlag( Qt::ItemFlag newFlag )
{
  setFlags( flags() | newFlag );
}


//! löscht ein einzelnes itemFlag.

void XQItem::clearFlag( Qt::ItemFlag newFlag )
{
  setFlags( flags() & ~newFlag);
}


//! gibt die itemFlags als string zurück.

QString XQItem::itemFlagsToString() const
{
  return'(' + data(XQItem::FlagsRole).toString() +')';
}

///
/// data() access shortcuts
///

//! gibt den renderStyle zurück.

XQItem::RenderStyle XQItem::renderStyle() const
{
  return data( RenderStyleRole ).value<RenderStyle>();
}


//! gibt den renderStyle als string zurück.

QString XQItem::renderStyleToString() const
{
  return XQItem::fetchRenderStyleToString( renderStyle() );
}



//! setzt den editorType. wird im itemType gespeichert.

void XQItem::setRenderStyle(RenderStyle renderStyle )
{
  setData( QVariant::fromValue(renderStyle), XQItem::RenderStyleRole );
}


//! gibt den editorType zurück.

XQItem::EditorType XQItem::editorType() const
{
  return data( EditorTypeRole ).value<EditorType>();
}


//! gibt den renderStyle als string zurück.

QString XQItem::editorTypeToString() const
{
  return XQItem::fetchEditorTypeToString( editorType() );
}

//! setzt den editorType. wird im itemType gespeichert.

void XQItem::setEditorType(EditorType editorType)
{
  setData( QVariant::fromValue(editorType), XQItem::EditorTypeRole);  
}


//! setzt den unitType.

XQItem::UnitType XQItem::unitType() const
{
  return data( XQItem::UnitTypeRole ).value<UnitType>();
}


//! gibt den unitType als string zurück.

QString XQItem::unitTypeToString() const
{
  return XQItem::fetchUnitTypeToString( unitType() );
}


//! setzt den editorType. wird im itemType gespeichert.

void XQItem::setUnitType(UnitType unitType)
{
  setData( QVariant::fromValue(unitType), XQItem::UnitTypeRole);
}


//! Verweist auf data(Qt::EditRole). Das ist der unformatierte text.

QString XQItem::rawText() const
{
  return data( Qt::EditRole ).toString();
}


//! Gibt den string-zeiger auf das attribut aus unseren XQNodePtr zurück.
/*
QString* XQItem::content() const
{
  // macht jetzt das, was draufsteht: gibt einen string* zurück
  return data( XQItem::ContentRole ).value<QString*>();
}
*/


//! Gibt den content-format string zurück

QString XQItem::contentFormat() const
{
  return data( XQItem::ContentFormatRole ).toString();
}

//! Setzt den den content format-string. wird im itemType gespeichert.

void XQItem::setContentFormat(const QString& contentFormat)
{
  setData( QVariant::fromValue(contentFormat), XQItem::ContentFormatRole);
}


//! Gibt das read-only auswahl-model zurück (wenn dieses item als
//! combobox gerendert wird). wird im itemType gespeichert.

QStandardItemModel* XQItem::fixedChoices() const
{
  return data( XQItem::FixedChoicesRole ).value<QStandardItemModel*>();
}


//! erzeugt einen string aus den werten des read-only auswahl-models

QString XQItem::fixedChoicesToString() const
{
  QStandardItemModel* model = fixedChoices();
  if( !model || model->rowCount() == 0)
    return QString("()");

  QString result = "(";

  int rc = model->rowCount();
  for (int row = 0; row < rc; ++row)
  {
    const QString text = model->item(row)->text();
    result += text;
    if(row < rc-1)
      result += "|";
  }
  result += ")";

  return result;
}

//! setzt das auswahl-model für read-only comboboxes

void XQItem::setfixedChoices( QStandardItemModel* newModel )
{  
  setData( QVariant::fromValue(newModel), XQItem::FixedChoicesRole);
}

//! true, wenn 'ich' ein header item bin

bool XQItem::isHeaderStyle()
{
  return renderStyle() == XQItem::HeaderStyle;
}


//! gibt den namen der datarole zurück

QString XQItem::dataRoleName(int role) const
{
  if( role < XQItem::NoRole && model() )
    return model()->roleNames()[role];
  return XQItem::fetchItemDataRoleName(role);
}

bool XQItem::hasContentPtr() const
{
  return !QStandardItem::data( XQItem::ContentRole ).isNull();
}

//! Gibt den content()-String zurück, sofern vorhanden.
//! sonst: gibt der ihnalt der Qt::DisplayRole als fallback
//! zurück.

QString XQItem::contentFallBackText() const
{
  if( hasContentPtr() )
  {
    const QString* contentPtr = QStandardItem::data( XQItem::ContentRole ).value<const QString*>();
    if(contentPtr)
      return *contentPtr;
  }

  // wenn wir keinen contentPtr haben, benutzen wir als fallback
  // die basis-text() role
  return QStandardItem::data( Qt::DisplayRole ).toString();

}

//! angespasste variante von qstandarditem::setData. geteilte attribute
//! werden vom xqitemtype geholt

QVariant XQItem::data(int role ) const
{
  //emitDataChanged()
  switch(role)
  {

    //
    // Die im ItemType ausgelagerten Daten werden
    // von da geholt.
    //

    case FlagsRole: // aka Qt::ItemDataRole(Qt::UserRole - 1),
    case IconRole:  // aka Qt::DecorationRole,
    case RenderStyleRole:
    case EditorTypeRole:
    case UnitTypeRole:
    case ContentFormatRole:
    case FixedChoicesRole:
    {
      return itemType().data(role);
    }

    case XQItem::ContentRole:
    {
      qDebug() << " --- data(XQItem::ContentRole) should NOT be called!";
      return *QStandardItem::data( XQItem::ContentRole ).value<QString*>();
    }

    // EditRole & ContentRole sollen den 'rohen' inhalt unseres string-pointers
    // auf den original inhalt im content node zurückgeben.

    case Qt::EditRole :  
    {
      // Zugriffe auf den text-inhalt geben den inhalt des string pointer
      // auf ein feld in content-node wieder. Wenn kein content-node vorhanden
      // ist (single-items), wird Qt::DisplayRole zurückgeliefert.

      return contentFallBackText();
      //[[fallthrough]];
    }

    // DisplayRole gibt den formatierten inhalt wieder. die formatierung übernimmt
    // der item type

    case Qt::DisplayRole :
    {
      QString plainText = contentFallBackText();
      //if( renderStyle() == XQItem::FormattedStyle)
      if( unitType() != XQItem::NoUnitType)
        return XQItemType::formatToSI( plainText, unitType() );
      return plainText;
    }

    case Qt::ToolTipRole:
    {
      return itemType().text() + ":: " + rawText() + ":"  + unitTypeToString() + ":" + renderStyleToString() + ":" + unitTypeToString() + ":" + itemFlagsToString();
    }

    //
    // Die lokal verwalteten Resourcen werden über QStandardItem::data(role)
    // abgewickelt.
    //

    case ContentNodeRole:
    {
      // Das Node-Besitzer-Item wohnt in der ersten Spalte,
      // wenn wir also der  Node-Besitzer item sind ...
      if( column() == 0)
      {
        QVariant value = QStandardItem::data( XQItem::ContentNodeRole );
        if( !value.isNull() )
          return value;

        // das gibt immerhin was zurück, was auf nullptr getestet werden kann,
        return QVariant::fromValue<XQNodePtr>(nullptr);

        // diese variante erzieht uns zur verwendung von 'hasContentNode()'
        // was ist besser ?
        throw XQException( "ContentNode is nullptr!");
      }

      // sonst: delegieren an den node-Besitzer
      QModelIndex pIndex = model()->index( row(), 0  );
      if( pIndex.isValid())
      {
        XQItem& firstItem =  xqItemFromIndex( pIndex );
        return firstItem.data( XQItem::ContentNodeRole );
      }
      throw XQException( "Item has no valid index (yet)!");
    }

    case Qt::StatusTipRole:
    case Qt::WhatsThisRole:
    case Qt::SizeHintRole:


    case Qt::FontRole:
    case Qt::TextAlignmentRole:
    case Qt::BackgroundRole:
    case Qt::ForegroundRole:
    case Qt::CheckStateRole:
    case Qt::InitialSortOrderRole:

    default:
      break;

  }

  return QStandardItem::data(role);

}

/*!

  Überschreibt setData() vom QStandardItem. Es wird unterschieden zwischen den data roles, die von
  'unserem' itemType verwaltet und gespeichert werden und unseren eigenen. ein XQItem enthält nur folgende
  Felder:

  // - die ItemFlags (geerbt vom QStandardItem) stimmt nicht!
  - den pointer auf den ItemType
  - den pointer auf den inhalts-string: content()

  Die anderen werte werden vom itemType verwaltet:

  - der EditorType
  - der RenderStyle
  - der UnitType
  - das ContentFormat
  - das Icon
  - ggf. das choiceModel, falls dieses Item als combobox gerendert wird.

*/

void XQItem::setData(const QVariant& value, int role )
{
  switch(role)
  {
    case RenderStyleRole :
    case EditorTypeRole :
    case UnitTypeRole:
    case ContentFormatRole:
    case FlagsRole: // Stimmt das?
    case IconRole:
    case FixedChoicesRole:
    {
      //qDebug() << " ---call type set Data: " << role << ": " << XQItem::fetchItemDataRoleName(role) << ":" << value.toString();
      XQItemType* oldType = &itemType();
      XQItemType* newType = oldType->replaceAttribute(value, role );
      if( oldType != newType )
        setItemType( newType );

      emitDataChanged();
      // no break, return!
      return;
    }

    case Qt::DisplayRole:
    case Qt::EditRole:
    case XQItem::ContentRole:
    {
      QVariant newValue;

      //if( itemType().renderStyle() == XQItem::FormattedStyle)
      if( unitType() != XQItem::NoUnitType)
        newValue = XQItemType::unFormatFromSI( value.toString() );
      else
        newValue = value;

      // fallback: wenns keinen content node gibt, dann nehmen wir
      // das standardverfahren.
      if( !hasContentPtr() )
        return QStandardItem::setData( newValue, Qt::DisplayRole );

      // wir nehmen den string pointer
      const QString* constContentPtr = QStandardItem::data( XQItem::ContentRole ).value<const QString*>();
      // aua, aua, muss aber sein, weil sonst alle anderen consts nicht durchgehalten werden könnten
      *const_cast<QString*>(constContentPtr) = newValue.toString();
      return;
    }

    // alles andere wie gehabt
    case ContentNodeRole:
    case SheetNodeRole:
    //case TypeKeyRole: not used
    default:
      break;
  }

  // hier: behandlung wie gehabt
  QStandardItem::setData( value,role);

}

//! erzeugt eine QVariant zur gegebenen rolle aus dem gegebenen string.
//! Hack: Das Item wird hier als zusätzliche datenquelle übergeben,
//! um _vorher_ erzeugte eigenschaften des items als parameter für _jetzt_
//! erzeugte eigenschaft verwenden zu können.
//! Beispiel

QVariant XQItem::makeVariant( XQItem* item, int dataRole, const QString& source )
{

  QVariant value;

  //qDebug() << " ----- makeVariant: " << XQItem::fetchItemDataRoleName( dataRole );

  switch(dataRole)
  {
  // das ist ein pointer auf den  original-string aus dem XML
  case XQItem::ContentRole:
  {
    // content() -> QString*
    value = QVariant::fromValue(&source);
    break;
  }

    /*
    case XQItem::ItemTypeRole:
    {
      // itemType() -> XQItemType*
      XQItemType* itemType = findItemTypeTemplate( source );
      value = QVariant::fromValue(itemType);
      break;
    }
    */

  case XQItem::RenderStyleRole:
  {
    XQItem::RenderStyle renderStyle = XQItem::fetchRenderStyle( source );
    value = QVariant::fromValue(renderStyle);
    break;
  }

  case XQItem::EditorTypeRole:
  {
    XQItem::EditorType editorType = XQItem::fetchEditorType( source );
    value = QVariant::fromValue(editorType);
    break;
  }

  case XQItem::UnitTypeRole:
  {
    //qDebug() << " --- make unit type: " << source;
    XQItem::UnitType unitType = XQItem::fetchUnitType( source );
    value = QVariant::fromValue(unitType);
    break;
  }

  case XQItem::ContentFormatRole:
  {
    // contentFormat() -> QString
    value = QVariant::fromValue(source);
    break;
  }

  case XQItem::FlagsRole:
  {
    QFlags itemFlags = Qt::NoItemFlags;
    const QStringList flagKeys = source.split( '|' );
    for( const QString& flagKey : flagKeys )
    {
      Qt::ItemFlag flag = XQItem::fetchItemFlag( flagKey );
      itemFlags.setFlag( flag );
    }
    value = QVariant::fromValue(itemFlags);
    break;
  }


  case XQItem::IconRole:
  {
    QIcon typeIcon = XQAppData::typeIcon(source);
    value = QVariant::fromValue(typeIcon);
    break;
  }


  case XQItem::FixedChoicesRole:
  {
    const QStringList choices = source.split( '|' );

    QStandardItemModel* fixedChoices = new QStandardItemModel();
    for( const QString& entry : choices )
    {
      QString result = entry;
      if( item->unitType() != XQItem::NoUnitType )
        result = XQItemType::formatToSI( entry, item->unitType() );
      fixedChoices->appendRow( new QStandardItem( result ) );
    }
    value = QVariant::fromValue(fixedChoices);
    break;
  }

    /*
    case XQItem::ContentNodeRole:
    {
      value = QVariant::fromValue(&source);
      break;
    }

    case XQItem::XQItem::SheetNodeRole:
    {
      value = QVariant::fromValue(&source);
      break;
    }
    */

  default:
  case XQItem::XQItem::NoRole:
  {
    break;
  }
  };

  //if( !value.toString().isEmpty())
  //  setData( value, dataRole);

  return value;

}

//! gibt ein statisches invalid-item zurück, anstelle von nullptr

XQItem& XQItem::fallBackDummyItem()
{
  static XQItem s_fallBackDummyItem;
  return s_fallBackDummyItem;
}


//! gibt das item für den gegebenen index aus.

XQItem& XQItem::xqItemFromIndex(const QModelIndex& index)
{
  if (index.isValid())
  {
    const XQViewModel* mdl = dynamic_cast<const XQViewModel*>(index.model());
    if (mdl)
      return mdl->xqItemFromIndex(index);
  }
  return fallBackDummyItem();
}


//! gibt den enum-type für den gegebenen datarole-key aus.

int XQItem::fetchItemDataRole( const QString& dataRoleKey )
{
  if(!dataRoleKey.isEmpty() && s_ItemDataRoleMap.contains(dataRoleKey) )
    return s_ItemDataRoleMap[ dataRoleKey ];

  return NoRole;
}


//! gibt die bezeichung für die gegebene datarole aus.

QString XQItem::fetchItemDataRoleName( int dataRole )
{
  return s_ItemDataRoleMap.key(dataRole);
}


//! gibt das flag für den gegebenen itemflag-key aus.

Qt::ItemFlag XQItem::fetchItemFlag( const QString& flagKey )
{
  if(!flagKey.isEmpty() && s_ItemFlagMap.contains(flagKey) )
    return Qt::ItemFlag( s_ItemFlagMap[ flagKey ] );
  return Qt::NoItemFlags;
}

//! gibt die bezeichung für das gegebene itemFlag aus.

QString XQItem::fetchItemFlagName( int flag )
{
  return s_ItemFlagMap.key(flag);
}


//! gibt den  enum-type für den gegebenen renderStyle-key aus.

XQItem::RenderStyle XQItem::fetchRenderStyle(const QString& styleKey )
{
  if(!styleKey.isEmpty() && s_RenderStyleMap.contains(styleKey) )
    return s_RenderStyleMap[styleKey];
  return NoRenderStyle;
}


//! gibt die bezeichung für den gegebenen renderStyle aus.

QString XQItem::fetchRenderStyleToString(XQItem::RenderStyle renderStyle )
{
  return s_RenderStyleMap.key(renderStyle);
}


//! gibt den renderstyle enum-type für den gegebenen editorType-key aus.

XQItem::EditorType XQItem::fetchEditorType( const QString& editorTypeKey )
{
  if(!editorTypeKey.isEmpty() && s_EditorTypeMap.contains(editorTypeKey) )
    return s_EditorTypeMap[editorTypeKey];
  return NoEditorType;
}


//! gibt die bezeichung für den gegebenen editorType aus.

QString XQItem::fetchEditorTypeToString( EditorType editorType )
{
  return s_EditorTypeMap.key(editorType);
}


//! gibt den unitType für den gegebenen unitType-key aus.

XQItem::UnitType XQItem::fetchUnitType(const QString& unitTypeKey)
{
  return s_UnitTypeMap.key(unitTypeKey);
}


//! gibt die bezeichung für den gegebenen unitType aus.

QString XQItem::fetchUnitTypeToString( UnitType unitType)
{
  return s_UnitTypeMap[unitType];
}
