/*=========================================================================
 
  $   Workfile: CSVFile.h $ 
  $   Revision: 4 $
  $       Date: 4/05/01 12:21p $
  $    Modtime: 4/05/01 12:22p $
  $     Author: Dalleyg $
  $    Archive: /vc/common/CSVFile.h $
  $ NoKeywords: $

Copyright (c) 2001 Gerald Dalley.

=========================================================================*/
// .NAME CCSVFile - 
//      Handles interaction with CSV (comma-separated spreadsheet) files
//
// .SECTION Description
//

#ifndef __CSVFile_h
#define __CSVFile_h

// Shorten template names so that the fully-expanded symbol names do not
// exceed 255 characters (MS debugger requirement)
#ifdef _DEBUG
//#define vector vv
//#define allocator alloca
#endif

#include <iostream>
#include <vector>

typedef class std::vector<const char*> CCSVFile_RowType;

class CCSVFile 
{
public:
    // Description:
    // Creates a new instance of a logical CSV file.  Note that this logical
    // file only exists as part of this object.  Use the standard stream
    // operators to read and write to streams using this object.
    CCSVFile();
    ~CCSVFile();

    // Description:
    // Creates a new CSVFile object and reads the file specified using the
    // specified delimiter character.  If throwOnError is true, an exception
    // is thrown and an error message is printed to cerr if there is a 
    // problem reading the file.  Else, the read simply fails.
    CCSVFile(const char *fname, char delim = ',', bool throwOnError = true);

    // Description:
    // Removes all data from the object
    void Initialize();

    // Description:
    // Returns the data from the current cell.  NULL indicates that
    // no data is at that cell.  Advances to the next column (may be empty).
    const char *GetCell();
    // Description:
    // Returns the data at the specified row and column.  The cell
    // accessed becomes the "current cell".  NULL indicates that
    // no data is at that cell.
    const char *GetCell(unsigned int row, unsigned int col);
    // Description:
    // Sets the data at the current cell and advances to the next
    // column.
    void SetCell(const char *val);
    // Description:
    // Sets the data at the specified row and column.  The cell 
    // one column to the right of the one accessed becomes the 
    // "current cell".
    void SetCell(unsigned int row, unsigned int col, const char *val);

    // Description:
    // Returns 0 if the cell is not a number or does not exist.
    double GetCellAsNumber();
    void SetCellAsNumber(double val);
    double GetCellAsNumber(unsigned int row, unsigned int col);
    void SetCellAsNumber(unsigned int row, unsigned int col, double val);

    unsigned int GetCurrRow()                  { return this->CurrRow;   };
    void SetCurrRow(unsigned int row)          { this->CurrRow  = row;   };
    int AdvanceCurrRow(unsigned int delta=1)     
        { this->CurrRow += delta; this->CurrCol=0; return this->CurrRow; };
    unsigned int GetCurrCol()                  { return this->CurrCol;   };
    void SetCurrCol(unsigned int col)          { this->CurrCol  = col;   };
    unsigned int AdvanceCurrCol(unsigned int delta=1)   
        { this->CurrCol += delta; return this->CurrCol; };

    // Description:
    // Returns the total number of columns with data in the current row.
    // Trailing columns that are NULL or have zero-length strings are
    // not counted in the total, e.g.:
    // <TABLE><TR><TH>Row contents</TH><TH>Result</TH></TR>
    //     <TR><TD>"abc","def"</TD><TD>2</TD></TR>
    //     <TR><TD>"abc","def","",""</TD><TD>2</TD></TR>
    //     <TR><TD>"abc","def",NULL</TD><TD>2</TD></TR>
    //     <TR><TD>"abc","def",NULL,"ghi"</TD><TD>4</TD></TR>
    //     <TR><TD>"abc","def",NULL,"ghi",NULL,""</TD><TD>4</TD></TR>
    // </TABLE>
    unsigned int GetNumColInCurrRow();

    // Description:
    // Returns the total number of rows that have data.  Unlike 
	// GetNumColInCurrRow, this method DOES count empty rows.  
	// To get behavior closer to that of GetNumColInCurrRow,
	// call Squeeze first.
    unsigned int GetNumRows()               { return this->Data.size(); };

    // Description:
    // Returns true if every cell after the current column in the
    // current row is either NULL or a 0-length string.
    bool RemainderOfRowIsEmpty();
    /*bool RemainderOfColIsEmpty();*/

    // Descrption:
    // Sets the delimiter character used to separate cells.  By default,
    // the delimiter is a comma.  Any character except '\0', '\r', '\n',
    // and '"' may be used as a delimiter.  Future versions of this class
    // may allow specifying multiple characters, but this is not supported
    // right now.
    void SetDelimiter(char d);
    // Description:
    // Returns the current delimiter character.
    char GetDelimiter()                        { return this->Delimiter; };

	// Description:
	// Removes any trailing blank or null cells at the end of any row and
	// removes any trailing blank or null rows from the object.
	// Does not modify the current row or column location.  The user will 
	// often want to call this method after adding data and before calling
	// GetNumRows.  
	void Squeeze();

protected:
    unsigned int CurrRow;
    unsigned int CurrCol;

    char Delimiter;

    // Description:
    // Holds the data.  
    //  Data[row][column][charNum]
    std::vector<CCSVFile_RowType> Data;

private:
    //BTX
    friend std::ostream& operator<<(std::ostream& os, CCSVFile& o);
    friend std::istream& operator>>(std::istream& is, CCSVFile& o);
    //ETX
};

#endif
