#!perl -w
# 
# script to calculate distances to other members from .node friend pages.
#
# this script assumes friend pages of .node members to be in the current directory,
# names as <userid>.friend.
#
# the friend page is http://dotnode.com/friends/<userid>/list
#
use strict;
use IO::File;
use Getopt::Long;
$|=1;
my $g_verbose= 0;
GetOptions( "v"=>\$g_verbose );


my %all;

for my $fn (glob("*.friends")) {
    my $fh= IO::File->new($fn, "r") or die "$fn: $@\n";
    
    my $html;
    $fh->read($html, -s $fh);
    $fh->close();


    my $id= ParseFriendsPage($html, $fn);
    next if (!defined $id);
    setforperson($id, 'filename', $fn);
    if ($fn ne "$id.friends") {
        warn "did not expect $id in $fn\n";
    }
}

my $maxcount= 0;
for my $p (values %all) {
    $p->{dlist}= calcdistances($p);
    for my $p2 (values %all) {
        my $d= getdistance($p->{dlist}, $p2);
        $p->{dcount}{$d}++;
        $maxcount= $d if ($d > $maxcount);
    }
}

sub ordering {
    my ($a, $b) = @_;

    for (my $i=$maxcount ; $i>=0 ; $i--) {
        if (($a->{dcount}{$i}||0) <=> ($b->{dcount}{$i}||0)) {
            return ($a->{dcount}{$i}||0) <=> ($b->{dcount}{$i}||0);
        }
    }
    return 0;
}
my $wj= $all{'737bef8bc43fc442c268ef0786211293'} || die "willem is missing\n";
print "                         id fn album com d_wj".join("", map { sprintf(" %3d", $_); } (1..$maxcount))."  sex      name           country\n";
for my $p (sort { ordering($a,$b) } values %all) {
    my $commonfriends= countcommonfriends($wj, $p);
    my $totalfriends= $p->{nfriends} || 0;
    my $foundfriends= scalar keys %{$p->{friends}};
    my $isdeleted= exists $p->{deleted};

    printf("%20s %s%s%s %3d %2d %2d %2d %2d [%-30s] %-8s %-20s\t%s\n", 
        $p->{id},
        (exists $p->{filename})?"Y":"N",
        (exists $p->{has_album})?"Y":"N",
        (exists $p->{has_blog})?"Y":"N",
        $commonfriends,
        getdistance($wj->{dlist}, $p),
        $totalfriends,
        $foundfriends,
        $totalfriends-$foundfriends,
        getdistgroupstr($maxcount, $p),
        $p->{sex} || "", 
        $p->{name} || "",
        $p->{country} || "");

}
sub getdistgroupstr {
    my $maxcount= shift;
    my $p= shift;
    my $str= "";
    for my $d (1..$maxcount) {
        $str .= sprintf(" %3d", $p->{dcount}{$d}||0);
    }
    return $str;
}

exit (0);
############################################
sub calcdistances {
    my ($p)= @_;

    my @levels= ( { $p->{id} => 1 } );

    my %list= ( $p->{id} => 0 );

    my $continue= 1;
    while ($continue) {
        $continue= 0;
        my %level;

        for my $f (keys %{$levels[-1]}) {
            for my $ff (keys %{$all{$f}{friends}}) {
                if (!exists $list{$ff}) {
                    $continue= 1;
                    $level{$ff}= 1;
                    $list{$ff}= $#levels+1;
                }
            }
        }
        push @levels, \%level;
    }
    #print "$#levels levels\n";
    return \%list;
}
sub getdistance {
    my ($list, $p)= @_;
    return  (exists $list->{$p->{id}}) ? $list->{$p->{id}} : -1;
}

sub countcommonfriends {
    my ($p, $q)= @_;
    my $n= 0;

    for my $f (values %{$p->{friends}}) {
        $n++ if (exists $q->{friends}{$f->{id}});
    }
    return $n;
}



##################################################
sub ParseMenuEntry {
    my $curid= shift;
    my $html= shift;

    if ($html =~ /<a href='\/(\w+)\/(\w+)'>.*?<\/a>/s) {
        my ($tag, $id)= ($1, $2);
        $curid= $id;
        setforperson($curid, "has_$tag", 1);
    }
    return $curid;
}
sub ParseMenuList {
    my $html= shift;

    my $i= 0;
    my $curid;
    for ($html =~ /<li>(.*?)<\/li>/gs) {
        my $entry= $_;
        $curid= ParseMenuEntry($curid, $entry);
    }

    return $curid;
}

sub ParseInfoEntry {
    my $curid= shift;
    my $html= shift;

    if ($html =~ /<a href='\/profile\/(\w+)'>(.*?)<\/a>/s) {
        my ($id, $name)= ($1, $2);
        $curid= $id;
        setforperson($curid, 'name', $name);
    }
    elsif ($html =~ /^(?:(Homme|Femme|Male|Female))?(?:, (.*))?$/s) {
        my ($sex, $status)= ($1, $2);

        setforperson($curid, 'sex', $sex) if ($sex);
        setforperson($curid, 'status', $status) if ($status);
    }
    elsif ($html =~ /<span class='label'>.*?<\/span><br \/>(.*)/s) {
        my ($interesses)= ($1);
        setforperson($curid, 'interesses', $interesses);
    }
    elsif ($html =~ /^&nbsp;$/) {
        # ignore
    }
    elsif ($html =~ /(.*)/s) {
        my ($country)= ($1);
        $country =~ s/\s+$//;
        setforperson($curid, 'country', $country);
    }

    return $curid;
}
sub ParseInfoList {
    my $html= shift;

    my $i= 0;
    my $curid;
    for ($html =~ /<li>(.*?)<\/li>/gs) {
        my $entry= $_;
        $curid= ParseInfoEntry($curid, $entry);
    }

    return $curid;
}
sub ParseFriendsEntry {
    my $curid= shift;
    my $html= shift;

    if ($html =~ /<a href='\/friends\/(\w+)\/list'>(.*?)<\/a> \((\d+)\)<br \/>\s+(?:(Homme|Femme|Male|Female))?,?(?:\s+(.*?))?<br \/>\s+(.*)/s) {
        my ($id, $name, $nfriends, $sex, $status, $country)= ($1, $2, $3, $4, $5, $6);

        $country =~ s/\s+$//;

        setforperson($id, 'name', $name);
        setforperson($id, 'sex', $sex);
        setmaxforperson($id, 'nfriends', $nfriends);
        setforperson($id, 'status', $status);
        setforperson($id, 'country', $country);

        addfriendforperson($curid, $id);
    }
}
sub ParseFriendsTable {
    my $curid= shift;
    my $html= shift;

    for ($html =~ /<td>(.*?)<\/td>/gs) {
        my $entry= $_;
        ParseFriendsEntry($curid, $entry);
    }
}
    
sub ParseFriendsPage {
    my $html= shift;
    my $fn= shift;

    my $curid;
    if ($html =~ /<ul class='info'>(.*?)<\/ul>/s) {
        my $listhtml= $1;
        $curid= ParseInfoList($listhtml);
    }
    if ($html =~ /<ul class='menu'>(.*?)<\/ul>/s) {
        my $listhtml= $1;
        $curid= ParseMenuList($listhtml);
    }

    if (!defined $curid) {
        warn "could not find id in page $fn\n";
    }
    elsif ($html =~ /<table>(.*?)<\/table>/s) {
        my $tablehtml= $1;
        ParseFriendsTable($curid, $tablehtml);
    }
    return $curid;
}

################################################
sub setforperson {
    my ($id, $field, $value)= @_;

    if (!defined $id) {
        warn "setforperson(undef, $field, $value)\n";
    }
    if (!exists $all{$id}) {
        $all{$id}= {
            id=>$id,
            friends=>{},
            members=>{},
        };
    }

    if (exists $all{$id}{$field}) {
        if ($all{$id}{$field} && $value && $all{$id}{$field} ne $value) {
            warn "$id: $field  ='$all{$id}{$field}' ne '$value'\n"
                if ($g_verbose);
        }
    }
    else {
        $all{$id}{$field}= $value;
    }
}
sub setmaxforperson {
    my ($id, $field, $value)= @_;

    if (!exists $all{$id}) {
        $all{$id}= {
            id=>$id,
            friends=>{},
            members=>{},
        };
    }

    if (exists $all{$id}{$field}) {
        if ($all{$id}{$field} && $value && $all{$id}{$field} > $value) {
            warn "$id: ignoring $field  ='$all{$id}{$field}' > '$value'\n"
                if ($g_verbose>1);
            return;
        }
    }
    $all{$id}{$field}= $value;
}
sub addfriendforperson {
    my ($id, $friend)= @_;

    if ($id eq $friend) {
        warn "friend to self: $id\n";
        return;
    }

    $all{$id}= { id=>$id, friends=>{}, members=>{} } if (!exists $all{$id});
    $all{$friend}= { id=>$friend, friends=>{}, members=>{} } if (!exists $all{$friend});

    $all{$id}{friends}{$friend}= $all{$friend};
    $all{$friend}{friends}{$id}= $all{$id};
}
