#!/usr/bin/perl -w

use strict;
use DBI;

# main variable declarations
my $this_rack = 1;  # Each app runs on the node computer and needs the rack_num set.
my $raw_card_id; # id that comes straight off the card: ;1234560?
my $cleaned_id; # id which has been cleaned: 123456
my $next_dock; # the dock which will be unlocked
my $next_vehicle; # the vehicle that will be signed out
my $elapsed_minutes = 60; # number of minutes to sing out vehicles for.


print `clear`;

print "Swipe Your Hampshire ID Card\n\n";

$raw_card_id = get_user_id();

#print "\nraw_card_id: $raw_card_id\n";

# determine whether the card id is in the correct format
if(verify_user_id_format($raw_card_id))
{
	#clean the id. get rid of extraneous characters (;?)
	$cleaned_id = clean_user_id($raw_card_id);
	
	#print "$clean_id is a valid id\n\n\n";
	
	# if the user_id is present in our user database
	if(look_up_user_id($cleaned_id))
	{
		# check to see if the user account is enabled
		if(check_user_account_status($cleaned_id))
		{
			# check to see if the user already has a vehicle checked out
			if(check_user_has_vehicle($cleaned_id)) # if already has scooter (true)
			{
				print "\nSorry, you already have a scooter signed out.\n";
			}
			else # All systems go, the user may sign out a vehicle
			{								
				print "Finding next available vehicle...\n"; 
				$next_dock = get_next_available_dock($this_rack);
				
				print "Next available vehicle is in dock: $next_dock\n";
				
				# Find the vehicle number in the dock
				$next_vehicle = look_up_vehicle_num($next_dock, $this_rack);
				print "\nGet vehicle ID...\n";
				print "Vehicle Number: $next_vehicle\n";
				
				# Add this event to the events table
				add_events_entry("sign_out",$cleaned_id,$this_rack,$next_dock,$next_vehicle,$elapsed_minutes);
				print "Sign-out added to events entry.\n";
				
				update_dock_status($next_dock,$this_rack,"empty");
				print "Update dock status. Set to empty\n";
				
				update_vehicle_availability($next_vehicle,$cleaned_id);
				print "Update vehicle availability. Set to user id: $cleaned_id\n\n";

				print "Please take vehicle $next_vehicle from dock: $next_dock\n\n";

				# unlock the vehicle - this needs to be called last because the unlock
				# process sleeps for a while
				unlock_vehicle($this_rack,$next_dock);
			}
		}
		else
		{
			print "\nSorry, your account is disabled.\n";
		}
	}
	else
	{
		print "\nSorry, you aren't in the database.\n";	
	}
}
else
{
	print "Hmmm, doesn't look like that's a Hampshire ID.\n\n\n";
}

################################################################################
# Function: get_user_id
# Description: This function gets the raw id from stdin.
# Arguments: none
# Returns: string raw_id
# Notes: This function is Hampshire ID specific.  The card reader
#        should be set to only read track 2 which is where the id is stored.
#        I need to verify that the new id's are the same.
#
#        This function is also *nix specific.  In order for the ID to not show 
#        up on the screen, `stty -echo` is used. 
#
#        Gotta figure out GUI stuff which may affect this and all sorts of other 
#        stuff
# Status: 100%
################################################################################
sub get_user_id
{
	my $card_id;
	
#	`stty -echo`;
	
	chomp($card_id = <STDIN>);
	
#	`stty echo`;
	
	return $card_id;
}

################################################################################
# Function: verify_user_id_format
# Description: Verifies that the raw id is in the right format: ;2345671?
# Arguments: raw_id
# Returns: bool - true if raw id matches format
# Notes: This function is Hampshire ID specific. For some reason, it appears the 
#        Hampshire ID has the first character '0' stripped of the beginning and 
#        added to the end.  Or so it appears.
#        The reg exp is currently very broad, it will match any 7 numbers in a  
#        row.
# Status: 90% (functional needs cleanup)
################################################################################
sub verify_user_id_format
{
	my $raw_id = $_[0];
	my $id_validity = 0;
	
#	print "\nraw_id: $raw_id";
#	print "\n\n";

    if($raw_id =~ /\;[0-9]{7}\?/)
#	if($raw_id =~ /[0-9]{6}/)
    {
    	$id_validity = "1";
    }
    else
    {
    	$id_validity = "0";
    }
    
    return $id_validity;
}

################################################################################
# Function: clean_user_id
# Description: strips the identifying characters from the beginning and end of 
#              the id.  Then puts the '0' at the end of the id back at the 
#              beginning (from the end).
# Arguments: raw_id
# Returns: clean id
# Notes: Hampshire ID Specific.
# Status: 90% (functional needs cleanup)
################################################################################
sub clean_user_id
{
	my $raw_id = $_[0];
	my $clean_id;
	
# match up the id for cleaning.  Strip any leading zeros.
# add \; and \? for real card swipe!
	$raw_id =~ /\;([0-9]{6})0\?/;

#	$raw_id =~ /0([0-9]{6})/;
	
	$clean_id = $1;
	
	return $clean_id;
}


################################################################################
# Function: look_up_user_id
# Description: Checks to see if user is in database.
# Arguments: user_id
# Returns: bool - depending on whether the user was present.
# Status: 90% (functional needs cleanup)
################################################################################
sub look_up_user_id
{
	my $user_id = $_[0];
	my $in_database = 0;

	my ($dsn) = "DBI:mysql:bike_program:localhost";
	my ($user_name) = "bike";
	my ($password) = "password";
	my ($dbh, $sth);
	my (@ary);
	
	# connect to database
	$dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1});
	
	#issue query
	$sth = $dbh->prepare ("SELECT user_id FROM user WHERE user_id='$user_id'");	
	$sth->execute();
	print "\nuserid: $user_id\n";
	
	if(@ary = $sth->fetchrow_array())
	{
		$in_database = "1";
	}
	else
	{
		$in_database = "0";
	}
	
	$sth->finish();
	
	$dbh->disconnect();
	
	return $in_database;
}

################################################################################
# Function: check_user_account_status
# Description: Checks the user table to see if the user's account is enabled
# Arguments: user_id
# Returns: bool - true if the user account is set to enabled
# Prerequisite: This function assumes that the user exists in the database (by
#               running the look_up_user_id() function.
# Status: 90% (functional needs cleanup)
################################################################################
sub check_user_account_status
{
	my $user_id = $_[0];
	my $account_status;
	my $account_enabled;
	
	my ($dsn) = "DBI:mysql:bike_program:localhost";
	my ($user_name) = "bike";
	my ($password) = "password";
	my ($dbh, $sth);
	my (@ary);
	
	# connect to database
	$dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1});
	
	#issue query
	$sth = $dbh->prepare ("SELECT account_status FROM user WHERE user_id='$user_id'");
	$sth->execute();

	#fetch results
	$account_status = $sth->fetchrow_array();
	
	print "\naccount_status: $account_status\n";
			
	if($account_status eq "enabled")
	{
		$account_enabled = "1";
	}
	else
	{
		$account_enabled = "0";
	}
			
	$sth->finish();
	
	$dbh->disconnect();

	return $account_enabled;
}

################################################################################
# Function: check_user_has_vehicle
# Description: Searches the availability field of the vehicle table to see if 
#              the user already has a vehicle assigned to them.
# Arguments: user_id
# Returns: bool - true if user already has vehicle (that's sort of backwards and
#          confusing but I can't think of a better function name.)
# Prerequsite: This function assumes id has already gone through validity checks
# Status: 90% (functional needs cleanup)
################################################################################
sub check_user_has_vehicle
{
	my $user_id = $_[0];
	my $count;
	my $has_scooter;
	
	my ($dsn) = "DBI:mysql:bike_program:localhost";
	my ($user_name) = "bike";
	my ($password) = "password";
	my ($query);
	my ($dbh, $sth);
	my (@ary);
	
	# connect to database
	$dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1});
	
	#build query
	$query = "SELECT COUNT(*) FROM vehicle WHERE availability='$user_id'";
	
#	print "\n$query\n";
	
	#build query
	$sth = $dbh->prepare($query);
	$sth->execute();

	#fetch results
	$count = $sth->fetchrow_array();
	
#	print "\ncount: $count\n";
	if($count)  # if any rows were returned
	{
		$has_scooter = "1"; # true: user already has a vehicle
	}
	else
	{
		$has_scooter = "0"; # false: user doesn't already have a vehicle
	}
			
	$sth->finish();
	
	$dbh->disconnect();
	
#	print "\nhas_scooter: $has_scooter\n\n";
	
	return $has_scooter;
}     

################################################################################
# Function: get_next_available_dock
# Description: looks at a particular rack and determines which dock to unlock.  
#              For example, when a vehicle is unlocked for a user, we need to 
#              look at the status of the docks and unlock one that has a vehicle 
#              in it, and ideally, one that is closest to the card swipe.
# Arguments: rack_num
# Returns: dock_num or "0" if the rack is full.
# Status: 80% (functional needs cleanup and dock selection could be a bit more
#         intelligent)
# BUG!!! - Need to check if the rack is empty
################################################################################
sub get_next_available_dock
{
	my $rack_num = $_[0];
	my $next_dock;

	my ($dsn) = "DBI:mysql:bike_program:localhost";
	my ($user_name) = "bike";
	my ($password) = "password";
	my ($dbh, $sth);
	my (@ary);
	
	# connect to database
	$dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1});
	
	#issue query
	$sth = $dbh->prepare ("SELECT dock_num FROM dock WHERE dock_status != 'empty' ORDER BY dock_num LIMIT 1");	
	$sth->execute();
	
	$next_dock = $sth->fetchrow_array();

#	print "\nnext_dock: $next_dock\n";

	if(!defined($next_dock)) #if there was an error or if the dock is full
	{
		$next_dock = "0";  #return false
#		print "\ngot into not defined\n";
	}
	elsif($next_dock == "0E0") #if no rows were affected
	{                          # I don't think this ever gets used but it doesn't hurt
		$next_dock = "0";
#		print "\ngot into the 0E0 Statement\n";
	}

	$sth->finish();
	
	$dbh->disconnect();
	
	return $next_dock;

}

################################################################################
# Function: unlock_vehicle
# Description: Unlocks a particular lock
# Arguments: rack_num, dock_num
# Returns: bool - success or failure
# Status: 50%
################################################################################
sub unlock_vehicle
{
	my $rack_num = $_[0];
	my $dock_num = $_[1];

	# Set the relay high (Unlock the lock)
	`echo SK3 >> /dev/ttyS0`;
	
	# Wait for the user to take the bike
	`sleep 8`;
	
	# Reset the relay (Lock the lock)
	`echo RK3 >> /dev/ttyS0`;
	
	return "1";	

}

################################################################################
# Function: look_up_vehicle_num
# Description: Look in dock table at specified dock_num to determine the 
#              vechicle_num which is in it.
# Arguments: dock_num, rack_num
# Returns: int vehicle_num or 0 on error
# Status: 90% (functional, needs clean up)
################################################################################
sub look_up_vehicle_num
{
	my $dock_num = $_[0];
	my $rack_num = $_[1];
	my $vehicle_num;
	my $outcome;

	my ($dsn) = "DBI:mysql:bike_program:localhost";
	my ($user_name) = "bike";
	my ($password) = "password";
	my ($query);
	my ($dbh, $sth);
	my (@ary);
	
	# connect to database
	$dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1});
	
	#build query
	$query = "SELECT dock_status FROM dock WHERE dock_num='$dock_num' AND rack_num='$rack_num' LIMIT 1";
	
#	print "\n$query\n";
	
	#build query
	$sth = $dbh->prepare($query);
	$sth->execute();

	#fetch results
	$vehicle_num = $sth->fetchrow_array();
	
#	print "\nvehicle_num: $vehicle_num\n";
	$sth->finish();
	$dbh->disconnect();

	if(!defined($vehicle_num)) #if there was an error
	{
		$outcome = "0";  #return false
	}
	elsif($vehicle_num eq "full")
	{
		$outcome = "0";
	}
	elsif($vehicle_num eq "empty")
	{
		$outcome = "0";
	}
	else
	{
		$outcome = $vehicle_num;
	}
	
	return $outcome;	
	
}

################################################################################
# Function: add_events_entry
# Description: Gathers all the variables and makes an entry in the events table
# Arguments: action, user_id, rack_num, dock_num, vehicle_num, [elapsed_time]
# Returns:
# Status: 75% (quasi-functional need to deal with the issue of optional arguments)
################################################################################
sub add_events_entry
{
	#arguments
	my $action = $_[0];
	my $user_id = $_[1];
	my $rack_num = $_[2];
	my $dock_num = $_[3];
	my $vehicle_num = $_[4];
	my $time_interval = $_[5];
	
	my $rows;
	my $outcome;

	my ($dsn) = "DBI:mysql:bike_program:localhost";
	my ($user_name) = "bike";
	my ($password) = "password";
	my ($query);
	my ($dbh, $sth);
	my (@ary);

			
	# connect to database
	$dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1});
	
	$query = "INSERT INTO events SET action='$action', user_id='$user_id', rack_num='$rack_num', dock_num='$dock_num', vehicle_num='$vehicle_num', time_interval='$time_interval'";

#	print "\nquery: $query\n";

	$rows = $dbh->do ($query);

	$dbh->disconnect();

	if(!defined($rows)) #if there was an error
	{
		$outcome = "0";  #return false
	}
	elsif($rows == "0E0") #if no rows were affected
	{
		$outcome = "0";
#		print "\ngot into the 0E0 Statement\n";
	}
	else
	{
		$outcome = "1";  # return true
	}

	return $outcome;
}


################################################################################
# Function: update_dock_status
# Description: updates status of dock_status in the dock database
# Arguments: dock_num, rack_num, dock_status (empty or vehicle_num)
# Returns: bool - true on success
# Status: 90% (functional needs cleanup)
################################################################################
sub update_dock_status
{
	my $dock_num = $_[0];
	my $rack_num = $_[1];
	my $dock_status = $_[2];
	my $rows;
	my $outcome;
	
	my ($dsn) = "DBI:mysql:bike_program:localhost";
	my ($user_name) = "bike";
	my ($password) = "password";
	my ($query);
	my ($dbh, $sth);
	my (@ary);
			
	# connect to database
	$dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1});
	
	$query = "UPDATE dock SET dock_status='$dock_status' WHERE rack_num='$rack_num' AND dock_num='$dock_num'";
#	print "\nquery: $query\n";

	$rows = $dbh->do ($query);

	$dbh->disconnect();

	if(!defined($rows)) #if there was an error
	{
		$outcome = "0";  #return false
	}
	elsif($rows == "0E0") #if no rows were affected
	{
		$outcome = "0";
#		print "\ngot into the 0E0 Statement\n";
	}
	else
	{
		$outcome = "1";  # return true
	}

	return $outcome;
}


################################################################################
# Function: update_vehicle_availability
# Description: updates availability of specified vehicle
# Arguments: vehicle_num, availability (available, unavailable, or user_id)
# Returns: bool - 1 on success, 0 on failure
# Status: 90% (functional needs cleanup)
################################################################################
sub update_vehicle_availability
{
	my $vehicle_num = $_[0];
	my $availability = $_[1];
	
	my $outcome;
	my $rows;
	
	my ($dsn) = "DBI:mysql:bike_program:localhost";
	my ($user_name) = "bike";
	my ($password) = "password";
	my ($query);
	my ($dbh, $sth);
	my (@ary);
	
	# connect to database
	$dbh = DBI->connect ($dsn, $user_name, $password, { RaiseError => 1});
	
	#build query
	$query = "UPDATE vehicle SET availability='$availability' WHERE vehicle_num='$vehicle_num'";
	
#	print "\nquery: $query\n";

	#run query
	$rows = $dbh->do ($query);
	
	$dbh->disconnect();
	
#	print "\nrows: $rows\n";

	if(!defined($rows)) #if there was an error
	{
		$outcome = "0";  #return false
#		print "\nnot defined\n";
	}
	elsif($rows == "0E0") #if no rows were affected
	{
		$outcome = "0";
#		print "\ngot into the 0E0 Statement\n";
	}
	else
	{
		$outcome = "1";  # return true
	}

	return $outcome;
}