/*
 * DBText library
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

#include "../../core/mem/shm_mem.h"
#include "../../core/mem/mem.h"
#include "../../core/dprint.h"

#include "dbt_util.h"
#include "dbt_lib.h"


/**
 * -1 - error
 *  0 - no change
 *  1 - changed
 */
int dbt_check_mtime(const str *tbn, const str *dbn, time_t *mt)
{
	char path[512];
	struct stat s;
	int ret = 0;

	path[0] = 0;
	if(dbn && dbn->s && dbn->len > 0) {
		if(dbn->len + tbn->len < 511) {
			strncpy(path, dbn->s, dbn->len);
			path[dbn->len] = '/';
			strncpy(path + dbn->len + 1, tbn->s, tbn->len);
			path[dbn->len + tbn->len + 1] = 0;
		}
	}
	if(path[0] == 0) {
		strncpy(path, tbn->s, tbn->len);
		path[tbn->len] = 0;
	}
	if(stat(path, &s) == 0) {
		if((int)s.st_mtime > (int)*mt) {
			ret = 1;
			*mt = s.st_mtime;
			LM_DBG("[%.*s] was updated\n", tbn->len, tbn->s);
		}
	} else {
		LM_DBG("stat failed on [%.*s]\n", tbn->len, tbn->s);
		ret = -1;
	}
	return ret;
}

/**
 *
 */
dbt_table_p dbt_load_file(const str *tbn, const str *dbn)
{
	FILE *fin = NULL;
	char path[512];
	char *buf;
	int c, crow, ccol, bp, sign, max_auto;
	dbt_val_t dtval = {0};
	dbt_table_p dtp = NULL;
	dbt_column_p colp, colp0 = NULL;
	dbt_row_p rowp, rowp0 = NULL;

	enum
	{
		DBT_FLINE_ST,
		DBT_NLINE_ST,
		DBT_DATA_ST
	} state;

	if(!tbn || !tbn->s || tbn->len <= 0 || tbn->len >= 255) {
		LM_ERR("invalid table name\n");
		return NULL;
	}
	if(!dbn || !dbn->s || dbn->len <= 0 || dbn->len >= 255) {
		LM_ERR("invalid database name\n");
		return NULL;
	}

	LM_DBG("request for table [%.*s] (len: %d)\n", tbn->len, tbn->s, tbn->len);
	LM_DBG("database is [%.*s] (len: %d)\n", dbn->len, dbn->s, dbn->len);

	strncpy(path, dbn->s, dbn->len);
	path[dbn->len] = '/';
	strncpy(path + dbn->len + 1, tbn->s, tbn->len);
	path[dbn->len + tbn->len + 1] = 0;

	LM_DBG("loading file [%s]\n", path);
	fin = fopen(path, "rt");
	if(!fin) {
		LM_ERR("failed to open file [%s]\n", path);
		return NULL;
	}

	buf = pkg_malloc(_db_text_read_buffer_size + 1);
	if(!buf) {
		LM_ERR("error allocating read buffer, %i\n", _db_text_read_buffer_size);
		goto done;
	}

	dtp = dbt_table_new(tbn, dbn, path);
	if(!dtp)
		goto done;

	state = DBT_FLINE_ST;
	crow = ccol = -1;
	colp = colp0 = NULL;
	rowp = rowp0 = NULL;
	c = fgetc(fin);
	max_auto = 0;
	while(c != EOF) {
		switch(state) {
			case DBT_FLINE_ST:
				//LM_DBG("state FLINE!\n");
				bp = 0;
				while(c == DBT_DELIM_C)
					c = fgetc(fin);
				if(c == DBT_DELIM_R && !colp0)
					goto clean;
				if(c == DBT_DELIM_R) {
					if(dtp->nrcols <= 0)
						goto clean;
					dtp->colv = (dbt_column_p *)shm_malloc(
							dtp->nrcols * sizeof(dbt_column_p));
					if(!dtp->colv)
						goto clean;
					colp0 = dtp->cols;
					for(ccol = 0; ccol < dtp->nrcols && colp0; ccol++) {
						dtp->colv[ccol] = colp0;
						colp0 = colp0->next;
					}
					state = DBT_NLINE_ST;
					break;
				}
				while(c != DBT_DELIM_C && c != '(' && c != DBT_DELIM_R) {
					if(c == EOF)
						goto clean;
					buf[bp++] = c;
					if(bp == _db_text_read_buffer_size) {
						LM_ERR("Buffer overflow for file [%s] row=[%d] "
							   "col=[%d] c=[%c]."
							   " Please increase 'file_buffer_size' param!\n",
								path, crow + 1, ccol + 1, c);
						goto clean;
					}
					c = fgetc(fin);
				}
				colp = dbt_column_new(buf, bp);
				if(!colp)
					goto clean;
				//LM_DBG("new col [%.*s]\n", bp, buf);
				while(c == DBT_DELIM_C)
					c = fgetc(fin);
				if(c != '(')
					goto clean;
				c = fgetc(fin);
				while(c == DBT_DELIM_C)
					c = fgetc(fin);

				switch(c) {
					case 's':
					case 'S':
						colp->type = DB1_STR;
						LM_DBG("column[%d] is STR!\n", ccol + 1);
						break;
					case 'i':
					case 'I':
						colp->type = DB1_INT;
						LM_DBG("column[%d] is INT!\n", ccol + 1);
						break;
					case 'd':
					case 'D':
						colp->type = DB1_DOUBLE;
						LM_DBG("column[%d] is DOUBLE!\n", ccol + 1);
						break;
					case 'b':
					case 'B':
						colp->type = DB1_BLOB;
						LM_DBG("column[%d] is BLOB!\n", ccol + 1);
						break;
					case 't':
					case 'T':
						colp->type = DB1_DATETIME;
						LM_DBG("column[%d] is TIME!\n", ccol + 1);
						break;
					default:
						LM_DBG("wrong column type!\n");
						goto clean;
				}

				while(c != '\n' && c != EOF && c != ')' && c != ',') {
					if(colp->type == DB1_STR && (c == 'i' || c == 'I')) {
						colp->type = DB1_STRING;
						LM_DBG("column[%d] is actually STRING!\n", ccol + 1);
					}
					c = fgetc(fin);
				}
				if(c == ',') {
					//LM_DBG("c=%c!\n", c);
					c = fgetc(fin);
					while(c == DBT_DELIM_C)
						c = fgetc(fin);
					if(c == 'N' || c == 'n') {
						LM_DBG("column[%d] NULL flag set!\n", ccol + 1);
						colp->flag |= DBT_FLAG_NULL;
					} else if(colp->type == DB1_INT && dtp->auto_col < 0
							  && (c == 'A' || c == 'a')) {
						//LM_DBG("AUTO flag set!\n");
						colp->flag |= DBT_FLAG_AUTO;
						dtp->auto_col = ccol + 1;
					} else
						goto clean;
					while(c != ')' && c != DBT_DELIM_R && c != EOF)
						c = fgetc(fin);
				}
				if(c == ')') {
					//LM_DBG("c=%c!\n", c);
					if(colp0) {
						colp->prev = colp0;
						colp0->next = colp;
					} else
						dtp->cols = colp;
					colp0 = colp;
					dtp->nrcols++;
					c = fgetc(fin);
				} else
					goto clean;
				ccol++;
				break;

			case DBT_NLINE_ST:
				//LM_DBG("state NLINE!\n");
				while(c == DBT_DELIM_R)
					c = fgetc(fin);
				if(rowp) {
					if(dbt_table_check_row(dtp, rowp))
						goto clean;

					if(!rowp0)
						dtp->rows = rowp;
					else {
						rowp0->next = rowp;
						rowp->prev = rowp0;
					}
					rowp0 = rowp;
					dtp->nrrows++;
				}
				if(c == EOF)
					break;
				crow++;
				ccol = 0;
				rowp = dbt_row_new(dtp->nrcols);
				if(!rowp)
					goto clean;
				state = DBT_DATA_ST;

				break;

			case DBT_DATA_ST:
				//LM_DBG("state DATA!\n");
				//while(c==DBT_DELIM)
				//	c = fgetc(fin);
				if(ccol == dtp->nrcols && (c == DBT_DELIM_R || c == EOF)) {
					state = DBT_NLINE_ST;
					break;
				}
				if(ccol >= dtp->nrcols)
					goto clean;

				switch(dtp->colv[ccol]->type) {
					case DB1_INT:
					case DB1_DATETIME:
						//LM_DBG("INT value!\n");
						dtval.val.int_val = 0;
						dtval.type = dtp->colv[ccol]->type;

						if(c == _dbt_delim
								|| (ccol == dtp->nrcols - 1
										&& (c == DBT_DELIM_R || c == EOF)))
							dtval.nul = 1;
						else {
							dtval.nul = 0;
							sign = 1;
							if(c == '-') {
								sign = -1;
								c = fgetc(fin);
							}
							if(c < '0' || c > '9')
								goto clean;
							while(c >= '0' && c <= '9') {
								dtval.val.int_val =
										dtval.val.int_val * 10 + c - '0';
								c = fgetc(fin);
							}
							dtval.val.int_val *= sign;
							//LM_DBG("data[%d,%d]=%d\n", crow,
							//	ccol, dtval.val.int_val);
						}
						if(c != _dbt_delim && c != DBT_DELIM_R && c != EOF)
							goto clean;
						if(dbt_row_set_val(
								   rowp, &dtval, dtp->colv[ccol]->type, ccol))
							goto clean;
						if(ccol == dtp->auto_col)
							max_auto = (max_auto < dtval.val.int_val)
											   ? dtval.val.int_val
											   : max_auto;
						break;

					case DB1_DOUBLE:
						//LM_DBG("DOUBLE value!\n");
						dtval.val.double_val = 0.0;
						dtval.type = DB1_DOUBLE;

						if(c == _dbt_delim
								|| (ccol == dtp->nrcols - 1
										&& (c == DBT_DELIM_R || c == EOF)))
							dtval.nul = 1;
						else {
							dtval.nul = 0;
							sign = 1;
							if(c == '-') {
								sign = -1;
								c = fgetc(fin);
							}
							if(c < '0' || c > '9')
								goto clean;
							while(c >= '0' && c <= '9') {
								dtval.val.double_val =
										dtval.val.double_val * 10 + c - '0';
								c = fgetc(fin);
							}
							if(c == '.') {
								c = fgetc(fin);
								bp = 1;
								while(c >= '0' && c <= '9') {
									bp *= 10;
									dtval.val.double_val +=
											((double)(c - '0')) / bp;
									c = fgetc(fin);
								}
							}
							dtval.val.double_val *= sign;
							//LM_DBG("data[%d,%d]=%10.2f\n",
							//	crow, ccol, dtval.val.double_val);
						}
						if(c != _dbt_delim && c != DBT_DELIM_R && c != EOF)
							goto clean;
						if(dbt_row_set_val(rowp, &dtval, DB1_DOUBLE, ccol))
							goto clean;
						break;

					case DB1_STR:
					case DB1_STRING:
					case DB1_BLOB:
						//LM_DBG("STR value!\n");

						dtval.val.str_val.s = NULL;
						dtval.val.str_val.len = 0;
						dtval.type = dtp->colv[ccol]->type;

						bp = 0;
						if(c == _dbt_delim
								|| (ccol == dtp->nrcols - 1
										&& (c == DBT_DELIM_R || c == EOF))) {
							/* If empty_string is enabled, we'll just return
							 * an empty string and avoid NULL
							 */
							if(empty_string == 0) {
								/* Default - NULL */
								dtval.nul = 1;
							}
						} else {
							dtval.nul = 0;
							while(c != _dbt_delim && c != DBT_DELIM_R
									&& c != EOF) {
								if(c == '\\') {
									c = fgetc(fin);
									switch(c) {
										case 'n':
											c = '\n';
											break;
										case 'r':
											c = '\r';
											break;
										case 't':
											c = '\t';
											break;
										case '\\':
											c = '\\';
											break;
										case '0':
											c = 0;
											break;
										default:
											if(c == _dbt_delim)
												break;
											goto clean;
									}
								}
								buf[bp++] = c;
								if(bp == _db_text_read_buffer_size) {
									LM_ERR("Buffer overflow for file [%s] "
										   "row=[%d] col=[%d] c=[%c]."
										   " Please increase "
										   "'file_buffer_size' param!\n",
											path, crow + 1, ccol + 1, c);
									goto clean;
								}
								c = fgetc(fin);
							}
							dtval.val.str_val.s = buf;
							dtval.val.str_val.len = bp;
							//LM_DBG("data[%d,%d]=%.*s\n",
							///	crow, ccol, bp, buf);
						}
						if(c != _dbt_delim && c != DBT_DELIM_R && c != EOF)
							goto clean;
						if(dbt_row_set_val(
								   rowp, &dtval, dtp->colv[ccol]->type, ccol))
							goto clean;
						break;
					default:
						goto clean;
				}
				if(c == _dbt_delim)
					c = fgetc(fin);
				ccol++;
				break; // state DBT_DATA_ST
		}
	}

	if(max_auto)
		dtp->auto_val = max_auto;

done:
	if(fin)
		fclose(fin);
	if(buf)
		pkg_free(buf);
	return dtp;
clean:
	// ????? FILL IT IN - incomplete row/column
	// memory leak?!?! with last incomplete row
	LM_DBG("error at row=%d col=%d c=%c\n", crow + 1, ccol + 1, c);
	if(dtp)
		dbt_table_free(dtp);
	if(fin)
		fclose(fin);
	if(buf)
		pkg_free(buf);
	return NULL;
}


/**
 *
 */
int dbt_print_table_header(dbt_table_p _dtp, FILE *fout)
{
	dbt_column_p colp = NULL;
	colp = _dtp->cols;
	while(colp) {
		switch(colp->type) {
			case DB1_INT:
				fprintf(fout, "%.*s(int", colp->name.len, colp->name.s);
				break;
			case DB1_DOUBLE:
				fprintf(fout, "%.*s(double", colp->name.len, colp->name.s);
				break;
			case DB1_STR:
				fprintf(fout, "%.*s(str", colp->name.len, colp->name.s);
				break;
			case DB1_STRING:
				fprintf(fout, "%.*s(string", colp->name.len, colp->name.s);
				break;
			case DB1_BLOB:
				fprintf(fout, "%.*s(blob", colp->name.len, colp->name.s);
				break;
			case DB1_DATETIME:
				fprintf(fout, "%.*s(time", colp->name.len, colp->name.s);
				break;
			default:
				if(fout != stdout)
					fclose(fout);
				return -1;
		}

		if(colp->flag & DBT_FLAG_NULL)
			fprintf(fout, ",null");
		else if(colp->type == DB1_INT && colp->flag & DBT_FLAG_AUTO)
			fprintf(fout, ",auto");
		fprintf(fout, ")");

		colp = colp->next;
		if(colp)
			fprintf(fout, "%c", DBT_DELIM_C);
	}
	fprintf(fout, "%c", DBT_DELIM_R);
	return 0;
}

int dbt_print_table_row_ex(
		dbt_table_p _dtp, dbt_row_p rowp, FILE *fout, int newline)
{
	int ccol;
	char *p;
	for(ccol = 0; ccol < _dtp->nrcols; ccol++) {
		switch(_dtp->colv[ccol]->type) {
			case DB1_DATETIME:
			case DB1_INT:
				if(!rowp->fields[ccol].nul)
					fprintf(fout, "%d", rowp->fields[ccol].val.int_val);
				break;
			case DB1_DOUBLE:
				if(!rowp->fields[ccol].nul)
					fprintf(fout, "%.2f", rowp->fields[ccol].val.double_val);
				break;
			case DB1_STR:
			case DB1_STRING:
			case DB1_BLOB:
				if(!rowp->fields[ccol].nul) {
					p = rowp->fields[ccol].val.str_val.s;
					while(p < rowp->fields[ccol].val.str_val.s
									  + rowp->fields[ccol].val.str_val.len) {
						switch(*p) {
							case '\n':
								fprintf(fout, "\\n");
								break;
							case '\r':
								fprintf(fout, "\\r");
								break;
							case '\t':
								fprintf(fout, "\\t");
								break;
							case '\\':
								fprintf(fout, "\\\\");
								break;
							case '\0':
								fprintf(fout, "\\0");
								break;
							default:
								if(*p == _dbt_delim) {
									fprintf(fout, "\\%c", _dbt_delim);
									break;
								}
								fprintf(fout, "%c", *p);
						}
						p++;
					}
				}
				break;
			default:
				if(fout != stdout)
					fclose(fout);
				return -1;
		}
		if(ccol < _dtp->nrcols - 1)
			fprintf(fout, "%c", _dbt_delim);
	}
	if(newline)
		fprintf(fout, "%c", DBT_DELIM_R);

	return 0;
}

int dbt_print_table_row(dbt_table_p _dtp, dbt_row_p rowp, FILE *fout)
{
	return dbt_print_table_row_ex(_dtp, rowp, fout, 1);
}

int dbt_print_table_rows(dbt_table_p _dtp, FILE *fout)
{
	dbt_row_p rowp = _dtp->rows;
	while(rowp) {
		if(dbt_print_table_row(_dtp, rowp, fout))
			return -1;
		rowp = rowp->next;
	}

	return 0;
}

int dbt_print_table_content(dbt_table_p _dtp, FILE *fout)
{
	if(dbt_print_table_header(_dtp, fout))
		return -1;
	return dbt_print_table_rows(_dtp, fout);
}

int dbt_print_table(dbt_table_p _dtp, str *_dbn)
{
	FILE *fout = NULL;
	int res = 0;
	char path[512];

	if(!_dtp || !_dtp->name.s || _dtp->name.len <= 0)
		return -1;

	if(!_dbn || !_dbn->s || _dbn->len <= 0) {
		fout = stdout;
		fprintf(fout, "\n Content of [%.*s::%.*s]\n", _dtp->dbname.len,
				_dtp->dbname.s, _dtp->name.len, _dtp->name.s);
	} else {
		if(_dtp->name.len + _dbn->len > 510)
			return -1;
		strncpy(path, _dbn->s, _dbn->len);
		path[_dbn->len] = '/';
		strncpy(path + _dbn->len + 1, _dtp->name.s, _dtp->name.len);
		path[_dbn->len + _dtp->name.len + 1] = 0;
		fout = fopen(path, "wt");
		if(!fout)
			return -1;
	}

	res = dbt_print_table_content(_dtp, fout);
	if(fout != stdout)
		fclose(fout);

	return res;
}
