#!/usr/bin/perl
#

use strict;
use vars qw(%maint);

require "$ENV{'LJHOME'}/cgi-bin/statslib.pl";

# filled in by ljmaint.pl, 0=quiet, 1=normal, 2=verbose
$LJ::Stats::VERBOSE = $LJ::LJMAINT_VERBOSE >= 2 ? 1 : 0;

$maint{'genstats'} = sub
{
    my @which = @_ || qw(users countries 
                         states gender clients
                         pop_interests meme pop_faq);
    
    # popular faq items
    LJ::Stats::register_stat
        ({ 'type' => "global",
           'jobname' => "popfaq",
           'statname' => "pop_faq",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   return undef unless $db;

                   my $sth = $db->prepare("SELECT faqid, COUNT(*) FROM faquses WHERE " .
                                          "faqid<>0 GROUP BY 1 ORDER BY 2 DESC LIMIT 50");
                   $sth->execute;
                   die $db->errstr if $db->err;

                   my %ret;
                   while (my ($id, $count) = $sth->fetchrow_array) {
                       $ret{$id} = $count;
                   }

                   return \%ret;
               },

        });

    # popular interests
    LJ::Stats::register_stat
        ({ 'type' => "global",
           'jobname' => "pop_interests",
           'statname' => "pop_interests",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   return undef unless $db;

                   return {} if $LJ::DISABLED{'interests-popular'};

                   # see what the previous min was, then subtract 20% of max from it
                   my ($prev_min, $prev_max) = $db->selectrow_array("SELECT MIN(statval), MAX(statval) " .
                                                                    "FROM stats WHERE statcat='pop_interests'");
                   my $stat_min = int($prev_min - (0.2*$prev_max));
                   $stat_min = 1 if $stat_min < 1;

                   my $sth = $db->prepare("SELECT interest, intcount FROM interests WHERE intcount>? " .
                                          "ORDER BY intcount DESC, interest ASC LIMIT 400");
                   $sth->execute($stat_min);
                   die $db->errstr if $db->err;

                   my %ret;
                   while (my ($int, $count) = $sth->fetchrow_array) {
                       $ret{$int} = $count;
                   }

                   return \%ret;
               },

       });

    # popular memes
    LJ::Stats::register_stat
        ({ 'type' => "global",
           'jobname' => "meme",
           'statname' => "popmeme",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   return undef unless $db;

                   return {} if $LJ::DISABLED{'meme'};

                   my $sth = $db->prepare("SELECT url, count(*) FROM meme " .
                                          "GROUP BY 1 ORDER BY 2 DESC LIMIT 100");
                   $sth->execute;
                   die $db->errstr if $db->err;

                   my %ret;
                   while (my ($url, $count) = $sth->fetchrow_array) {
                       $ret{$url} = $count;
                   }

                   return \%ret;
               },
         });

    # clients
    LJ::Stats::register_stat
        ({ 'type' => "global",
           'jobname' => "clients",
           'statname' => "client",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   return undef unless $db;

                   return {} if $LJ::DISABLED{'clientversionlog'};

                   my $tsql = "SELECT MAX(userid) FROM user";
                   $tsql .= " where userid < " . $LJ::LJR_IMPORTED_USERIDS if $LJ::LJR_IMPORTED_USERIDS;

                   my $usertotal = $db->selectrow_array($tsql);
                   my $blocks = LJ::Stats::num_blocks($usertotal);

                   my %ret;
                   foreach my $block (1..$blocks) {
                       my ($low, $high) = LJ::Stats::get_block_bounds($block);

                       $db = $db_getter->(); # revalidate connection
                       my $sth = $db->prepare("SELECT c.client, COUNT(*) AS 'count' FROM clients c, clientusage cu " .
                                              "WHERE c.clientid=cu.clientid AND cu.userid BETWEEN $low AND $high " .
                                              "AND cu.lastlogin > DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY 1 ORDER BY 2");
                       $sth->execute;
                       die $db->errstr if $db->err;

                       while ($_ = $sth->fetchrow_hashref) {
                           $ret{$_->{'client'}} += $_->{'count'};
                       }

                       print LJ::Stats::block_status_line($block, $blocks);
                   }

                   return \%ret;
               },
         });


    # user table analysis
    LJ::Stats::register_stat
        ({ 'type' => "global",
           'jobname' => "users",
           'statname' => ["account", "newbyday", "age", "userinfo"],
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   return undef unless $db;

                   my $tsql = "SELECT MAX(userid) FROM user";
                   $tsql .= " where userid < " . $LJ::LJR_IMPORTED_USERIDS if $LJ::LJR_IMPORTED_USERIDS;

                   my $usertotal = $db->selectrow_array($tsql);
                   my $blocks = LJ::Stats::num_blocks($usertotal);

                   my %ret; # return hash, (statname => { arg => val } since 'statname' is arrayref above

                   # iterate over user table in batches
                   foreach my $block (1..$blocks) {

                       my ($low, $high) = LJ::Stats::get_block_bounds($block);

                       # user query: gets user,caps,age,status,allow_getljnews
                       $db = $db_getter->(); # revalidate connection
                       my $sth = $db->prepare
                           ("SELECT user, caps, " .
                            "FLOOR((TO_DAYS(NOW())-TO_DAYS(bdate))/365.25) AS 'age', " .
                            "status, allow_getljnews " .
                            "FROM user WHERE userid BETWEEN $low AND $high");
                       $sth->execute;
                       die $db->errstr if $db->err;
                       while (my $rec = $sth->fetchrow_hashref) {

                           # account types
                           my $capnameshort = LJ::name_caps_short($rec->{'caps'});
                           $ret{'account'}->{$capnameshort}++;

                           # ages
                           $ret{'age'}->{$rec->{'age'}}++
                               if $rec->{'age'} > 4 && $rec->{'age'} < 110;

                           # users receiving news emails
                           $ret{'userinfo'}->{'allow_getljnews'}++
                               if $rec->{'status'} eq "A" && $rec->{'allow_getljnews'} eq "Y";
                       }
                       
                       # userusage query: gets timeupdate,datereg,nowdate
                       my $sth = $db->prepare
                           ("SELECT DATE_FORMAT(timecreate, '%Y-%m-%d') AS 'datereg', " .
                            "DATE_FORMAT(NOW(), '%Y-%m-%d') AS 'nowdate', " .
                            "UNIX_TIMESTAMP(timeupdate) AS 'timeupdate' " .
                            "FROM userusage WHERE userid BETWEEN $low AND $high");
                       $sth->execute;
                       die $db->errstr if $db->err;

                       while (my $rec = $sth->fetchrow_hashref) {

                           # date registered
                           $ret{'newbyday'}->{$rec->{'datereg'}}++
                               unless $rec->{'datereg'} eq $rec->{'nowdate'};
                
                           # total user/activity counts
                           $ret{'userinfo'}->{'total'}++;
                           if (my $time = $rec->{'timeupdate'}) {
                               my $now = time();
                               $ret{'userinfo'}->{'updated'}++;
                               $ret{'userinfo'}->{'updated_last30'}++ if $time > $now-60*60*24*30;
                               $ret{'userinfo'}->{'updated_last7'}++ if $time > $now-60*60*24*7;
                               $ret{'userinfo'}->{'updated_last1'}++ if $time > $now-60*60*24*1;
                           }
                       }

                       print LJ::Stats::block_status_line($block, $blocks);
                   }

                   return \%ret;
               },
           });


    LJ::Stats::register_stat
        ({ 'type' => "clustered",
           'jobname' => "countries",
           'statname' => "country",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   my $cid = shift;
                   return undef unless $db && $cid;

                   my $upc = LJ::get_prop("user", "country");
                   die "Can't find country userprop.  Database populated?\n" unless $upc;

                   my $tsql = "SELECT MAX(userid) FROM userproplite2";
                   $tsql .= " where userid < " . $LJ::LJR_IMPORTED_USERIDS if $LJ::LJR_IMPORTED_USERIDS;

                   my $usertotal = $db->selectrow_array($tsql);
                   my $blocks = LJ::Stats::num_blocks($usertotal);

                   my %ret;
                   foreach my $block (1..$blocks) {
                       my ($low, $high) = LJ::Stats::get_block_bounds($block);

                       $db = $db_getter->(); # revalidate connection
                       my $sth = $db->prepare("SELECT u.value, COUNT(*) AS 'count' FROM userproplite2 u " .
                                              "LEFT JOIN clustertrack2 c ON u.userid=c.userid " .
                                              "WHERE u.upropid=? AND u.value<>'' AND u.userid=c.userid " .
                                              "AND u.userid BETWEEN $low AND $high " .
                                              "AND (c.clusterid IS NULL OR c.clusterid=?)" .
                                              "GROUP BY 1 ORDER BY 2");
                       $sth->execute($upc->{'id'}, $cid);
                       die "clusterid: $cid, " . $db->errstr if $db->err;

                       while ($_ = $sth->fetchrow_hashref) {
                           $ret{$_->{'value'}} += $_->{'count'};
                       }

                       print LJ::Stats::block_status_line($block, $blocks);
                   }

                   return \%ret;
               },
           });


    LJ::Stats::register_stat
        ({ 'type' => "clustered",
           'jobname' => "states",
           'statname' => "stateus",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   my $cid = shift;
                   return undef unless $db && $cid;

                   my $upc = LJ::get_prop("user", "country");
                   die "Can't find country userprop.  Database populated?\n" unless $upc;

                   my $ups = LJ::get_prop("user", "state");
                   die "Can't find state userprop.  Database populated?\n" unless $ups;

                   my $tsql = "SELECT MAX(userid) FROM userproplite2";
                   $tsql .= " where userid < " . $LJ::LJR_IMPORTED_USERIDS if $LJ::LJR_IMPORTED_USERIDS;

                   my $usertotal = $db->selectrow_array($tsql);
                   my $blocks = LJ::Stats::num_blocks($usertotal);

                   my %ret;
                   foreach my $block (1..$blocks) {
                       my ($low, $high) = LJ::Stats::get_block_bounds($block);

                       $db = $db_getter->(); # revalidate connection
                       my $sth = $db->prepare("SELECT ua.value, COUNT(*) AS 'count' " .
                                              "FROM userproplite2 ua, userproplite2 ub " .
                                              "WHERE ua.userid=ub.userid AND ua.upropid=? AND " .
                                              "ub.upropid=? and ub.value='US' AND ub.value<>'' " .
                                              "AND ua.userid BETWEEN $low AND $high " .
                                              "GROUP BY 1 ORDER BY 2");
                       $sth->execute($ups->{'id'}, $upc->{'id'});
                       die $db->errstr if $db->err;

                       while ($_ = $sth->fetchrow_hashref) {
                           $ret{$_->{'value'}} += $_->{'count'};
                       }

                       print LJ::Stats::block_status_line($block, $blocks);
                   }

                   return \%ret;
               },

           });


    LJ::Stats::register_stat
        ({ 'type' => "clustered",
           'jobname' => "gender",
           'statname' => "gender",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   my $cid = shift;
                   return undef unless $db && $cid;

                   my $upg = LJ::get_prop("user", "gender");
                   die "Can't find gender userprop.  Database populated?\n" unless $upg;

                   my $tsql = "SELECT MAX(userid) FROM userproplite2";
                   $tsql .= " where userid < " . $LJ::LJR_IMPORTED_USERIDS if $LJ::LJR_IMPORTED_USERIDS;

                   my $usertotal = $db->selectrow_array($tsql);
                   my $blocks = LJ::Stats::num_blocks($usertotal);

                   my %ret;
                   foreach my $block (1..$blocks) {
                       my ($low, $high) = LJ::Stats::get_block_bounds($block);

                       $db = $db_getter->(); # revalidate connection
                       my $sth = $db->prepare("SELECT value, COUNT(*) AS 'count' FROM userproplite2 up " .
                                              "LEFT JOIN clustertrack2 c ON up.userid=c.userid " .
                                              "WHERE up.upropid=? AND up.userid BETWEEN $low AND $high " .
                                              "AND (c.clusterid IS NULL OR c.clusterid=?) GROUP BY 1");
                       $sth->execute($upg->{'id'}, $cid);
                       die "clusterid: $cid, " . $db->errstr if $db->err;

                       while ($_ = $sth->fetchrow_hashref) {
                           $ret{$_->{'value'}} += $_->{'count'};
                       }

                       print LJ::Stats::block_status_line($block, $blocks);
                   }

                   return \%ret;
               },

         });

    # run stats
    LJ::Stats::run_stats(@which);

    #### dump to text file
    print "-I- Dumping to a text file.\n";

    {
        my $dbh = LJ::Stats::get_db("dbh");
        my $sth = $dbh->prepare("SELECT statcat, statkey, statval FROM stats ORDER BY 1, 2");
        $sth->execute;
        die $dbh->errstr if $dbh->err;

        open (OUT, ">$LJ::HTDOCS/stats/stats.txt");
        while (my @row = $sth->fetchrow_array) {
            next if grep { $row[0] eq $_ } @LJ::PRIVATE_STATS;
            print OUT join("\t", @row), "\n";
        }
        close OUT;
    }

    print "-I- Done.\n";

};

$maint{'genstats_size'} = sub {

    LJ::Stats::register_stat
        ({ 'type' => "global",
           'jobname' => "size-accounts",
           'statname' => "size",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   return undef unless $db;

                   my $tsql = "SELECT MAX(userid) FROM user";
                   $tsql .= " where userid < " . $LJ::LJR_IMPORTED_USERIDS if $LJ::LJR_IMPORTED_USERIDS;

                   # not that this isn't a total of current accounts (some rows may have 
                   # been deleted), but rather a total of accounts ever created
                   my $size = $db->selectrow_array($tsql);
                   return { 'accounts' => $size };
               },
         });

    LJ::Stats::register_stat
        ({ 'type' => "clustered",
           'jobname' => "size-accounts_active",
           'statname' => "size",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   return undef unless $db;

                   my $period = 30;  # one month is considered active
                   my $active = $db->selectrow_array
                       ("SELECT COUNT(*) FROM clustertrack2 WHERE ".
                        "timeactive > UNIX_TIMESTAMP()-86400*$period");
                   
                   return { 'accounts_active' => $active };
               },
         });

    print "-I- Generating account size stats.\n";
    LJ::Stats::run_stats("size-accounts", "size-accounts_active");
    print "-I- Done.\n";
};


$maint{'genstats_weekly'} = sub
{
    LJ::Stats::register_stat
        ({ 'type' => "global",
           'jobname' => "supportrank",
           'statname' => "supportrank",
           'handler' =>
               sub {
                   my $db_getter = shift;
                   return undef unless ref $db_getter eq 'CODE';
                   my $db = $db_getter->();
                   return undef unless $db;

                   my %supportrank;
                   my $rank = 0;
                   my $lastpoints = 0;
                   my $buildup = 0;

                   my $sth = $db->prepare
                       ("SELECT u.userid, SUM(sp.points) AS 'points' " .
                        "FROM user u, supportpoints sp " .
                        "WHERE u.userid=sp.userid GROUP BY 1 ORDER BY 2 DESC");
                   $sth->execute;
                   die $db->errstr if $db->err;

                   while ($_ = $sth->fetchrow_hashref) {
                       if ($lastpoints != $_->{'points'}) {
                           $lastpoints = $_->{'points'};
                           $rank += (1 + $buildup);
                           $buildup = 0;
                       } else {
                           $buildup++;
                       }
                       $supportrank{$_->{'userid'}} = $rank;
                   }

                   # move old 'supportrank' stat to supportrank_prev
                   # no API for this :-/
                   {
                       my $dbh = LJ::Stats::get_db("dbh");
                       $dbh->do("DELETE FROM stats WHERE statcat='supportrank_prev'");
                       $dbh->do("UPDATE stats SET statcat='supportrank_prev' WHERE statcat='supportrank'");
                   }

                   return \%supportrank;
               }
        });

    print "-I- Generating weekly stats.\n";
    LJ::Stats::run_stats('supportrank');
    print "-I- Done.\n";
};

$maint{'build_randomuserset'} = sub
{
    ## this sets up the randomuserset table daily (or whenever) that htdocs/random.bml uses to
    ## find a random user that is both 1) publicly listed in the directory, and 2) updated
    ## within the past 24 hours.

    ## note that if a user changes their privacy setting to not be in the database, it'll take
    ## up to 24 hours for them to be removed from the random.bml listing, but that's acceptable.

    my $dbh = LJ::get_db_writer();

    print "-I- Building randomuserset.\n";
    $dbh->do("TRUNCATE TABLE randomuserset");
    $dbh->do("REPLACE INTO randomuserset (userid) " .
             "SELECT uu.userid FROM userusage uu, user u " .
             "WHERE u.userid=uu.userid AND u.allow_infoshow='Y' " .
             "AND uu.timeupdate > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY RAND() LIMIT 5000");
    my $num = $dbh->selectrow_array("SELECT MAX(rid) FROM randomuserset");
    $dbh->do("REPLACE INTO stats (statcat, statkey, statval) " .
             "VALUES ('userinfo', 'randomcount', $num)");

    print "-I- Done.\n";
};

$maint{'memeclean'} = sub
{
    my $dbh = LJ::get_db_writer();

    print "-I- Cleaning memes.\n";
    my $sth = $dbh->prepare("SELECT statkey FROM stats WHERE statcat='popmeme'");
    $sth->execute;
    die $dbh->errstr if $dbh->err;

    while (my $url = $sth->fetchrow_array) {
        my $copy = $url;
        LJ::run_hooks("canonicalize_url", \$copy);
        unless ($copy) {
            my $d = $dbh->quote($url);
            $dbh->do("DELETE FROM stats WHERE statcat='popmeme' AND statkey=$d");
            print "    deleting: $url\n";
        }
    }
    print "-I- Done.\n";
};

1;
