#!/usr/bin/perl -w

eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
  if 0;    # not running under some shell
###############################################################################
# Copyright (c) 2012 Allen Winter <winter@kde.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
###############################################################################
use strict;
use Getopt::Long;
use File::Basename;
use File::Find;
use Class::CSV;
use Cwd 'abs_path';

my ($Prog) = 'descpacks.pl';
my $VERSION = '0.1';    #split line so MakeMaker can find the version here

my ($help)    = '';
my ($version) = '';
my ($quiet)   = '';
my ($modules) = '';
my ($export)  = 'csv';    #comma-separated

my (%Values) = ();

exit 1
  if (
  !GetOptions(
    'help'     => \$help,
    'version'  => \$version,
    'quiet'    => \$quiet,
    'modules'  => \$modules,
    'export=s' => \$export
  )
  );

&Help() if ($help);
if ( $#ARGV >= 0 ) { &Help(); exit 0; }
&Version() if ($version);

$export = lc($export);
if ( $export ne "wiki" && $export ne "csv" ) {
  print "Invalid export TYPE \"$export\" specified\n";
  print "$Prog --help for more info\n";
  exit 1;
}

my ($PROJ) = basename( abs_path(".") );

my ( $cmake, @cmakes, @tmp );
find( \&buildCMakeList, "." );

sub buildCMakeList {
  -f &&
  !-d &&
  $_ =~ /CMakeLists\.txt$/ &&
  push( @tmp, $File::Find::name );
}
push @cmakes, @tmp;

my ($pack);
my (%Packs);       # optional packages
my (%Reqs);        # required packages
my (@Errors);      #error messages
my (@Warnings);    #warning messages

# Process the CMakeLists.txt files
for $cmake (@cmakes) {
  next if ( $cmake =~ m/(example|qtonly|templates|samples)/ );
  &doIt( abs_path($cmake) );
}

# Post process the Package hashes
foreach $pack ( keys %Reqs ) {
  $Reqs{$pack}{'where'} =~ s/,$//;
}

#variable substitution
foreach $pack ( keys %Packs ) {
  $Packs{$pack}{'feature'}  = &reFoo( $Packs{$pack}{'feature'} );
  $Packs{$pack}{'desc'}     = &reFoo( $Packs{$pack}{'desc'} );
  $Packs{$pack}{'url'}      = &reFoo( $Packs{$pack}{'url'} );
  $Packs{$pack}{'req'}      = &reFoo( $Packs{$pack}{'req'} );
  $Packs{$pack}{'version'}  = &reFoo( $Packs{$pack}{'version'} );
  $Packs{$pack}{'comments'} = &reFoo( $Packs{$pack}{'comments'} );
}

# More Sanity checks
foreach $pack ( keys %Packs ) {
  if ( defined( $Reqs{$pack} ) ) {
    push( @Errors, "OPTIONAL $pack is also listed as REQUIRED" );
  }
}
foreach $pack ( keys %Reqs ) {
  if ( defined( $Packs{$pack} ) ) {
    push( @Errors, "REQUIRED $pack is also liste as OPTIONAL" );
  }
  if ( $Reqs{$pack}{'count'} > 1 ) {
    push( @Errors, "MULTIPLE REQUIRED $pack ($Reqs{$pack}{'count'}), $Reqs{$pack}{'where'}" );
  }
}

# Print Error Messages. Exit if there are any.
if ( $#Errors >= 0 ) {
  if ( !$quiet ) {
    print "\nThe Following Errors Were Encountered:\n";
    foreach my $e (@Errors) {
      print "$e\n";
    }
    print "\nExiting until all errors are fixed.\n";
  }
  exit $#Errors + 1;
}

# Print the packages
if ( $export eq "wiki" ) {
  &wikiPrint();
} else {
  &csvPrint();
}

#Print Warning messages
if ( !$quiet ) {
  if ( $#Warnings >= 0 ) {
    print "\nThe Following Warnings Were Encountered:\n";
    foreach my $w (@Warnings) {
      print "$w\n";
    }
  }
}

# Help function: print help message and exit.
sub Help {
  &Version();
  print "Print a list of optional packages for the KDE buildsystem.\n\n";
  print "Usage: $Prog [OPTION]\n";
  print "  --help    display help message and exit\n";
  print "  --version display version information and exit\n";
  print "  --quiet   do not print errors\n";
  print "  --modules print the modules column (off by default)\n";
  print "  --export=TYPE, where TYPE can be:\n";
  print "     \"csv\"  => comma-separated values (default)\n";
  print "     \"wiki\" => suitable for copy+paste into a MediaWiki\n";
  print "\n";
  exit 0 if $help;
}

sub doIt {
  my ($f) = @_;
  open( F, "<$f" ) || die "Couldn't open $f";
  my ($line);
  my ($linecnt) = 0;

  my ($punc)      = '\/:=,\.\-\+\/\'\(\)\[\]~\$\{\}\?\&\!';    #punctuation regex
  my ($foundre)   = "[-\\w\\s]+";                              #FOUND VAR regex
  my ($featurere) = "[\\w\\s$punc]*";                          #FEATURE STRING regex
  my ($descre)    = "[\\w\\s$punc]*";                          #DESCRIPTION STRING regex
  my ($urlre)     = "[\\w$punc]*";                             #URL STRING regex
  my ($reqre)     = "[\\w\\s$punc]*";                          #REQUIRED BOOL regex
  my ($minre)     = "[\\w\\s$punc]*";                          #MIN VERSION STRING regex
  my ($commre)    = "[\\w\\s$punc]*";                          #COMMENT STRING regex

  my ($module) = $f;
  $module =~ s+^.*KDE/++;
  $module =~ s+/.*$++;

  while ( $line = <F> ) {
    $linecnt++;
    chomp($line);

    $line =~ s/\#.*$//;

    if ( $line !~ s/\)\s*$// ) {
      $line .= <F>;
      $linecnt++;
      redo unless eof(F);
    }

    $line =~ s/^\s*//;

    if ( $line =~ m/^\s*set\s*\(\s*/i ) {
      my ($d) = $line;
      $d =~ s/^\s*set\s*\(\s*//i;
      $d =~ s/\s*CACHE.*$//;
      my ($a) = split( " ", $d );
      $a =~ s+\).*$++;
      $d =~ s+$a\s++;
      if ( $d =~ m/\".*/ ) {
        my ( $x, $y ) = split( "\"", $d );
        $d = $y;
      } else {
        $d =~ s/\s.*$//;
      }
      $Values{$a} = $d;
    }

    if ( $line =~ m/^\s*find_package\s*\(\s*(\w+)\s+REQUIRED/i ) {
      next if ( $1 =~ m/(Qt4|KDE4|Carbon|KdepimLibs|KDEGames)/ );
      $Reqs{$1}{'module'} = $module;
      $Reqs{$1}{'count'}++;
      $Reqs{$1}{'where'} .= $f . "#" . $linecnt . ",";
      next;
    }

    next if ( $line !~ m/macro_log_feature/i );
    next if ( $line =~ m/FAM.*into your kernel/ );

    if ( $line =~
m/macro_log_feature\s*\(\s*($foundre)\s+\"($featurere)\"\s+\"($descre)\"\s+\"($urlre)\"\s+($reqre)\s+\"($minre)\"\s+\"($commre)\"/
      ) {
      if ( !defined( $Packs{$1} ) ) {
        $Packs{$1}{'feature'}  = $2;
        $Packs{$1}{'desc'}     = $3;
        $Packs{$1}{'url'}      = $4;
        $Packs{$1}{'req'}      = $5;
        $Packs{$1}{'version'}  = $6;
        $Packs{$1}{'module'}   = $module;
        $Packs{$1}{'comments'} = $7;
        $Packs{$1}{'modules'}  = $module;
        $Packs{$1}{'where'}    = $f . ":" . $linecnt;
      } else {
        my ($p) = $1;
        my ($c) = $7;
        if ( $c && $Packs{$p}{'comments'} !~ m/$c/ ) {
          if ( $Packs{$p}{'comments'} !~ m/\.$/ ) {
            $Packs{$p}{'comments'} .= ".";
          }
          $Packs{$p}{'comments'} .= " " . $c;
        }
        $Packs{$p}{'modules'} .= "," . $module;
        $Packs{$p}{'where'}   .= "," . $f . ":" . $linecnt;
      }
      $Packs{$1}{'count'}++;

      if ( !$quiet &&
           ( $2 ne $Packs{$1}{'feature'} ||
             $3 ne $Packs{$1}{'desc'} ||
             $4 ne $Packs{$1}{'url'} ||
          #	  $5 ne $Packs{$1}{'req'} ||
             $6 ne $Packs{$1}{'version'} ) ) {
        push( @Errors,
          "MIS-MATCH for $1 in $module (vs $Packs{$1}{'module'}): $Packs{$1}{'where'}" );
        print " $2 vs $Packs{$1}{'feature'}\n"
          if ( $2 ne $Packs{$1}{'feature'} );
        print " $3 vs $Packs{$1}{'desc'}\n"
          if ( $3 ne $Packs{$1}{'desc'} );
        print " $4 vs $Packs{$1}{'url'}\n"
          if ( $4 ne $Packs{$1}{'url'} );

        #	print " $5 vs $Packs{$1}{'req'}\n" if($5 ne $Packs{$1}{'req'});
        print " $6 vs $Packs{$1}{'version'}\n"
          if ( $6 ne $Packs{$1}{'version'} );
      }
    } else {
      if ( !$quiet ) {
        push( @Errors, "Bad line $f ($linecnt) \"$line\"" );
      }
    }
  }
  close(F);
}

sub reFoo {
  my ($line) = @_;
  return '' if ( !$line );

  my ($nline) = '';
  for my $word ( split( ' ', $line ) ) {
    if ( $word =~ m/\$\{(.*)\}/ ) {
      if ( defined( $Values{$1} ) ) {
        $word = $Values{$1};
      } else {
        push( @Warnings, "Warning: there is no variable setting for $1\n" );
      }
    }
    if ( !$nline ) {
      $nline = $word;
    } else {
      $nline = $nline . " " . $word;
    }
  }
  return $nline;
}

sub csvPrint {
  my $csv = Class::CSV->new( fields => [qw/count feature desc url req version comments module/] );

  my ($pack);
  foreach $pack ( sort keys %Packs ) {
    $csv->add_line(
      {
        count    => $Packs{$pack}{'count'},
        feature  => $Packs{$pack}{'feature'},
        desc     => $Packs{$pack}{'desc'},
        url      => $Packs{$pack}{'url'},
        req      => $Packs{$pack}{'req'},
        version  => $Packs{$pack}{'version'},
        comments => $Packs{$pack}{'comments'},
        module   => $Packs{$pack}{'module'}
      }
    );
  }
  $csv->print();
}

sub wikiPrintHeader {
  my ($title) = @_;
  print "== $title ==\n";
  print "{| border=\"1\" cellpadding=\"5\" cellspacing=\"0\" align=\"center\"\n";
  print "|-\n";
  print "! Package\n";
  print "! Level\n";
  print "! Description\n";
  print "! Comments\n";

  #  print "! Modules\n";  #Todo: add this column only if processing more than 1 module
}

sub wikiPrintFooter {
  print "|}\n";
}

sub wikiPrintReqReq {
  my ($pack) = @_;
  print "|- valign=\"top\"\n";
  print "| $pack\n";
  print "|style=\"background:red\"| Required\n";
  print "||\n";
  print "||\n";
  print "||\n" if ($modules);
}

sub wikiPrintReqPack {
  my ($pack) = @_;
  print "|- valign=\"top\"\n";
  print "| [$Packs{$pack}{'url'} $Packs{$pack}{'feature'}";
  if ( $Packs{$pack}{'version'} ) {
    print " >= $Packs{$pack}{'version'}";
  }
  print "]\n";
  print "|style=\"background:red\"| Required\n";
  print "|| $Packs{$pack}{'desc'}\n";
  print "|| $Packs{$pack}{'comments'}\n";
  print "|| $Packs{$pack}{'module'}\n" if ($modules);
}

sub wikiPrintRecPack {
  my ($pack) = @_;
  print "|- valign=\"top\"\n";
  print "| [$Packs{$pack}{'url'} $Packs{$pack}{'feature'}";
  if ( $Packs{$pack}{'version'} ) {
    print " >= $Packs{$pack}{'version'}";
  }
  print "]\n";
  if ( $Packs{$pack}{'comments'} =~ m/strongly recommended/i ) {
    print "|style=\"background:yellow\"| STRONGLY RECOMMENDED\n";
  } else {
    print "|style=\"background:yellow\"| Recommended\n";
  }
  print "|| $Packs{$pack}{'desc'}\n";
  my ($comments) = $Packs{$pack}{'comments'};
  $comments =~ s/strongly recommended[:]*\s*//i;
  print "|| $comments\n";
  print "|| $Packs{$pack}{'module'}\n" if ($modules);
}

sub wikiPrintOptPack {
  my ($pack) = @_;
  print "|- valign=\"top\"\n";
  print "| [$Packs{$pack}{'url'} $Packs{$pack}{'feature'}";
  if ( $Packs{$pack}{'version'} ) {
    print " >= $Packs{$pack}{'version'}";
  }
  print "]\n";
  print "|style=\"background:green\"| Optional\n";
  print "|| $Packs{$pack}{'desc'}\n";
  print "|| $Packs{$pack}{'comments'}\n";
  print "|| $Packs{$pack}{'module'}\n" if ($modules);
}

sub wikiPrint {
  my ($pack);

  #  foreach $pack (sort keys %Reqs) {
  #    &wikiPrintReqReq($pack);
  #  }
  &wikiPrintHeader( $PROJ . " Package Dependencies" );
  foreach $pack ( sort keys %Packs ) {
    next unless ( $Packs{$pack}{'req'} =~ m/true/i );
    &wikiPrintReqPack($pack);
  }
  foreach $pack ( sort keys %Packs ) {
    next
      unless ( $Packs{$pack}{'req'} =~ m/false/i
      && $Packs{$pack}{'comments'} =~ m/recommend/i );
    &wikiPrintRecPack($pack);
  }
  foreach $pack ( sort keys %Packs ) {
    next
      unless ( $Packs{$pack}{'req'} =~ m/false/i
      && $Packs{$pack}{'comments'} !~ m/recommend/i );
    &wikiPrintOptPack($pack);
  }
  &wikiPrintFooter();
}

# Version function: print the version number and exit.
sub Version {
  print "$Prog, version $VERSION\n";
  exit 0 if $version;
}
