/* =================================================================================================
 * =================================================================================================
 * Authors: Alan Mislove, Yabing Liu
 * Email:   ybliu@ccs.neu.edu
 * 
 * Goal: Given the targeting parameters, send query to Facebook advertising servers, 
 *       then get and parse the suggested bid data.
 * =================================================================================================
 * =================================================================================================
 */

# ----------------------------------------------------------------------------------------
# Libraries and Basic Setting
# ----------------------------------------------------------------------------------------

use LWP::UserAgent;
use JSON;
use threads;
use threads::shared;
use Time::HiRes qw/ time usleep /;
use IO::Socket::SSL;
use Scalar::Util;

# ----------------------------------------------------------------------------------------
# Set your defined variables to be "shared" among different threads 
# ----------------------------------------------------------------------------------------

my $cond :shared;
my $lock :shared;

my $at :shared;
my $aid :shared;
my $final : shared;

my $start :shared;
my $country :shared;
my $gender :shared;
my $age_min :shared;
my $age_max :shared;

# ----------------------------------------------------------------------------------------
# Step1: Log into your account and go to the Ad creation page
# Step2: Get the access token information by monitoring the outgoing request urls,
#        find the one starting from 
#        "https://graph.facebook.com/act_Your_Aid/reachestimate?...&method=get&access_token
#         =Your_Account_Access_Token&pretty=0&callback=__globalCallbacks.Your_Final_String"
#        From this url, you can find the aid/ access_token/ final string.
# Note:  The access_token usually expires ~ 12 hours, so update it after it gets expired.
# ----------------------------------------------------------------------------------------

my $aid = "Your_Aid";
my $at = "Your_Account_Access_Token";
my $final = "Your_Final_String";

# ----------------------------------------------------------------------------------------
# Method1: You can specify the basic attributes below.
# You can get full details from the below link:
# https://developers.facebook.com/docs/reference/ads-api/targeting-specs/
# Using gender attribute as an example, "" means all, "1" means male, and "2" means female.
# Note: You can also specify more attributes, such as the Broad category, Interest, 
#       Education and so on by abstracting the corresponding string from the url
#       and set the value above.
# ----------------------------------------------------------------------------------------

my $country = "Country";
my $gender = "Gender"; 
my $age_min = "Min_Age";
my $age_max = "Max_Age";

# ----------------------------------------------------------------------------------------
# Set the number of threads and cycles
# For example, we use 40*25 to get 1,000 data points, and 5*5 to get 25 data points.
# ----------------------------------------------------------------------------------------

my $num_threads = 40;
my $num_cycles = 25;

# ----------------------------------------------------------------------------------------
# Main Part of the codes
# ----------------------------------------------------------------------------------------

$start = time;
print "The start timestamp is $start.\n";

my @thr;

# Method2: You can also specify the attribute values using ARGV 
# to repeat queries for given different input attributes.

foreach my $c (@ARGV) {
  $country = $c;
  for (my $i=0; $i<$num_threads; $i++) {
    my $t = fork();
    if ($t == 0) { myrun(); exit(0); }    
    push @thr, $t;
  }
  foreach my $t (@thr) { 
    waitpid($t, 0);
  }
  @thr = ();
  sleep(3);
}

sub myrun {
  my $sock = IO::Socket::SSL->new(PeerHost => "graph.facebook.com", PeerPort => "https", SSL_verify_mode => SSL_VERIFY_NONE) or die "failed to connect or ssl handshare: $!,$SSL_ERROR";

  for (my $j=0; $j<$num_cycles; $j++) {
    # Below is the request url.
    print $sock "GET /act_$aid/reachestimate?endpoint=%2Fact_" . $aid . "%2Freachestimate&accountId=" . $aid . "&locale=en_US&targeting_spec=%7B%22genders%22%3A%5B" . $gender . "%5D%2C%22age_max%22%3A" . $age_max. "%2C%22age_min%22%3A" . $age_min. "%2C%22broad_age%22%3Atrue%2C%22regions%22%3A%5B%5D%2C%22countries%22%3A%5B%22" . $country . "%22%5D%2C%22cities%22%3A%5B%5D%2C%22zips%22%3A%5B%5D%2C%22radius%22%3A0%2C%22keywords%22%3A%5B%5D%2C%22connections%22%3A%5B%5D%2C%22excluded_connections%22%3A%5B%5D%2C%22friends_of_connections%22%3A%5B%5D%2C%22relationship_statuses%22%3Anull%2C%22interested_in%22%3A%5B%5D%2C%22college_networks%22%3A%5B%5D%2C%22college_majors%22%3A%5B%5D%2C%22college_years%22%3A%5B%5D%2C%22education_statuses%22%3A%5B0%5D%2C%22locales%22%3A%5B%5D%2C%22work_networks%22%3A%5B%5D%2C%22user_adclusters%22%3A%5B%5D%2C%22user_os%22%3A%5B%5D%2C%22user_device%22%3A%5B%5D%7D&currency=USD&method=get&access_token=" . $at . "&pretty=0&callback=__globalCallbacks." . $key . " HTTP/1.1\r\nAccept: */*\r\nReferer: https://www.facebook.com/ads/create/\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22\r\nHost: graph.facebook.com\r\nConnection: keep-alive\r\n\r\n";
    my $length = -1;
    my $done = 0;
    while (! $done) { 
      my $line = <$sock>;
      if ($line =~ m|Content-Length: ([0-9]*)|) { 
        $length = $1;
      } elsif ($line =~ m|^[\r\n]*$|) {
        $done = 1;
      }
    }

    sysread($sock, $data, $length);
    parse($data);
  }

  close($sock);
  
  threads->exit();
}

sub mylog {
  my ($msg) = @_;
  my $dur = (time - $start);
  print sprintf("%.6f", $dur) . "\t$msg\n";
}

sub parse {
  my ($d) = @_;

  if ($d =~ m|\((.*)\)|) {
    my $ref = decode_json($1);
    my $time = time;

    if ($last > 0) {
      $time -= $last;
    }
  
  mylog("$country\t$ref->{users}\t$ref->{bid_estimations}[0]->{cpm_min}\t$ref->{bid_estimations}[0]->{cpm_median}\t$ref->{bid_estimations}[0]->{cpm_max}\t$ref->{bid_estimations}[0]->{cpc_min}\t$ref->{bid_estimations}[0]->{cpc_median}\t$ref->{bid_estimations}[0]->{cpc_max}"); #\t$ref->{bid_estimations}[0]->{cpc_min}\t$ref->{bid_estimations}[0]->{cpc_median}\t$ref->{bid_estimations}[0]->{cpc_max}\n";
  
  }
}

# ----------------------------------------------------------------------------------------
# End of the file
# ----------------------------------------------------------------------------------------
