Jdbc = {
    connect: function (driverClassName, url, user, pass) {
	new Packages[driverClassName](); // cause the driver to load itself
	var c = java.sql.DriverManager.getConnection(url, user, pass);
	return new Jdbc.Connection(c);
    },

    _typeMap: {
	T0: {name: "NULL"},
	T1111: {name: "OTHER"},
	T12: {name: "VARCHAR", mapper: String},
	T16: {name: "BOOLEAN"},
	T1: {name: "CHAR", mapper: String},
	T2000: {name: "JAVA_OBJECT"},
	T2001: {name: "DISTINCT"},
	T2002: {name: "STRUCT"},
	T2003: {name: "ARRAY"},
	T2004: {name: "BLOB"},
	T2005: {name: "CLOB"},
	T2006: {name: "REF"},
	T2: {name: "NUMERIC", mapper: Number},
	T3: {name: "DECIMAL", mapper: Number},
	T4: {name: "INTEGER", mapper: Number},
	T5: {name: "SMALLINT", mapper: Number},
	T6: {name: "FLOAT", mapper: Number},
	T70: {name: "DATALINK"},
	T7: {name: "REAL", mapper: Number},
	T8: {name: "DOUBLE", mapper: Number},
	T91: {name: "DATE"},
	T92: {name: "TIME"},
	T93: {name: "TIMESTAMP"},
	T_1: {name: "LONGVARCHAR", mapper: String},
	T_2: {name: "BINARY"},
	T_3: {name: "VARBINARY"},
	T_4: {name: "LONGVARBINARY"},
	T_5: {name: "BIGINT", mapper: Number},
	T_6: {name: "TINYINT", mapper: Number},
	T_7: {name: "BIT", mapper: Boolean},
    },

    typeForNumber: function (n) {
	var key;
	if (n < 0) {
	    key = "T_" + String(-n);
	} else {
	    key = "T" + String(n);
	}
	return this._typeMap[key];
    },
}

Jdbc.Connection = Class.create();
Jdbc.Connection.prototype = {
    initialize: function (connection) {
	this.connection = connection;
	this._execArgs = [];
    },

    close: function () {
	this.connection.close();
    },

    prepareStatement: function () {
	var ps = this.connection.prepareStatement.apply(this.connection, arguments);
	return new Jdbc.StatementWrapper(ps);
    },

    createStatement: function () {
	var s = this.connection.createStatement.apply(this.connection, arguments);
	return new Jdbc.StatementWrapper(s);
    },

    withStatement: function (proc) {
	var s = this.connection.createStatement.apply(this.connection, arguments.slice(1));
	try {
	    return proc(new Jdbc.StatementWrapper(s));
	} finally {
	    s.close();
	}
    },

    execArgs: function() {
	this._execArgs = Array.from(arguments);
	return this;
    },

    _each: function (iterator) {
	var s = this.connection.createStatement();
	var rp = new Jdbc.ResultProcessor(s, s.execute.apply(s, this._execArgs));
	try {
	    rp._each(iterator);
	} finally {
	    s.close();
	}
    }
}
Object.extend(Jdbc.Connection.prototype, Enumerable);

Jdbc.StatementWrapper = Class.create();
Jdbc.StatementWrapper.prototype = {
    initialize: function (s) {
	this.statement = s;
    },

    close: function () {
	this.statement.close();
    },

    execute: function () {
	return new Jdbc.ResultProcessor(this.statement,
					this.statement.execute.apply(this.statement, arguments));
    },
}

Jdbc.ResultProcessor = Class.create();
Jdbc.ResultProcessor.prototype = {
    initialize: function (s, execResult) {
	this.statement = s;
	this.currentResultSet = null;
	this.updateCount = null;
	this.processExecResult(execResult);
    },

    processExecResult: function (execResult) {
	if (execResult) {
	    this.currentResultSet = this.statement.getResultSet();
	    this.updateCount = null;
	} else {
	    this.currentResultSet = null;
	    this.updateCount = this.statement.getUpdateCount();
	}
    },

    _each: function (iterator) {
	while (this.currentResultSet) {
	    var rs = this.currentResultSet;
	    var m = rs.getMetaData();
	    var columnCount = m.getColumnCount();
	    var mappers = $R(1,columnCount).map(function (index) {
						    var t =
							Jdbc.typeForNumber(m.getColumnType(index));
						    return (t && t.mapper) ? t.mapper : null;
						});
	    while (rs.next()) {
		var row = [];
		for (var column = 0; column < columnCount; column++) {
		    var v = rs.getObject(column + 1); // 1-based
		    if (mappers[column]) {
			row.push(mappers[column](v));
		    } else {
			row.push(v);
		    }
		}
		iterator(row);
	    }
	    rs.close();
	    this.processExecResult(this.statement.getMoreResults());
	}
    },
}
Object.extend(Jdbc.ResultProcessor.prototype, Enumerable);

Jdbc.ObjectFactory = Class.create();
Jdbc.ObjectFactory.prototype = {
    initialize: function (options) {
	this.tableName = options.tableName;
	this.idColumn = options.idColumn;
	this.columns = options.columns;
	this.instancePrototype = null;
	this.buildPrototype();
    },

    capitalize: function (s) {
	return s[0].toUpperCase() + s.substring(1);
    },

    buildPrototype: function () {
	var p = {};
	this.instancePrototype = p;

	p._factory = this;

	this.columns.each
	((function (column, columnIndex) {
	      if (column.instanceLink) {
		  // %%%
	      } else if (column.hashLink) {
		  throw "column.hashLink unimplemented";
	      } else if (column.listLink) {
		  throw "column.listLink unimplemented";
	      } else {
		  // Normal field, nothing to do
	      }
	  }).bind(this));
    }
}

//===========================================================================

// db = Jdbc.connect("com.mysql.jdbc.Driver",
// 		  "jdbc:mysql://localhost/vc",
// 		  "vc",
// 		  "");

// VC = {}
// VC.Transaction =
//     new Jdbc.ObjectFactory(db,
// 			   {tableName: "transactions",
// 			    idColumn: "id",
// 			    columns: [{name: "id"},
// 				      {name: "username"},
// 				      {name: "joblist"},
// 				      {name: "log_message"},
// 				      {name: "mtime"},
// 				      {name: "ctime"},
// /*
// 				      {name: "jobs",
// 				       hashLink: {myColumn: "id",
// 						  otherTable: "job_xref",
// 						  otherColumn: "transaction",
// 						  hashKeyColumn: "job_id"}},
// 				      {name: "entries",
// 				       listLink: {myColumn: "id",
// 						  otherTable: "transaction_entry",
// 						  otherColumn: "transaction",
// 						  orderColumn: null}},
// */
// 				     ]});
// VC.TransactionEntry =
//     new Jdbc.ObjectFactory(db,
// 			   {tableName: "transaction_entry",
// 			    idColumn: null,
// 			    columns: [{name: "transaction",
// 				       instanceLink: {myColumn: "transaction",
// 						      otherTable: "transactions",
// 						      otherColumn: "id"}},
// 				      {name: "oldversion"},
// 				      {name: "newversion"},
// 				      {name: "filepath"},
// 				      {name: "branch"},
// 				     ]});

