// JdbcClient.cpp : Defines the entry point for the console application.
//

#include <iostream>
#include <iomanip>
#include <string>
#include <ctime>
#include <jni.h>

JavaVM *jvm;                      // Pointer to the JVM (Java Virtual Machine)
JNIEnv *env;                      // Pointer to native interface
jclass  jdi;
jobject myo;

void PrintQuery(int);

int main(int argc, char* argv[])
{
	using namespace std;

	//================== prepare loading of Java VM ============================
	JavaVMInitArgs vm_args;                        // Initialization arguments
	JavaVMOption* options = new JavaVMOption[1];   // JVM invocation options
	// where to find java .class
	options[0].optionString =
	"-Djava.class.path="
	"D:\\Jprojects\\JdbcInterface\\bin;"
	"D:\\mysql-connector-java-5.1.17";
	vm_args.version = JNI_VERSION_1_6;             // minimum Java version
	vm_args.nOptions = 1;                          // number of options
	vm_args.options = options;
	vm_args.ignoreUnrecognized = false;     // invalid options make the JVM init fail

	//=============== load and initialize Java VM and JNI interface =============
	jint rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);  // YES !!
	delete options;    // we then no longer need the initialisation options.

	if (rc != JNI_OK) {
		// TO DO: error processing... 
		cin.get();
		exit(EXIT_FAILURE);
	} // endif JNI_OK

	//=============== Display JVM version =======================================
	cout << "JVM load succeeded: Version ";
	jint ver = env->GetVersion();
	cout << ((ver>>16)&0x0f) << "."<<(ver&0x0f) << endl;

	// TO DO: add the code that will use JVM <============  (see next steps)
	// try to find the class
	jdi = env->FindClass("JdbcInterface");

	if (jdi == nullptr) {
		cerr << "ERROR: class not found !" << endl;
		return 0;
	} // endif cls2
	
	// if class found, continue
	cout << "Class JdbcInter found" << endl;

	jmethodID ctor = env->GetMethodID(jdi, "<init>", "()V");

	if (ctor == nullptr) {
		cerr << "ERROR: constructor not found !" << endl;
	} else {
		myo = env->NewObject(jdi, ctor);

		// If the object is successfully constructed, 
		// we can then search for the method we want to call, 
		// and invoke it for the object:
		if (myo) {
			cout << "Object successfully constructed !" << endl;

			// find method
			jmethodID cid = env->GetMethodID(jdi, "JdbcConnect", "([Ljava/lang/String;)I");

			if (env->ExceptionCheck()) {
				cerr << "ERROR: method JdbcConnect() not found !" << endl;
				env->ExceptionDescribe();
				env->ExceptionClear();
			} else {
				// Build the java string array
				jobjectArray parms = env->NewObjectArray(4,    // constructs java array of 4
					env->FindClass("java/lang/String"), NULL);   // Strings

				// change some elements
				env->SetObjectArrayElement(parms, 1, 
					env->NewStringUTF("jdbc:mysql://localhost/test"));
				env->SetObjectArrayElement(parms, 2, env->NewStringUTF("root"));

				// call method
				rc = env->CallIntMethod(myo, cid, parms);
				cout << "Connection rc=" << rc << endl;

				// Not used anymore
				env->DeleteLocalRef(parms);

				if (rc == (jint)0) {
					jmethodID xid = env->GetMethodID(jdi, "Execute", "(Ljava/lang/String;)I");
					jmethodID grs = env->GetMethodID(jdi, "GetResult", "()I");

					if (xid != nullptr && grs != nullptr) {
						jint   n, ncol;
						string query;

						while (true) {
							cout << "Query: ";
							getline(cin, query);

							if (!query.length())
								break;

							cout << "Trying to execute " << query << endl;
							jstring qry = env->NewStringUTF(query.c_str());
							n = env->CallIntMethod(myo, xid, qry);
							cout << "Execute n=" << n << endl;
							env->DeleteLocalRef(qry);

							if ((ncol = env->CallIntMethod(myo, grs)))
								PrintQuery((int)ncol);
							else
								cout << "Affected rows = " << n << endl;

						} // endwhile

					}	else
						cerr << "ERROR: method Execute() not found !" << endl;

					jmethodID did = env->GetMethodID(jdi, "JdbcDisconnect", "()I");

					if (did == nullptr)
						cerr << "ERROR: method JdbcDisconnect() not found !" << endl;
					else {
						rc = env->CallIntMethod(myo, did);
						cout << "Disconnect rc=" << rc << endl;
					} // endif did

					}	// endif rc

			} // endif mid

		} // endif myo

	} // endif ctor

	jvm->DestroyJavaVM();
//cin.get();
	return 0;
}	// end of main

void PrintQuery(int ncol)
{
	using namespace std;
	char        dat[20];
	int         i, n = 0;
	jint				ctyp;
	jlong       dtv;
	jstring     cn;
	jobject     job;
	double      dbl;
	jfloat      jflt;
	
	const char *colName, *field;
	jmethodID   fldid;

	jmethodID colid = env->GetMethodID(jdi, "ColumnName", "(I)Ljava/lang/String;");

	if (colid == nullptr) {
		cout << "ColumnName not found" << endl;
		return;
	}	// endif colid

	// Get the column names; column indices start from 1
	for (i = 1; i <= ncol; i++) {
		cn = (jstring)env->CallObjectMethod(myo, colid, (jint)i);
		colName = env->GetStringUTFChars(cn, (jboolean)false);

		if (i > 1)
			cout << '\t';

		cout << colName;
	} // endfor i

	cout << endl;

	jmethodID readid = env->GetMethodID(jdi, "ReadNext", "()Z");

	if (readid == nullptr) {
		cout << "ReadNext not found" << endl;
		return;
	}	// endif readid

	jmethodID typid = env->GetMethodID(jdi, "ColumnType", "(I)I");

	if (typid == nullptr) {
		cout << "ColumnType not found" << endl;
		return;
	}	// endif typid

	// change output format settings with member functions
	cout.setf(ios::fixed, ios::floatfield);  // set fixed floating format
	cout.precision(2);  // for fixed format, two decimal places

	while (env->CallBooleanMethod(myo, readid)) {
		for (i = 1; i <= ncol; i++) {
			if (i > 1)
				cout << "\t";

			ctyp = env->CallIntMethod(myo, typid, i);

			switch (ctyp) {
			case 12:          // VARCHAR
			case -1:          // LONGVARCHAR
			case 1:           // CHAR
				fldid = env->GetMethodID(jdi, "StringField", "(I)Ljava/lang/String;");

				if (fldid != nullptr) {
					cn = (jstring)env->CallObjectMethod(myo, fldid, (jint)i);

					if (cn) {
						field = env->GetStringUTFChars(cn, (jboolean)false);
						cout << field;
					} else
						cout << "<null>";

				} else
					cout << "Fail";

				break;
/*		case java.sql.Types.TIMESTAMP:
				if (DEBUG)
					System.out.print("(timestamp)");

				//			    	System.out.print(rs.getTimestamp(i));
				System.out.print(jdi.TimeField(i));
				break;	*/
			case 4:           // INTEGER
			case 5:           // SMALLINT
			case -6:          // TINYINT
				fldid = env->GetMethodID(jdi, "IntField", "(I)I");

				if (fldid != nullptr)
					cout << (int)env->CallIntMethod(myo, fldid, i);
				else
					cout << "Fail";

				break;
			case 8:           // DOUBLE
			case 3:           // DECIMAL
				fldid = env->GetMethodID(jdi, "DoubleField", "(I)D");

				if (fldid != nullptr) {
					dbl = env->CallDoubleMethod(myo, fldid, i);
					cout << setw(8) << dbl;
				} else
					cout << "Fail";

				break;
			case 7:           // REAL
			case 6:           // FLOAT
				fldid = env->GetMethodID(jdi, "FloatField", "(I)F");

				if (fldid != nullptr) {
					jflt = env->CallFloatMethod(myo, fldid, i);
					cout << setw(8) << jflt;
				} else
					cout << "Fail";

				break;
			case 91:          // DATE
			case 92:          // TIME
			case 93:          // TIMESTAMP
				fldid = env->GetMethodID(jdi, "TimestampField", "(I)Ljava/sql/Timestamp;");

				if (fldid != nullptr) {
					job = env->CallObjectMethod(myo, fldid, (jint)i);

					if (job) {
						jclass jts = env->FindClass("java/sql/Timestamp");

						if (env->ExceptionCheck()) {
							cout << "Fail";
						} else {
							jmethodID getTime = env->GetMethodID(jts, "getTime", "()J");

							if (getTime != nullptr) {
								dtv = env->CallLongMethod(job, getTime);
								time_t t = (time_t)dtv / 1000;
								const struct tm *ts = localtime(&t);

								switch (ctyp) {
								case 91: strftime(dat, sizeof(dat), "%Y-%m-%d", ts); break;
								case 92: strftime(dat, sizeof(dat), "%H:%M:%S", ts); break;
								case 93: strftime(dat, sizeof(dat), "%Y-%m-%d %H:%M:%S", ts);
								} // endswitch ctyp

								cout << dat;
							} else
								cout << "Fail";

						} // endif check

					} else
						cout << "<null>";

				} else
					cout << "Fail";

				break;
			case -5:          // BIGINT
				fldid = env->GetMethodID(jdi, "BigintField", "(I)J");

				if (fldid != nullptr) {
					dtv = env->CallLongMethod(myo, fldid, (jint)i);
					cout << dtv;
				} else
					cout << "Fail";

				break;
/*			case java.sql.Types.SMALLINT:
				System.out.print(jdi.IntField(i));
				break;
			case java.sql.Types.BOOLEAN:
				System.out.print(jdi.BooleanField(i)); */
			default:
				break;
			} // endswitch Type

		} // endfor i

		cout << endl;
		n++;
	}	// endwhile

	cout << "Result has " << n << " line(s)" << endl;
}	// end of PrintQuery

