#include "util.h"
#include "CsvWriter.h"
#include "SqlLexer.h"
#include <QApplication>
#include <QTextStream>
#include <QClipboard>
#include <sstream>
#include <stdexcept>

// Supported range from microseconds to seconds
// min:sec to hours::min::sec
QString msfloatToHumanReadableString(float ms)
{
	QString unit;
	float val;
	int deci = 2;
	if (ms < 1.0f) {
		val = ms * 1000.f;
		//result = QString::asprintf("%0.3f", ms * 1000.0f);
		unit = u8"μs";
	}
	else if (ms >= 1000.0) {
		val = ms / 1000.0f;
		unit = "s";
		if (val >= 60.0) {
			int secs = val;
			int min = secs / 60.0;
			secs -= min * 60;
			if (min >= 60) {
				int hour = min / 60;
				min -= hour * 60;
				return QString::asprintf("%d:%02d:%02d", hour, min, secs);
			}
			else {
				return QString::asprintf("%02d:%02d", min, secs);
			}
		}
	}
	else {
		val = ms;
		unit = "ms";
	}

//		if (val >= 1000.f) {
//			deci = 0;
//		}
//		else
	if (val >= 100.f) {
		deci = 0;
	}
	else if (val >= 10.f) {
		deci = 1;
	}
	QString result = QString::asprintf("%0.*f", deci, val);
	return result + unit;
}

void exportTable(const QTableView *view, QTextStream &out)
{
	auto model = view->model();
	if (model) {
		CsvWriter csv(&out);
		csv.setSeperator('\t');
		csv.setQuote('"');

		auto cols = model->columnCount();
		auto rows = model->rowCount();

        // header
        for (int col = 0; col < cols; ++col) {
            auto display_text = model->headerData(col, Qt::Horizontal, Qt::DisplayRole).toString();
            csv.writeField(display_text);
        }
        csv.nextRow();

		for (auto row = 0; row < rows; ++row) {
			for (int col = 0; col < cols; ++col) {
				auto idx = model->index(row, col);
				auto display_text = idx.data(Qt::DisplayRole).toString();
				csv.writeField(display_text);
			}
			csv.nextRow();
		}
		out.flush();
	}
}

void copySelectionToClipboard(const QTableView *view)
{
	//QAbstractItemModel * model = resultModel; //view->model();
	QItemSelectionModel * selection = view->selectionModel();

	QModelIndexList selectedIndexes = selection->selectedIndexes();

	if (selectedIndexes.count() > 0) {
		QString clipboard_string;
        QTextStream out(&clipboard_string, QTextStream::WriteOnly);
		CsvWriter csv(&out);
		csv.setSeperator('\t');
		csv.setQuote('"');

		int previous_row = selectedIndexes[0].row();
	//		for (int i = 0; i < selectedIndexes.count(); ++i) {
	//			QModelIndex current = selectedIndexes[i];
		for (auto current : selectedIndexes) {
			if (current.row() > previous_row) {
				csv.nextRow();
				previous_row = current.row();
			}
			QString display_text = current.data(Qt::DisplayRole).toString();
			csv.writeField(display_text);
		}
		out.flush();
		QApplication::clipboard()->setText(clipboard_string);
	}
}

QString EscapeForCString(QString qi)
{
	int escape_count = 0;
	for (QChar c : qi)
		if (c == '"' || c == '\\') ++escape_count;

	QString out;
	out.reserve(qi.size() + escape_count);
	for (QChar c : qi)
		if (c == '"' || c == '\\') {
			out += "\\";
			out += c;
		}
		else
			out += c;
	return out;
}

QString ConvertToMultiLineCString(const QString &in_)
{
	// We need to atleast escape " and \ and also any multi byte utf8 char

	// remove empty lines at start
	int last_nl_idx = 0;
	for (int idx = 0; idx < in_.length(); ++idx) {
		QChar c = in_[idx];
		if (c == '\n') last_nl_idx = idx+1;
		if (!c.isSpace()) {
			break;
		}
	}
	QString in = in_.right(in_.length() - last_nl_idx);
	int idx;
	for (idx = in.length() - 1; idx >= 0 && in[idx].isSpace(); --idx) ;
	++idx;
	in.truncate(idx);

	SqlLexer lexer(in, LexerState::Null, true);
	QString out;
	QString line = "\"";
	QString comment;
	while (true) {
		SqlToken token = lexer.nextBasicToken();
		if (token.ok) {
			if (token.tokenType == BasicTokenType::Comment) {
				// save comment is seperate variable
                comment = "//" + token.out.right(token.out.length()-2);
				// Trim whitespace on right
				int idx;
				for (idx = comment.length() - 1; idx >= 0 && comment[idx].isSpace(); --idx) ;
				++idx;
				comment.truncate(idx);
			}
			else if (token.tokenType == BasicTokenType::End || token.tokenType == BasicTokenType::NewLine) {
				// trim right
				{
					int idx;
					for (idx = line.length() - 1; idx >= 0 && line[idx].isSpace(); --idx) ;
					++idx;
					if (!comment.isEmpty()) {
						// put the whitespace in front of the comment so it will be outside the contents of the string literal but alignment of comments is preserved
                        comment = line.right(line.length() - (idx)) + comment;
					}
					line.truncate(idx);
				}

				out += line;
				if (token.tokenType == BasicTokenType::End) {
					out += "\"";
					out += comment;
					break;
				}
				else {
					out += "\\n\"";
					out += comment;
					out += "\n";
					line = "\"";
				}
				comment.clear();
			}
			else {
				line += EscapeForCString(token.out);
			}
		}
		else {
			// error during lexical analysis, need to recover
			throw std::runtime_error("Unrecognized input");
		}
	}
	return out;
}


QString ConvertToMultiLineRawCppString(const QString &in)
{
    const QString delim = "__SQL__";
    
    QString out;
    out.append("R\"" + delim + "(\n");
    out.append(in.trimmed());
    out.append("\n)" + delim + "\"");
    return out;
}

namespace {

    class Token {
    public:
        enum Type {
            Code,
            StringLiteral,
            SingleLineComment,
            MultiLineComment
        };

        const Type type;
        /// Depends on type
        /// Code: literal copy of the matched code
        /// StringLiteral: the contents of the string literal, escapes have been unescaped
        /// *Comment, the text in the comment
        const QString data;

        Token(Type type, QString data)
            : type(type)
            , data(data)
        {}
    };

    /// Tokenizer to get SQL out of a piece of programming language code
    ///
    /// It works by ignoring most input and only get's triggered by string literals
    /// and comments. It does return tokens for the code in between just so
    class ProgLangTokenizer {
    public:
        ProgLangTokenizer(const QString &in)
            : input(in)
        {}

    private:
        const QString input;
        int position = 0;

    };

}

QString ConvertLangToSqlString(const QString &in)
{
	// Assume mostly C++ for now but allow some other things like
	// - single quotes (php)
	// - concattenation operators . (php) and + (java)

	// Allow cpp prefixes L u8 u U

	// Parser flow, we start in whitespace state and search for prefix|opening quote
	// parse string and process escapes
	// if escape is \r strip it if \n go to new line
	// until we reach matching end quote
	// skip whitespace and + or .


	QString output;

	enum {
		WHITESPACE,
		PREFIX,
		IN_STRING,
        SingleLineComment,
		END,
		ERROR
	} state = WHITESPACE;
	int index = 0;
	QChar quote = '\0';
	while (state != ERROR && state != END && index < in.length()) {
		if (state == WHITESPACE) {
			// skip all whitespace untill we encounter something else
			// we also skip concatenation operators. Note this code is not trying to validate
			// for correct syntax so it will quite happily accept many incorrect constructs
			// that doesn't matter however as we are just trying to strip everything which is not SQL.
			while (index < in.length() && (in[index].isSpace() || in[index] == '+' || in[index] == '.')) ++index;
			if (index == in.length()) {
				state = END;
				break;
			}
			// Assume quotes can vary
			if (in[index] == '\'' || in[index] == '\"') {
				quote = in[index];
				++index;
				state = IN_STRING;
			}
			else {
				state = PREFIX;
			}
		}
		else if (state == PREFIX) {
			auto c = in[index];
			if (c == 'L' || c == 'U') {
				// C++ prefix expect C++ double quote
				if (in.length() > index+ 1 && in[index+1] == '"') {
					index += 2;
					state = IN_STRING;
				}
				else {
					state = ERROR;
					break;
				}
			}
			if (c == 'u') {
				// C++ prefix expect C++ double quote
				if (in.length() > index+ 2 && in[index+1] == '8' && in[index+2] == '"') {
					index += 3;
					state = IN_STRING;
				}
				else if (in.length() > index+ 1 && in[index+1] == '"') {
					index += 2;
					state = IN_STRING;
				}
				else {
					state = ERROR;
					break;
				}
			}
			else {
				state = ERROR;
				break;
			}
		}
		else if (state == IN_STRING) {
			// scan contents of string and process any escapes encountered
			bool escape = false;
			while (state != ERROR && index < in.length()) {
				QChar c = in[index];
				if (escape) {
					if (c == 'a') output += '\x07';
					else if (c == 'a') output += '\x07';
					else if (c == 'b') output += '\x08';
					else if (c == 'f') output += '\x0c';
					else if (c == 'n') output += '\n';
					else if (c == 'r') ;
					else if (c == 'v') ;
					else if (c >= '0' && c <= '7') {
						// process octal escape
						if (in.length() > index + 2) {
							char buf[4];
							buf[0] = c.toLatin1();
							buf[1] = in[++index].toLatin1();
							buf[2] = in[++index].toLatin1();
							buf[3] = 0;
							long int v = strtol(buf, nullptr, 8);
							if (v < 0x80) {
								output += static_cast<QChar>(static_cast<char>(v));
							}
							else {
								state = ERROR;
								break;
							}
						}
						else {
							state = ERROR;
							break;
						}
					}
					else if (c == 'x') {
						// hex byte
						if (in.length() > index + 2) {
							char buf[3];
							buf[0] = in[++index].toLatin1();
							buf[1] = in[++index].toLatin1();
							buf[2] = 0;
							long int v = strtol(buf, nullptr, 16);
							output += static_cast<QChar>(static_cast<char>(v));
						}
						else {
							state = ERROR;
							break;
						}
					}
					else if (c == 'u') {
						// 4 digit hax unicode codepoint
						// hex byte
						if (in.length() > index + 4) {
							char buf[5];
							buf[0] = in[++index].toLatin1();
							buf[1] = in[++index].toLatin1();
							buf[2] = in[++index].toLatin1();
							buf[3] = in[++index].toLatin1();
							buf[4] = 0;
							long int v = strtol(buf, nullptr, 16);
							output += static_cast<QChar>(static_cast<ushort>(v));
						}
						else {
							state = ERROR;
							break;
						}

					}
					else if (c == 'U') {
						// 8 digit hax unicode codepoint
						if (in.length() > index + 8) {
							char buf[9];
							buf[0] = in[++index].toLatin1();
							buf[1] = in[++index].toLatin1();
							buf[2] = in[++index].toLatin1();
							buf[3] = in[++index].toLatin1();
							buf[4] = in[++index].toLatin1();
							buf[5] = in[++index].toLatin1();
							buf[6] = in[++index].toLatin1();
							buf[7] = in[++index].toLatin1();
							buf[8] = 0;
							uint v = static_cast<uint>(strtol(buf, nullptr, 16));
							if (QChar::requiresSurrogates(v)) {
								output += QChar(QChar::highSurrogate(v));
								output += QChar(QChar::lowSurrogate(v));
							}
						}
						else {
							state = ERROR;
							break;
						}

					}
					else {
						output += c;
					}
					escape = false;
				}
				else {
					if (c == quote) {
						state = WHITESPACE;
						++index;
						break;
					}
					// Is there any language where string literals do not have to be terminated before the end of the line
					// Not considering string literals that explicitly allow for multiline strings as these are often raw strings
					// and can be copy pasted normally.
					else if (c == '\n') {
						state = WHITESPACE;
						++index;
						break;
					}
					else if (c == '\\') {
						escape = true;
					}
					else {
						output += c;
					}
				}
				++index;
			}
		}
	}

	return output;
}
