#!/usr/bin/perl -w

# This script is Public Domain.
# Original version: Hanno Hecker (ankh-morph.org)
# Changes: Stephan Knauss (osm-tools.org)

use strict;
use Date::Parse;
use POSIX qw(strftime);
use File::stat;
use Time::localtime;
use LWP::Simple;



if (scalar(@ARGV) != 1) {
   print STDERR "Detect problematic OSM nodes\n";
   print STDERR "Syntax: check.pl <osmfile>\n";
   exit 1;
}

open OSM,$ARGV[0];



# list is updated every hour on the server. So no need to recheck more often than every 30 minutes
if (! -e 'users_agreed.txt' || stat("users_agreed.txt")->mtime < time-30*60) {
   my $res;
   print STDERR "downloading user acceptance data from server...\n";
   $res = getstore('http://planet.openstreetmap.org/users_agreed/users_agreed.txt', 'users_agreed.txt');
   if (is_error($res)) {
      print STDERR "failed downloading users_agreed.txt: ".status_message($res)."\n";
      exit 1;
   }
   $res = getstore('http://planet.openstreetmap.org/users_agreed/users_disagreed.txt', 'users_disagreed.txt');
   if (is_error($res)) {
      print STDERR "failed downloading users_disagreed.txt: ".status_message($res)."\n";
      exit 1;
   }
   $res = getstore('http://planet.openstreetmap.org/users_agreed/anon_changesets_agreed.txt', 'anon_changesets_agreed.txt');
   if (is_error($res)) {
      print STDERR "failed downloading anon_changesets_agreed.txt: ".status_message($res)."\n";
      exit 1;
   }
   print STDERR "... done.\n";
} else {
   print STDERR "Acceptance data still current, using cached copy.\n";
}


my %accepted;
my $acceptedDate = stat("users_agreed.txt")->mtime;
open ACCEPT, "users_agreed.txt";
while (<ACCEPT>) {
    next unless /^\s*(\d+)\s*$/;
    $accepted{$1} = 1;
}
close ACCEPT;

my %declined;
my $declinedDate = stat("users_disagreed.txt")->mtime;
open DECLINE, "users_disagreed.txt";
while (<DECLINE>) {
    next unless /^\s*(\d+)\s*$/;
    $declined{$1} = 1;
}
close DECLINE;

my %anonAccepted;
my $anonAcceptedDate = stat("anon_changesets_agreed.txt")->mtime;
open ACCEPT, "anon_changesets_agreed.txt";
while (<ACCEPT>) {
    next unless /^\s*(\d+)\s*$/;
    $anonAccepted{$1} = 1;
}
close ACCEPT;


# treat anonymous edits as declined unless changelist contained in acceptance list
$declined{0} = 1;
$accepted{-1} = 1;

# http://wiki.openstreetmap.org/wiki/Quick_History_Service/Changeset_Lists#balrog-kun_bot.2Fbbox_edits
$accepted{20587} = 1;

my %users = ();
my %stats = ();
my $newestEdit = '';

$stats{loss_node} = 0; $stats{loss_way} = 0; $stats{loss_relation} = 0;
$stats{unknown_node} = 0; $stats{unknown_way} = 0; $stats{unknown_relation} = 0;

print STDERR "Reading OSM data...\n";

while (<OSM>) {
    if (/<(node|way|relation) /) {
        my $what = $1;
        $stats{"all_$what"}++;
        my ($user, $uid, $changeset, $version, $ts);
        if (/uid="(\d+)"/) {
            $uid = $1;
        }
        else {
            $uid = 0;
        }

        if (/user="([^"]+)"/) {
            $user = $1;
        }
        else {
            $user = "UNKNOWN (old anonymous edit)";
        }
        if ($uid == 0 && /changeset="(\d+)"/) {
            $changeset = $1;
            if (exists $anonAccepted{$changeset}) {
               $uid = -1;
               $user = "UNKNOWN (accepted ct)";
            }
        }
        # manual adjust changed user names
        if ($user eq 'Sam Kuat') { 
            $user = 'Tom Layo';
        }
        
        my $currentUser = \$users{$user};
        $$currentUser->{$what}++;
        $$currentUser->{uid} = $uid;
        
        # first occurence of user. determine ODbL status
        if (!exists $$currentUser->{odbl}) {
          $stats{user_all}++;
          if (!exists $$currentUser->{node}) {
              $$currentUser->{node} = 0; # ensure that node counter exists
          }
          if ($uid >= 286582 || exists $accepted{ $uid }) {
              $stats{user_odbl}++;
              $$currentUser->{odbl} = 1;
          } elsif (exists $declined{$uid}) {
              $stats{user_declined}++;
              $$currentUser->{odbl} = -1;
          } else {
              $$currentUser->{odbl} = 0;
              $stats{user_unknown}++;
          }
        }

        if (/version="(\d+)"/) {
            $version = $1;
        }
        else {
            $version = 0;
        }
        
        # version is 1 -> ODbL commitment of user is definitly valid for this
        if ($version == 1) {
            $$currentUser->{"sole_$what"}++;
        }
        
        if ($$currentUser->{odbl} == 1) {
            if ($version == 1) {
                ++$stats{"ok_$what"};
            }
            else {
                ++$stats{"partial_$what"};
            }
        } elsif ($$currentUser->{odbl} == -1) {
            ++$stats{"loss_$what"};
        } elsif ($version == 1) {
            ++$stats{"unknown_$what"};
        }
                
        if (/timestamp="([^"]+)"/) {
             $ts = $1;
             if ($ts gt $newestEdit) {
                 $newestEdit = $ts;
             }
        }
        if (!$ts) {
            $ts = 0;
        }

        if (!exists $$currentUser->{last}) {
            $$currentUser->{last} = $ts;
        }
        elsif ($$currentUser->{last} lt $ts) {
            $$currentUser->{last} = $ts;
        }
    }
}

print "<html><head>
 <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\">
 <style type=\"text/css\" title=\"default\">
body {
  background-color: #fff;
  font-family: sans-serif;
  font-size: 12px;
  color: #000;
}
table {
  border-collapse: collapse;
}
td, th {
  border: 1px solid #000;
  font-family: sans-serif;
  font-size: 12px;
}
a:link {
   text-decoration: none
}
a:visited {
   text-decoration: none
}
a:active {
   text-decoration: none
}
a:hover {
   text-decoration: underline;
}
.is_odbl {
  background-color: #afa;
}
.no_odbl {
  background-color: #faa;
}
.bold {
  font-face: bold;
}
.w {
  font-variant: small-caps;
  vertical-align: super;
}
 </style>
</head><body>\n";

my %rel  = map { ($_, $users{$_}->{relation}) } keys %users;
my %way  = map { ($_, $users{$_}->{way}) } keys %users;
my %node = map { ($_, $users{$_}->{node}) } keys %users;

print "<h1>Global statistics</h1>\n";

print "Statistics based on edits up to: ".strftime("%Y-%m-%d", gmtime(str2time($newestEdit)))."<br>\n";
print "Agreed ODbL users up to: ".strftime("%Y-%m-%d", gmtime($acceptedDate))."<br>\n";
print "Declined ODbL users up to: ".strftime("%Y-%m-%d", gmtime($declinedDate))."<br>\n";
print "<p>\n";

printf "Total nodes in DB: $stats{all_node}<br>- of this safe for ODbL: %d (%.2f%%)<br>- of this possibly ODbL: %d (%.2f%%)<br>- of this possible loss: %d (%.2f%%)<br>- of this definitive loss: %d (%.2f%%)<p>\n",
    $stats{ok_node}, ($stats{ok_node}/$stats{all_node})*100, 
    $stats{partial_node}, ($stats{partial_node}/$stats{all_node})*100,
    $stats{unknown_node}, ($stats{unknown_node}/$stats{all_node})*100,
    $stats{loss_node}, ($stats{loss_node}/$stats{all_node})*100; 
printf "Total ways in DB: $stats{all_way}<br>- of this safe for ODbL: %d (%.2f%%)<br>- of this possibly ODbL: %d (%.2f%%)<br>- of this possible loss: %d (%.2f%%)<br>- of this definitive loss: %d (%.2f%%)<p>\n",
    $stats{ok_way}, ($stats{ok_way}/$stats{all_way})*100, 
    $stats{partial_way}, ($stats{partial_way}/$stats{all_way})*100,
    $stats{unknown_way}, ($stats{unknown_way}/$stats{all_way})*100,
    $stats{loss_way}, ($stats{loss_way}/$stats{all_way})*100; 
printf "Total relations in DB: $stats{all_relation}<br>- of this safe for ODbL: %d (%.2f%%)<br>- of this possibly ODbL: %d (%.2f%%)<br>- of this possible loss: %d (%.2f%%)<br>- of this definitive loss: %d (%.2f%%)<p>\n",
    $stats{ok_relation}, ($stats{ok_relation}/$stats{all_relation})*100, 
    $stats{partial_relation}, ($stats{partial_relation}/$stats{all_relation})*100,
    $stats{unknown_relation}, ($stats{unknown_relation}/$stats{all_relation})*100,
    $stats{loss_relation}, ($stats{loss_relation}/$stats{all_relation})*100; 
printf "Total users in DB: %d<br>- of this already agreed to ODbL: %d (%.2f%%)<br>- of this not yet answered: %d (%.2f%%)<br>- of this actively declined new CTs: %d (%.2f%%)<p>\n",
    $stats{user_all}, 
    $stats{user_odbl}, ($stats{user_odbl}/$stats{user_all})*100,
    $stats{user_unknown}, ($stats{user_unknown}/$stats{user_all})*100,
    $stats{user_declined}, ($stats{user_declined}/$stats{user_all})*100;

print "<dl>";
print "<dt>safe for ODbL:</dt><dd>an element is considered safe in case it exists in version 1 and the user already agreed to ODbL</dd>";
print "<dt>possible loss:</dt><dd>an element is possibly lost in case it exists in version 1 and the user has not yet answered</dd>";
print "<dt>possibly ODbL:</dt><dd>the element has a version greater 1. The last editor agreed to ODbL. As this statistics does not evaluate the history it could be possible that the element was created/modified by a user not agreed to ODbL. In this case the whole or parts of the element would be lost. In case all previous versions are done by users that agreed to ODbL (or changes are irrelevant because replaced by newer changes) these elements are safe for ODbL. This is the optimistic assumption most users agree to ODbL.</dd>\n";
print "<dt>definitive loss:</dt><dd>the element was created/editied by a user who actively declined ODbL. In case of license switch this object is removed/edits reverted</dd>\n";
print "</dl><dl>\n";
print "<dt>Last version by user:</dt><dd>The last modification of the element was done by the given user.</dd>\n";
print "<dt>Sole version by user:</dt><dd>The element exists in version 1 and was created by the given user.</dd>\n";
print "</dl>\n";

print "<h1>Special handling</h1>\n";
print "The edits of user 'balrog-kun' ID 20587 are considered clean, see <a href=\"http://wiki.openstreetmap.org/wiki/Quick_History_Service/Changeset_Lists#balrog-kun_bot.2Fbbox_edits\">http://wiki.openstreetmap.org/wiki/Quick_History_Service/Changeset_Lists#balrog-kun_bot.2Fbbox_edits</a>\n<br>";

print "<h1>Detailed statistics</h1>\n";


my ($node_odbl, $node_sole, $node_noodbl, $way_odbl, $way_sole, $way_noodbl, $rel_odbl, $rel_sole, $rel_noodbl);

my $declineFilter = "--keep=";
my $unknownFilter = "--keep=";
my $rank=0;
print "<table>\n";
print "<tr><th></th><th><th></th><th colspan=6>Last version by user (not relevant for ODbL)</th><th colspan=6>Sole version by user</th><th></th></tr>\n";
print "<tr><th>Rank</th><th>User (uid)</th><th>ODbL</th><th>nodes</th><th>nodes %</th><th>ways</th><th>ways %</th><th>relations</th><th>relations %</th><th>nodes</th><th>nodes %</th><th>ways</th><th>ways %</th><th>relations</th><th>relations %</th><th>last edit</th></tr>\n";
foreach (sort { $node{$b} <=> $node{$a} } grep { defined $node{$_} } keys %node)
{
    $rank++;
    print " <tr class=\"",($users{$_}->{odbl} == '1' ? "is_odbl" : ($users{$_}->{odbl} == '-1' ? "no_odbl" : "")) ,"\">\n";
    print "  <td>$rank</td>\n";
    print "  <td><a href=\"http://www.openstreetmap.org/user/$_\">$_</a> ($users{$_}->{uid}) <a href=\"http://wdye.osm-tools.org/?$_\" class=\"w\">w</a>";
    if ($users{$_}->{odbl} != '1') {
       print " <a href=\"http://localhost:8111/import?url=http://overpass-api.de/api/xapi%3F*[bbox=97.33,5.6,105.66,20.47][\@uid=$users{$_}->{uid}][\@meta]\" class=\"w\">j</a>";
    }
    print "</td>\n";
    print "  <td>",$users{$_}->{odbl},"</td>\n";
    print "  <td>",node_perc($node{$_}) ,"</td>\n";
    print "  <td>",way_perc($way{$_}) ,"</td>\n";
    print "  <td>",rel_perc($rel{$_}) ,"</td>\n";
    print "  <td>",node_perc($users{$_}->{sole_node}) ,"</td>\n";
    print "  <td>",way_perc($users{$_}->{sole_way}) ,"</td>\n";
    print "  <td>",rel_perc($users{$_}->{sole_relation}) ,"</td>\n";
    print "  <td>",strftime("%Y-%m-%d", gmtime(str2time($users{$_}->{last}))),"</td>\n";
    print " </tr>\n";
    
    # build osmfilter line
    if ($users{$_}->{odbl} == '-1') {
        $declineFilter .= '@uid='.$users{$_}->{uid}." ";
    } elsif ($users{$_}->{odbl} == '0') {
        $unknownFilter .= '@uid='.$users{$_}->{uid}." ";
    }
}

print "<tr>\n";
print "<td colspan=2>Total sums</td>\n";
print "<td>$stats{user_all}</td>\n";
print "<td>$stats{all_node}</td>\n";
print "<td>100%</td>\n";
print "<td>$stats{all_way}</td>\n";
print "<td>100%</td>\n";
print "<td>$stats{all_relation}</td>\n";
print "<td>100%</td>\n";
printf "<td>%d</td>\n",$stats{ok_node}+$stats{unknown_node};
printf "<td>%.2f%%</td>\n",($stats{ok_node}+$stats{unknown_node})*100/$stats{all_node};
printf "<td>%d</td>\n",$stats{ok_way}+$stats{unknown_way};
printf "<td>%.2f%%</td>\n",($stats{ok_way}+$stats{unknown_way})*100/$stats{all_way};
printf "<td>%d</td>\n",$stats{ok_relation}+$stats{unknown_relation};
printf "<td>%.2f%%</td>\n",($stats{ok_relation}+$stats{unknown_relation})*100/$stats{all_relation};
print "<td></td>\n";
print "</tr>\n";

print "</table>\n";

print "<h1>Filter for osmfilter</h1>\n";
print "<h3>get all edits from declined:<h3><pre>$declineFilter</pre><p>\n";
print "<h3>get all edits from undecided:<h3><pre>$unknownFilter</pre><p>\n";

print "<p>Statistics created by <a href=\"http://wiki.openstreetmap.org/wiki/User:Stephankn\">Stephan Knauss</a>\n";

print "</body></html>\n";

sub node_perc {
    my $num = shift;
    return "&nbsp;</td><td>&nbsp;" unless $num;
    return sprintf "%d</td><td>%.2f%%", $num, ($num/$stats{all_node}) * 100;
}

sub way_perc {
    my $num = shift;
    return "&nbsp;</td><td>&nbsp;" unless $num;
    return sprintf "%d</td><td>%.2f%%", $num, ($num/$stats{all_way}) * 100;
}

sub rel_perc {
    my $num = shift;
    return "&nbsp;</td><td>&nbsp;" unless $num;
    return sprintf "%d</td><td>%.2f%%", $num, ($num/$stats{all_relation}) * 100;
}

