#!/usr/bin/perl

use CGI; 
use DBI;
use MD5; 

$DB = "DBI:mysql:a348"; 
$username = "a348"; 
$password = "a348AG"; 

$DB_TABLE = "dgerman_mar4"; 

$SECRET = "something secret"; 
$EXPIRE = 30 * 60 * 60 * 24; # one month
$MAX_TRIES = 10; 
$ID_LENGTH = 8;

$q = new CGI; 

$DBH = DBI->connect($DB, $username, $password, { PrintError => 0 }) 
       || die "Couldn't open database: ", $DBI::errstr; 

my ($session_id) = &get_session_id(); 

print $q->header, $q->start_html; 

my $state          = &get_state($session_id); 

if (! $state->{one}) { $state = &initialize($state); }

$state = &calculate($state); 


&save_state($state, $session_id); 

&status($state); 

&show_form();

print $q->end_html;

$DBH->disconnect; 

#--------------------------------(end of main program)------

sub show_form {

  print $q->start_form(), 

        "When done please press ", 

        $q->submit(-value=>'Proceed'),

        $q->end_form(); 
}

#--------------------------------(this was our basic form)---

sub get_session_id {

  &expire_old_sessions(); 

  my ($id) = $q->path_info =~ m:^/([a-h0-9]{$ID_LENGTH}):o;
  return $id if $id and &check_id($id); 

  my $session_id = &generate_id; 
  die "Couldn't make a new session_id" unless $session_id; 

  print $q->redirect($q->script_name() . "/$session_id"); 

  exit(0);   

}

#--------------------------------(needed above)--------------

sub expire_old_sessions {

  $DBH->do(<<END);
DELETE FROM $DB_TABLE
  WHERE (unix_timestamp() - unix_timestamp(modified)) > $EXPIRE
END

}

#--------------------------------(also needed above)---------

sub generate_id {

  my $tries = 0; 

  my $id = &hash($SECRET . rand()); 

  while ($tries++ < $MAX_TRIES) {
    last if 
      $DBH->do("INSERT INTO $DB_TABLE (session_id) VALUES ('$id')"); 
    $id = &hash($SECRET . rand()); 
  }

  return undef if $tries >= $MAX_TRIES;

  return $id; 

}

sub hash {
  my $value = shift; 
  return substr(MD5->hexhash($value), 0, $ID_LENGTH);   
}

#--------------------------------(last one needed)-----------

sub check_id {
  my $id = shift; 
  return $id 
    if $DBH->do("SELECT 1 FROM $DB_TABLE WHERE session_id = '$id'") > 0; 
  return $id 
    if $DBH->do("INSERT INTO $DB_TABLE (session_id) VALUES ('$id')"); 
  return ''; 
}

#--------------------------------(retrieve acc)--------------

sub get_state {

  my $id = shift; 

  my $query = "SELECT * FROM $DB_TABLE WHERE session_id = '$id'"; 

  my $sth = $DBH->prepare($query) || die "Prepare: ", $DBH->errstr; 

  $sth->execute || die "Execute: ", $sth->errstr;

  my $state = $sth->fetchrow_hashref;

  $sth->finish; 

  return $state; 
}

#--------------------------------(calculate new acc)---------

sub calculate { 

  my $state = shift; 

  $state->{one} = $state->{two};

  $state->{two} = $state->{three}; 

  $state->{three} = $state->{one} + $state->{two};

  return $state; 
}

#--------------------------------(store new acc)-------------

sub save_state {
  my ($state, $id) = @_; 
  my $sth = $DBH->prepare(<<END) || die "Prepare: ", $DBH->errstr; 
UPDATE $DB_TABLE
  SET one = ?, two = ?, three = ?  
  WHERE session_id = '$id'
END
  $sth->execute(@{$state}{qw(one two three)}) 
  || die "Execute: ", $DBH->errstr; 
  $sth->finish; 
}

#--------------------------------(print current acc)---------

sub status {
  my ($state) = @_; 

  print  qq{
          One: $state->{one} <p> 
          Two: $state->{two} <p> 
          Three: $state->{three} <p> 
        };

}

sub initialize { 
  my $state = shift; 
  $state = {} unless $state; 

  $state->{one} = 1; 
  $state->{two} = 1; 
  $state->{three} = 2; 

  return $state; 
}