#!/usr/bin/perl
## Checks entries in the indra/newview/gpu_table.txt file against sample data
##
## Copyright (c) 2011, Linden Research, Inc.
##
## Permission is hereby granted, free of charge, to any person obtaining a copy
## of this software and associated documentation files (the "Software"), to deal
## in the Software without restriction, including without limitation the rights
## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
## copies of the Software, and to permit persons to whom the Software is
## furnished to do so, subject to the following conditions:
##
## The above copyright notice and this permission notice shall be included in
## all copies or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
## THE SOFTWARE.

use English;
use Getopt::Long;

( $MyName = $0 ) =~ s|.*/||;
my $mini_HELP = "
  $MyName {--gpu-table|-g} <gpu_table.txt> {--table-only|-t}
  
    Checks for duplicates and invalid lines in the gpu_table.txt file.

  $MyName {--gpu-table|-g} <gpu_table.txt> [ <gpu-strings-file> ... ]
          [{--unmatched|-u}]

    Tests the recognition of values in the gpu-strings-files (or 
    standard input if no files are given).   The results of attempting to match 
    each input line are displayed in report form, showing:
     - NO MATCH, unsupported, or supported
     - the class of the GPU
     - the label for the recognizer line from the gpu_table that it matched

    If the --unmatched option is specified, then no output is produced for
    values that are matched.

  $MyName {--gpu-table|-g} <gpu_table.txt> {--diff|-d} <old_results> [ <gpu-strings-file> ...]

    With the --diff option, the report compares the current results to <old-results>,
    which should be the output from a previous run without --diff.  The report shows each
    input value with the old result and the new result if it is different.
";

&GetOptions("help"               => \$Help
            ,"unmatched"         => \$UnMatchedOnly
            ,"table-only"        => \$TableOnly
            ,"gpu-table=s"       => \$GpuTable
            ,"diff=s"            => \$Diff
    )
    || die "$mini_HELP";

if ($Help)
{
    print $mini_HELP;
    exit 0;
}

$ErrorsSeen = 0;
$NoMatch = 'NO MATCH'; # constant

die "Must specify a --gpu-table <gpu_table.txt> value"
    unless $GpuTable;

open(GPUS, "<$GpuTable")
    || die "Failed to open gpu table '$GpuTable':\n\t$!\n";

my $FirstLine = <GPUS>;
die "First line of gpu table does not begin with '//GPU_TABLE'"
    unless $FirstLine =~ m|^//GPU_TABLE|;

# Parse the GPU table into these tables, indexed by the name
my %NameLine;       # name -> line number on which a given name was found (catches duplicate names)
my %RecognizerLine; # name -> line number on which a given name was found (catches duplicate names)
my %Name;           # recognizer -> name
my %Recognizer;     # name -> recognizer
my %Class;          # recognizer -> class
my %Supported;      # recognizer -> supported
my @InOrder;        # lowercased recognizers in file order - these are the ones really used to match
my %StatsBased;
my %ExpectedOpenGL;

$Name{$NoMatch}      = $NoMatch;
$NameLine{$NoMatch}  = '(hard-coded)'; # use this for error messages in table parsing
$Class{$NoMatch}     = '';
$Supported{$NoMatch} = '';
$StatsBased{$NoMatch} = '';
$ExpectedOpenGL{$NoMatch} = '';

while (<GPUS>)
{
    next if m|^//|;    # skip comments
    next if m|^\s*$|;  # skip blank lines

    chomp;
    my ($name, $regex, $class, $supported, $stats_based, $expected_opengl, $extra) = split('\t+');
    my $errsOnLine = $ErrorsSeen;
    if (!$name)
    {
        print STDERR "No name found on $GpuTable line $INPUT_LINE_NUMBER\n";
        $ErrorsSeen++;
    }
    elsif ( defined $NameLine{$name} )
    {
        print STDERR "Duplicate name '$name' on $GpuTable lines $NameLine{$name} and $INPUT_LINE_NUMBER:\n";
        print STDERR "     $NameLine{$name}: /$Recognizer{$name}/  $Supported{$Recognizer{$name}}  class $Class{$Recognizer{$name}}\n";
        print STDERR "     $INPUT_LINE_NUMBER: /$regex/  " . ($supported ? "supported" : "unsupported") .  " class $class - ignored\n";
        $ErrorsSeen++;
    }
    if (!$regex)
    {
        print STDERR "No recognizer found on $GpuTable line $INPUT_LINE_NUMBER\n";
        $ErrorsSeen++;
    }
    elsif ( defined $RecognizerLine{$regex} )
    {
        print STDERR "Duplicate recognizer /$regex/ found on $GpuTable lines $RecognizerLine{$regex} and $INPUT_LINE_NUMBER (ignored)\n";
        print STDERR "     $RecognizerLine{$regex}: name '$Name{$regex}'  $Supported{$regex}  class $Class{$regex}\n";
        print STDERR "     $INPUT_LINE_NUMBER: name '$name'  " . ($supported ? "supported" : "unsupported") .  "  class $class - ignored\n";
        $ErrorsSeen++;
    }
    if ($class !~ m/[012345]/)
    {
        print STDERR "Invalid class value '$class' on $GpuTable line $INPUT_LINE_NUMBER\n";
        $ErrorsSeen++;
    }
    if ($supported !~ m/[0123]/)
    {
        print STDERR "Invalid supported value '$supported' on $GpuTable line $INPUT_LINE_NUMBER\n";
        $ErrorsSeen++;
    }
    if ($stats_based !~ m/[01]/)
    {
        print STDERR "Invalid stats_based value '$stats_based' on $GpuTable line $INPUT_LINE_NUMBER\n";
        $ErrorsSeen++;
    }
    if ($expected_opengl !~ m/\d+(\.\d+)?/)
    {
        print STDERR "Invalid expected_opengl value '$expected_opengl' on $GpuTable line $INPUT_LINE_NUMBER\n";
        $ErrorsSeen++;
    }
    if ($extra)
    {
        print STDERR "Extra data '$extra' on $GpuTable line $INPUT_LINE_NUMBER\n";
        $ErrorsSeen++;
    }
    
    if ($errsOnLine == $ErrorsSeen) # no errors found on this line
    {
        push @InOrder,$regex;
        $NameLine{$name} = $INPUT_LINE_NUMBER;
        $RecognizerLine{$regex} = $INPUT_LINE_NUMBER;
        $Name{$regex} = $name;
        $Recognizer{$name} = $regex;
        $Class{$regex} = $class;
        $Supported{$regex} = $supported ? "supported" : "unsupported";
        $StatsBased{$regex} = $stats_based;
        $ExpectedOpenGL{$regex} = $expected_opengl;
    }
}

close GPUS;

print STDERR "\n" if $ErrorsSeen;

exit $ErrorsSeen if $TableOnly;


# Loop over input lines, find the results for each 
my %RecognizedBy;
while (<>) 
{
    chomp;
    $_ = substr($_,0,100);
    my $lcInput = lc $_;  # the real gpu table parser lowercases the input string
    my $recognizer;
    $RecognizedBy{$_} = $NoMatch;
    foreach $recognizer ( @InOrder ) # note early exit if recognized
    {
        my $lcRecognizer = lc $recognizer; # the real gpu table parser lowercases the recognizer
        if ( $lcInput =~ m/$lcRecognizer/ )
        {
            $RecognizedBy{$_} = $recognizer;
            last; # exit recognizer loop
        }
    }
}

format STDOUT_TOP =
GPU String                                                                                               Supported?  Class  Stats  OpenGL  Recognizer
------------------------------------------------------------------------------------------------------   ----------- -----  -----  ------  ------------------------------------
.
format STDOUT =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<<<<<<   @>    @>     @<<<<  @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<...
$_, $Supported{$RecognizedBy{$_}},$Class{$RecognizedBy{$_}},$StatsBased{$RecognizedBy{$_}},$ExpectedOpenGL{$RecognizedBy{$_}},$Name{$RecognizedBy{$_}}
.

my $ReportLineTemplate = "A102xxxA12xxxA2xxxxA2xxxxA5A*"; # Used to read a previous report - MUST match the format STDOUT above

my ( $oldSupported, $oldClass, $newSupported, $newClass );

format DIFF_TOP =
                                                                                                         ------------- OLD -------------  ----------- NEW --------------------
GPU String                                                                                               Supported?  Class Stats OpenGL   Supported?  Class Stats OpenGL  Line
------------------------------------------------------------------------------------------------------   ----------- ----- ----- ------   ----------- ----- ----- ------ -----
.                                                                                                                                             
format DIFF =                                                                                                                                 
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<   @<<<<<<<<<<   @>    @>  @<<<<    @<<<<<<<<<<    @>   @>  @<<<<  @>>>>
$_, $oldSupported, $oldClass, $oldStatsBased, $oldExpectedOpenGL, $newSupported, $newClass, $newStatsBased, $newExpectedOpenGL, $newRecognizedLine
.

if ( ! $Diff )
{
    ## Print results of testing each input line and how it was recognized. 
    foreach ( sort keys %RecognizedBy )
    {
        write if ! $UnMatchedOnly || $Name{$RecognizedBy{$_}} eq $NoMatch;
        $-++; # suppresses pagination
    }
}
else
{
    ## Print a comparison of how the recognition this time compared to the results from the $Diff file
    open OLD, "<$Diff"
        || die "Failed to open --diff file '$Diff'\n\t$!\n";
    my $discard = 2;
    while ( <OLD> )
    {
        if ( $discard <= 0 )
        {
            my ( $gpu, $supported, $class, $stats, $opengl ) = unpack $ReportLineTemplate;
            $gpu =~ s/\s*$//;
            ( $OldSupported{$gpu} = $supported ) =~ s/\s*$//;
            ( $OldClass{$gpu} = $class ) =~ s/\s*$//;
            ( $OldStatsBased{$gpu} = $stats ) =~ s/\s*$//;
            ( $OldExpectedOpenGL{$gpu} = $opengl ) =~ s/\s*$//;
        }
        else
        {
            $discard--;
        }
    }
    close OLD;

    $FORMAT_TOP_NAME = 'DIFF_TOP';
    $FORMAT_NAME = 'DIFF';
    foreach ( sort keys %RecognizedBy )
    {
        $newSupported = $Supported{$RecognizedBy{$_}} || $NoMatch;
        $newClass     = $Class{$RecognizedBy{$_}};
        $newStatsBased     = $StatsBased{$RecognizedBy{$_}};
        $newExpectedOpenGL = $ExpectedOpenGL{$RecognizedBy{$_}};
        $newRecognizedLine = $RecognizerLine{$RecognizedBy{$_}};

        if ( ! defined $OldSupported{$_} )
        {
            $oldSupported = 'NEW';
            $oldClass = '-';
            $oldStatsBased = '-';
            $oldExpectedOpenGL = '-';
            write;
            $-++; # suppresses pagination
        }
        else
        {
            $oldSupported = $OldSupported{$_} || $NoMatch;
            $oldClass     = $OldClass{$_};
            $oldStatsBased     = $OldStatsBased{$_};
            $oldExpectedOpenGL = $OldExpectedOpenGL{$_};

            if (   ( $oldSupported ne $newSupported )
                || ( $oldClass  ne $newClass )
                || ( $oldStatsBased ne $newStatsBased )
                || ( $oldExpectedOpenGL ne $newExpectedOpenGL )
                )
            {
                write;
                $-++; # suppresses pagination
            }
        }

    }
}

exit $ErrorsSeen;