use warnings;
use strict;

package Jifty::Web::Session;
use base qw/Jifty::Object/;
use CGI::Cookie ();
use DateTime ();
 
=head1 NAME

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

=cut

=head2 new

Returns a new, empty session.

=cut

sub new {
    my $class = shift;

    my $session_class = Jifty->config->framework('Web')->{'SessionClass'};
    if ($session_class and $class ne $session_class) {
        Jifty::Util->require( $session_class );
        return $session_class->new(@_);
    }
    else {
        return bless {}, $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 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);
    $self->{cache} = undef;
}

sub _get_session_id_from_client {
        my $self = shift;
        my %cookies    = CGI::Cookie->fetch();
        my $cookie_name = $self->cookie_name;
        my $session_id
            = $cookies{$cookie_name} ? $cookies{$cookie_name}->value() : undef;
}

=head2 unload

Flush the session, and leaves the session object blank.

=cut

sub unload {
    my $self = shift;

    return unless $self->loaded;
    $self->_session(undef);
}

=head2 loaded

Returns true if the session has already been loaded.

=cut

sub loaded {
    my $self = shift;
    return $self->_session;
}

sub _session {
    my $self = shift;
    $self->{'_session'} = shift if (@_);
    return ( $self->{'_session'} );
}

=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;

    if ($key_type eq "continuation" or $key_type eq "session") {
        my $setting = Jifty::Model::Session->new;
        $setting->load_by_cols(
            session_id => $self->id,
            key_type   => $key_type,
            data_key   => $key
        );
        return $setting->value;
    } else {
        unless ($self->{cache}) {
            my $settings = Jifty::Model::SessionCollection->new;
            $settings->limit( column => 'session_id', value => $self->id, case_sensitive => '1' );
            $settings->limit( column => 'key_type',   value => 'continuation', operator => '!=', entry_aggregator => 'and', case_sensitive => '1' );
            $settings->limit( column => 'key_type',   value => 'session', operator => '!=', entry_aggregator => 'and', case_sensitive => '1' );
            while (my $row = $settings->next) {
                $self->{cache}{$row->key_type}{$row->data_key} = $row->value;
            }
        }

        return $self->{cache}{$key_type}{$key};
    }

}

=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 structue -- 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
        );
    }

    $self->{cache}{$key_type}{$key} = $value
      if $self->{cache};

}

=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 set_continuation ID CONT

Stores a continuation in the session.

=cut

sub set_continuation {
    my $self = shift;
    $self->set( @_, "continuation" );
}

=head2 get_continuation ID

Pulls a continuation from the current session. Expects a continuation
C<ID>.

=cut

sub get_continuation {
    my $self = shift;
    $self->get( @_, "continuation" );

}

=head2 remove_continuation ID

Removes a continuation with id C<ID> from the store.

=cut

sub remove_continuation {
    my $self = shift;
    $self->remove( @_, "continuation" );
}

=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", case_sensitive => '1' );
    $conts->limit( column => "session_id", value => $self->id, case_sensitive=> '1' );

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

=head2 set_cookie

Sets the session cookie.

=cut

sub set_cookie {
    my $self = shift;

    my $cookie_name = $self->cookie_name;
    my %cookies     = CGI::Cookie->fetch();
    my $session_id
        = $cookies{$cookie_name} ? $cookies{$cookie_name}->value() : undef;
    my $cookie = new CGI::Cookie(
        -name    => $cookie_name,
        -value   => $self->id,
        -expires => $self->expires,
    );

    # XXX TODO might need to change under mod_perl
    if ( not $cookies{$cookie_name}
        or ( $cookies{$cookie_name} ne $cookie->as_string ) )
    {
        Jifty->web->response->add_header(
            'Set-Cookie' => $cookie->as_string );
    }
}

=head2 cookie_name

Returns the current session's cookie_name -- it is the same for all
users, but varies according to the port the server is running on.

=cut

sub cookie_name {
    my $self = shift;
    my $cookie_name = "JIFTY_SID_" . ( $ENV{'SERVER_PORT'} || 'NOPORT' );
    return ($cookie_name);
}

=head2 expires [VALUE]

Get or set the session's expiration date, in a format expected by
Cache::Cache.

=cut

sub expires {
    my $self = shift;
    $self->set( 'expires' => shift, "metadata" ) if @_;
    return ( $self->get( 'expires', "metadata" ) );
}

1;
