use warnings;
use strict;

package Jifty::Web::Session::JDBI;
use base qw/Jifty::Web::Session/;
use DateTime    ();
use Storable    ();
$Storable::Deparse    = 1;
$Storable::Eval       = 1;
$Storable::forgive_me = 1;

=head1 NAME

Jifty::Web::Session - A Jifty session handler

=head1 SYNOPSIS

In your F<etc/config.yml> (optional):

  framework:
    Web:
      # The default ($PORT is replaced by the port the app is running on)
      SessionCookieName: JIFTY_SID_$PORT

=head2 new

Returns a new, empty session.

=cut

sub new {
    my $class = shift;
   return bless { _cookie_name => Jifty->config->framework('Web')->{'SessionCookieName'} }, $class;
}

=head2 id

Returns the session's id if it has been loaded, or C<undef> otherwise.

=cut

sub id {
    my $self = shift;
    return $self->loaded ? $self->_session->session_id : undef;
}

=head2 create

Creates a new row in the L<Jifty::Model::Session> table.

=cut

sub create {
    my $self = shift;
    my $session = Jifty::Model::Session->new;
    $session->create( key_type => "session" );
    $self->_session($session);
}

=head2 load [ID]

Load up the current session from the given C<ID>, or the appropriate
cookie (see L</cookie_name>) otherwise.  If both of those fail,
creates a session in the database.

=cut

sub load {
    my $self       = shift;
    my $session_id = shift;

    $session_id ||= $self->_get_session_id_from_client();

    my $session = Jifty::Model::Session->new;
    $session->load_by_cols(
        session_id => $session_id,
        key_type   => "session"
    ) if $session_id;

    $session->create( key_type => "session" ) unless $session->id;
    $self->_session($session);
}

=head2 load_by_kv key => value 

Load up the current session from the given (key, value) pair. If no matching
session could be found, it will create a new session with the key, value set.
Be sure that what you're loading by is unique. If you're loading a session
based on, say, a timestamp, then you're asking for trouble.

=cut

sub load_by_kv {
    my $self = shift;
    my $k    = shift;
    my $v    = shift;

# XXX TODO: we store this data in a storable. so we now want to match on the storable version
# It would be so nice if Jifty::DBI could do this for us.
    my $encoded = Storable::nfreeze( \$v );

    my $session = Jifty::Model::Session->new;
    $session->load_by_cols(
        key_type => 'key',
        data_key => $k,
        value    => $encoded,
    );
    my $session_id = $session->session_id;

    # XXX: if $session_id is undef, then bad things happen. This *can* happen.

    $self->load($session_id);
    $self->set( $k => $v ) if !$session_id;
}


sub _get_session_id_from_client {
    my $self        = shift;
    my $cookies     = Jifty->web->request
        ? Jifty->web->request->cookies : {};
    return $cookies->{$self->cookie_name};
}

=head2 get KEY [TYPE]

Returns the value for C<KEY> for the current user's session.  C<TYPE>,
which defaults to "key", allows accessing of other namespaces in the
session, including "metadata" and "continuation".

=cut

sub get {
    my $self     = shift;
    my $key      = shift;
    my $key_type = shift || "key";

    return undef unless $self->loaded;

    my $setting = Jifty::Model::Session->new;
    $setting->load_by_cols(
        session_id => $self->id,
        key_type   => $key_type,
        data_key   => $key
    );
    return $setting->value;

}

=head2 set KEY => VALUE, [TYPE]

Sets the value C<VALUE> for C<KEY> for the session.  C<TYPE>, which
defaults to "key", allows values to be set in other namespaces,
including "metadata" and "continuation". C<VALUE> can be an arbitrary
perl data structure -- C<Jifty::Web::Session> will serialize it for
you.

=cut

sub set {
    my $self     = shift;
    my $key      = shift;
    my $value    = shift;
    my $key_type = shift || "key";

    return undef unless $self->loaded;
    $self->_session->set_updated( DateTime->now );

    my $setting = Jifty::Model::Session->new;
    $setting->load_by_cols(
        session_id => $self->id,
        key_type   => $key_type,
        data_key   => $key
    );
    if ( $setting->id ) {
        $setting->set_value($value);
    } else {
        $setting->create(
            session_id => $self->id,
            key_type   => $key_type,
            data_key   => $key,
            value      => $value
        );
    }

}

=head2 remove KEY, [TYPE]

Remove key C<KEY> from the cache.  C<TYPE> defaults to "key".

=cut

sub remove {
    my $self     = shift;
    my $key      = shift;
    my $key_type = shift || "key";

    return undef unless $self->loaded;
    $self->_session->set_updated( DateTime->now );

    my $setting = Jifty::Model::Session->new;
    $setting->load_by_cols(
        session_id => $self->id,
        key_type   => $key_type,
        data_key   => $key
    );
    $setting->delete if $setting->id;
}

=head2 remove_all

Removes the session from the database entirely.

=cut

sub remove_all {
    my $self = shift;
    return unless $self->loaded;
    my $settings = Jifty::Model::SessionCollection->new;
    $settings->limit( column => "session_id", value => $self->id );
    $_->delete while $_ = $settings->next;
    $self->unload;
}

=head2 continuations

Return a hash of all the continuations in this session, keyed by the
continuations' C<id>.

=cut

sub continuations {
    my $self = shift;

    return () unless $self->loaded;

    my $conts = Jifty::Model::SessionCollection->new;
    $conts->limit( column => "key_type",   value => "continuation" );
    $conts->limit( column => "session_id", value => $self->id );

    my %continuations;
    $continuations{ $_->data_key } = $_->value while $_ = $conts->next;
    return %continuations;
}

1;
