#!/usr/bin/perl

package FCC::threadminer;

#############################################################
#                                                           #
#     FCC Thread Miner functions                            #
#                                                           #
#    (C) 2020 OnEhIppY Domero                               #
#    Leaves are less strict, the node will check all        #
#                                                           #
#############################################################

use strict;
no strict 'refs';
use warnings;
use Exporter;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

$VERSION     = '1.0.1';
@ISA         = qw(Exporter);
@EXPORT      = qw(fac initperm perm minehash solhash);
@EXPORT_OK   = qw();

use FCC::miner;

1;

sub new {
    my ($class,$NODEIP,$NODEPORT,$POOLMINER)=@_;
    my $self={
        problem => undef,
        PSIZE => 0,
        PREST => 0,
        FAC => 0,
        POS => 0,
        HINTS => "",
        HINTPOS => 0,
        HINT => "",
        EHINTS => "",
        EHINT => "",
        EHINTPOS => 0,
        START => 0,
        ROUND => 0,
        LOOP => 0,
        SOLUTION => "",
        STARTTIME => 0,
        DISPTIME => time,
        HASHSTART => 0,
        BEGIN => 0,
        RUNTIME => time,
        POOLMINER => $POOLMINER
    };
    bless $self;
    gthreads::createset('threadminer');
    gthreads::write('threadminer','tothash',0);
    gthreads::write('threadminer','problemcount',0);
    gthreads::write('threadminer','success',0);
    gthreads::write('threadminer','start',time);
    gthreads::write('threadminer','new',0);
    gthreads::write('threadminer','solution',"");

    $self->{leaf}=FCC::leaf::startleaf($NODEIP,$NODEPORT,\&handle,0,1);
    if ($self->{leaf}->{error}) { print $self->{leaf}->{error}."\n"; exit }


    return $self
}

sub takeloop {
    my ($self)=@_;
    if ($self->{leaf} && !$self->{leaf}{quit}) {
        $self->mineloop();
        $self->{leaf}->leafloop()
    }
}

sub start {
    my ($self)=@_;
    while ($self->{leaf} && !$self->{leaf}{quit}) { $self->takeloop() }
    if ($self->{leaf}{error}) { print $self->{leaf}{error}."\n" }
    else { print "Terminated OK\n" }
    exit;
}

sub addhash {
    my ($self,$num) = @_;
    gthreads::inc('threadminer','tothash',$num)
}

sub hashrate {
    my $th=gthreads::read('threadminer','tothash');
    my $start=gthreads::read('threadminer','start');
    my $dtm=time-$start;
    return int(1000 * $th / $dtm) / 1000
}


sub newproblem {
    my ($self,$data) = @_;
    $self->{problem}=$data;
    if (!$data->{ehints}) { $data->{ehints}="" }
    gthreads::write('threadminer','tryinit',"");
    for (my $i=0;$i<$data->{length};$i++) {
        my $c=chr(65+$i);
        my $tryinit=gthreads::read('threadminer','tryinit');
        if (!$data->{tryhint} || $data->{tryhint} !~ /$c/gs) {
            gthreads::write('threadminer','tryinit',$tryinit.$c)
        }
    }
    gthreads::write('threadminer','trymax',fac(length(gthreads::read('threadminer','tryinit'))));
    gthreads::write('threadminer','try',int rand(gthreads::read('threadminer','trymax')));
    gthreads::write('threadminer','trystart',gthreads::read('threadminer','try'));
    print "\n"; print ">"x79; print "\n";
    print "New challenge [$data->{coincount}]: diff=$data->{diff} len=$data->{length} Init=$data->{init} hints=$data->{hints} ehints=$data->{ehints}\n";
    print "<"x79; print "\n";
    gthreads::inc('threadminer','problemcount',1);
    $self->{HASHSTART}=gthreads::read('threadminer','tothash');
    if ($data->{hints}) {
        $self->{FAC}=fac($data->{length}-1);
        $self->{HINTS}=perm($data->{hints},int(rand(fac(length($self->{HINTS})))));
        $self->{HINTPOS}=0;
        $self->{HINT}=substr($self->{HINTS},$self->{HINTPOS},1);
        if ($data->{ehints}) {
            $self->{FAC}=fac($data->{length}-2);
            $self->{EHINTS}=perm($data->{ehints},int(rand(fac(length($self->{EHINTS})))));
        }
    } else {
        $self->{FAC}=fac($data->{length});
        $self->{HINTS}="";
        $self->{HINT}="";
        $self->{EHINTS}="";
    }
    $self->{PSIZE} = int($self->{FAC} / $self->{THREADS});
    $self->{PREST} = $self->{FAC} % $self->{THREADS};
    $self->{POS} = int(rand($self->{FAC}));
    $self->{START} = $self->{POS};
    $self->{LOOP} = 0;
  # signal active miners
  my $tm=gettimeofday();
  if ($STARTTIME) {
    my $ptm=int( ( $tm - $STARTTIME ) * 1000 ) / 1000;
    my $min=int($ptm / 60);
    my $sec=sprintf("%02d",$ptm % 60);
    my $avg='[n/a]';
    if (!$BEGIN) {
      $BEGIN=time
    } else {
      my $num=gthreads::read('threadminer','problemcount'); $num-=2;
      my $vtm=time - $BEGIN;
      my $vdtm = int($vtm / $num);
      my $vmin = int($vdtm / 60);
      my $vsec=sprintf("%02d",$vdtm % 60);
      $avg=$vmin.'m'.$vsec.'s';
    }
    print "Coinbase Circulation Time: $min".'m'.$sec.'s'." Average: $avg\n\n";
  }
  gthreads::write('threadminer','new',1);
  $STARTTIME=$tm
}

sub minerblock {
  my ($id,$init,$challenge,$coincount,$pos,$fac,$psize,$hint,$ehint) = @_;
  my $stm=gettimeofday(); my $mc=0; my $cnt=0; my $try; my $perm; my $m; my $dn; my $ind; my $thint=$hint.$ehint; my $ilen=length($init);
  while ($psize>0) {
    $cnt=0; my $todo=50000;
    if ($todo>$psize) { $todo=$psize }
    $psize-=$todo;
    for (my $i=0; $i<$todo; $i++) {
      $cnt++;
      $try=$init; $perm=""; $m=$pos; $dn=$ilen;
      while ($dn>0) {
        $ind=$m % $dn;
        $m=$m / $dn;  
        $dn--;
        $perm.=substr($try,$ind,1,substr($try,$dn,1));
      }
      #$perm=perm($init,$pos);
      my $hash=minehash($coincount,$thint.$perm);
      if ($hash eq $challenge) {
        gthreads::write('threadminer','solution',$thint.$perm);
        addhash($cnt);
        return 1
      }
      $pos++; if ($pos >= $fac) { $pos = 0 }      
    }
    $mc++; addhash($todo);
    # new challenge signalled?
    if (gthreads::read('threadminer','new')) {
      return 1
    }
    if ($mc == 10) {
      $mc=0;
      my $tm=gettimeofday();
      my $dtm=$tm-$stm;
      $stm=$tm;
      my $hr=int(500000 / $dtm);
      print " [ HR $id = $init $thint $hint : $hr Fhs ]\n"
    }
  }
  return 0
}

sub minerthread {
  my ($id,$problem,$pos,$fac,$psize,$hint,$ehints) = @_;
  print "Thread $id: pos=$pos, fac=$fac, psize=$psize, init=$problem->{init},  hint=$hint, ehints=$ehints\n";
  my $init=""; my $ehint="";
  if ($ehints) {
    for (my $ehp=0; $ehp<length($ehints); $ehp++) {
      $ehint=substr($ehints,$ehp,1);
      if ($ehint eq $hint) { next }
      $init="";
      for (my $i=0;$i<$problem->{length};$i++) {
        my $c=chr(65+$i);
        if (($c ne $hint) && ($c ne $ehint)) { $init.=$c }
      }
      my $end=$pos+$psize-1;
      if ($end > $fac) {
        my $rst=$end-$fac; $end="$fac..$rst"
      }
      print " -> $id $init $hint $ehint [$pos..$end]\n";
      if (minerblock($id,$init,$problem->{challenge},$problem->{coincount},$pos,$fac,$psize,$hint,$ehint,$problem->{init})) {
        $MINEDATA->{try}++;
  if ($MINEDATA->{try} >= $MINEDATA->{trymax}) {
    $MINEDATA->{try}=0
  }
  if ($MINEDATA->{try} == $MINEDATA->{trystart}) {
    if($MINEDATA->{init}){
      print "Nop, not this block $MINEDATA->{init}.... at $MINEDATA->{fhash} Fhs...                        \n";
      if ($MINER->{client}) { wsmessage($MINER->{client},"miner Nop, not this block $MINEDATA->{init} :-|") }
      $MINER->outnode({command=>'challenge',nope=>$MINEDATA->{init}});
        gthreads::done($id); return
      }
    }
  } else {
    for (my $i=0;$i<$problem->{length};$i++) {
      my $c=chr(65+$i);
      if ($c ne $hint) { $init.=$c }
    }
    minerblock($id,$init,$problem->{challenge},$problem->{coincount},$pos,$fac,$psize,$hint,"")
  }
  gthreads::done($id);
}

sub mineloop {
    my ($self->{leaf}) = @_;
    if (!$PROBLEM) { usleep(10000); return }
    if (gthreads::read('threadminer','new')) {
        if (gthreads::running() > 0) {
            return
        } else {
            gthreads::write('threadminer','new',0)
        }
    }
    if (gthreads::running() < $THREADS) {
        $LOOP++;
    print "Starting thread $LOOP/$THREADS ($POS, $HINT, $EHINTS)\n";
    if ($LOOP == $THREADS) {
      gthreads::start('threadminer',\&minerthread,$PROBLEM,$POS,$FAC,$PSIZE+$PREST,$HINT,$EHINTS);
      $LOOP = 0;
      $POS+=$PSIZE+$PREST;
      if ($HINT) {
        $HINTPOS++;
        $HINT=substr($HINTS,$HINTPOS,1)
      }
    } else {
      gthreads::start('threadminer',\&minerthread,$PROBLEM,$POS,$FAC,$PSIZE,$HINT,$EHINTS);
      $POS+=$PSIZE;
    }
    if ($POS>$FAC) { $POS-=$FAC }
  }
  my $sol=gthreads::read('threadminer','solution');
  if ($sol && ($sol ne $SOLUTION)) {
    my $txt="* SOLUTION: $sol .. Yeah! :) *";
    my $sl=length($txt);
    print "\n"; print '*'x$sl; print "\n$txt\n"; print '*'x$sl; print "\n\n";
    if ($POOLMINER) {
      solution($self->{leaf},$WALLET,$sol);
    } else {
      solution($self->{leaf},$WALLET,solhash($WALLET,$sol));
    }
    $SOLUTION=$sol;
  }
  if (time - $DISPTIME > 60) {
    $DISPTIME = time;
    my $hr=hashrate();
    my $p=gthreads::read('threadminer','problemcount');
    my $s=gthreads::read('threadminer','success');
    my $dp = $p - 1; my $perc='0';
    if ($dp) { $perc = int (10000 * $s / $dp) / 100 }
    my $str="$hr Fhs - Problems $p - Found solutions $s ($perc %) - Threads $THREADS";
    my $th=gthreads::read('threadminer','tothash');
    my $dh=$th-$HASHSTART;
    my $hp=int(10000 * $dh / $PROBLEM->{diff}) / 100;
    my $dtm=int((time - $RUNTIME) / 60);
    my $hour=int($dtm / 60); my $min=sprintf("%02d",$dtm % 69);
    my $days = int($hour / 24); $hour=sprintf("%02d",$hour % 24);
    my $str2="$days".'d'.":$hour".'h'.$min."m Curr: $hp% Diff=$PROBLEM->{diff} Len=$PROBLEM->{length} Init=$PROBLEM->{init} Hints=$PROBLEM->{hints} ($PROBLEM->{ehints})";
    my $sl=length($str);
    my $sp=(90-$sl)>>2;
    my $spl=""; if ($sl % 2) { $spl=' ' }
    print '   '; print '-'x73; print "\n";
    print ' 'x$sp; print "$str\n";
    print ' 'x$sp; print "$str2\n";
    print '   '; print '-'x73; print "\n";
  }
  usleep(100000)
}
