package GT::Session::TempTable;
# ===============================================================
# Pragmas
    use strict;
    use vars qw| $ATTRIBS @ISA $ERRORS |;

# Internal nodules
    use GT::Base;
    use GT::SQL;
    use GT::MD5 qw| md5_hex |;

# Global variable init
    @ISA = qw| GT::Base |;

    $ATTRIBS = {
        id              => undef,
        tb              => undef,

        def_path        => '',
        db              => undef,
        set_name        => 'Set_Sessions',
        create_session  => undef,
        delete_session  => undef,
        seconds         => 60*60,
        sid             => ''
    };

    $ERRORS = {
        'NODB'          => "No GT::SQL object, need to set 'db' or 'def_path'",
        'NOCS'          => "No session creation hook specified",
        'CSNOTCODE'     => "Session creation hook is not a coderef",
        'NOSID'         => "No session ID",
        'BADDATA'       => "Invalid data in session: '%s'. Reason: '%s'",
        'CLASSFUNC'     => "This is a class function only.",
        'INVALIDSESSION'=> "Invalid session id: '%s'.",
        'BADARGS'       => "Invalid arguments: %s",

    };

sub install {
#-------------------------------------------------------------------------------
# creates the controller table
#
    my $self    = shift;
    my $DB      = $self->_db();

    my $c       = $DB->creator( $self->{set_name} );

    $c->cols(
        ID         => { pos => 1, type => 'INT', not_null => 1, unsigned => 1, regex => '^d+$' },
        SessID     => { pos => 2, type => 'CHAR', size => 100, not_null => 1 },
        SessTable  => { pos => 3, type => 'CHAR', size => 100, not_null => 1 },
        Timestmp   => { pos => 4, type => 'TIMESTAMP', time_check => 1 }
    );

    $c->pk('ID');
    $c->ai('ID');
    $c->create('force');
    $c->set_defaults();
    $c->save_schema();
}

sub uninstall {
#-------------------------------------------------------------------------------
# drops the controller table along with all the 
#
    my $self    = shift;
    my $DB      = $self->_db() or return;
    my $err     = 1;

# drop all the associated temp tables...,
    eval {
        my $tb      = $DB->table( $self->{set_name} );
        my $sth     = $tb->select( [ 'SessTable' ] );
        while ( my $aref = $sth->fetchrow_arrayref() ) {
            my $table_name  = $aref->[0];
            eval {
                my $e       = $DB->editor( $table_name );
                $e->drop_table("remove") or die "Can't drop table";
            };
            $@ and $err = undef;

        }

# now drop the master control table
        my $e       = $DB->editor( $self->{set_name});
        $e->drop_table("remove") or die "Can't drop table";
    };

    return $@ ? undef : 1;
}

sub new_set {
#-------------------------------------------------------------------------------
# creates a new temp table
#
    my $self    = shift;
    my $create_session = ( ref $_[0] eq 'CODE' ? shift : $self->{create_session} ) or return $self->error( 'NOCS', 'WARN' );

    my $sid     = $self->{id} or return $self->error( 'NOSID', 'WARN' );
    my $DB      = $self->_db();
    my $Session = $DB->table( $self->{set_name} );

# create a new sesson
    my $table_name = generate_session_id();
    my $newid   = $Session->add({ SessTable => $table_name, SessID => $sid }) or return;

# create the new table, extra parameters are passed into the create_session sub procedure
    if ( my $result = &{$create_session}( $DB, $table_name, $newid, @_ ) ) {
        my $tbl = $DB->table( $table_name );
        return wantarray ? ( $tbl, $newid ) : $tbl;
    }
    else {
        $Session->delete($newid);
        return;
    }

}

sub get_set {
#-------------------------------------------------------------------------------
# returns a table reference to the sethandle
#
    my $self    = shift;
    my $set_id  = shift or return;
    my $sid     = $self->{id} or return $self->error( 'NOSID', 'WARN' );

    my $DB      = $self->_db();
    my $Session = $DB->table( $self->{set_name} ) or return;
    my $sth     = $Session->select({ ID => $set_id, SessID => $sid }) or return undef;
    my $href    = $sth->fetchrow_hashref() or return undef;
    $href->{Timestmp} = \'NOW()';
    $Session->update( $href );

    if ( my $table_name = $href->{'SessTable'} ) {
        my $tbl     = $DB->table( $table_name );
        return $tbl;
    }
    else {
        return;
    }
}

sub list_sets {
#-------------------------------------------------------------------------------
# returns a hashref of ID => tablenames, of tables that the current session ID owns
#
    my $self    = shift;
    my $DB      = $self->_db();

    my $sid     = $self->{id} or return $self->error( 'NOSID', 'WARN' );
    my $Session = $DB->table( $self->{set_name} ) or return;

    my $sth     = $Session->select({ SessID => $sid }, [ 'ID', 'SessTable' ]);
    my $list    = {};
    while ( my $aref = $sth->fetchrow_arrayref() ) {
        my ( $id, $sesstable ) = @{$aref};
        $list->{$id}    = $sesstable;
    }

    return $list;
}

sub delete {
#-------------------------------------------------------------------------------
# deletes all sets associated with the session
#
    my $self    = shift;
    my $DB      = $self->_db();

    my $Session = $DB->table( $self->{set_name} ) or return;
    my $sid     = ( shift || $self->{id} ) or return $self->error( 'NOSID', 'WARN' );
    my $sth     = $Session->select({ SessID => $sid },['SessTable']);

# delete all created temp tables
    while ( my $aref = $sth->fetchrow_arrayref() ) {
        my $tbl_name    = $aref->[0];
        eval {
            my $e           = $DB->editor($tbl_name);
            $e->drop_table( "remove" );
        }
    }

    $Session->delete({ SessID => $sid });

# cheap workaround
    shift or $self->GT::Session::SQL::delete();
}

sub delete_set {
#-------------------------------------------------------------------------------
# deletes a single set
#
    my $self    = shift;
    my $set_id  = shift;

    my $DB      = $self->_db();
    my $Session = $DB->table( $self->{set_name} ) or return;
    my $sid     = $self->{id} or return $self->error( 'NOSID', 'WARN' );
    my $sth     = $Session->select( { ID => $set_id, SessID => $sid }, [ 'SessTable' ] ) or return;
    my $aref    = $sth->fetchrow_arrayref() or return;

    if ($aref->[0]) {
        my $e   = $DB->editor($aref->[0]);
        $e->drop_table();
        $Session->delete( { ID => $set_id } );
    }
}

sub cleanup {
#-------------------------------------------------------------------------------
    my $self    = shift;
    my $seconds = shift || $self->{seconds};

    if ($seconds == 0) {
        $self->debug ("Cleanup not erasing anything, seconds set to 0.") if ($self->{_debug});
        return;
    }

    my $DB = $self->_db() or return;
    my $tb = $DB->table( $self->{set_name} );

    defined $seconds or ($seconds = 3600);
    my $new_sec = time - $seconds;
    my @time    = localtime ($new_sec);

    my $date_str = sprintf ("%4d-%02d-%02d %02d:%02d:%02d", 
        $time[5] + 1900, $time[4] + 1, $time[3], $time[2], $time[1], $time[0]);

    my $sth = $tb->select( GT::SQL::Condition->new('Timestmp', '<', $date_str), [ 'SessID' ] ) or return $self->error ($GT::SQL::error);
    while ( my $aref = $sth->fetchrow_arrayref() ) {
        $self->delete( $aref->[0], 1 );
    }
    $tb->delete (GT::SQL::Condition->new ('Timestmp', '<', $date_str)) or return $self->error ($GT::SQL::error);

    return 1;
}

sub _db {
#-------------------------------------------------------------------------------
# returns a database handle
#
    my $self    = shift;
    if ( my $db = $self->{'db'} ) {
        return $db;
    }

    elsif ( my $def_path = $self->{'def_path'} ) {
        $db = GT::SQL->new( $def_path );
        return $db;
    }

    else {
        $self->error( 'NODB', 'FATAL' );
    }
}

sub generate_session_id {
# ---------------------------------------------------------------
# Generates a session id.
#
    return md5_hex( time . $$ . rand (16000) );
}

1;

__END__

=head1 NAME

GT::Session::TempTable - A session management module, subclassing GT::Session::SQL providing temp table support

=head1 SYNOPSIS

Create a session:
    my $session = new GT::Session::TempTable({
                                                db => GT::SQL->new( '/path/to/defs' ),
                                                def_path => '/path/to/defs',
                                                create_session => \&create_table_sub
                                            });

Create temp table controller table. (do once before using this module)
    $session->initial_create();

Create a new temp table:
    my ( $GT_SQL_Table_ref, $tmp_id ) = $session->new_set();

Get the GT::SQL::Table ref to a previous table:
    my $GT_SQL_Table_ref = $session->get_set( $tmp_id );

List all the sets for current session:
    my $href = $session->list_sets();

Save data with the session:
    $session->data ("Save this information!");

Load a session.
    my $session = new GT::Session::TempTable ( $id ) or die "Can't load session: '$id'."

Delete a session:
    $session->delete();

Delete a table set:
    $session->delete_set( $tmp_id );

Cleanup old sessions, takes argument of number of seconds old.
    $session->cleanup ( 5000 );

=cut

