#!/usr/bin/env raku
use v6.d;
use lib 'lib';

use Selkie::Store;
use Selkie::Widget;
use Selkie::Widget::ViewportedCardList;
use Selkie::Layout::VBox;
use Selkie::Sizing;

class BenchWidget does Selkie::Widget {
    method render() { self.clear-dirty }
}

sub json-escape(Str:D $s --> Str) {
    $s.trans(['\\', '"', "\n", "\r", "\t"] => ['\\\\', '\"', '\n', '\r', '\t']);
}

sub emit-json(%results --> Nil) {
    say '{';
    say '  "schema": 1,';
    say '  "project": "Selkie",';
    say '  "generated_at": "' ~ json-escape(DateTime.now.Str) ~ '",';
    say '  "benchmarks": {';
    my @names = %results.keys.sort;
    for @names.kv -> $i, $name {
        my %r = %results{$name};
        say '    "' ~ json-escape($name) ~ '": {'
          ~ '"iterations": ' ~ %r<iterations>
          ~ ', "seconds": ' ~ %r<seconds>.fmt('%.9f')
          ~ ', "ops_per_second": ' ~ %r<ops_per_second>.fmt('%.3f')
          ~ ' }' ~ ($i == @names.end ?? '' !! ',');
    }
    say '  }';
    say '}';
}

sub timed(Str:D $name, Int:D $iterations, &body, %results --> Nil) {
    body(); # warm once so module-level laziness is not charged
    my $start = now;
    body() for ^$iterations;
    my $seconds = (now - $start).Num max 0.000000001e0;
    %results{$name} = %(
        :$iterations,
        :$seconds,
        ops_per_second => $iterations / $seconds,
    );
}

sub make-vcl(Int:D $n --> Selkie::Widget::ViewportedCardList) {
    my $list = Selkie::Widget::ViewportedCardList.new(follow-bottom => True);
    $list.resize(24, 100);
    for ^$n -> $i {
        my UInt $h = (3 + ($i % 11)).UInt;
        my $widget = BenchWidget.new;
        my $root = Selkie::Layout::VBox.new(sizing => Sizing.fixed($h));
        $list.add-item($widget, :$root, height => $h);
    }
    $list;
}

my %results;

{
    my $store = Selkie::Store.new;
    my $w = BenchWidget.new;
    $store.register-handler('inc', -> $st, % {
        (db => { counter => ($st.get-in('counter') // 0) + 1 },);
    });
    for ^500 -> $i {
        $store.subscribe-path-callback("counter-$i", ['counter'], -> $ {}, $w,
            :identity-check-only);
    }
    timed 'store.dispatch_tick.500_path_subscribers', 1000, {
        $store.dispatch('inc');
        $store.tick;
    }, %results;
}

{
    my $list = make-vcl(1000);
    my $max = $list.content-height - $list.viewport-height;
    my $i = 0;
    timed 'vcl.scroll_to.1000_variable_height_items', 5000, {
        $list.scroll-to(($i * 17) % $max);
        $i++;
    }, %results;
}

{
    my $list = make-vcl(1000);
    my $i = 0;
    timed 'vcl.select_index.1000_variable_height_items', 5000, {
        $list.select-index(($i * 13) % $list.count);
        $i++;
    }, %results;
}

{
    my $list = make-vcl(1000);
    my $i = 0;
    timed 'vcl.set_item_height.1000_variable_height_items', 5000, {
        my $idx = ($i * 19) % $list.count;
        $list.set-item-height($idx, 3 + (($i + $idx) % 11));
        $i++;
    }, %results;
}

emit-json(%results);
