<commit_before>/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.untamedears.JukeAlert.storage;

import com.untamedears.JukeAlert.JukeAlert;
import com.untamedears.JukeAlert.group.GroupMediator;
import com.untamedears.JukeAlert.manager.ConfigManager;
import com.untamedears.JukeAlert.model.LoggedAction;
import com.untamedears.JukeAlert.model.Snitch;
import com.untamedears.citadel.entity.Faction;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

/**
 *
 * @author <NAME>
 */
public class JukeAlertLogger {

    private JukeAlert plugin;
    private ConfigManager configManager;
    private GroupMediator groupMediator;
    private Database db;
    private String snitchsTbl;
    private String snitchDetailsTbl;
    private PreparedStatement getSnitchIdFromLocationStmt;
    private PreparedStatement getAllSnitchesStmt;
    private PreparedStatement getAllSnitchesByWorldStmt;
    private PreparedStatement getLastSnitchID;
    private PreparedStatement getSnitchLogStmt;
    private PreparedStatement insertSnitchLogStmt;
    private PreparedStatement insertNewSnitchStmt;
    private PreparedStatement deleteSnitchStmt;
    private PreparedStatement updateGroupStmt;
    private PreparedStatement updateCuboidVolumeStmt;
    
    private Integer lastSnitchID;

    public JukeAlertLogger() {
    	plugin = JukeAlert.getInstance();
    	configManager = plugin.getConfigManager();
    	groupMediator = plugin.getGroupMediator();
    	
        String host   = configManager.getHost();
        int port   = configManager.getPort();
        String dbname = configManager.getDatabase();
        String username = configManager.getUsername();
        String password = configManager.getPassword();
        String prefix = configManager.getPrefix();

        snitchsTbl = prefix + "snitchs";
        snitchDetailsTbl = prefix + "snitch_details";

        db = new Database(host, port, dbname, username, password, prefix, this.plugin.getLogger());
        boolean connected = db.connect();
        if (connected) {
            genTables();
            initializeStatements();
        } else {
        	this.plugin.getLogger().log(Level.SEVERE, "Could not connect to the database! Fill out your config.yml!");
        }
    }

    public Database getDb() {
        return db;
    }

    /**
     * Table generator
     */
    private void genTables() {
        //Snitches
        db.execute("CREATE TABLE IF NOT EXISTS `" + snitchsTbl + "` ("
                + "`snitch_id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
                + "`snitch_world` varchar(40) NOT NULL,"
                + "`snitch_x` int(10) NOT NULL,"
                + "`snitch_y` int(10) NOT NULL,"
                + "`snitch_z` int(10) NOT NULL,"
                + "`snitch_group` varchar(40) NOT NULL,"
                + "`snitch_cuboid_x` int(10) NOT NULL,"
                + "`snitch_cuboid_y` int(10) NOT NULL,"
                + "`snitch_cuboid_z` int(10) NOT NULL,"
                + "`snitch_should_log` BOOL,"
                + "PRIMARY KEY (`snitch_id`));");
        //Snitch Details
        // need to know:
        // action: (killed, block break, block place, etc), can't be null
        // person who initiated the action (player name), can't be null
        // victim of action (player name, entity), can be null
        // x, (for things like block place, bucket empty, etc, NOT the snitch x,y,z) can be null
        // y, can be null
        // z, can be null
        // block_id, can be null (block id for block place, block use, block break, etc)
        db.execute("CREATE TABLE IF NOT EXISTS `" + snitchDetailsTbl + "` ("
                + "`snitch_details_id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
        		+ "`snitch_id` int(10) unsigned NOT NULL," // reference to the column in the main snitches table
                + "`snitch_log_time` datetime,"
                + "`snitch_logged_action` tinyint unsigned NOT NULL,"
                + "`snitch_logged_initiated_user` varchar(16) NOT NULL,"
                + "`snitch_logged_victim_user` varchar(16), "
                + "`snitch_logged_x` int(10), "
                + "`snitch_logged_Y` int(10), "
                + "`snitch_logged_z` int(10), "
                + "`snitch_logged_materialid` smallint unsigned," // can be either a block, item, etc
                + "PRIMARY KEY (`snitch_details_id`));");
    }

    private void initializeStatements() {

    	getAllSnitchesStmt = db.prepareStatement(String.format(
    		"SELECT * FROM %s", snitchsTbl
    	));
    	
    	getAllSnitchesByWorldStmt = db.prepareStatement(String.format(
    		"SELECT * FROM %s WHERE snitch_world = ?", snitchsTbl    			
    	));
    	
    	getLastSnitchID = db.prepareStatement(String.format(
        	"SHOW TABLE STATUS LIKE '%s'", snitchsTbl    			
        ));

        // statement to get LIMIT entries OFFSET from a number from the snitchesDetailsTbl based on a snitch_id from the main snitchesTbl
        // LIMIT ?,? means offset followed by max rows to return 
        getSnitchLogStmt = db.prepareStatement(String.format(
            "SELECT * FROM %s"
            + " WHERE snitch_id=? ORDER BY snitch_log_time ASC LIMIT ?,?",
            snitchDetailsTbl));
        
        // statement to get the ID of a snitch in the main snitchsTbl based on a Location (x,y,z, world)
        getSnitchIdFromLocationStmt = db.prepareStatement(String.format("SELECT snitch_id FROM %s"
        		+ " WHERE snitch_x=? AND snitch_y=? AND snitch_z=? AND snitch_world=?", snitchsTbl));
        
        // statement to insert a log entry into the snitchesDetailsTable
        insertSnitchLogStmt = db.prepareStatement(String.format(
            "INSERT INTO %s (snitch_id, snitch_log_time, snitch_logged_action, snitch_logged_initiated_user," +
            " snitch_logged_victim_user, snitch_logged_x, snitch_logged_y, snitch_logged_z, snitch_logged_materialid) " +
            " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
            snitchDetailsTbl));
        
        // 
        insertNewSnitchStmt = db.prepareStatement(String.format(
            "INSERT INTO %s (snitch_world, snitch_x, snitch_y, snitch_z, snitch_group, snitch_cuboid_x, snitch_cuboid_y, snitch_cuboid_z)"
            + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
            snitchsTbl));
        
        // 
        deleteSnitchStmt = db.prepareStatement(String.format(
            "DELETE FROM %s WHERE snitch_world=? AND snitch_x=? AND snitch_y=? AND snitch_z=?",
            snitchsTbl));
        
        // 
        updateGroupStmt = db.prepareStatement(String.format(
            "UPDATE %s SET snitch_group=? WHERE snitch_world=? AND snitch_x=? AND snitch_y=? AND snitch_z=?",
            snitchsTbl));
        
        // 
        updateCuboidVolumeStmt = db.prepareStatement(String.format(
            "UPDATE %s SET snitch_cuboid_x=?, snitch_cuboid_y=?, snitch_cuboid_z=?"
            + " WHERE snitch_world=? AND snitch_x=? AND snitch_y=? AND snitch_z=?",
            snitchsTbl));
    }

    public static String snitchKey(final Location loc) {
        return String.format(
            "World: %s X: %d Y: %d Z: %d",
            loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
    }
    
    public Map<World, Map<Location, Snitch>> getAllSnitches() {
    	Map<World, Map<Location, Snitch>> snitches = new HashMap<World, Map<Location, Snitch>>();
    	List<World> worlds = this.plugin.getServer().getWorlds();
    	for(World world : worlds) {
    		Map<Location, Snitch> snitchesByWorld = getAllSnitchesByWorld(world);
    		snitches.put(world, snitchesByWorld);
    	}
    	return snitches;
    }
    
    public Map<Location, Snitch> getAllSnitchesByWorld(World world) {
    	Map<Location, Snitch> snitches = new HashMap<Location, Snitch>();
    	try {
    		Snitch snitch = null;
    		getAllSnitchesByWorldStmt.setString(1, world.getName());
    		ResultSet rs = getAllSnitchesByWorldStmt.executeQuery();
    		while(rs.next()) {
    			double x = rs.getInt("snitch_x");
    			double y = rs.getInt("snitch_y");
    			double z = rs.getInt("snitch_z");
    			String groupName = rs.getString("snitch_group");
    			
    			Faction group = groupMediator.getGroupByName(groupName);

    			Location location = new Location(world, x, y, z);
    			
    			snitch = new Snitch(location, group);
    			snitch.setId(rs.getInt("snitch_id"));
    			snitches.put(location, snitch);
    		}
    		ResultSet rsKey = getLastSnitchID.executeQuery();
    		if(rsKey.next()) {
    			lastSnitchID = rsKey.getInt("Auto_increment");
    		}
    	} catch (SQLException ex) {
    		System.out.println(ex);
    		this.plugin.getLogger().log(Level.SEVERE, "Could not get all Snitches from World " + world + "!");
    	}
    	return snitches;
    }
    
    /**
     * Gets @limit events about that snitch. 
     * @param loc - the location of the snitch
     * @param offset - the number of entries to start at (10 means you start at the 10th entry and go to @limit)
     * @param limit - the number of entries to limit
     * @return a Map of String/Date objects of the snitch entries, formatted nicely
     */
    public Map<String, Date> getSnitchInfo(Location loc, int limit) {
        Map<String, Date> info = new HashMap<String, Date>();

        	// get the snitch's ID based on the location, then use that to get the snitch details from the snitchesDetail table
        	int interestedSnitchId = -1;
        	try {
        		// params are x(int), y(int), z(int), world(tinyint), column returned: snitch_id (int)
        		getSnitchIdFromLocationStmt.setInt(1, loc.getBlockX());
        		getSnitchIdFromLocationStmt.setInt(2, loc.getBlockY());
        		getSnitchIdFromLocationStmt.setInt(3, loc.getBlockZ());
        		getSnitchIdFromLocationStmt.setString(4,  loc.getWorld().getName());
        		
        		ResultSet snitchIdSet = getSnitchIdFromLocationStmt.executeQuery();
        		
        		// make sure we got a result
        		boolean didFind = false;
        		while (snitchIdSet.next()) {
        			didFind = true;
        			interestedSnitchId = snitchIdSet.getInt("snitch_id");
        		}
        		
        		// only continue if we actually got a result from the first query
        		if (!didFind) {
        			this.plugin.getLogger().log(Level.SEVERE, "Didn't get any results trying to find a snitch in the snitches table at location " + loc);
        		} else {
        			// we got a snitch id from the location, so now get the records that we want from the snitches detail table
        			try {
	        	        // params are snitch_id (int), returns everything
	                    getSnitchLogStmt.setInt(1, interestedSnitchId);
	                    
	                    //Offset
	                    getSnitchLogStmt.setInt(2, 0);
	                    
	                    //Number of rows
	                    getSnitchLogStmt.setInt(3, limit);
	                    
	                    ResultSet set = getSnitchLogStmt.executeQuery();

	                    didFind = false;
	                    if (!set.isBeforeFirst() ) {    
	                    	 System.out.println("No data"); 
	                    	} 
	                    while (set.next()) {
	                    	didFind = true;
	                    	// TODO: need a function to create a string based upon what things we have / don't have in this result set
	                    	// so like if we have a block place action, then we include the x,y,z, but if its a KILL action, then we just say
	                    	// x killed y, etc
	                    	String resultString = String.format("%s did action %d", set.getString("snitch_logged_initiated_user"), (int)set.getByte("snitch_logged_action"));
	                        info.put(resultString, set.getDate("snitch_log_time"));
	                        
	                    }
	                    if (!didFind) {
	                    	// Output something like 'no snitch action recorded" or something
	                    }
	                } catch (SQLException ex) {
	                    this.plugin.getLogger().log(Level.SEVERE, "Could not get Snitch Details from the snitchesDetail table using the snitch id " + interestedSnitchId, ex);
	                    // rethrow
	                    throw ex;
	                }
        		} // end if..else (didFind)
        		
        	} catch (SQLException ex1) {
        		 this.plugin.getLogger().log(Level.SEVERE, "Could not get Snitch Details! loc: " + loc, ex1);
        	}
        	
        	

        return info;
    }

    
    /**
     * Logs info to a specific snitch with a time stamp.
     * 
     * example: 
     * 
     * ------DATE-----------DETAIL------
     * 2013-4-24 12:14:35 : Bob made an entry at [Nether(X: 56 Y: 87 Z: -1230)]
     * 2013-4-25 12:14:35 : Bob broke a chest at X: 896 Y: 1 Z: 8501
     * 2013-4-28 12:14:35 : Bob killed Trevor.
     * ----Type /ja more to see more----
     * 
     * @param snitch - the snitch that recorded this event, required
     * @param material - the block/item/whatever that was part of the event, if there was one , null if no material was part of the event
     * @param loc - the location where this event occured, if any
     * @param date - the date this event occurred , required
     * @param action - the action that took place in this event
     * @param initiatedUser - the user who initiated the event, required
     * @param victimUser - the user who was victim of the event, can be null
     */
    public void logSnitchInfo(Snitch snitch, Material material, Location loc, Date date, LoggedAction action, String initiatedUser, String victimUser) {
        try {
        	// snitchid
        	insertSnitchLogStmt.setInt(1,  snitch.getId());
        	// snitch log time
        	insertSnitchLogStmt.setDate(2, new java.sql.Date(date.getTime()));
        	// snitch logged action
        	insertSnitchLogStmt.setByte(3,  (byte)action.getLoggedActionId());
        	// initiated user
        	insertSnitchLogStmt.setString(4,  initiatedUser);
        	
        	// These columns, victimUser, location and materialid can all be null so check if it is an insert SQL null if it is
        	
        	// victim user
        	if (victimUser != null) {
        		insertSnitchLogStmt.setString(5, victimUser);
        	} else {
        		insertSnitchLogStmt.setNull(5, java.sql.Types.VARCHAR);
        	}
        	
        	// location, x, y, z
        	if (loc != null) {
	        	insertSnitchLogStmt.setInt(6,  loc.getBlockX());
	        	insertSnitchLogStmt.setInt(7,  loc.getBlockY());
	        	insertSnitchLogStmt.setInt(8,  loc.getBlockZ());
        	} else {
        		insertSnitchLogStmt.setNull(6, java.sql.Types.INTEGER);
        		insertSnitchLogStmt.setNull(7, java.sql.Types.INTEGER);
        		insertSnitchLogStmt.setNull(8, java.sql.Types.INTEGER);
        	}<commit_msg>Changed date logging to timestamp logging<commit_after>/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.untamedears.JukeAlert.storage;

import com.untamedears.JukeAlert.JukeAlert;
import com.untamedears.JukeAlert.group.GroupMediator;
import com.untamedears.JukeAlert.manager.ConfigManager;
import com.untamedears.JukeAlert.model.LoggedAction;
import com.untamedears.JukeAlert.model.Snitch;
import com.untamedears.citadel.entity.Faction;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

/**
 *
 * @author <NAME>
 */
public class JukeAlertLogger {

    private JukeAlert plugin;
    private ConfigManager configManager;
    private GroupMediator groupMediator;
    private Database db;
    private String snitchsTbl;
    private String snitchDetailsTbl;
    private PreparedStatement getSnitchIdFromLocationStmt;
    private PreparedStatement getAllSnitchesStmt;
    private PreparedStatement getAllSnitchesByWorldStmt;
    private PreparedStatement getLastSnitchID;
    private PreparedStatement getSnitchLogStmt;
    private PreparedStatement insertSnitchLogStmt;
    private PreparedStatement insertNewSnitchStmt;
    private PreparedStatement deleteSnitchStmt;
    private PreparedStatement updateGroupStmt;
    private PreparedStatement updateCuboidVolumeStmt;
    
    private Integer lastSnitchID;

    public JukeAlertLogger() {
    	plugin = JukeAlert.getInstance();
    	configManager = plugin.getConfigManager();
    	groupMediator = plugin.getGroupMediator();
    	
        String host   = configManager.getHost();
        int port   = configManager.getPort();
        String dbname = configManager.getDatabase();
        String username = configManager.getUsername();
        String password = configManager.getPassword();
        String prefix = configManager.getPrefix();

        snitchsTbl = prefix + "snitchs";
        snitchDetailsTbl = prefix + "snitch_details";

        db = new Database(host, port, dbname, username, password, prefix, this.plugin.getLogger());
        boolean connected = db.connect();
        if (connected) {
            genTables();
            initializeStatements();
        } else {
        	this.plugin.getLogger().log(Level.SEVERE, "Could not connect to the database! Fill out your config.yml!");
        }
    }

    public Database getDb() {
        return db;
    }

    /**
     * Table generator
     */
    private void genTables() {
        //Snitches
        db.execute("CREATE TABLE IF NOT EXISTS `" + snitchsTbl + "` ("
                + "`snitch_id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
                + "`snitch_world` varchar(40) NOT NULL,"
                + "`snitch_x` int(10) NOT NULL,"
                + "`snitch_y` int(10) NOT NULL,"
                + "`snitch_z` int(10) NOT NULL,"
                + "`snitch_group` varchar(40) NOT NULL,"
                + "`snitch_cuboid_x` int(10) NOT NULL,"
                + "`snitch_cuboid_y` int(10) NOT NULL,"
                + "`snitch_cuboid_z` int(10) NOT NULL,"
                + "`snitch_should_log` BOOL,"
                + "PRIMARY KEY (`snitch_id`));");
        //Snitch Details
        // need to know:
        // action: (killed, block break, block place, etc), can't be null
        // person who initiated the action (player name), can't be null
        // victim of action (player name, entity), can be null
        // x, (for things like block place, bucket empty, etc, NOT the snitch x,y,z) can be null
        // y, can be null
        // z, can be null
        // block_id, can be null (block id for block place, block use, block break, etc)
        db.execute("CREATE TABLE IF NOT EXISTS `" + snitchDetailsTbl + "` ("
                + "`snitch_details_id` int(10) unsigned NOT NULL AUTO_INCREMENT,"
        		+ "`snitch_id` int(10) unsigned NOT NULL," // reference to the column in the main snitches table
                + "`snitch_log_time` datetime,"
                + "`snitch_logged_action` tinyint unsigned NOT NULL,"
                + "`snitch_logged_initiated_user` varchar(16) NOT NULL,"
                + "`snitch_logged_victim_user` varchar(16), "
                + "`snitch_logged_x` int(10), "
                + "`snitch_logged_Y` int(10), "
                + "`snitch_logged_z` int(10), "
                + "`snitch_logged_materialid` smallint unsigned," // can be either a block, item, etc
                + "PRIMARY KEY (`snitch_details_id`));");
    }

    private void initializeStatements() {

    	getAllSnitchesStmt = db.prepareStatement(String.format(
    		"SELECT * FROM %s", snitchsTbl
    	));
    	
    	getAllSnitchesByWorldStmt = db.prepareStatement(String.format(
    		"SELECT * FROM %s WHERE snitch_world = ?", snitchsTbl    			
    	));
    	
    	getLastSnitchID = db.prepareStatement(String.format(
        	"SHOW TABLE STATUS LIKE '%s'", snitchsTbl    			
        ));

        // statement to get LIMIT entries OFFSET from a number from the snitchesDetailsTbl based on a snitch_id from the main snitchesTbl
        // LIMIT ?,? means offset followed by max rows to return 
        getSnitchLogStmt = db.prepareStatement(String.format(
            "SELECT * FROM %s"
            + " WHERE snitch_id=? ORDER BY snitch_log_time ASC LIMIT ?,?",
            snitchDetailsTbl));
        
        // statement to get the ID of a snitch in the main snitchsTbl based on a Location (x,y,z, world)
        getSnitchIdFromLocationStmt = db.prepareStatement(String.format("SELECT snitch_id FROM %s"
        		+ " WHERE snitch_x=? AND snitch_y=? AND snitch_z=? AND snitch_world=?", snitchsTbl));
        
        // statement to insert a log entry into the snitchesDetailsTable
        insertSnitchLogStmt = db.prepareStatement(String.format(
            "INSERT INTO %s (snitch_id, snitch_log_time, snitch_logged_action, snitch_logged_initiated_user," +
            " snitch_logged_victim_user, snitch_logged_x, snitch_logged_y, snitch_logged_z, snitch_logged_materialid) " +
            " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
            snitchDetailsTbl));
        
        // 
        insertNewSnitchStmt = db.prepareStatement(String.format(
            "INSERT INTO %s (snitch_world, snitch_x, snitch_y, snitch_z, snitch_group, snitch_cuboid_x, snitch_cuboid_y, snitch_cuboid_z)"
            + " VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
            snitchsTbl));
        
        // 
        deleteSnitchStmt = db.prepareStatement(String.format(
            "DELETE FROM %s WHERE snitch_world=? AND snitch_x=? AND snitch_y=? AND snitch_z=?",
            snitchsTbl));
        
        // 
        updateGroupStmt = db.prepareStatement(String.format(
            "UPDATE %s SET snitch_group=? WHERE snitch_world=? AND snitch_x=? AND snitch_y=? AND snitch_z=?",
            snitchsTbl));
        
        // 
        updateCuboidVolumeStmt = db.prepareStatement(String.format(
            "UPDATE %s SET snitch_cuboid_x=?, snitch_cuboid_y=?, snitch_cuboid_z=?"
            + " WHERE snitch_world=? AND snitch_x=? AND snitch_y=? AND snitch_z=?",
            snitchsTbl));
    }

    public static String snitchKey(final Location loc) {
        return String.format(
            "World: %s X: %d Y: %d Z: %d",
            loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
    }
    
    public Map<World, Map<Location, Snitch>> getAllSnitches() {
    	Map<World, Map<Location, Snitch>> snitches = new HashMap<World, Map<Location, Snitch>>();
    	List<World> worlds = this.plugin.getServer().getWorlds();
    	for(World world : worlds) {
    		Map<Location, Snitch> snitchesByWorld = getAllSnitchesByWorld(world);
    		snitches.put(world, snitchesByWorld);
    	}
    	return snitches;
    }
    
    public Map<Location, Snitch> getAllSnitchesByWorld(World world) {
    	Map<Location, Snitch> snitches = new HashMap<Location, Snitch>();
    	try {
    		Snitch snitch = null;
    		getAllSnitchesByWorldStmt.setString(1, world.getName());
    		ResultSet rs = getAllSnitchesByWorldStmt.executeQuery();
    		while(rs.next()) {
    			double x = rs.getInt("snitch_x");
    			double y = rs.getInt("snitch_y");
    			double z = rs.getInt("snitch_z");
    			String groupName = rs.getString("snitch_group");
    			
    			Faction group = groupMediator.getGroupByName(groupName);

    			Location location = new Location(world, x, y, z);
    			
    			snitch = new Snitch(location, group);
    			snitch.setId(rs.getInt("snitch_id"));
    			snitches.put(location, snitch);
    		}
    		ResultSet rsKey = getLastSnitchID.executeQuery();
    		if(rsKey.next()) {
    			lastSnitchID = rsKey.getInt("Auto_increment");
    		}
    	} catch (SQLException ex) {
    		System.out.println(ex);
    		this.plugin.getLogger().log(Level.SEVERE, "Could not get all Snitches from World " + world + "!");
    	}
    	return snitches;
    }
    
    /**
     * Gets @limit events about that snitch. 
     * @param loc - the location of the snitch
     * @param offset - the number of entries to start at (10 means you start at the 10th entry and go to @limit)
     * @param limit - the number of entries to limit
     * @return a Map of String/Date objects of the snitch entries, formatted nicely
     */
    public Map<String, Date> getSnitchInfo(Location loc, int limit) {
        Map<String, Date> info = new HashMap<String, Date>();

        	// get the snitch's ID based on the location, then use that to get the snitch details from the snitchesDetail table
        	int interestedSnitchId = -1;
        	try {
        		// params are x(int), y(int), z(int), world(tinyint), column returned: snitch_id (int)
        		getSnitchIdFromLocationStmt.setInt(1, loc.getBlockX());
        		getSnitchIdFromLocationStmt.setInt(2, loc.getBlockY());
        		getSnitchIdFromLocationStmt.setInt(3, loc.getBlockZ());
        		getSnitchIdFromLocationStmt.setString(4,  loc.getWorld().getName());
        		
        		ResultSet snitchIdSet = getSnitchIdFromLocationStmt.executeQuery();
        		
        		// make sure we got a result
        		boolean didFind = false;
        		while (snitchIdSet.next()) {
        			didFind = true;
        			interestedSnitchId = snitchIdSet.getInt("snitch_id");
        		}
        		
        		// only continue if we actually got a result from the first query
        		if (!didFind) {
        			this.plugin.getLogger().log(Level.SEVERE, "Didn't get any results trying to find a snitch in the snitches table at location " + loc);
        		} else {
        			// we got a snitch id from the location, so now get the records that we want from the snitches detail table
        			try {
	        	        // params are snitch_id (int), returns everything
	                    getSnitchLogStmt.setInt(1, interestedSnitchId);
	                    
	                    //Offset
	                    getSnitchLogStmt.setInt(2, 0);
	                    
	                    //Number of rows
	                    getSnitchLogStmt.setInt(3, limit);
	                    
	                    ResultSet set = getSnitchLogStmt.executeQuery();

	                    didFind = false;
	                    if (!set.isBeforeFirst() ) {    
	                    	 System.out.println("No data"); 
	                    	} 
	                    while (set.next()) {
	                    	didFind = true;
	                    	// TODO: need a function to create a string based upon what things we have / don't have in this result set
	                    	// so like if we have a block place action, then we include the x,y,z, but if its a KILL action, then we just say
	                    	// x killed y, etc
	                    	String resultString = String.format("%s did action %d", set.getString("snitch_logged_initiated_user"), (int)set.getByte("snitch_logged_action"));
	                        info.put(resultString, set.getDate("snitch_log_time"));
	                        
	                    }
	                    if (!didFind) {
	                    	// Output something like 'no snitch action recorded" or something
	                    }
	                } catch (SQLException ex) {
	                    this.plugin.getLogger().log(Level.SEVERE, "Could not get Snitch Details from the snitchesDetail table using the snitch id " + interestedSnitchId, ex);
	                    // rethrow
	                    throw ex;
	                }
        		} // end if..else (didFind)
        		
        	} catch (SQLException ex1) {
        		 this.plugin.getLogger().log(Level.SEVERE, "Could not get Snitch Details! loc: " + loc, ex1);
        	}
        	
        	

        return info;
    }

    
    /**
     * Logs info to a specific snitch with a time stamp.
     * 
     * example: 
     * 
     * ------DATE-----------DETAIL------
     * 2013-4-24 12:14:35 : Bob made an entry at [Nether(X: 56 Y: 87 Z: -1230)]
     * 2013-4-25 12:14:35 : Bob broke a chest at X: 896 Y: 1 Z: 8501
     * 2013-4-28 12:14:35 : Bob killed Trevor.
     * ----Type /ja more to see more----
     * 
     * @param snitch - the snitch that recorded this event, required
     * @param material - the block/item/whatever that was part of the event, if there was one , null if no material was part of the event
     * @param loc - the location where this event occured, if any
     * @param date - the date this event occurred , required
     * @param action - the action that took place in this event
     * @param initiatedUser - the user who initiated the event, required
     * @param victimUser - the user who was victim of the event, can be null
     */
    public void logSnitchInfo(Snitch snitch, Material material, Location loc, Date date, LoggedAction action, String initiatedUser, String victimUser) {
        try {
        	// snitchid
        	insertSnitchLogStmt.setInt(1,  snitch.getId());
        	// snitch log time
        	insertSnitchLogStmt.setTimestamp(2,  new java.sql.Timestamp(new java.util.Date().getTime()));
        	// snitch logged action
        	insertSnitchLogStmt.setByte(3,  (byte)action.getLoggedActionId());
        	// initiated user
        	insertSnitchLogStmt.setString(4,  initiatedUser);
        	
        	// These columns, victimUser, location and materialid can all be null so check if it is an insert SQL null if it is
        	
        	// victim user
        	if (victimUser != null) {
        		insertSnitchLogStmt.setString(5, victimUser);
        	} else {
        		insertSnitchLogStmt.setNull(5, java.sql.Types.VARCHAR);
        	}
        	
        	// location, x, y, z
        	if (loc != null) {
	        	insertSnitchLogStmt.setInt(6,  loc.getBlockX());
	        	insertSnitchLogStmt.setInt(7,  loc.getBlockY());
	        	insertSnitchLogStmt.setInt(8,  loc.getBlockZ());
        	} else {
        		insertSnitchLogStmt.setNull(6, java.sql.Types.INTEGER);
        		insertSnitchLogStmt.setNull(7, java.sql.Types.INTEGER);
        		insertSnitchLogStmt.setNull(8, java.sql.Types.INTEGER);
        	}
{'fraction_non_alphanumeric': 0.10266097775461681, 'fraction_numerical': 0.0074911246458000845, 'mean_word_length': 2.2103722291928065, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3832140', 'n_tokens_mistral': 9670, 'n_tokens_neox': 9214, 'n_words': 2414}