/**
 * Copyright (c) 2022 Dominic Masters
 * 
 * This software is released under the MIT License.
 * https://opensource.org/licenses/MIT
 */

#include "csv.hpp"

char * csvGetCell(csv_t *csv, int32_t row, int32_t cell) {
  return csv->rows[(row * CSV_COLUMN_COUNT_MAX) + cell];
}

void csvParse(char *string, csv_t *csv) {
  char c;
  size_t i, j, length;
  csvparsestate_t state;
  int32_t rowCellCount;

  length = strlen(string);
  csv->buffer = (char *)malloc(sizeof(char) * (length+1) * CSV_COLUMN_COUNT_MAX * CSV_ROW_COUNT_MAX);
  csv->cellCounts = (int32_t *)malloc(sizeof(int32_t) * CSV_ROW_COUNT_MAX);
  csv->rows = (char**)malloc(sizeof(char*) * CSV_ROW_COUNT_MAX * CSV_COLUMN_COUNT_MAX);

  i = 0;
  j = 0;
  rowCellCount = 0;
  csv->rowCount = 0;
  state = CSV_PARSE_STATE_FIND_CELL;
  while(i < length) {
    c = string[i++];

    // What are we doing
    switch(state) {
      case CSV_PARSE_STATE_FIND_CELL:
        if(c == '"') {
          state = CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES;
          csv->rows[(csv->rowCount * CSV_COLUMN_COUNT_MAX) + rowCellCount] = csv->buffer + j;
          rowCellCount++;
          continue;
        } else if(c == '\r' || c == '\n') {
          // Newline (todo: is this a blank line?)
          state = CSV_PARSE_STATE_LINE_END;
          continue;
        } else if(c == ',') {
          csv->rows[(csv->rowCount * CSV_COLUMN_COUNT_MAX) + rowCellCount] = csv->buffer + j;
          csv->buffer[j++] = '\0';
          rowCellCount++;
          continue;
        } else {
          state = CSV_PARSE_STATE_PARSE_CELL;
          csv->rows[(csv->rowCount * CSV_COLUMN_COUNT_MAX) + rowCellCount] = csv->buffer + j;
          csv->buffer[j++] = c;
          rowCellCount++;
          continue;
        }
      
      case CSV_PARSE_STATE_PARSE_CELL:
        if(c == '\r' || c == '\n') {
          state = CSV_PARSE_STATE_LINE_END;
          csv->buffer[j++] = '\0';
          continue;
        } else if(c == ',') {
          state = CSV_PARSE_STATE_FIND_CELL;
          csv->buffer[j++] = '\0';
          continue;
        }
        csv->buffer[j++] = c;
        continue;

      case CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES:
        if((c == '\\' && string[i] == '"') || (c == '"' && string[i] == '"')) {
          // Handle escaped quotes. I normally see [\"] but excel does [""] in
          // most cases
          csv->buffer[j++] = '"';
          i++;
          continue;
        } else if(c == '"') {
          // Handle end of quoted string
          state = CSV_PARSE_STATE_FIND_CELL;
          csv->buffer[j++] = '\0';
          // Because we tend to do [",] at the end of a quoted cell, we do this
          // to prevent [,,] cases being treated the same
          if(string[i] == ',') i++;
          continue;
        }

        // Normal character.
        csv->buffer[j++] = c;
        continue;

      case CSV_PARSE_STATE_LINE_END:
        // Skip blanks
        if(c == '\r' || c == '\n') continue;
        csv->cellCounts[csv->rowCount] = rowCellCount;
        csv->rowCount++;
        rowCellCount = 0;
        state = CSV_PARSE_STATE_FIND_CELL;
        i--;
        continue;

      default:
        printf("Error occured during parse operation.");
        free(NULL);
    }
  }

  csv->buffer[j++] = '\0';

  if(rowCellCount != 0) {
    csv->cellCounts[csv->rowCount] = rowCellCount;
    csv->rowCount++;
  }
}

void csvDispose(csv_t *csv) {
  free(csv->buffer);
  free(csv->cellCounts);
  free(csv->rows);
}