#include "die.h"
#include "strerr.h"
#include "sub_sql.h"
#include "subdb.h"
#include <sys/types.h>
#include <sqlite3.h>
#include <unistd.h>

static stralloc line = {0};

static sqlite3_stmt *_sqlquery(const struct subdbinfo *info, const stralloc *str)
{
	sqlite3_stmt *stmt;

	if (sqlite3_prepare_v2((sqlite3*)info->conn, str->s, str->len, &stmt, NULL) != SQLITE_OK)
		return NULL;

	return stmt;
}

/* close connection to SQL server, if open */
static void _closesub(struct subdbinfo *info)
{
  if ((sqlite3*)info->conn)
    sqlite3_close((sqlite3*)info->conn);
  info->conn = 0;					/* destroy pointer */
}

/* open connection to the SQL server, if it isn't already open. */
static const char *_opensub(struct subdbinfo *info)
{
  if (!(sqlite3*)info->conn) {
    if (!stralloc_copys(&line,info->db)) die_nomem();
    if (!stralloc_cats(&line,".db")) die_nomem();
    if (!stralloc_0(&line)) die_nomem();
    if (sqlite3_open_v2(line.s, (sqlite3**)&info->conn, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL) != SQLITE_OK)
	return sqlite3_errmsg((sqlite3*)info->conn); /* init */
  }
  return (char *) 0;
}

static void die_sqlerror(struct subdbinfo *info)
{
  strerr_die2x(111,FATAL,sqlite3_errmsg((sqlite3*)info->conn));
}

int sql_exec(struct subdbinfo *info,
	     struct stralloc *q,
	     unsigned int nparams,
	     struct stralloc *params)
{
  sqlite3_stmt *stmt;
  int rows = 0;

  stmt = sql_select(info,q,nparams,params);
  switch (sqlite3_step(stmt)) {
  case SQLITE_DONE:
    rows = sqlite3_changes((sqlite3*)info->conn);
    break;
  case SQLITE_CONSTRAINT:	/* duplicated key on insert */
    break;
  default:
    die_sqlerror(info);
  }
  sqlite3_finalize(stmt);
  return rows;
}

void *sql_select(struct subdbinfo *info,
		 struct stralloc *q,
		 unsigned int nparams,
		 struct stralloc *params)
{
  unsigned int i;
  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2((sqlite3*)info->conn,q->s,q->len,&stmt,NULL) != SQLITE_OK)
    die_sqlerror(info);
  for (i = 0; i < nparams; ++i) {
    if (sqlite3_bind_text(stmt,i+1,params[i].s,params[i].len,SQLITE_STATIC) != SQLITE_OK)
      die_sqlerror(info);
  }
  return stmt;
}

int sql_fetch_row(struct subdbinfo *info,
		  void *result,
		  unsigned int ncolumns,
		  struct stralloc *columns)
{
  unsigned int i;
  sqlite3_stmt *stmt = result;

  switch (sqlite3_step(stmt)) {
  case SQLITE_DONE:
    return 0;
  case SQLITE_ROW:
    break;
  default:
    die_sqlerror(info);
  }

  for (i = 0; i < ncolumns; ++i) {
    if (!stralloc_copyb(&columns[i],(char*)sqlite3_column_text(stmt,i),sqlite3_column_bytes(stmt,i)))
      die_nomem();
  }
  return 1;
}

extern void sql_free_result(struct subdbinfo *info,
			    void *result)
{
  sqlite3_finalize((sqlite3_stmt*)result);
  (void)info;
}

int sql_table_exists(struct subdbinfo *info,
		     const char *name)
{
  sqlite3_stmt *stmt;
  int res;

  if (!stralloc_copys(&line,"SELECT name FROM sqlite_master WHERE name='")) return -1;
  if (!stralloc_cats(&line,name)) return -1;
  if (!stralloc_append(&line,'\'')) return -1;
  if (!stralloc_0(&line)) return -1;

  if ((stmt = _sqlquery(info, &line)) == NULL)
	  return -1;
  res = sqlite3_step(stmt);
  sqlite3_finalize(stmt);
  if (res == SQLITE_ROW)
	  return 1;
  else if (res != SQLITE_DONE)
	  return -1;
  return 0;
}

const char *sql_create_table(struct subdbinfo *info,
			     const char *definition)
{
  sqlite3_stmt *stmt;
  int res;

  if (!stralloc_copys(&line,definition)) die_nomem();
  if (!stralloc_0(&line)) die_nomem();

  if ((stmt = _sqlquery(info, &line)) == NULL)
	  return sqlite3_errmsg((sqlite3*)info->conn);

  res = sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  if (res != SQLITE_DONE)
	  return sqlite3_errmsg((sqlite3*)info->conn);
  return 0;
}

/* Address table */
/* Need varchar. Domain = 3 chars => fixed length, as opposed to varchar
 * Always select on domain and hash, so that one index should do primary
 * key(address) is very inefficient for MySQL.  MySQL tables do not need
 * a primary key. Other RDBMS require one. For the log tables, just add
 * an INT AUTO_INCREMENT. For the address table, do that or use address
 * as a primary key. */
const char sql_sub_table_defn[] =
  "  hash    INT4 NOT NULL,"
  "  address VARCHAR(255) NOT NULL PRIMARY KEY";

/* Subscription log table. No addr idx to make insertion fast, since
 * that is almost the only thing we do with this table */
const char sql_slog_table_defn[] =
  "  tai	INTEGER,"
  "  address	VARCHAR(255) NOT NULL,"
  "  fromline	VARCHAR(255) NOT NULL,"
  "  edir	CHAR NOT NULL,"
  "  etype	CHAR NOT NULL";

/* main list inserts a cookie here. Sublists check it */
const char sql_cookie_table_defn[] =
  "  msgnum	INT4 NOT NULL PRIMARY KEY,"
  "  tai	INTEGER NOT NULL,"
  "  cookie	CHAR(20) NOT NULL,"
  "  chunk	INT4 NOT NULL DEFAULT 0,"
  "  bodysize	INT4 NOT NULL DEFAULT 0";

/* main and sublist log here when the message is done done=0 for
 * arrived, done=4 for sent, 5 for receit.  tai reflects last change */
const char sql_mlog_table_defn[] =
  "msgnum	INT4 NOT NULL,"
  "listno	INT4 NOT NULL,"
  "tai		TIMESTAMP,"
  "subs		INT4 NOT NULL DEFAULT 0,"
  "done		INT4 NOT NULL DEFAULT 0,"
  "PRIMARY KEY (listno,msgnum,done)";

/* Definition of WHERE clauses for checktag */
const char sql_checktag_listno_where_defn[] = "listno=? AND msgnum=? AND done > 3";
const char sql_checktag_msgnum_where_defn[] = "msgnum=? AND cookie=?";

/* Definition of WHERE clause for selecting addresses in issub */
const char sql_issub_where_defn[] = "address LIKE ?";

/* Definition of VALUES clause for insert in logmsg */
const char sql_logmsg_values_defn[] = "(?,?,?,?)";

/* Definition of WHERE clause for selecting addresses in putsubs */
const char sql_putsubs_where_defn[] = "hash BETWEEN ? AND ?";

/* Definition of clauses for searchlog query */
const char sql_searchlog_select_defn[] = "tai, edir||etype||' '||address||' '||fromline";
const char sql_searchlog_where_defn[] = "fromline LIKE concat('%',?,'%') OR address LIKE concat('%',?,'%')";

/* Definition of clauses for subscribe queries */
const char sql_subscribe_select_where_defn[] = "address=?";
const char sql_subscribe_list_values_defn[] = "(?,?)";
const char sql_subscribe_delete1_where_defn[] = "address LIKE ? and hash BETWEEN 0 AND 52";
const char sql_subscribe_delete2_where_defn[] = "address LIKE ? and hash=?";
const char sql_subscribe_slog_values_defn[] = "(?,?,?,?)";

/* Definition of VALUES clause for insert in tagmsg */
const char sql_tagmsg_values_defn[] = "(?,'now',?,?,?)";

const char *sql_drop_table(struct subdbinfo *info,
			   const char *name)
{
  sqlite3_stmt *stmt;
  int res;

  if (!stralloc_copys(&line,"DROP TABLE ")) die_nomem();
  if (!stralloc_cats(&line,name)) die_nomem();
  if (!stralloc_0(&line)) die_nomem();

  if ((stmt = _sqlquery(info, &line)) == NULL)
	  return sqlite3_errmsg((sqlite3*)info->conn);

  res = sqlite3_step(stmt);
  sqlite3_finalize(stmt);

  if (res != SQLITE_DONE)
	  return sqlite3_errmsg((sqlite3*)info->conn);
  return 0;
}

struct sub_plugin sub_plugin = {
  SUB_PLUGIN_VERSION,
  sub_sql_checktag,
  _closesub,
  sub_sql_issub,
  sub_sql_logmsg,
  sub_sql_mktab,
  _opensub,
  sub_sql_putsubs,
  sub_sql_rmtab,
  sub_sql_searchlog,
  sub_sql_subscribe,
  sub_sql_tagmsg,
};
