/*=========================================================================
 
  $   Workfile: CSVFile.cpp $ 
  $   Revision: 4 $
  $       Date: 4/05/01 12:21p $
  $    Modtime: 4/05/01 12:22p $
  $     Author: Dalleyg $
  $    Archive: /vc/common/CSVFile.h $
  $ NoKeywords: $

   by Gerald Dalley.
   Copyright (c) 2001, The Ohio State University

=========================================================================*/

#pragma warning (disable : 4786)

#include <stdio.h>
#include <fstream>
#include "CSVFile.h"

using namespace std;

CCSVFile::CCSVFile() 
{
    this->CurrRow = 0;
    this->CurrCol = 0;
    this->Delimiter = ',';
}

CCSVFile::~CCSVFile()
{
    this->Initialize();
}

CCSVFile::CCSVFile(const char *fname, char delim, bool throwOnError)
{
    ifstream    f(fname);

    if ((f.rdstate() & (ios::badbit | ios::failbit)) != 0)
    {
        cerr << "Could not open \"" << fname << "\"." << endl;
        if (throwOnError)
            throw;
        else
            return;
    }

    this->SetDelimiter(delim);

    f >> *this;
    f.close();
}

void CCSVFile::Initialize()
{
    this->CurrCol = 0;
    this->CurrRow = 0;

    /*
    this->Data.resize(0);
    FOR SOME REASON, STL DOESN'T LIKE ME DESTROYING POINTERS THAT *I* CREATED!
    UNTIL I UNDERSTAND IT BETTER, I'M USING A SIMPLER INITIALIZE WHICH MIGHT
    HAVE A MEMORY LEAK.
*/
    unsigned int numRows = this->Data.size();
    unsigned int numCols;
    unsigned int rowNum, colNum;
    const char *cell;

    for (rowNum=0; rowNum<numRows; rowNum++) 
    {
        CCSVFile_RowType &row = this->Data[rowNum];
        numCols = row.size();
        for (colNum=0; colNum<numCols; colNum++)
        {
            cell = row[colNum];
            if (cell != NULL) {
                free((void*)cell);

            }
            row[colNum] = NULL;
        }
    }
}

typedef const char *CellContents;

const char *CCSVFile::GetCell()
{
    if (this->CurrRow >= this->Data.size()) return NULL;
    CCSVFile_RowType &row = this->Data[this->CurrRow];
    if (this->CurrCol >= row.size()) return NULL;
    //const char *val = row[this->CurrCol];
    CellContents &val = row[this->CurrCol];
    this->CurrCol++;
#ifdef _DEBUG
    const char *checkVal = this->Data[this->CurrRow][this->CurrCol-1];
    int sz = row.size();
#endif
    return val;
}

const char *CCSVFile::GetCell(unsigned int row, unsigned int col)
{
    this->CurrRow = row;
    this->CurrCol = col;
    return this->GetCell();
}

void CCSVFile::SetCell(const char *val)
{
    if (val == NULL) return;

    // Copy val to a new memory location
    int l = strlen(val) + 1;
    char *cell = (char*)malloc(l);
    memcpy(cell, val, l);
    
    // Make sure there are enough rows
    if (this->CurrRow >= this->Data.size()) 
    {
        this->Data.resize(this->CurrRow+1);
    }

    // Make sure there are enough columns
    CCSVFile_RowType &row = this->Data[this->CurrRow];
    if (this->CurrCol >= row.size()) 
    {
        row.resize(this->CurrCol+1, NULL);
    }

    // We need to free the memory ourselves because the default Microsoft 
    // allocator doesn't free char* elements at all.
    if (row[this->CurrCol] != NULL) 
    {
        free((void*)row[this->CurrCol]);
    }
    row[this->CurrCol] = cell;

    this->CurrCol++;
}

void CCSVFile::SetCell(unsigned int row, unsigned int col, const char *val)
{
    this->CurrRow = row;
    this->CurrCol = col;
    this->SetCell(val);
}

double CCSVFile::GetCellAsNumber()
{
    const char *cell = this->GetCell();
    if (cell == NULL) return 0;
    return atof(cell);
}

void CCSVFile::SetCellAsNumber(double val)
{
    char buff[256];
    sprintf(buff, "%1.15g", val);
    SetCell(buff);
}

double CCSVFile::GetCellAsNumber(unsigned int row, unsigned int col)
{
    this->CurrRow = row;
    this->CurrCol = col;
    return this->GetCellAsNumber();
}

void CCSVFile::SetCellAsNumber(unsigned int row, unsigned int col, double val)
{
    this->CurrRow = row;
    this->CurrCol = col;
    this->SetCellAsNumber(val);
}

unsigned int CCSVFile::GetNumColInCurrRow()
{
    if (this->CurrRow >= this->Data.size()) return 0;
    CCSVFile_RowType &row = this->Data[this->CurrRow];
    int totalNumCols = row.size();

    // colpo is the "COLumn number Plus One".  We use it to get
    // the loop to be able to jump out since we're using
    // unsigned numbers.
    for (unsigned int colpo=totalNumCols; colpo>0; colpo--)
    {
        if ((row[colpo-1] != NULL) && (strlen(row[colpo-1]) > 0))
        {
            return colpo;
        }
    }
    return 0;
}

bool CCSVFile::RemainderOfRowIsEmpty() 
{
    return this->GetNumColInCurrRow() <= this->CurrCol;
}

void CCSVFile::SetDelimiter(char d)
{ 
    switch (d) 
    {
    case '"':
    case '\0':
    case '\r':
    case '\n':
        throw "Illegal delimiter character.  "
            "Must not be a double quote, "
            "end of line marker, or the null character.";
        break;
    default:
        this->Delimiter = d;    
    }
}


void CCSVFile::Squeeze()
{
	int r;
	int totalNumCols;
	int numValidRows = 0;
	int origNumRows = this->GetNumRows();

	for (r=0; r<origNumRows; r++) 
	{
		CCSVFile_RowType &row = this->Data[r];
		totalNumCols = row.size();

		while ((totalNumCols > 0) && 
			   ((row[totalNumCols-1] == NULL) || 
			    (strlen(row[totalNumCols-1]) == 0)))
		{
			totalNumCols--;
		}

		row.resize(totalNumCols, NULL);

		if (totalNumCols > 0)
		{
			numValidRows = r+1;
		}
	}

	this->Data.resize(numValidRows);
}

char *csvEscape(const char *s, const char d) 
{
    static char o[8192] = "\"";

    int sPos = 0;
    int oPos = 1;
    bool needsQuotes = false;

    while (1)
    {
        switch (s[sPos]) 
        {
            case '\0' :
                if (needsQuotes) o[oPos++] = '\"';
                o[oPos++] = '\0';
                return (needsQuotes ? o : o+1);
            case '"' :
                needsQuotes = true;
                o[oPos++] = '\"';
                o[oPos++] = '\"';
                break;
            default:
                if (s[sPos] == d)
                {
                    needsQuotes = true;
                    o[oPos++] = d;
                } 
                else 
                {
                    o[oPos++] = s[sPos];
                }
                break;
        }
        sPos++;
    }
}

ostream& operator<<(ostream& os, CCSVFile& o)
{
    const char *cell;
    unsigned int numRows = o.Data.size();
    for (unsigned int rowNum=0; rowNum<numRows; rowNum++) 
    {
        CCSVFile_RowType &row = o.Data[rowNum];
        unsigned int numCols = row.size();
        for (unsigned int col=0; col<numCols; col++)
        {
            cell = row[col];
            if (cell != NULL) os << csvEscape(cell, o.Delimiter);
            os << o.Delimiter;
        }
        os<<'\n';
    }

    return os;
}

#define READ_STATE_NO_QUOTES 0
#define READ_STATE_OPEN_QUOTE 1
#define READ_STATE_IN_QUOTES 2

istream& operator>>(istream& is, CCSVFile& o)
{
    char c;
    char buff[4096] = "";
    int buffEnd = 0;
    int readState = READ_STATE_NO_QUOTES;

    o.Initialize();

    while (is.read(&c, 1))
    {
        switch (c) 
        {
            case '"':
                if (buffEnd == 0) {
                    readState = READ_STATE_IN_QUOTES;
                } else {
                    switch (readState) 
                    {
                        case READ_STATE_NO_QUOTES:
                            // This means that there is a spurious quote
                            // in the middle of a cell value.  Don't
                            // do any translation.
                            buff[buffEnd++] = '"';
                            break;
                        case READ_STATE_IN_QUOTES:
                            readState = READ_STATE_OPEN_QUOTE;
                            break;
                        case READ_STATE_OPEN_QUOTE:
                            buff[buffEnd++] = '"';
                            readState = READ_STATE_IN_QUOTES;
                            break;
                    }
                }
                break;
            case '\n':
                // Technically, it's an error if the readState is
                // READ_STATE_IN_QUOTES, but we'll just ignore that
                // and close the open strings

                readState = READ_STATE_NO_QUOTES;

                // Cut off the buffer
                buff[buffEnd] = '\0';

                // Insert cell at end of row
                o.SetCell(buff);

                // Go to the next row
                o.CurrCol = 0;
                o.CurrRow++;

                // Reset buff
                buffEnd = 0;
                break;
            case '\r':
                // Ignore carriage returns
                break;
            default:
                if (c == o.Delimiter)
                {
                    switch (readState)
                    {
                        case READ_STATE_NO_QUOTES:
                        case READ_STATE_OPEN_QUOTE:
                            buff[buffEnd] = '\0';
                            o.SetCell(buff);
                            readState = READ_STATE_NO_QUOTES;
                            buffEnd = 0;
                            break;
                        case READ_STATE_IN_QUOTES:
                            buff[buffEnd++] = o.Delimiter;
                            break;
                    }
                }
                else
                {
                    buff[buffEnd++] = c;
                }
                break;
        }
    }

    // Cut off the buffer
    buff[buffEnd] = '\0';
    // Insert cell at end of row
    o.SetCell(buff);

    o.CurrCol = 0;
    o.CurrRow = 0;
    return is;
}
