// $Id: RepGenSlotText.cpp,v 1.12 2010/11/11 01:47:57 philw Exp $
//
//   class RepGenSlotText : public RepGenSlot
//
//--

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

static const int MAX_SLOT_DATA_ROWS (300);

#ifndef RepGenSlotTextINCLUDED
#include "RepGenSlotText.hpp"
#endif

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

#ifndef RwModelReportINCLUDED
#include "RwModelReport.hpp"
#endif

#ifndef RepGenUtilsINCLUDED
#include "RepGenUtils.hpp"
#endif

#ifndef cwfstreamINCLUDED
#include "cwfstream.hpp"
#endif

#ifndef SlotINCLUDED
#include "Slot.hpp"
#endif

#ifndef SeriesSlotINCLUDED
#include "SeriesSlot.hpp"
#endif

#ifndef ListSlotINCLUDED
#include "ListSlot.hpp"
#endif

#ifndef SlotColRefINCLUDED
#include "SlotColRef.hpp"
#endif

#ifndef SimObjINCLUDED
#include "SimObj.hpp"
#endif

#ifndef SlotGUIUtilsINCLUDED
#include "SlotGUIUtils.hpp"
#endif

#include "rwStr.hpp"

#include <QStringList>
#include <QString>

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

static const QString nbspQstr ("&nbsp;");

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

// constructor 1 of 1
RepGenSlotText::RepGenSlotText (RwModelReport* rep, const Slot* slotPtr)
  : RepGenSlot (rep, slotPtr)
{
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

RepGenSlotText::~RepGenSlotText()
{
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

void RepGenSlotText::writeSlotListHeader (bool measureOnly, 
                                          QVector<RepGen::ColDat>& colDatVec,
                                          SlotListType slotListType, 
                                          const QString& listTitle,
                                          cwofstream& os) const
{
   static const char (*mname) ("RepGenSlotText::writeSlotListHeader");
   QStringList columnLabelStrList;

   if (!measureOnly && !listTitle.isEmpty())
   {
      const QString titleBox = RepGen::textBox (listTitle);
      os << qPrintable (titleBox)  << endl;
   }

   switch (slotListType)
   {
     case SlotList_Scalar:
        columnLabelStrList << "Type"            // [0]
                           << "Slot Name"       // [1]  
                           << "Value"           // [2]  
                           << "Units";          // [3]  
        break;

      case SlotList_Series: 
        columnLabelStrList << "Type"            // [0]
                           << "Slot Name"       // [1]  
                           << "Units"           // [2]  
                           << "Rows"            // [3] 
                           << "Cols"            // [4]
                           << "Step"            // [5] 
                           << "Start"           // [6] 
                           << "End";            // [7] 
        break;

      case SlotList_Other:  
        columnLabelStrList << "Type"            // [0]
                           << "Slot Name"       // [1]
                           << "Rows"            // [3]
                           << "Cols"            // [4]
                           << "Column Labels";  // [5]
        break;
   }

   // Record minimum sizes in QVector<RepGen::ColDat>& colDatVec
   const int colCnt = columnLabelStrList.size();
   colDatVec.resize (colCnt);

   for (int col = 0;  col < colCnt;  ++col)
   {
      const QString colLab (columnLabelStrList .value (col));

      const bool alignRgt = ( (colLab == "Value") ||
                              (colLab == "Rows")  ||
                              (colLab == "Cols")  );

      colDatVec [col] .setMinimumWidth (colLab);
      colDatVec [col] .setAlignRight (alignRgt);
   }

   if (measureOnly) return;
   //====================>>

   // ***************************
   // ***  Write List Header  ***
   // ***************************

   const QString line1 (RepGen::textTableLine (colDatVec, '+', '-'));
   const QString line2 (RepGen::textTableLine (colDatVec, columnLabelStrList));
   const QString line3 (RepGen::textTableLine (colDatVec, '|', '='));

   os << qPrintable (line1) << "\n"
      << qPrintable (line2) << "\n"
      << qPrintable (line3) << endl;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

void RepGenSlotText::writeSlotListRow (bool measureOnly,
                                       QVector<RepGen::ColDat>& colDatVec,
                                       SlotListType slotListType,
                                       cwofstream& os) const
{
   static const char (*mname) ("RepGenSlotText::writeScalarSlotListRow");

   if (_slot == NULL) return;
   //---------------------->>

   QStringList rowCellStrList;

   switch (slotListType)
   {
     case SlotList_Scalar:
        rowCellStrList << slotTypStrTerse()      // [0]
                       << slotNameStr()          // [1]
                       << slotValStr()           // [2]
                       << slotUnitsStr (false);  // [3], no brackets
        break;

      case SlotList_Series: 
        rowCellStrList << slotTypStrTerse()      // [0]
                       << slotNameStr()          // [1]
                       << slotUnitsStr (false)   // [2], no brackets
                       << slotRowsStr()          // [3]
                       << slotColsStr()          // [4]
                       << seriesStepStr()        // [5]
                       << seriesStartStr()       // [6]
                       << seriesEndStr();        // [7]
        break;

      case SlotList_Other:  
        rowCellStrList << slotTypStrTerse()  // [0]
                       << slotNameStr()      // [1]
                       << slotRowsStr()      // [3]
                       << slotColsStr()      // [4]
                       << slotLabelsStr();   // [5]
        break;
   }

   // Record minimum sizes in QVector<RepGen::ColDat>& colDatVec
   const int colCnt = rowCellStrList.size();
   colDatVec.resize (colCnt);

   if (measureOnly)
   {
      for (int col = 0;  col < colCnt;  ++col)
      {
         const QString colLab (rowCellStrList .value (col));
         colDatVec [col] .setMinimumWidth (colLab);
      }

      return;  // measure only
      //====>>
   }

   // ************************
   // ***  Write Slot Row  ***
   // ************************

   const QString line1 (RepGen::textTableLine (colDatVec, rowCellStrList));
   os << qPrintable (line1) << endl;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

// private
void RepGenSlotText::writeSlotDetailTitleSection (cwofstream& os)
{
   if (_slot == NULL) return;
   //---------------------->>

   SimObj* obj (_slot->getSimObj());  // may be NULL

   // ***************************
   // ***  Slot Detail Title  ***
   // ***************************

   const QString objName (obj ? obj->getCompleteName() : "Object?");
   const QString objTyp  (obj ? obj->getLongTypeName(true) : "Type?");
   const QString slotName (slotNameStr());
   const QString slotTyp  (slotTypStr (true)); // verbose
   const int maxNameLen = std::max (objName.length(), slotName.length());

   static const QString objFmt  ("Object:  %1  / %2");
   static const QString slotFmt ("  Slot:  %1  / %2");

   const QString oLine = objFmt  .arg (objName,  -maxNameLen) .arg (objTyp);
   const QString sLine = slotFmt .arg (slotName, -maxNameLen) .arg (slotTyp);

   const QStringList titleLines = (QStringList() << oLine << sLine);
   const QString titleBox = RepGen::textBox (titleLines);
   os << qPrintable (titleBox)  << endl;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

// private
void RepGenSlotText::writeSlotDataTable (cwofstream& os)
{
   if (_slot == NULL) return;
   //---------------------->>

   // ******************************
   // ***  Row Count Assessment  ***
   // ******************************

   const int dataRowCnt = SlotGUIUtils::getNumSlotRows (_slot);
   const bool truncateRows = (dataRowCnt > MAX_SLOT_DATA_ROWS);

   if (truncateRows)
   {
      static const QString truncMsgFmt (
         "The number of rows in this slot (%1) exceeds the maximum \n"
         "number of slot rows for this report (%2).  Only the first \n"
         "and last several rows are shown." );

      const QString truncMsg = truncMsgFmt .arg (dataRowCnt)
                                           .arg (MAX_SLOT_DATA_ROWS);

      os << qPrintable (truncMsg) << endl;
   }

   // ***************************************************
   // ***  (1) Measure Slot Data Table Column Widths  ***
   // ***************************************************

   static const int EXCERPT_ROW_CNT (4);
   QVector<RepGen::ColDat> colDatVec;
   
   // Measure column headers
   writeSlotDetailHeader (true, colDatVec, os); // MEASURE ONLY

   // Measure data rows
   for (int rowInx = 0;  rowInx < dataRowCnt;  ++rowInx)
   {
      writeSlotDetailRow (true, colDatVec, rowInx, os);  // MEASURE ONLY

      if (truncateRows)
      {
         if (rowInx == EXCERPT_ROW_CNT)
         {
            // Measure ellipsis (truncation gap) row
            writeSlotDetailRow (true, colDatVec, (-1), os);  // MEASURE ONLY

            // Skip ahead
            rowInx = std::max (EXCERPT_ROW_CNT, dataRowCnt-EXCERPT_ROW_CNT-1);
         }
      }
   }

   // ********************************************
   // ***  (2) Write Pre-Table Notes (if any)  ***
   // ********************************************

   QString colMapEntityName ("");
   const bool colMapEntityDef = 
      SlotGUIUtils::getColMapEntityName (_slot, colMapEntityName);

   if (colMapEntityDef && !colMapEntityName.isEmpty())
   {
      // Write Column Map Entity description
      static const QString colMapEntFmt (
        "Numeric column header value entity: %1\n");

      QString colMapEntStr = colMapEntFmt .arg (colMapEntityName);
      os << qPrintable (colMapEntStr) << endl;
   }

   // ***********************************
   // ***  (3) Write Slot Data Table  ***
   // ***********************************

   // Write column headers
   writeSlotDetailHeader (false, colDatVec, os); // Not "measure only"

   for (int rowInx = 0;  rowInx < dataRowCnt;  ++rowInx)
   {
      writeSlotDetailRow (false, colDatVec, rowInx, os);  // Not "measure only"

      if (truncateRows)
      {
         if (rowInx == EXCERPT_ROW_CNT)
         {
            // Write ellipsis (truncation gap) row
            writeSlotDetailRow (false, colDatVec, (-1), os);  // Not "measure"

            // Skip ahead
            rowInx = std::max (EXCERPT_ROW_CNT, dataRowCnt-EXCERPT_ROW_CNT-1);
         }
      }
   }

   const QString botLine (RepGen::textTableLine (colDatVec, '+', '-'));
   os << qPrintable (botLine) << "\n" << endl;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

// private
void RepGenSlotText::writeSlotDetailHeader (bool measureOnly, 
                                            QVector<RepGen::ColDat>& colDatVec,
                                            cwofstream& os) const
{
   if (_slot == NULL) return;
   //---------------------->>

   if (!measureOnly)
   {
      // Write Detail Table Top Horizontal Line
      const QString topLine (RepGen::textTableLine (colDatVec, '+', '-'));
      os << qPrintable (topLine) << endl;
   }

   // Process the Slot's Column Map (Numeric Dimension), if it has one.
   writeSlotDetailColMap (measureOnly, colDatVec, os);

   // ListSlots don't need a slot data table header
   if (_slot->isA (Slot::ListSlotBit)) return;
   //--------------------------------------->>

   const int dataColCnt = SlotGUIUtils::getNumCols (_slot);
   colDatVec.resize (dataColCnt+1); // (extra initial column for row headers)

   QStringList headerColNames;
   QStringList headerColUnits;
   int nonEmptyColLabCnt (0);

   // First column header is blank. That's for the row indicies or datetime.
   headerColNames << " ";
   headerColUnits << " ";

   for (int datCol = 0;  datCol < dataColCnt;  ++datCol)
   {
      const int tabCol = datCol+1;

      const QString colLab =
         SlotGUIUtils::getColLabel (_slot, datCol).toLocal8Bit().constData();

      const ScaledUnitPtr scUnit =
         SlotGUIUtils::getScaledUnitPtr (_slot, datCol);

      const QString colUnits = QString ("[%1]")
         .arg (SlotGUIUtils::scaledUnitDisplayStr (scUnit));

      if (measureOnly)
      {
         const int labelLen = std::max (colLab.length(), colUnits.length());
         colDatVec [tabCol] .setMinimumWidth (labelLen);
         colDatVec [tabCol] .setAlignRight (true);
      }
      else
      {
         headerColNames << colLab;
         headerColUnits << colUnits;
      }

      if (!colLab.isEmpty())
      {
         ++nonEmptyColLabCnt;
      }
   }

   if (!measureOnly)
   {
      const QString line1 (RepGen::textTableLine (colDatVec, headerColNames));
      const QString line2 (RepGen::textTableLine (colDatVec, headerColUnits));
      const QString line3 (RepGen::textTableLine (colDatVec, '|', '='));

      if (nonEmptyColLabCnt > 0)
      {
         os << qPrintable (line1) << "\n";
      }

      os << qPrintable (line2) << "\n"
         << qPrintable (line3) << endl;
   }
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

// private
void RepGenSlotText::writeSlotDetailColMap (bool measureOnly, 
                                            QVector<RepGen::ColDat>& colDatVec,
                                            cwofstream& os) const
{
   if (_slot == NULL) return;
   //---------------------->>

   // Only TableSlots (incl. subclasses) support Column Maps (NumericDimension)
   if (!_slot->isA (Slot::TableSlotBit)) return;
   //----------------------------------------->>

   QStringList colMapUsrValStrs;
   QString colMapUnitsStr ("");
   const bool colMapDataFound =
      SlotGUIUtils::getColMapUsrValues (_slot, colMapUsrValStrs, 
                                               colMapUnitsStr);

   if (!colMapDataFound) return;
   //------------------------->>

   if (!colMapUnitsStr.startsWith (QChar ('[')))
      colMapUnitsStr = QString ("[%1]") .arg (colMapUnitsStr);

   const int colMapValCnt = colMapUsrValStrs.size();
   if (colDatVec.size() <= colMapValCnt)
   {
      colDatVec.resize (colMapValCnt+1);
   }

   QStringList colMapValStrs;
   QStringList colMapUnitStrs;
   const int colMapUnitsStrLen (colMapUnitsStr.length());

   // First column header is blank. That's for the row indicies or datetime.
   colMapValStrs << " ";
   colMapUnitStrs << " ";

   for (int colMapCol = 0;  colMapCol < colMapValCnt;  ++colMapCol)
   {
      const int tabCol = colMapCol+1;
      const QString colLab = colMapUsrValStrs .value (colMapCol);

      if (measureOnly)
      {
         const int labelLen = std::max (colLab.length(), colMapUnitsStrLen);
         colDatVec [tabCol] .setMinimumWidth (labelLen);
         colDatVec [tabCol] .setAlignRight (true);
      }
      else
      {
         colMapValStrs << colLab;
         colMapUnitStrs << colMapUnitsStr;
      }
   }

   if (!measureOnly)
   {
      const QString line1 (RepGen::textTableLine (colDatVec, colMapValStrs));
      const QString line2 (RepGen::textTableLine (colDatVec, colMapUnitStrs));
      const QString line3 (RepGen::textTableLine (colDatVec, '|', '-'));

      os << qPrintable (line1) << "\n"
         << qPrintable (line2) << "\n"
         << qPrintable (line3) << endl;
   }
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

// private
void RepGenSlotText::writeSlotDetailRow (bool measureOnly, 
                                         QVector<RepGen::ColDat>& colDatVec,
                                         int rowInx, cwofstream& os) const
{
   static const QString ellipsis ("...");

   //--------------------------------------------------------
   // NOTE: if (row < 0), show a divider row (with ellipsis).
   //--------------------------------------------------------

   if (_slot == NULL) return;
   //---------------------->>

   const bool isListSlot (_slot->isA (Slot::ListSlotBit));

   QStringList rowCellStrList;

   // ***********************************
   // ***  Row Header (First Column)  ***
   // ***********************************

   const bool supportsTextRowLabel (_slot->isA (Slot::TableSlotBit)  ||
                                    _slot->isA (Slot::SeriesSlotBit) );

   QString rowLab (" ");                            // tentative
   bool alignRight (!isListSlot || (rowInx == 0));  // tentative

   if (rowInx < 0)
   {
      rowLab = ellipsis;
   }
   else // (rowInx >= 0)
   {
      rowLab = QString::number (rowInx);  // tentative
      if (supportsTextRowLabel)
      {
         const QString rowLabStr = SlotGUIUtils::getRowLabel (_slot, rowInx);
         if (!rowLabStr.isEmpty())
         {
            rowLab = rowLabStr;
            alignRight = false;
         }
      }
   }

   rowCellStrList << rowLab; 

   if (measureOnly && (rowInx == 0))
   {
      if (colDatVec.size() == 0)
      {
         colDatVec.resize (1);
      }

      colDatVec [0] .setAlignRight (alignRight);
   }

   // ************************************************
   // *** Special Column for ListSlots: Item Icon  ***
   // ************************************************

   if (isListSlot)
   {
      QString lslotItemTypStr (ellipsis);  // tentative
      if (rowInx >= 0)
      {
         const ListSlotBase* lslot = dynamic_cast<const ListSlotBase*> (_slot);
         if (lslot)
         {
            IconHandle lsIcon ((SimObj*) NULL, false);
            const bool iconOk = lslot->getItemIcon (rowInx, lsIcon);
            if (iconOk)
            {
               QString descStr (lsIcon.iconDescStr());

               // Remove "Object" from long-winded object type descriptions,
               // but not from "Data Object".
               if ((descStr.length() > 12) && (descStr.contains ("Object")))
               {
                  descStr.remove ("Object");
               }

               lslotItemTypStr = descStr.simplified();
            }
         }
      }

      rowCellStrList << lslotItemTypStr;
   }

   // ***************************
   // ***  Slot Data Columns  ***
   // ***************************

   const int dataColCnt = SlotGUIUtils::getNumCols (_slot);
   for (int colInx = 0;  colInx < dataColCnt;  ++colInx)
   {
      QString valStr (ellipsis);  // tentative
      if (rowInx >= 0)
      {
         valStr = SlotGUIUtils::getValueUsrStr (_slot, rowInx, colInx,
                                                false,  //-- for edit
                                                false); //-- alt units
      }

      rowCellStrList << valStr;
   }

   // ********************************
   // ***  Process rowCellStrList  ***
   // ********************************

   if (measureOnly)
   {
      // Record minimum sizes in QVector<RepGen::ColDat>& colDatVec
      const int colCnt = rowCellStrList.size();
      if (colDatVec.size() < colCnt)
      {
         colDatVec.resize (colCnt);
      }

      for (int col = 0;  col < colCnt;  ++col)
      {
         colDatVec [col] .setMinimumWidth (rowCellStrList .value (col));
      }
   }

   else // (not measureOnly)
   {
      // *****************************
      // ***  Write Slot Data Row  ***
      // *****************************

      const QString line1 (RepGen::textTableLine (colDatVec, rowCellStrList));
      os << qPrintable (line1) << endl;
   }
}

//--- (end RepGenSlotText.cpp) ---