#! /usr/bin/perl -w

#/*@@
#  @file     ConfigurationParser.pl
#  @date     Tue Feb  8 17:36:48 2000
#  @author   Tom Goodale
#  @desc
#            Parser for configuration.ccl files
#  @enddesc
#  @version  $Header$
#@@*/


#/*@@
#  @routine    CreateConfigurationDatabase
#  @date       Tue Feb  8 17:47:26 2000
#  @author     Tom Goodale
#  @desc
#              Parses the information in all the thorns' configuration.ccl files
#              and creates a database from it
#  @enddesc
#@@*/
sub CreateConfigurationDatabase
{
  my($config_dir, %thorns) = @_;
  my(%cfg) = ();
  my(%thorn_dependencies);

  # Loop through each thorn's configuration file.
  foreach my $thorn (sort keys %thorns)
  {
    my $filename = "$thorns{$thorn}/configuration.ccl";
    next if (! -r $filename);

    # Get the configuration data from it
    &ParseConfigurationCCL($config_dir, $thorn, \%cfg, \%thorns, $filename);

#    if($debug)
#    {
#      print "   $thorn\n";
#      print "           Provides:          ", $cfg{"\U$thorn\E PROVIDES"}, "\n"
#        if ($cfg{"\U$thorn\E PROVIDES"});
#      print "           Requires:          ", $cfg{"\U$thorn\E REQUIRES"}, "\n"
#        if ($cfg{"\U$thorn\E REQUIRES"});
#      print "           Optional:          ", $cfg{"\U$thorn\E OPTIONAL"}, "\n"
#        if ($cfg{"\U$thorn\E OPTIONAL"});
#      print "           Optional-ifactive: ", $cfg{"\U$thorn\E OPTIONAL_IFACTIVE"}, "\n"
#        if ($cfg{"\U$thorn\E OPTIONAL_IFACTIVE"});
#    }

    $cfg{"\U$thorn\E USES THORNS"} = '';

    # verify that all required thorns are there in the ThornList
    next if (! $cfg{"\U$thorn\E REQUIRES THORNS"});

    my @missing = ();
    foreach my $required (split (' ', $cfg{"\U$thorn\E REQUIRES THORNS"}))
    {
      push (@missing, $required)
        if ((! $thorns{"$required"}) && (! $thorns{"\U$required\E"}));
    }
    if (@missing == 1)
    {
      &CST_error (0, "Thorn '$thorn' requires thorn '@missing'. " .
                     'Please add this thorn to your ThornList or remove ' .
                     "'$thorn' from it !");
    }
    elsif (@missing > 1)
    {
      &CST_error (0, "Thorn '$thorn' requires thorns '@missing'. " .
                     'Please add these thorns to your ThornList or ' .
                     "remove '$thorn' from it !");
    }

    $cfg{"\U$thorn\E USES THORNS"} .= $cfg{"\U$thorn\E REQUIRES THORNS"} . ' ';
  }

  # Turn optional capabilities into required capabilities, if the
  # capability is provided. This way we don't have to treat required
  # and optional requirements differently.
  my %providedcaps;
  foreach my $thorn (sort keys %thorns)
  {
      if ($cfg{"\U$thorn\E PROVIDES"})
      {
          foreach my $providedcap (split (' ', $cfg{"\U$thorn\E PROVIDES"}))
          {
              $providedcaps{$providedcap} = 1;
          }
      }
  }
  foreach my $thorn (sort keys %thorns)
  {
      if ($cfg{"\U$thorn\E REQUIRES"})
      {
          foreach my $requiredcap (split (' ', $cfg{"\U$thorn\E REQUIRES"}))
          {
              $cfg{"\U$thorn\E ACTIVATES"} .= "$requiredcap ";
          }
      }
  }
  foreach my $thorn (sort keys %thorns)
  {
      if ($cfg{"\U$thorn\E OPTIONAL"})
      {
          foreach my $optionalcap (split (' ', $cfg{"\U$thorn\E OPTIONAL"}))
          {
              if ($providedcaps{$optionalcap})
              {
                  $cfg{"\U$thorn\E REQUIRES"} .= "$optionalcap ";
                  $cfg{"\U$thorn\E ACTIVATES"} .= "$optionalcap ";
              }
          }
      }
      if ($cfg{"\U$thorn\E OPTIONAL_IFACTIVE"})
      {
          foreach my $optionalcap (split (' ', $cfg{"\U$thorn\E OPTIONAL_IFACTIVE"}))
          {
              if ($providedcaps{$optionalcap})
              {
                  $cfg{"\U$thorn\E REQUIRES"} .= "$optionalcap ";
                  # nothing is activated
              }
          }
      }
  }

  foreach my $thorn (sort keys %thorns)
  {
    # verify that all required capabilities are there in the ThornList
    next if (! $cfg{"\U$thorn\E REQUIRES"});

    foreach my $requiredcap (split (' ', $cfg{"\U$thorn\E REQUIRES"}))
    {
      my @found = ();
      foreach my $thorncap (sort keys %thorns)
      {
        foreach my $cap (split (' ', $cfg{"\U$thorncap\E PROVIDES"}))
        {
          push (@found, $thorncap)
            if ("\U$cap\E" eq "\U$requiredcap\E");
        }
      }

      # there must be exactly one thorn providing a required capability
      if (@found == 0)
      {
        &CST_error (0, "Thorn '$thorn' requires the capability " .
                       "'$requiredcap'.\n" .
                       "     Please add a thorn that provides '$requiredcap' " .
                       "to your ThornList or remove '$thorn' from it !")
      }
      elsif (@found > 1)
      {
        &CST_error (0, "More than one thorn provides the capability " .
                       "'$requiredcap'.\n" .
                       "     These thorns are: '@found'.\n" .
                       "     Please use only one !\n");
      }
      else
      {
        $cfg{"\U$thorn\E USES THORNS"} .= $found[0] . ' ';
      }
    }
  }

  # Translate capability to thorn names
  my %capabilities;
  foreach my $thorn (sort keys %thorns)
  {
      next if ! $cfg{"\U$thorn\E PROVIDES"};
      foreach my $cap (split (' ', $cfg{"\U$thorn\E PROVIDES"}))
      {
          $capabilities{"\U$cap\E"} = $thorn;
      }
  }
  foreach my $thorn (sort keys %thorns)
  {
      my $activates = '';
      foreach my $cap (split (' ', $cfg{"\U$thorn\E ACTIVATES"}))
      {
          my $cap_thorn = $capabilities{"\U$cap\E"};
          $activates .= " $cap_thorn";
      }
      $cfg{"\U$thorn\E ACTIVATES THORNS"} = $activates;
  }

  # Check for cyclic dependencies
  # create a hash with thorn-> used thorns (no prefix)
  foreach my $thorn (sort keys %thorns)
  {
    $thorn_dependencies{uc($thorn)}=$cfg{"\U$thorn\E USES THORNS"};
    $thorn_dependencies{uc($thorn)} =~ s/\b$thorn\b//i;
  }

  my $message = &find_dep_cycles(%thorn_dependencies);
  if ("" ne $message)
  {
    $message =~ s/^\s*//g;
    $message =~ s/\s*$//g;
    $message =~ s/\s+/->/g;
    $message = "Found a cyclic dependency in configuration requirements:$message\n";
    &CST_error(0, $message);
  }

  return \%cfg;
}


#/*@@
#  @routine    ParseConfigurationCCL
#  @date       Tue Feb  8 19:23:18 2000
#  @author     Tom Goodale
#  @desc
#  Parses a configuration.ccl file and generates a database of the values
#  @enddesc
#@@*/
sub ParseConfigurationCCL
{
  my($config_dir, $thorn, $cfg, $thorns, $filename) = @_;
  my(@data);
  my($line_number, $line);
  my($provides, $script, $lang, $options);
  my($optional, $define);

  # Initialise some stuff to prevent perl -w from complaining.

  $cfg->{"\U$thorn\E PROVIDES"} = '';
  $cfg->{"\U$thorn\E REQUIRES"} = '';
  $cfg->{"\U$thorn\E REQUIRES THORNS"} = '';
  $cfg->{"\U$thorn\E OPTIONAL"} = '';
  $cfg->{"\U$thorn\E OPTIONAL_IFACTIVE"} = '';
  $cfg->{"\U$thorn\E ACTIVATES"} = '';
  $cfg->{"\U$thorn\E OPTIONS"}  = '';

  # Read the data
  @data = &read_file($filename);

  for($line_number = 0; $line_number < @data; $line_number++)
  {
    $line = $data[$line_number];
    # Parse the line
    if($line =~ m/^\s*PROVIDES\s*/i)
    {
      $lang = $script = '';
      ($provides, $script, $lang, $options, $line_number) = &ParseProvidesBlock($line_number, \@data);
      if ($provides !~ m{^[A-Za-z0-9_.]+$}) {
        &CST_error (0, "Illegal capability name '$provides' line '$line' in configure.ccl of thorn '$thorn'");
      }
      if ($script !~ m{^[A-Za-z0-9_.]*$}) {
        &CST_error (0, "Illegal script name '$script' line '$line' in configure.ccl of thorn '$thorn'");
      }
      if ($lang !~ m{^[A-Za-z0-9_.]*$}) {
        &CST_error (0, "Illegal script language '$lang' line '$line' in configure.ccl of thorn '$thorn'");
      }
      $cfg->{"\U$thorn\E PROVIDES"} .= "$provides ";
      if($script)
      {
        $cfg->{"\U$thorn\E PROVIDES \U$provides\E SCRIPT"} = "$thorns->{$thorn}/$script";
      }
      $cfg->{"\U$thorn\E PROVIDES \U$provides\E LANG"} = $lang;
      $cfg->{"\U$thorn\E PROVIDES \U$provides\E OPTIONS"} = $options;

#      if ($script)
#      {
#        print "Running configuration script '$script'\n";
#
#        &ParseConfigScript($config_dir, $provides, $lang, $script,
#                           $thorn, $cfg);
#        print "\n";
#      }

      next;
    }
    elsif($line =~ m/^\s*REQUIRES\s+THORNS\s*:\s*(.*)/i)
    {
      my $newlist = $1;
      $newlist =~ s/\b$thorn\b//i;
      $newlist =~ s/,/ /g;
      my $oldlist = $cfg->{"\U$thorn\E REQUIRES THORNS"};
      my $list = $oldlist . ' ' . $newlist;
      $list = join (' ', sort split (' ', $list));
      $cfg->{"\U$thorn\E REQUIRES THORNS"} = $list;
#      if ($cfg->{"\U$thorn\E REQUIRES THORNS"})
#      {
#        &CST_error (3, '\'Requires Thorns\' will not be supported in release beta-14' .
#        "\n Please adjust thorn \U$thorn\E to use \'Requires\' instead.");
#      }
    }
    elsif($line =~ m/^\s*REQUIRES\s+(.*)/i)
    {
      my $cap = $1;
      if ($cap !~ m{^[A-Za-z0-9_. ]+$}) {
        &CST_error (0, "Illegal required capability '$cap' line '$line' in configure.ccl of thorn '$thorn'");
      }
      $cfg->{"\U$thorn\E REQUIRES"} .= "$cap ";
    }
    elsif($line =~ m/^\s*OPTIONAL\s+/i)
    {
      ($optional, $define, $line_number) = &ParseOptionalBlock($filename, $line_number, \@data);
      if ($optional !~ m{^[A-Za-z0-9_. ]+$}) {
        &CST_error (0, "Illegal optional capability '$optional' line '$line' in configure.ccl of thorn '$thorn'");
      }
      $cfg->{"\U$thorn\E OPTIONAL"} .= "$optional ";
      $cfg->{"\U$thorn\E OPTIONAL \U$optional\E DEFINE"} = $define;
    }
    elsif($line =~ m/^\s*OPTIONAL_IFACTIVE\+*/i)
    {
      ($optional, $define, $line_number) = &ParseOptionalBlock($filename, $line_number, \@data);
      if ($optional !~ m{^[A-Za-z0-9_. ]+$}) {
        &CST_error (0, "Illegal optional capability '$optional' line '$line' in configure.ccl of thorn '$thorn'");
      }
      $cfg->{"\U$thorn\E OPTIONAL_IFACTIVE"} .= "$optional ";
      $cfg->{"\U$thorn\E OPTIONAL_IFACTIVE \U$optional\E DEFINE"} = $define;
    }
    elsif($line =~ m/^\s*NO_SOURCE\s*$/i)
    {
      $cfg->{"\U$thorn\E OPTIONS"} .= "NO_SOURCE";
    }
    else
    {
      chomp($line);
      &CST_error (0, "Unrecognised line '$line' in configure.ccl of thorn '$thorn'");
    }
  }
}


#/*@@
#  @routine    ParseProvidesBlock
#  @date       Mon May  8 15:52:40 2000
#  @author     Tom Goodale
#  @desc
#  Parses the PROVIDES block in a configuration.ccl file.
#  @enddesc
#@@*/
sub ParseProvidesBlock
{
  my ($line_number, $data) = @_;
  my ($provides, $script, $lang, $options);

  $provides = "";
  $script   = "";
  $lang     = "";
  $options  = [];

  $data->[$line_number] =~ m/^\s*PROVIDES\s*(.*)/i;

  $provides = $1;

  $line_number++;
  if($data->[$line_number] !~ m/^\s*\{\s*$/)
  {
    &CST_error (0, "Error parsing provides block line '$data->[$line_number]' $file_name:$line_number ".
                   'Missing { at start of block');
    $line_number++ while(defined($data->[$line_number]) and $data->[$line_number] !~ m:\s*\}\s*:);
  }
  else
  {
    while(defined($data->[$line_number]) and $data->[$line_number] !~ m:\s*\}\s*:)
    {
      $line_number++;
      if($data->[$line_number] =~ m/^\s*SCRIPT\s*(.*)$/i)
      {
        $script = $1;
        next;
      }
      elsif($data->[$line_number] =~ m/^\s*LANG[^\s]*\s*(.*)$/i)
      {
        $lang = $1;
        next;
      }
      elsif($data->[$line_number] =~ m/^\s*OPTIONS[^\s]*\s*(.*)$/i)
      {
        push(@$options, split(' ',$1));
        next;
      }
      elsif($data->[$line_number] =~ m:\s*\}\s*:)
      {
        # do nothing.
      }
      else
      {
        print STDERR "Error parsing provides block line '$data->[$line_number]'\n";
        &CST_error (0, 'Unrecognised statement');
      }
    }
  }

  return ($provides, $script, $lang, $options, $line_number);
}


#/*@@
#  @routine    ParseOptionalBlock
#  @date       Mon May  8 15:52:40 2000
#  @author     Tom Goodale
#  @desc
#  Parses the OPTIONAL or OPTIONAL_IFACTIVE block in a configuration.ccl file.
#  @enddesc
#@@*/
sub ParseOptionalBlock
{
  my ($file_name, $line_number, $data) = @_;
  my ($optional, $define);

  $data->[$line_number] =~ m/^\s*OPTIONAL(_IFACTIVE)?\s*(.*)/i;

  $optional = $2;

  $define = "";

  $line_number++;

  if($data->[$line_number] !~ m/^\s*\{\s*$/)
  {
    &CST_error (0, "Error parsing optional block line '$data->[$line_number]' $file_name:$line_number".
                ' Missing { at start of block.');
    $line_number++ while(defined($data->[$line_number]) and $data->[$line_number] !~ m:\s*\}\s*:);
  }
  else
  {
    while(defined($data->[$line_number]) and $data->[$line_number] !~ m:\s*\}\s*:)
    {
      $line_number++;
      if($data->[$line_number] =~ m/^\s*DEFINE\s*(.*)$/i)
      {
        if($define eq "")
        {
          $define = $1;
          next;
        }
        else
        {
          &CST_error (0, "Error parsing optional block line '$data->[$line_number]' " . 'Only one define allowed.');
        }
      }
      elsif($data->[$line_number] =~ m:\s*\}\s*:)
      {
        # do nothing.
      }
      else
      {
        &CST_error (0, "Error parsing provides block line '$data->[$line_number]' " . 'Unrecognised statement.');
      }
    }
  }

  return ($optional, $define, $line_number);
}

1;
