/*
 * Copyright (C) 1999-2003 The Omega Project for Statistical Computing.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <R.h>
#include <Rinternals.h>
#include "vendor/sqlite3/sqlite3.h"

char* RS_sqlite_getline(FILE* in, const char* eol);

/* The following code comes directly from SQLite's shell.c, with
 * obvious minor changes.
 */
int RS_sqlite_import(
  sqlite3* db,
  const char* zTable, /* table must already exist */
  const char* zFile,
  const char* separator,
  const char* eol,
  int skip
) {
  sqlite3_stmt* pStmt; /* A statement */
  int rc;              /* Result code */
  int nCol;            /* Number of columns in the table */
  int nByte;           /* Number of bytes in an SQL string */
  int i, j;            /* Loop counters */
  int nSep;            /* Number of bytes in separator[] */
  char* zSql;          /* An SQL statement */
  char* zLine = NULL;  /* A single line of input from the file */
  char** azCol;        /* zLine[] broken up into columns */
  FILE* in;            /* The input file */
  int lineno = 0;      /* Line number of input file */
  char* z;

  nSep = strlen(separator);
  if (nSep == 0) {
    Rf_error("RS_sqlite_import: non-null separator required for import");
  }
  zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable);
  if (zSql == 0) {
    return 0;
  }
  nByte = strlen(zSql);
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  sqlite3_free(zSql);
  if (rc != SQLITE_OK) {
    sqlite3_finalize(pStmt);
    Rf_error("RS_sqlite_import: %s", sqlite3_errmsg(db));
    nCol = 0;
  } else {
    nCol = sqlite3_column_count(pStmt);
  }
  sqlite3_finalize(pStmt);
  if (nCol == 0) {
    return 0;
  }
  zSql = malloc(nByte + 20 + nCol * 2);
  if (zSql == 0) {
    return 0;
  }
  sqlite3_snprintf(nByte + 20, zSql, "INSERT INTO '%q' VALUES(?", zTable);
  j = strlen(zSql);
  for (i = 1; i < nCol; i++) {
    zSql[j++] = ',';
    zSql[j++] = '?';
  }
  zSql[j++] = ')';
  zSql[j] = 0;
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  free(zSql);
  if (rc != SQLITE_OK) {
    sqlite3_finalize(pStmt);
    Rf_error("RS_sqlite_import: %s", sqlite3_errmsg(db));
  }
  in = fopen(zFile, "rb");
  if (in == 0) {
    fclose(in);
    sqlite3_finalize(pStmt);
    Rf_error("RS_sqlite_import: cannot open file %s", zFile);
  }
  azCol = malloc(sizeof(azCol[0]) * (nCol + 1));
  if (azCol == 0) {
    return 0;
  }

  while ((zLine = RS_sqlite_getline(in, eol)) != NULL) {
    lineno++;
    if (lineno <= skip) {
      free(zLine);
      zLine = NULL;
      continue;
    }
    i = 0;
    azCol[0] = zLine;
    for (i = 0, z = zLine; *z && *z != '\n' && *z != '\r'; z++) {
      if (*z == separator[0] && strncmp(z, separator, nSep) == 0) {
        *z = 0;
        i++;
        if (i < nCol) {
          azCol[i] = &z[nSep];
          z += nSep - 1;
        }
      }
    }
    if (i + 1 != nCol) {
      free(zLine);
      free(azCol);
      fclose(in);
      sqlite3_finalize(pStmt);
      Rf_error(
        "RS_sqlite_import: %s line %d expected %d columns of data but found %d",
        zFile,
        lineno,
        nCol,
        i + 1
      );
    }

    for (i = 0; i < nCol; i++) {
      if (azCol[i][0] == '\\' && azCol[i][1] == 'N') { /* insert NULL for NA */
        sqlite3_bind_null(pStmt, i + 1);
      } else {
        sqlite3_bind_text(pStmt, i + 1, azCol[i], -1, SQLITE_STATIC);
      }
    }

    rc = sqlite3_step(pStmt);
    if (rc != SQLITE_DONE && rc != SQLITE_SCHEMA) {
      free(zLine);
      free(azCol);
      fclose(in);
      sqlite3_finalize(pStmt);
      Rf_error("RS_sqlite_import: %s", sqlite3_errmsg(db));
    }
    rc = sqlite3_reset(pStmt);
    free(zLine);
    zLine = NULL;
    if (rc != SQLITE_OK) {
      free(azCol);
      fclose(in);
      sqlite3_finalize(pStmt);
      Rf_error("RS_sqlite_import: %s", sqlite3_errmsg(db));
    }
  }
  free(azCol);
  fclose(in);
  sqlite3_finalize(pStmt);
  return 1;
}

/* the following is only needed (?) on windows (getline is a GNU extension
 * and it gave me problems with minGW).  Note that we drop the (UNIX)
 * new line character.  The R function safe.write() explicitly uses
 * eol = '\n' even on Windows.
 */

char* RS_sqlite_getline(FILE* in, const char* eol) {
  /* caller must free memory */
  char *buf, ceol;
  size_t nc, i;
  int c, j, neol;
  int found_eol = 0;

  nc = 1024;
  i = 0;
  buf = malloc(nc);
  if (!buf) {
    Rf_error("RS_sqlite_getline could not malloc");
  }

  neol = strlen(eol);   /* num of eol chars */
  ceol = eol[neol - 1]; /* last char in eol */
  while (TRUE) {
    c = fgetc(in);
    if (i == nc) {
      nc = 2 * nc;
      buf = (char*)realloc((void*)buf, nc);
      if (!buf) {
        Rf_error("RS_sqlite_getline could not realloc");
      }
    }
    if (c == EOF) {
      break;
    }
    buf[i++] = c;
    if (c == ceol) {
      /* see if we've got eol */
      found_eol = 1;
      for (j = neol - 1; j > 0; j--) {
        if (buf[(i - 1) - j] != eol[neol - 1 - j]) {
          found_eol = 0;
          break;
        }
      }
      if (found_eol) {
        buf[i - neol] = '\0'; /* drop the newline char(s) */
        break;
      }
    }
  }

  if (i == 0 || strlen(buf) == 0) { /* empty line */
    free(buf);
    buf = NULL;
  }

  return buf;
}
