#!/usr/bin/perl -w
# This tests the various methods of the Graphics::Framebuffer module
# and shows the only way threads will work with it.

use strict;
use Switch;

use threads;
use threads::shared;
use Graphics::Framebuffer;
use Sys::CPU;
use Term::ANSIScreen qw(:cursor :screen :color);

$| = 1;

our $VERSION = 2.00;

my $mali          = 0;
my $file          = 0;
my $hold : shared = 0;
my @waiting : shared;
my $RUNNING : shared = 1;
my $arg = join('', @ARGV);
my $Threads = $arg =~ /(\d+)/ ? $1 : Sys::CPU::cpu_count();
$mali = 1 if ($arg =~ /mali/i);    # Mali GPU correction mode
$file = 1 if ($arg =~ /file/i);    # File Handle mode

print "\nRunning on a ", Sys::CPU::cpu_type(), " with $Threads threads\n";
sleep 2;

my @framebuffer;
my $splash = 1;
foreach my $thr (0 .. $Threads) {
    $framebuffer[$thr] = Graphics::Framebuffer->new('FB_DEVICE' => '/dev/fb0', 'FILE_MODE' => $file, 'MALI' => $mali, 'SPLASH' => $splash);
    $splash = 0;
}
my ($screen_width, $screen_height) = $framebuffer[0]->screen_dimensions();

my $top = 16 * $Threads;    # Font height * # of threads

$framebuffer[0]->cls('OFF');
cls;
print STDERR setscroll(1, $Threads) if ($screen_width > 255);    # prevents Perl errors from flooding the screen
$SIG{'QUIT'} = \&finish;
$SIG{'INT'}  = \&finish;
my @thrd;
foreach my $page (1 .. $Threads) {
    $waiting[$page] = 0;
    $thrd[$page]    = threads->create(
        sub {
            my $Page = shift;
            my $top  = shift;
            if (defined($framebuffer[$Page]->{'ERROR'})) {
                print STDERR locate($Page, 1), clline, "ERROR: Thread $Page, $framebuffer[$Page]->{'ERROR'}" if ($screen_width > 255);
            }
            $framebuffer[$page]->clip_set({'x' => 0, 'y' => $top, 'xx' => $screen_width, 'yy' => $screen_height}) if ($screen_width > 255);
            while ($RUNNING) {
                attract($Page);
                while ($hold && $RUNNING) {
                    print locate($Page, 1), clline, colored(['white on_yellow'], "Thread $Page"), ' Waiting for other threads to finish drawing before clearing screen...' if ($screen_width > 255);
                    threads->yield();
                    $waiting[$Page] = 0;
                    sleep 1;
                } ## end while ($hold && $RUNNING)
                $waiting[$Page] = 1;
            } ## end while ($RUNNING)
            print locate($Page + 1, 1), clline, colored(['black on_green'], "Thread $Page"), ' Finished' if ($screen_width > 255);
        },
        $page,
        $top
    );
} ## end foreach my $page (1 .. $Threads)
while (1) {
    $hold = 0;
    sleep 30;
    $hold = 1;
    foreach my $page (1 .. $Threads) {
        while ($waiting[$page] && $thrd[$page]->is_running()) {
            sleep 1;
        }
    }
    $framebuffer[0]->cls();
} ## end while (1)

##############################################################################
##                             ATTRACT MODE                                 ##
##############################################################################
# Remeniscent of the "Atract Mode" of the old Atari 8 bit computers, this    #
# mode merely puts random patterns on the screen.                            #
##############################################################################

sub attract {
    my $page   = shift;
    my $red    = int(rand(256));
    my $grn    = int(rand(256));
    my $blu    = int(rand(256));
    my $x      = int(rand($screen_width));
    my $y      = int(rand($screen_height));
    my $w      = int(rand($screen_width / 3));
    my $h      = int(rand($screen_height / 3));
    my $rx     = int(rand($screen_width / 5));
    my $ry     = int(rand($screen_height / 5));
    my $sd     = int(rand(360));
    my $ed     = int(rand(360));
    my $gr     = (rand(10) / 10);
    my $mode   = int(rand(4));
    my $type   = int(rand(13));
    my $size   = int(rand(3));
    my $arc    = int(rand(3));
    my $filled = int(rand(2));

    $framebuffer[$page]->set_color({'red' => $red, 'green' => $grn, 'blue' => $blu});
    $framebuffer[$page]->draw_mode($mode);
    switch ($type) {
        case 0 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Plotting point at %04d,%04d with pixel size %02d', $x, $y, $size);
            $framebuffer[$page]->plot({'x' => $x, 'y' => $y, 'pixel_size' => $size});
        }
        case 1 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing from %04d,%04d to %04d,%04d with pixel size %02d', $page, $x, $y, $w, $h, $size);
            $framebuffer[$page]->plot({'x' => $x, 'y' => $y, 'pixel_size' => $size});
            $framebuffer[$page]->drawto({'x' => $w, 'y' => $h, 'pixel_size' => $size});
        }
        case 2 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Circle at %04d,%04d with Radius %04d with pixel size %d, filled? %s', $x, $y, $rx, $size, ($filled) ? 'Yes' : 'No');
            $framebuffer[$page]->circle({'x' => $x, 'y' => $y, 'radius' => $rx, 'filled' => $filled, 'pixel_size' => $size});
        }
        case 3 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Ellipse at %04d,%04d with Radii %04d,%04d with pixel size %d, filled? %s', $x, $y, $rx, $ry, $size, ($filled) ? 'Yes' : 'No');
            $framebuffer[$page]->ellipse({'x' => $x, 'y' => $y, 'xradius' => $rx, 'yradius' => $ry, 'filled' => $filled, 'factor' => 1, 'pixel_size' => $size});
        }
        case 4 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Box at %04d,%04d and %04d,%04d with pixel size %d, filled? %s', $x, $y, $w, $h, $size, ($filled) ? 'Yes' : 'No');
            $framebuffer[$page]->rbox({'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h, 'filled' => $filled, 'pixel_size' => $size});
        }
        case 5 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Arc at %04d,%04d with Radius %04d, Start %03d, End %03d with pixel size %d, Type? %s', $x, $y, $ry, $sd, $ed, $size, arctype($arc));
            $framebuffer[$page]->draw_arc({'x' => $x, 'y' => $y, 'radius' => $ry, 'start_degrees' => $sd, 'end_degrees' => $ed, 'granularity' => $gr, 'mode' => $arc, 'pixel_size' => $size});
        }
        case 6 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Gradiant Box at %04d,%04d and %04d,%04d', $x, $y, $w, $h);
            $framebuffer[$page]->rbox(
                {
                    'x'          => $x,
                    'y'          => $y,
                    'width'      => $w,
                    'height'     => $h,
                    'filled'     => 1,
                    'pixel_size' => 1,
                    'gradient'   => {
                        'start' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        },
                        'end' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        }
                    }
                }
            );
        } ## end case 6
        case 7 {
            my @poly;
            foreach my $count (0 .. int(rand(6))) {
                push(@poly, int(rand($screen_width)));
                push(@poly, int(rand($screen_height)));
            }
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Polygon at points %s with pixel size %d, filled? %s', join(',', @poly), $size, ($filled) ? 'Yes' : 'No');
            $framebuffer[$page]->polygon({'pixel_size' => $size, 'coordinates' => \@poly, 'filled' => (int(rand(2)))});
        } ## end case 7
        case 8 {
            my @poly;
            foreach my $count (0 .. int(rand(6))) {
                push(@poly, int(rand($screen_width)));
                push(@poly, int(rand($screen_height)));
            }
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Gradient Polygon at points %s', join(',', @poly));
            $framebuffer[$page]->polygon(
                {
                    'pixel_size'  => 1,
                    'coordinates' => \@poly,
                    'filled'      => 1,
                    'gradient'    => {
                        'start' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        },
                        'end' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        }
                    }
                }
            );
        } ## end case 8
        case 9 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Gradient Circle at %04d,%04d with Radius %04d', $x, $y, $rx);
            $framebuffer[$page]->circle(
                {
                    'x'        => $x,
                    'y'        => $y,
                    'radius'   => $rx,
                    'filled'   => 1,
                    'gradient' => {
                        'start' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        },
                        'end' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        }
                    }
                }
            );
        } ## end case 9
        case 10 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Gradient Ellipse at %04d,%04d with Radii %04d,%04d', $x, $y, $rx, $ry);
            $framebuffer[$page]->ellipse(
                {
                    'x'        => $x,
                    'y'        => $y,
                    'xradius'  => $rx,
                    'yradius'  => $ry,
                    'filled'   => 1,
                    'factor'   => 1,
                    'gradient' => {
                        'start' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        },
                        'end' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        }
                    }
                }
            );
        } ## end case 10
        case 11 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Rounded Box at %04d,%04d and %04d,%04d with pixel size %d, filled? %s', $x, $y, $w, $h, $size, ($filled) ? 'Yes' : 'No');
            $framebuffer[$page]->rbox({'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h, 'filled' => $filled, 'radius' => rand(30) + 2, 'pixel_size' => $size});
        }
        case 12 {
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Drawing Rounded Gradiant Box at %04d,%04d and %04d,%04d', $x, $y, $w, $h);
            $framebuffer[$page]->rbox(
                {
                    'x'          => $x,
                    'y'          => $y,
                    'width'      => $w,
                    'height'     => $h,
                    'filled'     => 1,
                    'pixel_size' => 1,
                    'radius'     => rand(30) + 2,
                    'gradient'   => {
                        'start' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        },
                        'end' => {
                            'red'   => rand(256),
                            'green' => rand(256),
                            'blue'  => rand(256)
                        }
                    }
                }
            );
        } ## end case 12
        else {    # This can be a large memory hog.  Disabled (random number doesn't go this high) for the moment.
            print locate($page, 1), clline, colored(['black on_green'], "Thread $page"), sprintf(' Flood Fill starting at %04d,%04d', $x, $y);
            $framebuffer[$page]->fill({'x' => $x, 'y' => $y});
        }
    } ## end switch
} ## end sub attract

sub arctype {
    my $type = shift;
    if ($type == 0) {
        return ('Arc');
    } elsif ($type == 1) {
        return ('Pie');
    } else {
        return ('Poly Arc');
    }
} ## end sub arctype

sub finish {
    $RUNNING = 0;
    print setscroll(1, $Threads + 1);
    print locate(1, 1), "WAITING FOR THREADS TO FINISH...";
    while (threads->list(threads::running)) {
        threads->yield();
    }
    print locate(1, 1), clline, "Shutting down...\n";
    foreach my $thr (threads->list(threads::joinable)) {
        $thr->join;
    }
    sleep 1;
    cls;
    exec('reset');
} ## end sub finish

__END__

=head1 NAME

Threads Test

=head1 DESCRIPTION

Demonstrates how to use threads with "Graphics::Framebuffer"

=head1 SYNOPSIS

perl threadstest.pl [mali] [file] [# of threads]

=head1 OPTIONS

The options can be passed in any order and all are optional.

=over 1
=item B< mali >

Causes the Graphics::Framebuffer module to correct for driver errors
with some MALI GPUs in ARM chipsets.  While it is mainly designed to
override some of the calculations for Mali chipsets, it may work with
others.  Only use if you are either getting errors, or weirdness in
drawing.

Contact the Graphics::Framebuffer author if you are having troubles
getting it to work.

=item B< file >

Changes the internal logic of Graphics::Framebuffer to instead draw
using a file handle, instead of using a Mmapped variable.

Only use if you are having stability issues, as it is slower.

=item B< # of threads >

This is an integer (yes, just a number) indicating how many threads
to spawn to draw.

Typically this is done automatically with the number of cores the
script detects, but this overrides the detection.

=back
=cut
