importClass(java.sql.DatabaseMetaData);
importClass(java.sql.PreparedStatement);
importClass(java.sql.ResultSet);
importClass(java.util.List);
importClass(java.util.ArrayList);

/**
   Rhino implementation of ActiveRecord (sort of...)
   By extending this, fields from the table of the same
   name in the DB will automatically be managed.
   
   author: Tom Austin
 */
function DynaRecord(caller, properties) {
   // Gets the table name by parsing the function definition.
   // Ugly, but I was not sure how else to do it.
   if (caller) {
      var namePat = /\s*function (\w+).*/
      var funText = caller.toString();
      var result = funText.match(namePat);
      
      this.tableName = (result[1] + 's').toLowerCase();
      
      //Now load the field names from the table.
      this.loadTableFields(properties);
      
      //Add class methods and properties
      caller.findFirst = DynaRecord.findFirst;
      caller.tableName = this.tableName;
      caller.attributes = this.attributes;
   }
}

// Handles the details of connecting to the DB.
DynaRecord.connect = function() {
   var jdbcUrl = "jdbc:mysql://localhost:3306/DynaRecord";
   var jdbcUser = "root";
   var jdbcPassword = "";
   var jdbcDriver = "com.mysql.jdbc.Driver";
   
   java.lang.Class.forName(jdbcDriver);
   return java.sql.DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
}

// Initializes fields when this is first loaded.
DynaRecord.prototype.loadTableFields = function(props) {
   this.attributes = new Array();

   var con = DynaRecord.connect();

   var md = con.getMetaData();
   var rs = md.getColumns(null, null, this.tableName, null);

   while (rs.next()) {
      var name = rs.getString("COLUMN_NAME");
      this.attributes.push(name);
      this[name] = props[name];
   }
   
   con.close();
} 

// Saves a record to the DB.
DynaRecord.prototype.save = function() {
   //Update the record if it has already been saved, insert it otherwise.
   if (this.id !== undefined) this.updateInDatabase();
   else this.insertToDatabase();
}

// Inserts a new record into the DB.
DynaRecord.prototype.insertToDatabase = function() {
   var con = DynaRecord.connect();
   
   var insert = "INSERT INTO " + this.tableName + " (";
   var values = "   VALUES (";
   for (var i in this.attributes) {
      var name = this.attributes[i];
      if (name != 'id') {
         insert += name;
         values += "?";
         if (i != this.attributes.length - 1) {
            insert += ", ";
            values += ", ";
         }
      }
   }
   var stmt = con.prepareStatement(insert + ")" + values + ")");
   
   var key = 1;
   for (var i=0; i<this.attributes.length; i++) {
      var name = this.attributes[i];
      if (name != 'id') {
         if (this[name] !== undefined) {
            stmt.setString(key, this[name]);
         }
         else {
            stmt.setString(key, null);
         }
         key++;
      }
   }
   
   var result = stmt.executeUpdate();
   
   //The ad has been saved now, so we need to fetch and save the id
   rs = stmt.getGeneratedKeys();
   if (rs.next()) {
      this.id = rs.getInt(1);
   }
   else {
      //Probably should throw an exception here.
   }
   
   con.close();
}

// Updates database record
DynaRecord.prototype.updateInDatabase = function() {
   var con = DynaRecord.connect();
   
   var sql = "UPDATE " + this.tableName + "    SET ";
   for (var i in this.attributes) {
      var name = this.attributes[i];
      if (name != 'id') {
         sql += name + "=?"; 
         if (i != this.attributes.length - 1) {
            sql += ", ";
         }
      }
   }
   sql += "   WHERE id = " + this.id;
   
   var stmt = con.prepareStatement(sql);
   var key = 1;
   for (var i=0; i<this.attributes.length; i++) {
      var name = this.attributes[i];
      if (name != 'id') {
         if (this[name] !== undefined) {
            stmt.setString(key, this[name]);
         }
         else {
            stmt.setString(key, null);
         }
         key++;
      }
   }
   
   var result = stmt.executeUpdate();
   con.close();
}

// Delete a record from the database
DynaRecord.prototype.removeFromDatabase = function() {
   //TODO: check for the existence of an ID first.
   var con = DynaRecord.connect();
   
   var stmt = con.createStatement();
   stmt.execute("DELETE FROM " + this.tableName + " WHERE id = "  + this.id);
   con.close();
}


// Get a record matching the specified criteria 
DynaRecord.findFirst = function(params) {
   var con = DynaRecord.connect();
   var values = new Array();
   
   var sql = "SELECT * FROM " + this.tableName;
   var keyword = " WHERE ";
   for (var name in params) {
      sql += keyword + name + "=?";
      values.push(params[name]); 
      keyword = " AND ";
   }
   
   var stmt = con.prepareStatement(sql);
   for (var i=0; i<values.length; i++) {
      stmt.setString(i+1, values[i]);
   }
   
   var rs = stmt.executeQuery();
   
   var record = undefined;
   if (rs.next()) {
      record = {};
      for (var i in this.attributes) {
         var name = this.attributes[i];
         var val = rs.getString(name);
         if (val === null) val = undefined;
         record[name] = val;
      }
   }
   
   con.close();
   
   return new Employee(record);
}




/* In an ideal world, this would just look something like:
function Employee() {
   this.superclass = DynaRecord;
}
*/
// Example DynaRecord
function Employee(props) {
   this.superclass = DynaRecord;
   this.superclass(arguments.callee, props);
}
Employee.prototype = new DynaRecord();
Employee.prototype.constructor = Employee;



//   SAMPLE EMPLOYEES
//Create
var aubrey = new Employee({firstname: 'Jack', lastname: 'Aubrey', title: 'Master and Commander', salary: 10000});
var maturin = new Employee({firstname: 'Stephen', lastname: 'Maturin', title: 'Surgeon'});
aubrey.save();
maturin.save();

//Read
var mal = Employee.findFirst({lastname: 'Reynolds'});
print("id:" + mal.id + " name:" + mal.lastname + "," + mal.firstname + " title:" + mal.title);
mal.title = "Captain";
mal.save();

//Update
maturin.title = 'Master Spy';
maturin.save();

//Delete
aubrey.removeFromDatabase();
