OZ++ Class: TableView
/******************************************************************************
 *
 * Copyright (c) 2015 Antillia.com TOSHIYUKI ARAI. ALL RIGHTS RESERVED.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *      TableView.h
 *
 *****************************************************************************/

//2017/10/16 Modified to use create method.
//2019/06/10 Modified to be able to show or no-show columnHeader and rowHeader. 

#pragma once

#include <string>

#include <vector>
#include <iostream>
#include <fstream>

#include <oz++/CommonObject.h>
#include <oz++/SmartArray.h>
#include <oz++/StringTokenizerT.h>
#include <oz++/motif/BulletinBoard.h>
#include <oz++/motif/ColumnData.h>
#include <oz++/motif/ColumnHeader.h>
#include <oz++/motif/RowData.h>

#include <oz++/motif/RowHeader.h>

#include <oz++/motif/LabelCell.h>
#include <oz++/motif/PushButtonCell.h>

#include <oz++/motif/Color.h>
#include <oz++/motif/CompoundString.h>
#include <oz++/motif/TableModel.h>

namespace OZ {

#define XmNtableColumnHeight    "_tableColumnHeight"
#define XmNtableColumns         "_tableColumns"
#define XmNtableRows            "_tableRows"
#define XmNtableNumColumns      "_tableNumColumns"
#define XmNtableNumRows         "_tableNumRows"
#define XmNtableColumnData      "_tableColumnData"
#define XmNtablenNumColumnData  "_tableNumColumnData"

#define XmNtableRowWidth         "_tableRowWidth"
#define XmNtableRowData          "_tableRowData"
#define XmNtableNumRowData       "_tableNumRowData"
#define XmNtableShowRowHeader    "_tableShowRowHeader"
#define XmNtableShowColumnHeader "_tableShowColumnHeader"

class TableView :public BulletinBoard {

private:
  int            numColumns;
  int            numRows;
  int            columnHeight;
    
  int            rowWidth;
    
  Boolean showColumnHeader = FALSE;
  Boolean showRowHeader = FALSE;
  ColumnData* columnData = NULL;
  SmartArray<ColumnHeader*>         columnHeader;
  SmartArray<RowHeader*>            rowHeader;
 // SmartArray<SmartArray<Gadget*>* > rowColumns;
  SmartArray<SmartArray<Label*>* > rowColumns;
    
  static const int MARGIN                = 2;
  int          columnCount;      //The number of visible columns
  int          rowCount;         //The number of visible rows
  TableModel*  tableModel;

  int          rowTopPosition;
  int          columnLeftPosition;
  static const int DEFAULT_COLUMN_HEIGHT = 20; 

public:
  TableView(View* parent, const char* name, Args& args)
    :BulletinBoard(),
    numColumns(0),
    numRows(0),
    columnHeight(0),
    columnCount(0),
    rowCount(0),
    tableModel(NULL),
    rowTopPosition(0),
    columnLeftPosition(0)
  {
    TableView::create(parent, name, args);
  }

  TableView()
  :BulletinBoard(),
    numColumns(0),
    numRows(0),
    columnHeight(0),
    columnCount(0),
    rowCount(0),
    tableModel(NULL),
    rowTopPosition(0),
    columnLeftPosition(0)
  {
  }

  void create(View* parent, const char* name, Args& args)
  {
    BulletinBoard::create(parent, name, args.set(XmNmarginWidth, 0)
                                     .set(XmNmarginHeight, 0) );
    

    columnHeight = DEFAULT_COLUMN_HEIGHT;
    if (args.has (XmNtableColumnHeight)) {
      columnHeight = args.get(XmNtableColumnHeight);
      if (columnHeight <= 0) {
        columnHeight = DEFAULT_COLUMN_HEIGHT;
      }
    }

    if (args.has (XmNtableNumColumns)) {
      numColumns = args.get(XmNtableNumColumns);
    }
    if (args.has (XmNtableNumRows)) {
      numRows = args.get(XmNtableNumRows);
    }

    columnData = NULL;
    
    if (args.has(XmNtableColumnData)) {
        columnData = (ColumnData*)args.get(XmNtableColumnData);
    }
    
    RowData* rowData = NULL;
    
    if (args.has(XmNtableRowData)) {
        rowData = (RowData*)args.get(XmNtableRowData);
    }
    
    if (numColumns <= 0 || numRows <=0 || columnData == NULL) {
      return;   
    }
    
    rowWidth = 0;
    if (args.has(XmNtableRowWidth)) {
       rowWidth = (int)args.get(XmNtableRowWidth);
    }
    
    if (rowData) {
      int w = 0;
      for (int i = 0; i<numRows; i++) {
        if (rowData[i].width > w) {
          w = rowData[i].width;
        }
      }
      if (w > rowWidth) {
        rowWidth = w;
      }
    }
    //2019/06/06
    showRowHeader = FALSE;
    if (args.has(XmNtableShowRowHeader)) {
        showRowHeader= (Boolean)args.get(XmNtableShowRowHeader);
    }
    
    if (showRowHeader == FALSE) {
      rowWidth = 0;
    }
    //
 
   
    Args ar;
    
    int x = MARGIN + rowWidth;
    int y = MARGIN ;
    showColumnHeader = FALSE;
    if (args.has(XmNtableShowColumnHeader)) {
      showColumnHeader = args.get(XmNtableShowColumnHeader);
    }

    // Create column header
    for (int i = 0; i< numColumns; i++) {
      ar.reset();
      ar.set(XmNx, x);
      ar.set(XmNy, y);

      ar.set(XmNwidth, columnData[i].width + MARGIN); // - ColumnHeader::BORDER_WIDTH); 
     // ar.set(XmNheight, columnHeight+ MARGIN);  
      ar.set(XmNalignment, columnData[i].alignment);
      CompoundString cs(columnData[i].label);
      ar.set(XmNlabelString, cs);
      if (showColumnHeader) {
        ColumnHeader* ch = new ColumnHeader(this, "", ar);
        columnHeader[i] = ch ;//new ColumnHeader(this, "", ar);
        ch -> get(XmNheight, (XtArgVal)&columnHeight);
      }
 
      x = x + columnData[i].width + MARGIN;
    }

    //Create row header
    if (rowWidth > 0) {
      //y = MARGIN + columnHeight + MARGIN + ColumnHeader::BORDER_WIDTH;
      y = MARGIN;
      if (showColumnHeader) {
       y+= columnHeight;// + MARGIN;// + ColumnHeader::BORDER_WIDTH*2;
      } 
      for (int i = 0; i< numRows; i++) {
        ar.reset();
        ar.set(XmNx, 0);
        ar.set(XmNy, y);

        ar.set(XmNwidth, rowWidth + MARGIN - RowHeader::BORDER_WIDTH); 
        ar.set(XmNheight, columnHeight); //+ MARGIN);// -1);  
        char item[256];
        if (rowData) {
            memset(item, 0, sizeof(item));
            if (strlen(rowData[i].label) < sizeof(item)) {
              strcpy(item, rowData[i].label); 
            } else {
                strncpy(item, rowData[i].label, sizeof(item)-1);    
            }
            ar.set(XmNalignment, rowData[i].alignment);             
        } else {
          if (showRowHeader) {
            sprintf(item, "%d", i+1);
            ar.set(XmNalignment, XmALIGNMENT_END); 
          }
        }
        CompoundString cs(item); 
        ar.set(XmNlabelString, cs);
        rowHeader[i] = new RowHeader(this, "", ar);
        y = y + columnHeight;// + RowHeader::BORDER_WIDTH;//MARGIN;
      }
    }
    
    //Show sample rowData 
    x = MARGIN + rowWidth;
    y = MARGIN ;
    if (showColumnHeader) {
      y += columnHeight;
    }

    for(int j = 0; j< numRows; j++) {
      x = MARGIN + rowWidth;
      rowColumns[j] = new SmartArray<Label*>() ;

      for (int i = 0; i<numColumns; i++) {
        char item[256];
        sprintf(item, "Cell(%d, %d)", j+1, i+1);
        CompoundString cs(item);
        ar.reset();
        ar.set(XmNx, x);
        ar.set(XmNy, y);
        ar.set(XmNwidth, columnData[i].width + MARGIN + ColumnHeader::BORDER_WIDTH);
        ar.set(XmNheight, columnHeight);//+ MARGIN); //-1);// + ColumnHeader::BORDER_WIDTH );  
        ar.set(XmNalignment, columnData[i].alignment);
        ar.set(XmNlabelString, cs);
        LabelCell* label = new LabelCell(this, "", ar);

        rowColumns[j] ->put(i,  label);
        x = x+ columnData[i].width + MARGIN;
      } 
      y = y + columnHeight;// + ColumnHeader::BORDER_WIDTH; //MARGIN;
    }       
  }

  void addEventHandler(int row, int column, unsigned long eventMask, CommonObject* object,
        Handler handler, void* data)
  {
    if (row < numRows && column < numColumns && eventMask >0 && object != NULL  && handler !=NULL) {
        Label* label = rowColumns[row] ->get(column);
        if (label) {
          label -> set(XmNtraversalOn, TRUE);
          label -> addEventHandler(eventMask, object, handler, data);
        }
    }
  }
    
  void removeAllItems()
  {
    for(int j = 0; j< numRows; j++) {
      //One columns
      for (int i = 0; i<numColumns; i++) {
       CompoundString cs("");
        Label* label = rowColumns[j] ->get(i);
        
        if (label) {
          label -> set(XmNlabelString, cs);
        }
      }
    }
    rowCount = 0;
  }


  ~TableView() 
  {
      columnHeader.clear();
      rowHeader.clear();
  }

  void updateModel(TableModel* model)
  {
    if (tableModel != model) {
      delete tableModel;
    }
    tableModel = model;

    rowTopPosition = 0;
    
    updateView();
  }
    
  void updateView()
  {
    //to do
  }
    
  void setItem(int row, int column, const char* item)
  {
    if (row < numRows && column < numColumns) {
      if (item !=NULL) {
        CompoundString cs(item);
        Label* label = rowColumns[row] ->get(column);
        
        if (label) {
          label -> set(XmNlabelString, cs);
        }
      }
    } else {
        printf("Out of range row=%d column=%d\n", row, column); 
    }
  }

  void setItems(int row, const char** items, int num)
  {
    if (row < numRows) {
      if (rowCount <numRows && items !=NULL) {
       //One columns
        for (int i = 0; i<numColumns; i++) {
          if (num<=numColumns) {
            CompoundString cs(items[i]);
            Label* label = rowColumns[row] ->get(i);
        
            if (label) {
              label -> set(XmNlabelString, cs);
            }
          }
        }
      }
    }
  }

  Label* getGadget(int row, int column)
  {
    Label* rowColumn = NULL;
    if (row <rowCount && column <numColumns) {
        rowColumn = rowColumns[row] -> get(column); 
    }
    return rowColumn;
  }
    
  void addNewRow()
  {
     //printf("expanding rowCount=%d numRows=%d\n", rowCount, numRows);
     rowColumns[rowCount] = new SmartArray<Label*>() ;
     int x = MARGIN + rowWidth;
     int y = MARGIN;
     if (showColumnHeader) {
       y += columnHeight;
     }
     y +=  columnHeight *rowCount;

    for (int i = 0; i<numColumns; i++) {
        char item[256];
        sprintf(item, "%d", i+1);
        CompoundString cs(item);
        Args ar;
        ar.reset();
        ar.set(XmNx, x);
        ar.set(XmNy, y);
        ar.set(XmNwidth, columnData[i].width + MARGIN + ColumnHeader::BORDER_WIDTH);
        ar.set(XmNheight, columnHeight);//+ MARGIN); //-1);// + ColumnHeader::BORDER_WIDTH );
        ar.set(XmNalignment, columnData[i].alignment);
        ar.set(XmNlabelString, cs);
        LabelCell* label = new LabelCell(this, "", ar);

        rowColumns[rowCount] ->put(i,  label);
        x = x+ columnData[i].width + MARGIN;
    }
  }

  int addItems(const char** items, int num)
  {
    if (items == NULL || num > numColumns) {
        throw IException("Invalid argument");
    }
    if (rowCount >= numRows) {
      //printf("Expanding rowCount=%d numRows=%d\n", rowCount, numRows);
      addNewRow();
      numRows++;
    }
 
    if (rowCount <numRows) {
      for (int i = 0; i<numColumns; i++) {
        if (num<=numColumns) {
          CompoundString cs(items[i]);
          Label* cell = rowColumns[rowCount] ->get(i);
        
          if (cell) {
            cell -> set(XmNlabelString, cs);
          } else {
            printf("cell empty %d\n", i);
          }
        }
      }
      rowCount++;
    } else {
      throw IException("RowCount overflow %d", rowCount);  
    }
    return rowCount;
  }

  int addItems(std::vector<std::string>& items)
  {
    size_t num = items.size();

    if (num > numColumns) {
       throw IException("Invalid argument");
    }
    if (rowCount >= numRows) {
      //printf("Expanding rowCount=%d numRows=%d\n", rowCount, numRows);
      addNewRow();
      numRows++;
    }
 
    if (rowCount <numRows) {
      for (int i = 0; i<numColumns; i++) {
        if (num<=numColumns) {
          std::string item = items.at(i);
          CompoundString cs(item.c_str() );
          Label* cell = rowColumns[rowCount] ->get(i);
        
          if (cell) {
            cell -> set(XmNlabelString, cs);
          } else {
            printf("cell empty %d\n", i);
          }
        }
      }
      rowCount++;
    } else {
      throw IException("RowCount overflow %d", rowCount);  
    }
    return rowCount;
  }

  void parseCSVHeader(const char* line, std::vector<std::string>& items)
  {
    StringTokenizerT<char> tokenizer(line);
    while(tokenizer.hasMoreToken()) {
      char token[256] = { 0 };
      tokenizer.getToken(token, sizeof(token));
      items.push_back(std::string(token)); 
    }
  }

  //line is a set of space, comma, or tab separated strings.
  void addLine(const char* line)
  {
    std::vector<std::string> items;
    StringTokenizerT<char> tokenizer(line);
    while(tokenizer.hasMoreToken()) {
      char token[128] = { 0 };
      tokenizer.getToken(token, sizeof(token));
      items.push_back(std::string(token));
    }
    addItems(items);
  }

  void readCSVFile(const std::string& filename, bool ignore_header=true)
  {
    std::ifstream ifs(filename);
    if (!ifs) {
      throw IException("File not found %s", filename.c_str());
    }
    std::string line;
    bool first_line = true;

    while (!ifs.eof()) {
      std::getline(ifs, line);
      //Erase ¥n if line ends with it.
      if (line[line.size()-1] == '\n') {
        line.erase(line.size()-1);
      }
      //Erase ¥r if line ends with it.
      if (line[line.size()-1] == '\r') {
        line.erase(line.size()-1);
      }
      if (line.length() < 1) {
        continue;
      }
      if (first_line) {
        if (ignore_header) {
          std::vector<std::string> column_names;
          parseCSVHeader(line.c_str(), column_names);
        }
        first_line = false;
      } else {
        addLine(line.c_str());
      }
    }
  } 
};

}