//===----------------------------------------------------------------------===//
//                         DuckDB
//
// shell_state.hpp
//
//
//===----------------------------------------------------------------------===//

#pragma once

#include <vector>
#include <string>
#include <cstdint>
#include <memory>

struct sqlite3;
struct sqlite3_stmt;
enum class MetadataResult : uint8_t;

namespace duckdb_shell {
using std::string;
using std::unique_ptr;
using std::vector;
struct ColumnarResult;
struct RowResult;
class ColumnRenderer;
class RowRenderer;

using idx_t = uint64_t;

enum class RenderMode : uint32_t {
	LINE = 0,  /* One column per line.  Blank line between records */
	COLUMN,    /* One record per line in neat columns */
	LIST,      /* One record per line with a separator */
	SEMI,      /* Same as RenderMode::List but append ";" to each line */
	HTML,      /* Generate an XHTML table */
	INSERT,    /* Generate SQL "insert" statements */
	QUOTE,     /* Quote values as for SQL */
	TCL,       /* Generate ANSI-C or TCL quoted elements */
	CSV,       /* Quote strings, numbers are plain */
	EXPLAIN,   /* Like RenderMode::Column, but do not truncate data */
	ASCII,     /* Use ASCII unit and record separators (0x1F/0x1E) */
	PRETTY,    /* Pretty-print schemas */
	EQP,       /* Converts EXPLAIN QUERY PLAN output into a graph */
	JSON,      /* Output JSON */
	MARKDOWN,  /* Markdown formatting */
	TABLE,     /* MySQL-style table formatting */
	BOX,       /* Unicode box-drawing characters */
	LATEX,     /* Latex tabular formatting */
	TRASH,     /* Discard output */
	JSONLINES, /* Output JSON Lines */
	DUCKBOX    /* Unicode box drawing - using DuckDB's own renderer */
};

enum class PrintOutput { STDOUT, STDERR };

enum class LargeNumberRendering { NONE = 0, FOOTER = 1, ALL = 2, DEFAULT = 3 };

/*
** These are the allowed shellFlgs values
*/
#define SHFLG_Pagecache     0x00000001 /* The --pagecache option is used */
#define SHFLG_Lookaside     0x00000002 /* Lookaside memory is used */
#define SHFLG_Backslash     0x00000004 /* The --backslash option is used */
#define SHFLG_PreserveRowid 0x00000008 /* .dump preserves rowid values */
#define SHFLG_Newlines      0x00000010 /* .dump --newline flag */
#define SHFLG_CountChanges  0x00000020 /* .changes setting */
#define SHFLG_Echo          0x00000040 /* .echo or --echo setting */
#define SHFLG_HeaderSet     0x00000080 /* .header has been used */

/* ctype macros that work with signed characters */
#define IsSpace(X) isspace((unsigned char)X)
#define IsDigit(X) isdigit((unsigned char)X)
#define ToLower(X) (char)tolower((unsigned char)X)

/*
** State information about the database connection is contained in an
** instance of the following structure.
*/
struct ShellState {
public:
	ShellState();

	sqlite3 *db = nullptr;                    /* The database */
	uint8_t openMode = 0;                     /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */
	uint8_t doXdgOpen = 0;                    /* Invoke start/open/xdg-open in output_reset() */
	int outCount = 0;                         /* Revert to stdout when reaching zero */
	int lineno = 0;                           /* Line number of last line read from in */
	int openFlags = 0;                        /* Additional flags to open.  (SQLITE_OPEN_NOFOLLOW) */
	FILE *in = nullptr;                       /* Read commands from this stream */
	FILE *out = nullptr;                      /* Write results here */
	int nErr = 0;                             /* Number of errors seen */
	RenderMode mode = RenderMode::LINE;       /* An output mode setting */
	RenderMode modePrior = RenderMode::LINE;  /* Saved mode */
	RenderMode cMode = RenderMode::LINE;      /* temporary output mode for the current query */
	RenderMode normalMode = RenderMode::LINE; /* Output mode before ".explain on" */
	bool showHeader = false;                  /* True to show column names in List or Column mode */
	unsigned shellFlgs = 0;                   /* Various flags */
	unsigned priorShFlgs = 0;                 /* Saved copy of flags */
	int64_t szMax = 0;                        /* --maxsize argument to .open */
	char *zDestTable = nullptr;               /* Name of destination table when RenderMode::Insert */
	char *zTempFile = nullptr;                /* Temporary file that might need deleting */
	string colSeparator;                      /* Column separator character for several modes */
	string rowSeparator;                      /* Row separator character for RenderMode::Ascii */
	string colSepPrior;                       /* Saved column separator */
	string rowSepPrior;                       /* Saved row separator */
	vector<int> colWidth;                     /* Requested width of each column in columnar modes */
	string nullValue;                         /* The text to print when a NULL comes back from the database */
	int columns = 0;                          /* Column-wise DuckBox rendering */
	string outfile;                           /* Filename for *out */
	string zDbFilename;                       /* name of the database file */
	sqlite3_stmt *pStmt = nullptr;            /* Current statement if any. */
	FILE *pLog = nullptr;                     /* Write log output here */
	size_t max_rows = 0;                      /* The maximum number of rows to render in DuckBox mode */
	size_t max_width = 0; /* The maximum number of characters to render horizontally in DuckBox mode */
	//! Decimal separator (if any)
	char decimal_separator = '\0';
	//! Thousand separator (if any)
	char thousand_separator = '\0';
	//! When to use formatting of large numbers (in DuckBox mode)
	LargeNumberRendering large_number_rendering = LargeNumberRendering::DEFAULT;
	//! The command to execute when `-ui` is passed in
	string ui_command = "CALL start_ui()";

public:
	void PushOutputMode();
	void PopOutputMode();
	void OutputCSV(const char *z, int bSep);
	void PrintRowSeparator(idx_t nArg, const char *zSep, const vector<idx_t> &actualWidth);
	void PrintMarkdownSeparator(idx_t nArg, const char *zSep, const vector<int> &colTypes,
	                            const vector<idx_t> &actualWidth);
	void OutputCString(const char *z);
	void OutputQuotedString(const char *z);
	void OutputQuotedEscapedString(const char *z);
	void OutputHexBlob(const void *pBlob, int nBlob);
	void PrintSchemaLine(const char *z, const char *zTail);
	void PrintSchemaLineN(char *z, int n, const char *zTail);
	void PrintOptionallyQuotedIdentifier(const char *z);
	bool IsNumber(const char *z, int *realnum);
	void OutputJSONString(const char *z, int n);
	void PrintDashes(idx_t N);
	void UTF8WidthPrint(FILE *pOut, idx_t w, const string &str, bool right_align);
	bool SetOutputMode(const char *mode, const char *tbl_name);
	bool ImportData(const char **azArg, idx_t nArg);
	bool OpenDatabase(const char **azArg, idx_t nArg);
	bool SetOutputFile(const char **azArg, idx_t nArg, char output_mode);
	bool ReadFromFile(const string &file);
	bool DisplaySchemas(const char **azArg, idx_t nArg);
	MetadataResult DisplayEntries(const char **azArg, idx_t nArg, char type);
	void ShowConfiguration();

	idx_t RenderLength(const char *z);
	idx_t RenderLength(const string &str);
	void SetBinaryMode();
	void SetTextMode();
	static idx_t StringLength(const char *z);
	void SetTableName(const char *zName);
	int RunTableDumpQuery(const char *zSelect);
	void PrintValue(const char *str);
	void Print(PrintOutput output, const char *str);
	void Print(PrintOutput output, const string &str);
	void Print(const char *str);
	void Print(const string &str);
	void PrintPadded(const char *str, idx_t len);
	bool ColumnTypeIsInteger(const char *type);
	string strdup_handle_newline(const char *z);
	ColumnarResult ExecuteColumnar(sqlite3_stmt *pStmt);
	unique_ptr<ColumnRenderer> GetColumnRenderer();
	unique_ptr<RowRenderer> GetRowRenderer();
	unique_ptr<RowRenderer> GetRowRenderer(RenderMode mode);
	void ExecutePreparedStatementColumnar(sqlite3_stmt *pStmt);
	char **TableColumnList(const char *zTab);
	void ExecutePreparedStatement(sqlite3_stmt *pStmt);

	void PrintDatabaseError(const char *zErr);
	int ShellDatabaseError(sqlite3 *db);
	int RunInitialCommand(char *sql);

	int RenderRow(RowRenderer &renderer, RowResult &result);

	int ExecuteSQL(const char *zSql, /* SQL to be evaluated */
	               char **pzErrMsg   /* Error msg written here */
	);
	int RunSchemaDumpQuery(const char *zQuery);
	void OpenDB(int openFlags);

	void SetOrClearFlag(unsigned mFlag, const char *zArg);
	bool ShellHasFlag(int flag) {
		return (shellFlgs & flag) != 0;
	}

	void ShellSetFlag(int flag) {
		shellFlgs |= flag;
	}

	void ShellClearFlag(int flag) {
		shellFlgs &= ~flag;
	}
	void ResetOutput();
	void ClearTempFile();
	void NewTempFile(const char *zSuffix);
	int DoMetaCommand(char *zLine);

	int RunOneSqlLine(char *zSql);
	string GetDefaultDuckDBRC();
	void ProcessDuckDBRC(const char *file);
	void ProcessFile(const string &file, bool is_duckdb_rc = false);
	int ProcessInput();
};

} // namespace duckdb_shell
