#############################################################################
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#  Jabber
#  Copyright (C) 1998-1999 The Jabber Team http://jabber.org/
#
##############################################################################

package Jarl::Roster;

use vars qw($VERSION);
$VERSION = '0.2';

use Carp;
use strict;


##############################################################################
#
# new - creation function.  For right now this is a stub.  More functionality
#       might move in here later.
#
##############################################################################
sub new {
  my $proto = shift;
  my $class = ref($proto) || $proto;
  my $self  = {};

  bless ($self, $class);
  return $self;
}


##############################################################################
#
# Refresh - redraw the Roster calling the general function Draw.
#
##############################################################################
sub Refresh {
  my $self = shift;
  $self->Draw();
}


##############################################################################
#
# ActiveGroup - returns 1 if the group has any active JIDs in it.  Active is
#               defined as online with 1 or more resources.
#
##############################################################################
sub ActiveGroup {
  my $self = shift;
  my ($group) = @_;

  return unless exists($self->{groups}->{$group});

  foreach my $jid (keys(%{$self->{groups}->{$group}})) {
    return 1 if (exists($self->{jids}->{$jid}) &&
		 (scalar(keys(%{$self->{resources}->{$jid}})) != 0));
  }
  return 0;
}


##############################################################################
#
# DrawMode - Setup a way to turn on and off drawing...
#
##############################################################################
sub DrawMode {
  my $self = shift;
  my $mode = shift;
  $self->{varsDrawMode} = $mode if defined($mode);
  return $self->{varsDrawMode};
}


##############################################################################
#
# Draw - stub for drawing the roster
#
##############################################################################
sub Draw {
  my $self = shift;
}


##############################################################################
#
# Toggle - toggles the group/jid being open or not.
#
##############################################################################
sub Toggle {
  my $self = shift;
  my ($group,$jid) = @_;

  if (defined($jid)) {
    if ($group eq "") {
      return unless exists($self->{jids}->{$jid});
      $self->{jids}->{$jid}->{status} ^= 1;
    } else {
      return unless exists($self->{groups}->{$group});
      return unless exists($self->{groups}->{$group}->{$jid});
      $self->{groups}->{$group}->{$jid} ^= 1;
    }
  } else {
    return unless exists($self->{groups}->{$group});
    $self->{groups}->{$group}->{'__roster__:status'} ^= 1;
  }

  $self->Draw();
}


##############################################################################
#
# GetValue - if fields is defined then it gets the value of the specified
#            entry in the hash, if it exists.  If field is not defined, then
#            it returns if the jid exists in the roster or not.
#
##############################################################################
sub GetValue {
  my $self = shift;
  my($jid,$field) = @_;

  $field = "" unless defined($field);

  if ($field ne "") {
    return unless exists($self->{jids}->{$jid});

    if ($field ne "type") {
      if ($field ne "groups") {
	return $self->{jids}->{$jid}->{$field};
      } else {
	my @groups = grep { exists($self->{groups}->{$_}->{$jid}); } keys (%{$self->{groups}});
	shift(@groups) if $groups[0] eq '__roster__:none';
	return @groups;
      }
    } else {
      my @resources = keys(%{$self->{resources}->{$jid}});

      if ($#resources > -1) {
	return "available";
      } else {
	return "unavailable";
      }
    }
  } else {
    return exists($self->{jids}->{$jid});
  }
}


##############################################################################
#
# Names - returns the list of names for all of the JIDs in the roster in
#         sorted order.
#
##############################################################################
sub Names {
  my $self = shift;

  my @names;
  foreach my $jid (sort {$self->{jids}->{$a}->{name} cmp $self->{jids}->{$b}->{name}} keys(%{$self->{jids}})) {
    my @groups = $self->GetValue($jid,"groups");
    if ($#groups > -1) {
      foreach my $group (@groups) {
	if ($group eq "__roster__:none") {
	  push(@names,$self->{jids}->{$jid}->{name});
	} else {
	  push(@names,$self->{jids}->{$jid}->{name}." (Group: $group)");
	}
      }
    } else {
      push(@names,$self->{jids}->{$jid}->{name});
    }
  }

  return @names;
}


##############################################################################
#
# Resource - returns the highest ranking resource based on priority for the
#            JID.
#
##############################################################################
sub Resource {
  my $self = shift;
  my ($jid) = @_;

  return unless exists($self->{resources}->{$jid});

  my @resources = (sort {$self->{resources}->{$jid}->{$b}->{priority} <=>  $self->{resources}->{$jid}->{$a}->{priority}} keys(%{$self->{resources}->{$jid}}));

  return $resources[0] if ($#resources > -1);
  return;
}


##############################################################################
#
# GetGroups - returns an array of all of the groups in the roster.
#
##############################################################################
sub GetGroups {
  my $self = shift;
  return grep { !/__roster__:none/; } sort( {lc($a) cmp lc($b)} keys(%{$self->{groups}}));
}


##############################################################################
#
# AddGroup - Adds a group to the Roster and initializes it.
#
##############################################################################
sub AddGroup {
  my $self = shift;
  my ($group) = @_;

  return if exists($self->{groups}->{$group});

  $self->{groups}->{$group}->{'__roster__:status'} = 0;

  $self->RegisterGroup($group);
}


##############################################################################
#
# Selected - returns the selectedJID.
#
##############################################################################
sub Selected {
  my $self = shift;

  return $self->{varsSelectedJID};
}


##############################################################################
#
# ShowOnlineOnly - Sets the value of the online only variable to 1 and draws
#                  the Roster.
#
##############################################################################
sub ShowOnlineOnly {
  my $self = shift;

  $self->{varsOnlineOnly} = 1;
  $self->Draw();
}


##############################################################################
#
# ShowAll - Sets the value of the online only variable to 0 and draws the
#           Roster.
#
##############################################################################
sub ShowAll {
  my $self = shift;

  $self->{varsOnlineOnly} = 0;
  $self->Draw();
}


##############################################################################
#
# CreateTag - generates a unique string that represents the entry in the
#             Roster based off of group, JID, and resource.
#
##############################################################################
sub CreateTag {
  my $self = shift;
  my ($group,$jid,$resource) = @_;

  $group = "" unless defined($group);
  $jid = "" unless defined($jid);
  $resource = "" unless defined($resource);

  $group = $self->EscapeTag($group);
  $jid = $self->EscapeTag($jid);
  $resource = $self->EscapeTag($resource);

  my @tagString;
  push(@tagString,"group-${group}") unless ($group eq "");
  push(@tagString,"jid-${jid}") unless ($jid eq "");
  push(@tagString,"res-${resource}") unless ($resource eq "");
  my $tag = join(":::",@tagString);

  return $tag;
}


##############################################################################
#
# AddJID - adds the specified JID to the Roster.  The argument is a hash
#          that contains the Roster item.
#
##############################################################################
sub AddJID {
  my $self = shift;
  my (%args) = @_;

  my $name = $args{-jid};
  $name = $args{-name} if (exists($args{-name}) && ($args{-name} ne ""));

  if (exists($self->{jids}->{$args{-jid}})) {
    foreach my $group (keys(%{$self->{groups}})) {
      delete($self->{groups}->{$group}->{$args{-jid}})
	if exists($self->{groups}->{$group}->{$args{-jid}});
    }
  }

  $self->{jids}->{$args{-jid}}->{name} = $name;
  $self->{jids}->{$args{-jid}}->{status} = 0;
  $self->{jids}->{$args{-jid}}->{subscription} = $args{-subscription};
  $self->{jids}->{$args{-jid}}->{ask} = $args{-ask};
  $self->{jids}->{$args{-jid}}->{balloon} = "Offline";

  $self->{case}->{lc($args{-jid})} = $args{-jid};

  foreach my $group (@{$args{-groups}}) {
    $self->AddGroup($group);
    $self->{groups}->{$group}->{$args{-jid}} = 0;
    $self->RegisterJID($group,$args{-jid});
    foreach my $resource (keys(%{$self->{resources}->{$args{-jid}}})) {
      $self->RegisterJID($group,$args{-jid},$resource);
    }
  }

  my $res = $self->Resource($args{-jid});

  $self->{jids}->{$args{-jid}}->{balloon} =
    $self->{resources}->{$args{-jid}}->{$res}->{balloon}
      if defined($res);

  $self->GarbageCollect();
  $self->Draw();
}


##############################################################################
#
# UpdatePresence - takes a <presence> tag and updates the Roster based on the
#                  roster mode and the presence.
#
##############################################################################
sub UpdatePresence {
  my $self = shift;
  my ($presence) = @_;

  my $fromJID = $presence->GetFrom("jid");
  my $jid = $fromJID->GetJID();

  $jid = $self->{case}->{lc($jid)}
    unless($self->{varsUpdateMode} eq "presence");

  if (($jid eq "") || (!exists($self->{jids}->{$jid}))) {
    $jid = $fromJID->GetJID("full");
    ($jid = $self->{case}->{lc($jid)})
      unless($self->{varsUpdateMode} eq "presence");
  }

  return if (($self->{varsUpdateMode} eq "roster") &&
	     !exists($self->{jids}->{$jid}));

  my $resource = $fromJID->GetResource();

  if ($self->{varsUpdateMode} eq "presence") {
    $self->{jids}->{$jid}->{name} = $resource;
    $self->{jids}->{$jid}->{status} = 0;
    $self->{jids}->{$jid}->{subscription} = "both";
    $self->{groups}->{'__roster__:none'}->{$jid} = 0;
    $self->RegisterJID('__roster__:none',$jid);
  } else {
    foreach my $group (keys(%{$self->{groups}})) {
      $self->RegisterJID($group,$jid,$resource)
	if exists($self->{groups}->{$group}->{$jid});
    }
  }

  my $keyid = "";
  if ($main::options{gpg} == 1) {
    my @xsigned = $presence->GetX("jabber:x:signed");
    if ($#xsigned > -1 ) {
      $keyid =
	&main::jarlGPG_GetKeyID($presence->GetStatus(),
				$xsigned[0]->GetSignature());
    }
  }

  if ($presence->GetType() eq "unavailable") {
    if ($self->{varsUpdateMode} eq "roster") {
      delete($self->{resources}->{$jid}->{$resource});
      delete($self->{resources}->{$jid})
	if (scalar(keys(%{$self->{resources}->{$jid}})) == 0);
    }
    if ($self->{varsUpdateMode} eq "presence") {
      delete($self->{groups}->{'__roster__:none'}->{$jid});
      delete($self->{jids}->{$jid});
    }
  } else {
    $self->{resources}->{$jid}->{$resource}->{type} =
      $presence->GetType();
    $self->{resources}->{$jid}->{$resource}->{show} =
      $presence->GetShow();
    $self->{resources}->{$jid}->{$resource}->{status} =
      $presence->GetStatus();
    $self->{resources}->{$jid}->{$resource}->{priority} =
      $presence->GetPriority();
    $self->{resources}->{$jid}->{$resource}->{gpgkeyid} = $keyid;

    $self->{resources}->{$jid}->{$resource}->{balloon} = "";
    $self->{resources}->{$jid}->{$resource}->{balloon} .=
      $presence->GetShow()
	unless ($presence->GetShow() eq "");
    $self->{resources}->{$jid}->{$resource}->{balloon} .= ": "
      unless (($presence->GetShow() eq "") ||
	      ($presence->GetStatus() eq ""));
    $self->{resources}->{$jid}->{$resource}->{balloon} .=
      $presence->GetStatus()
	unless ($presence->GetStatus() eq "");
    $self->{resources}->{$jid}->{$resource}->{balloon} = "Online"
      if ($self->{resources}->{$jid}->{$resource}->{balloon} eq "");
    foreach my $group (keys(%{$self->{groups}})) {
      $self->RegisterBalloon($self->CreateTag($group,$jid,$resource),
			     \$self->{resources}->{$jid}->{$resource}->{balloon})
	if exists($self->{groups}->{$group}->{$jid});
    }
  }

  if (exists($self->{jids}->{$jid})) {
    my $highResource = $self->Resource($jid);
    if (defined($highResource)) {
      my $show = $self->{resources}->{$jid}->{$highResource}->{show};
      my $status = $self->{resources}->{$jid}->{$highResource}->{status};
      my $keyid = $self->{resources}->{$jid}->{$highResource}->{gpgkeyid};

      if ($keyid ne "") {
	$self->{jids}->{$jid}->{gpgkeyid} = $keyid;
      } else {
	delete($self->{jids}->{$jid}->{gpgkeyid});
      }

      $self->{jids}->{$jid}->{balloon} = "";
      $self->{jids}->{$jid}->{balloon} .= $show unless ($show eq "");
      $self->{jids}->{$jid}->{balloon} .= ": " unless (($show eq "") ||
						       ($status eq ""));
      $self->{jids}->{$jid}->{balloon} .= $status unless ($status eq "");
      $self->{jids}->{$jid}->{balloon} = "Online"
	if ($self->{jids}->{$jid}->{balloon} eq "");
    } else {
      $self->{jids}->{$jid}->{balloon} = "Offline";
    }
  }

  $self->Draw();
}


##############################################################################
#
# RegisterBalloon - save the pointer for the balloon.
#
##############################################################################
sub RegisterBalloon {
  my $self = shift;
  my ($tag,$balloon) = @_;

  $self->{balloons}->{$tag} = $balloon;
}


##############################################################################
#
# RegisterGroup - stub for children to have code for when a group is added.
#
##############################################################################
sub RegisterGroup {
  my $self = shift;
  my ($group) = @_;
}


##############################################################################
#
# RegisterJID - stub for children to have code for when a JID is added.
#
##############################################################################
sub RegisterJID {
  my $self = shift;
  my ($group,$jid,$resource) = @_;
}


##############################################################################
#
# Clear - deletes everything in the Roster.
#
##############################################################################
sub Clear {
  my $self = shift;

  $self->{groups} = {};
  $self->{jids} = {};
  $self->{resources} = {};
  $self->{balloons} = {};

}


##############################################################################
#
# RemoveJID - Remove the JID from the Roster.
#
##############################################################################
sub RemoveJID {
  my $self = shift;
  my ($jid) = @_;

  foreach my $group (keys(%{$self->{groups}})) {
    delete($self->{groups}->{$group}->{$jid})
      if exists($self->{groups}->{$group}->{$jid});
  }

  delete($self->{jids}->{$jid});
  delete($self->{resources}->{$jid});
  delete($self->{case}->{lc($jid)});

  $self->GarbageCollect();
  $self->Draw();
}


##############################################################################
#
# GarbageCollect - Remove the internal data for anything that no longer 
#                  exists to help free up memory.
#
##############################################################################
sub GarbageCollect {
  my $self = shift;

  $self->CleanupGroups();
}


##############################################################################
#
# CleanupGroups - for each group that is empty, delete it.
#
##############################################################################
sub CleanupGroups {
  my $self = shift;

  foreach my $group (keys(%{$self->{groups}})) {
    next if ($group eq '__roster__:none');
    delete($self->{groups}->{$group})
      if (scalar(keys(%{$self->{groups}->{$group}})) == 1);
  }
}


##############################################################################
#
# ExtractRoster - take the hash from Net::Jabber and Add/Remove JIDs from the
#                 Roster as directed.
#
##############################################################################
sub ExtractRoster {
  my $self = shift;
  my (%newRoster) = @_;

  foreach my $jid (keys(%newRoster)) {
    if ($newRoster{$jid}->{subscription} eq "remove") {
      $self->RemoveJID($jid);
    } else {
      $self->AddJID(-jid=>$jid,
		    -name=>$newRoster{$jid}->{name},
		    -subscription=>$newRoster{$jid}->{subscription},
		    -ask=>$newRoster{$jid}->{ask},
		    (($#{$newRoster{$jid}->{groups}} > -1) ?
		     (-groups=>$newRoster{$jid}->{groups}) :
		     (-groups=>['__roster__:none'])
		    )
		   );
    }
  }
}


##############################################################################
#
# EscapeTag - take the string and make sure that no funky stuff is in it.
#
##############################################################################
sub EscapeTag {
  my $self = shift;
  my $string = shift;

  $string =~ s/\:/\:colon;/g;
  $string =~ s/\!/\:exclam;/g;
  $string =~ s/\|/\:bar\;/g;
  $string =~ s/\&/\:amp\;/g;
  $string =~ s/\$/\:dsign\;/g;
  $string =~ s/\%/\:perc\;/g;
  $string =~ s/\^/\:carat\;/g;

  return $string;
}


##############################################################################
#
# UnescapeTag - take the string and put the funky stuff back in.
#
##############################################################################
sub UnescapeTag {
  my $self = shift;
  my $string = shift;

  $string =~ s/\:carat\;/\^/g;
  $string =~ s/\:perc\;/\%/g;
  $string =~ s/\:dsign\;/\$/g;
  $string =~ s/\:amp\;/\&/g;
  $string =~ s/\:bar\;/\|/g;
  $string =~ s/\:exclam\;/\!/g;
  $string =~ s/\:colon\;/\:/g;

  return $string;
}


sub ExistsGroup {
  my $self = shift;
  my $group = shift;

  return exists($self->{groups}->{$group});
}


sub NameToJID {
  my $self = shift;
  my $name = shift;

  return "" if ($name =~ /^\s*$/);

  my $group = "__roster__:none";
  ($name,$group) = ($name =~ /^(.+) \(Group\: (.+)\)\s*$/)
    if ($name =~ /\(Group\: /);

  foreach my $jid (sort {$self->{jids}->{$a}->{name} cmp $self->{jids}->{$b}->{name}} keys(%{$self->{groups}->{$group}})) {
    return $jid if ($self->{jids}->{$jid}->{name} eq $name);
  }
  return "" if ($name =~ /^\s*$/);
  return $name;
}


1;
