
package MasterServer::TCP::Syncer;

use strict;
use warnings;
use AnyEvent;
use AnyEvent::Handle;
use Exporter 'import';
use Data::Dumper 'Dumper';

our @EXPORT = qw| syncer_scheduler sync_with_master process_sync_list|;

################################################################################
## Syncer Scheduler
## Request the masterlist for selected or all games from other 
## 333networks-based masterservers.
################################################################################
sub syncer_scheduler {
  my $self = shift;
  
  # log active
  $self->log("load", "Synchronisation module active.");
  
  # go through the list of provided addresses
  my $i = 0;
  return AnyEvent->timer (
    after     => $self->{sync_time}[0],
    interval  => $self->{sync_time}[1],
    cb        => sub {
      # check if there's a master server entry to be synced. If not, return
      # to zero and go all over again.
      $i = 0 unless $self->{sync_masters}[$i];
      return if (!defined $self->{sync_masters}[$i]);
      
      # synchronze with master $i
      $self->log("tcp", "Attempting to synchronize with $self->{sync_masters}[$i]->{address}");
      $self->sync_with_master($self->{sync_masters}[$i]);

      #increment counter
      $i++;
    }
  );
}

################################################################################
## Sends synchronization request to another 333networks based master server and
## receives the list of games. 
################################################################################
sub sync_with_master {
  my ($self, $ms) = @_;

  # list to store all IPs in.
  my $sync_list = "";
  
  # connection handle
  my $handle; 
  $handle = new AnyEvent::Handle(
    connect  => [$ms->{address} => $ms->{port}],
    timeout  => 3,
    poll     => 'r',
    on_error => sub {$self->log("error","$! on $ms->{address} $ms->{port}"); $handle->destroy;},
    on_eof   => sub {$self->process_sync_list($sync_list, $ms);              $handle->destroy;},
    on_read  => sub {
      # receive and clear buffer
      my $m = $_[0]->rbuf;
      $_[0]->rbuf = "";

      # remove string terminator: sometimes trailing slashes are added or 
      # forgotten by sender, so \secure\abcdef is actually \secure\abcdef{\0}
      chop $m if $m =~ m/secure/;
      
      # part 1: receive \basic\\secure\$key
      if ($m =~ m/basic\\\\secure/) {

        # hash $m into %r
        my %r = ();
        $m =~ s/\\\\/\\undef\\/;
        $m =~ s/\n//;
        $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
        
        # respond to the validate challenge
        my $validate = $self->validate_string("333networks", $r{secure}, $r{enctype});

        # part 2: send \gamename\ut\location\0\validate\$validate\final\
        $handle->push_write("\\gamename\\333networks\\location\\0\\validate\\$validate\\final\\");
        
        # part 3: request the list \sync\gamenames consisting of space-seperated game names or "all"
        my $request  = "\\sync\\".(($self->{sync_games}[0] == 0) ? "all" : $self->{sync_games}[1])."\\final\\";
        
        # push the request to remote host
        $handle->push_write($request);
        
        # clean up $m for future receivings
        $m = "";
        
      } # end secure
      
      # part 4: receive the entire list in multiple steps
      $sync_list .= $m;
    },
  );
}

################################################################################
## Process the list of addresses that was received after querying the UCC applet
## and store them in the pending list.
################################################################################
sub process_sync_list {
  my ($self, $m, $ms) = @_;

  # replace empty values for the string "undef" and replace line endings from netcatters 
  # parse hash {gamename => list of ips seperated by space}
  my %r = ();
  $m =~ s/\\\\/\\undef\\/;
  $m =~ s/\n//;
  $m =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
  
  # counter
  my $c = 0;
  
  # iterate through the gamenames and addresses
  while ( my ($gn,$addr) = each %r) {
  
    # only process gamenames that are in our list for supported games (supportedgames.pl)
    if (defined $gn && exists $self->{game}->{lc $gn}) {
      
      # database types such as SQLite are slow, therefore use transactions.
      $self->{dbh}->begin_work;
      
      # l(ocations, \label\ip:port\) split up in a(ddress) and p(ort)
      foreach my $l (split(/ /, $addr)) {
        
        # search for \255.255.255.255:7778\, contains ':'
        if ($l =~ /:/) {
          my ($a,$p) = $l =~ /(.*):(.*)/;
          
          # check if address entry is valid
          if ($self->valid_address($a,$p)) {
            # count number of valid addresses
            $c++;

            # add server
            $self->syncer_add($a, $p, $gn, $self->secure_string());
            
            # print address
            $self->log("add", "syncer added $gn\t$a\t$p");
          }
          else {
            # invalid address, log
            $self->log("error", "invalid address found while syncing at $ms->{address}: $l!");
          }
       
        } # endif ($l =~ /:/)
      } # end for / /
      
      # end transaction, commit        
      $self->{dbh}->commit;
      
    } # end defined $gn
  } # end while
  
  # end message
  $self->log("sync", "received $c addresses after syncing from $ms->{address}");
}
    
1;
