#!/bin/sh
#  -*-Perl-*-  (for Emacs)    vim:set filetype=perl:  (for vim)
#======================================================================#
# Run the right perl version:
if [ -x /usr/local/bin/perl ]; then
  perl=/usr/local/bin/perl
elif [ -x /usr/bin/perl ]; then
  perl=/usr/bin/perl
else
  perl=`which perl| sed 's/.*aliased to *//'`
fi

exec $perl -x -S $0 "$@";     # -x: start from the following line
#======================================================================#
#! /Good_Path/perl -w
# line 17
#
# Name    : mkcparam
# Author  : wd (Wolfgang.Dobler@kis.uni-freiburg.de)
# Started : 06-Jul-2002
# CVS     : $Id$
#
# Usage:
#   mkcparam <file1> [<file2> [..]] [-o outfile]\n
#
# Description:
#   Checks the headers (everything before the first non-comment non-empty
#   line) of input files for lines of the pattern
#     ! MVAR CONTRIBUTION 3
#     ! MAUX CONTRIBUTION 1
#     ! MGLOBAL CONTRIBUTION 1
#     ! MSCRATCH CONTRIBUTION 1
#     ! COMMUNICATED AUXILIARIES 1
#     ! NDUSTSPEC CONTRIBUTION 17*4
#     ! NDUSTRAD  CONTRIBUTION 10
#     ! NPARTDISP  CONTRIBUTION 30
#     ! NCHEMSPEC CONTRIBUTION 3*1
#     ! NPSCALAR CONTRIBUTION 3
#   and accumulates the numbers mvar, maux, maux_com, mglobal, mscratch,
#   ndustspecs, etc. from these.
#
#   Note, that for NDUSTSPEC CONTRIBUTION and NCHEMSPEC CONTRIBUTION the specification
#   *has to be* of the form <number of species>*<number of degrees of freedom per species>.
#   In the case of dust, the number of degrees of freedom per species is typically 4 (velocity and density),
#   while in the case of chemistry, it is ignored, and the code assumes 1.
#
#   Lines of the form
#     ! PENCILS [PROVIDED|EXPECTED]   uu(3), u2, uij(3,3), gmi(3,ndustspec)
#   accumulate lists of pencils provided by the input files. The pencils
#   must be separated by a "," or ";", and for non-scalar pencils the
#   dimensions must be specified in parenthesis immediately after the
#   pencil name.
#   Multiple occurences of a ! PENCILS PROVIDED line for the same pencil
#   results in a warning, but no warning for multiple ! PENCILS EXPECTED
#   lines or ! PENCILS PROVIDED and ! PENCILS EXPECTED for the same pencil.
#
#   Output is written as a Fortran program and supposed to end up in the
#   local file src/cparam.inc .
#
# Options:
#   -o <file>,
#   --output=<file> -- write cparam.inc output to the given file name.
#                      This option does not make much sense any more,
#                      since by now we write at least three files, so all
#                      of them should just be hard-coded in this script.
#   --num-special=<n> -- number of special modules to be inserted in "cparam.inc".
#   --dynamic-aux=<n> -- make maux, maux_com, mfarray and mcom run-time
#                      variables which grow when more auxiliaries are
#                      registered than declared by MAUX CONTRIBUTION and
#                      COMMUNICATED AUXILIARIES. <n> is the extra capacity
#                      of the fixed-size per-variable arrays (bcx, ...),
#                      see maux_max, maux_com_max.
# Example:
#     mkcparam entropy.f90 nomagnetic.f90 hydro.f90 > src/cparam.inc
#
# History:
#
#   30-nov-02/tony: Modified to use makefile lines where the Make variable
#                   and module name differ by more than just case
#                     e.g.   VISCOSITY=visc_shock
#                   Also count maux variables too.
#
#   12-oct-03/tony: Modified to use in-code declarations of f-array
#                   contributions.
#                   Fortran files should contain a block at the top to
#                   declare any contribution they make to the f-array.
#
#   07-apr-05/tony: Added the ability to have 'communicated auxiliaries'
#                   ie. variables which are not evolved (hence have no
#                   part in the df array), but which are updated at the
#                   end of a timestep and communicated along with the mvar
#                   variables in the f-array.
#
# ---------------------------------------------------------------------- #
my $mvar_decl     = '^\s*!\s*MVAR\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $maux_decl     = '^\s*!\s*MAUX\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $mogaux_decl   = '^\s*!\s*MOGAUX\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $mglobal_decl  = '^\s*!\s*MGLOBAL\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $mscratch_decl = '^\s*!\s*MSCRATCH\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $maux_com_decl = '^\s*!\s*COMMUNICATED\s*AUXILIARIES\s*(-?[0-9]+)\s*$';
my $mpvar_decl    = '^\s*!\s*MPVAR\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $mpaux_decl    = '^\s*!\s*MPAUX\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $mqvar_decl    = '^\s*!\s*MQVAR\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $mqaux_decl    = '^\s*!\s*MQAUX\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $ndustrad_decl    = '^\s*!\s*NDUSTRAD\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $npartdisp_decl    = '^\s*!\s*NPARTDISP\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $dust_decl     = '^\s*!\s*NDUSTSPEC\s*CONTRIBUTION\s*(-?[0-9]+\*[0-9])\s*$';
my $chem_decl     = '^\s*!\s*NCHEMSPEC\s*CONTRIBUTION\s*(-?[0-9]+\*[0-9])\s*$';
my $pscalar_decl  = '^\s*!\s*NPSCALAR\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $pencils_decl  = '^\s*!\s*PENCILS\s*(PROVIDED|EXPECTED)\s*(.*)\s*$';
my $cparam_decl   = '^\s*!\s*CPARAM\s*(.*?)\s*$';
my $nadsspec_decl    = '^\s*!\s*NADSSPEC\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
my $nsurfreacspec_decl    = '^\s*!\s*NSURFREACSPEC\s*CONTRIBUTION\s*(-?[0-9]+)\s*$';
# ---------------------------------------------------------------------- #

use strict;
use Getopt::Long;

my $line;
my $dust_string = '1*4';
my $chem_string = '1';
my $ndustrad = 1;
my $npartdisp = 20;
my ($maux, $maux_com, $mvar, $mglobal, $mscratch, $mpvar, $mpaux, $mqvar, $mqaux) = (0) x 20;
my ($ndustspec, $nchemspec, $mvar_per_dust, $mvar_per_chem, $mogaux) = (0) x 20;
my $npscalar = 0;
my $nadsspec = 0;
my $nsurfreacspec = 0;
my @pencil_names;
my %pencil_sizes;
my %pencil_file_map;
my @cparam_lines;
(my $cmdname = $0) =~ s{.*/}{};
my %open_files;
my $penc_name_len='';

#
# Process command line.
#
my (%opts);	# Variables written by GetOptions
GetOptions(\%opts,
	   qw( -h   --help
	       -o=s --output=s
               -b   --backdate
                    --num-special=i
                    --dynamic-aux=i
                               ));

die usage() if ((@ARGV == 0) || $opts{h} || $opts{help});

my $outfile  = ($opts{o} || $opts{output}   || "cparam.inc");
my $backdate = ($opts{b} || $opts{backdate} || "");
if ($backdate) { warn "Option -b/--backdate was removed, please remove it."; }
my $num_special = ($opts{'num-special'} || 1);
my $dynamic_aux = $opts{'dynamic-aux'};

#
# Cycle through files (later files will overwrite effect of earlier files)
#
foreach my $infile (@ARGV) {
  # Extract `?VAR CONTRIBUTION' info from each file
  unless (open(INPUT,"< $infile")) {
    die "Can't open $infile for reading";
    next;
  }

  # Cycle through all lines up to first non-empty non-comment line in file:
  while (defined ($line = <INPUT>)) {
    next if ($line =~ /^\s*$/); # ignore empty lines
    last if ($line !~ /^\s*!/); # done if non-comment line
    extract_decl ($line, $mvar_decl    , \$mvar     );
    extract_decl ($line, $maux_decl    , \$maux     );
    extract_decl ($line, $mogaux_decl  , \$mogaux   );
    extract_decl ($line, $mglobal_decl , \$mglobal  );
    extract_decl ($line, $mscratch_decl, \$mscratch );
    extract_decl ($line, $maux_com_decl, \$maux_com );
    extract_decl ($line, $mpvar_decl   , \$mpvar    );
    extract_decl ($line, $mpaux_decl   , \$mpaux    );
    extract_decl ($line, $mqvar_decl   , \$mqvar    );
    extract_decl ($line, $mqaux_decl   , \$mqaux    );
    extract_decl ($line, $pscalar_decl , \$npscalar );
    extract_decl ($line, $nadsspec_decl, \$nadsspec );
    extract_decl ($line, $nsurfreacspec_decl, \$nsurfreacspec);
    extract_decl_reset ($line, $ndustrad_decl, \$ndustrad );
    extract_decl_reset ($line, $npartdisp_decl, \$npartdisp);
    # Check for information about number of chemical and dust species
    # and discretization type:
    if ($line =~ /$chem_decl/) {$chem_string=$1;}
    if ($line =~ /$dust_decl/) {$dust_string=$1;}
    if ($line =~ /$cparam_decl/) {unshift @cparam_lines, $1;}

    # Extract provided pencils from file headers:
    if ($line =~ /$pencils_decl/) {
      # my @pencils = split /\s*;\s*/, $2;
      # foreach my $pencil (@pencils) {
      my $provided = 0;
      if ($line =~ '^!\ PENCILS\ PROVIDED') {$provided=1};
      while ($line =~ s{
                           ^                          # anchor at start
                           (?:                        # group, don't capture
                               !\ PENCILS\ PROVIDED   # either initial marker,
                           |
                               !\ PENCILS\ EXPECTED   # alternative initial marker
                           |                          # ..or..
                               \s*[,;]                # separator
                           )
                           \s*                        # arbitrary whitespace
                           (                          # capture as $1
                               [a-zA-Z0-9_]+          # variable name
                               (?:                    # group, don't capture
                                   \(                 # (
                                   [a-zA-Z0-9_]+      #  <dim1>
                                   (,[a-zA-Z0-9_]+)*  # optional ,<dim2>[,<dim3>]...
                                   \)                 # )
                               )?                     # the (..) part is optional
                           )
                       }
                       {}x) {                         # delete everything matched
        my $pencil=$1;
        my $pencil_name=$pencil;
        my $pencil_size=$pencil;

        # Extract name and size of pencils:
        if ($pencil =~ /\(.*\)/) {
          $pencil_size=~ s/^.*\((.*)\)/$1/g;
        } else {
          $pencil_size='';
        }
        $pencil_name =~ s/\(.*\)//g;

        # Default pencil size is (nx)
        if ($pencil_size eq '') {
          $pencil_size='(nx)'
        } else {
          $pencil_size="(nx,$pencil_size)";
        }

        # Store pencil information in arrays:
        my $pencil_already_used=0;
        foreach my $pencil_name_used (@pencil_names){
          if ($pencil_name eq $pencil_name_used) {$pencil_already_used=1;}
        }
        if ($provided == 1){
          push @{$pencil_file_map{$pencil_name}}, $infile;
        }
        if ($pencil_already_used eq 0) {
          push @pencil_names, $pencil_name;
          $pencil_sizes{$pencil_name} = "$pencil_size";
        }
      }
    }
  }
}

# Warn about ambiguous pencil definitions.
# THIS WILL SOON BECOME A FATAL ERROR.
while (my ($name, $files_ref) = each %pencil_file_map) {
  my @files = @{$files_ref};
  if (@files > 1) {
    print "### ¡¡¡WARNING!!! ###: $name multiply declared: in @files\n";
  }
}

# Calculate the total number of pencils
my $npencils = $#pencil_names+1;

# More processing of dust information:
($ndustspec, $mvar_per_dust) = split /\*/, $dust_string;
if ($ndustspec >= 1) {
  if ($mvar_per_dust > 1) {
    if ($mvar_per_dust == 2 || $mvar_per_dust == 5) {
      $mvar = $mvar + ($ndustspec-1)*$mvar_per_dust + 1;
    } else {
      if ($mvar_per_dust == 9) {
        $mvar = $mvar + ($ndustspec)*$mvar_per_dust -1;
      } else {
        $mvar = $mvar + ($ndustspec-1)*$mvar_per_dust;
      }
    }
  } else {
    $mvar = $mvar + ($ndustspec-1)*$mvar_per_dust;
  }
}

# More processing of chemistry information:
($nchemspec, $mvar_per_chem) = split /\*/, $chem_string;
if ($nchemspec >= 1) {
  $mvar = $mvar + $nchemspec - 1;
}

# More processing of passive scalars information:
if ($npscalar > 1) {
  $mvar = $mvar + $npscalar - 1;
} else {
  $npscalar = 1;
}

# More processing of particle chemistry information:
if ($nsurfreacspec >= 1) {
  $mpvar = $mpvar + $nsurfreacspec;
}
if ($nadsspec >= 1) {
  $mpvar = $mpvar + $nadsspec;
}

#
# cparam.inc
#

my $data_cparam = <<"EOF";
!  -*-f90-*-  (for emacs)    vim:set filetype=fortran:  (for vim)
!  cparam.inc
!
! This file was automatically generated by $cmdname, so think twice before
! you modify it.
!
! It is included by cparam.f90 and defines some constants based on the
! settings in Makefile.local
!

EOF

if (defined $dynamic_aux) {
  # Auxiliary counts are run-time variables, which FArrayManager increases
  # when more auxiliaries are registered than declared.
  $data_cparam .= <<"EOF";
integer, parameter :: mvar=${\($mvar+0)}
integer, parameter :: maux_decl=${\($maux+0)}, maux_com_decl=${\($maux_com+0)}
integer, parameter :: mogaux=${\($mogaux+0)}
integer, parameter :: mglobal=${\($mglobal+0)}
integer, parameter :: mscratch=${\($mscratch+0)}
logical, parameter :: ldynamic_aux=.true.
integer, parameter :: maux_max=maux_decl+${\($dynamic_aux+0)}, maux_com_max=maux_com_decl+${\($dynamic_aux+0)}
! The C names are used by the GPU host code; "! DSL:" gives the compile-time
! value for Astaroth's DSL (capacity), see scripts/cparam.sed.
integer(C_INT), bind(C,name='pc_maux') :: maux=maux_decl  ! DSL: maux_max
integer(C_INT), bind(C,name='pc_maux_com') :: maux_com=maux_com_decl  ! DSL: maux_com_max
integer(C_INT), bind(C,name='pc_mfarray') :: mfarray=mvar+maux_decl+mglobal+mscratch  ! DSL: mvar+maux_max+mglobal+mscratch
integer(C_INT), bind(C,name='pc_mcom') :: mcom=mvar+maux_com_decl  ! DSL: mvar+maux_com_max
EOF
} else {
  $data_cparam .= <<"EOF";
integer, parameter :: mvar=${\($mvar+0)}, maux=${\($maux+0)}
integer, parameter :: maux_com=${\($maux_com+0)}
integer, parameter :: mogaux=${\($mogaux+0)}
integer, parameter :: mglobal=${\($mglobal+0)}
integer, parameter :: mscratch=${\($mscratch+0)}
logical, parameter :: ldynamic_aux=.false.
integer, parameter :: maux_decl=maux, maux_com_decl=maux_com
integer, parameter :: maux_max=maux, maux_com_max=maux_com
integer, parameter :: mfarray=mvar+maux+mglobal+mscratch
integer, parameter :: mcom=mvar+maux_com
EOF
}

$data_cparam .= <<"EOF";
integer, parameter :: mpvar=${\($mpvar+0)}
integer, parameter :: mpaux=${\($mpaux+0)}
integer, parameter :: mqvar=${\($mqvar+0)}
integer, parameter :: mqaux=${\($mqaux+0)}
integer, parameter :: ndustspec=${\($ndustspec+0)}
integer, parameter :: nchemspec=${\($nchemspec+0)}
integer, parameter :: npscalar=${\($npscalar+0)}
integer, parameter :: nadsspec=${\($nadsspec+0)}
integer, parameter :: nsurfreacspec=${\($nsurfreacspec+0)}
integer, parameter :: ndustrad=${\($ndustrad+0)}
integer, parameter :: npartdisp=${\($npartdisp+0)}
integer, parameter :: n_special_modules=${\($num_special+0)}

EOF

# Set npar (number of particles) to one if particles are not used:
if ($mpvar == 0) {
  $data_cparam .= "integer, parameter :: npar=1\n\n";
} else {
  $data_cparam .= "! npar should be declared and set in cparam.local\n\n";
}

# Set nqpar (number of massive particles, i.e, point masses) to one if point masses are not used:
if ($mqvar == 0) {
  $data_cparam .= "integer, parameter :: nqpar=1\n\n";
} else {
  $data_cparam .= "! nqpar should be declared and set in cparam.local\n\n";
}

#
# Read contents of cparam.local
#
open CPARAM, "cparam.local";
my @cparam = <CPARAM>;
close CPARAM;

#
# Find out which particle parameters are defined in cparam.local.
#
my $mpar_loc=0;
my $npar_mig=0;
my $npar_species=0;
my $nqpar=0;
my $npar_stalk=0;
my $nbrickx=0;
my $nbricky=0;
my $nbrickz=0;
my $nblockmax=0;
foreach $line (@cparam) {
  $line=substr($line, 0, index($line,"!")); # ignore comment lines
  $mpar_loc=1     if ($line =~ 'mpar_loc');
  $npar_mig=1     if ($line =~ 'npar_mig');
  $npar_species=1 if ($line =~ 'npar_species');
#  $nqpar=1        if ($line =~ 'nqpar');
  $npar_stalk=1   if ($line =~ 'npar_stalk');
  $nbrickx=1      if ($line =~ 'nbrickx');
  $nbricky=1      if ($line =~ 'nbricky');
  $nbrickz=1      if ($line =~ 'nbrickz');
  $nblockmax=1    if ($line =~ 'nblockmax');
}

# Write default values of undefined particle parameters to cparam.inc:
add_line_avoid_duplicate ($data_cparam, "integer, parameter :: mpar_loc=npar")  unless $mpar_loc;
add_line_avoid_duplicate ($data_cparam, "integer, parameter :: npar_mig=1")     unless $npar_mig;
add_line_avoid_duplicate ($data_cparam, "integer, parameter :: npar_species=1") unless $npar_species;
#add_line_avoid_duplicate ($data_cparam, "integer, parameter :: nqpar=1")        unless $nqpar;
add_line_avoid_duplicate ($data_cparam, "integer, parameter :: npar_stalk=0")   unless $npar_stalk;
add_line_avoid_duplicate ($data_cparam, "integer, parameter :: nbrickx=1")      unless $nbrickx;
add_line_avoid_duplicate ($data_cparam, "integer, parameter :: nbricky=1")      unless $nbricky;
add_line_avoid_duplicate ($data_cparam, "integer, parameter :: nbrickz=1")      unless $nbrickz;
add_line_avoid_duplicate ($data_cparam, "integer, parameter :: nblockmax=1")    unless $nblockmax;

# Insert all CPARAM lines, extracted from the headers of the chosen physics modules:
foreach $line (sort @cparam_lines) {
    add_line_avoid_duplicate ($data_cparam, $line);
}
update_file ($outfile, $data_cparam);


#
# cparam_pencils.inc
#

my $data_cparam_pencil = <<"EOF";
!  -*-f90-*-  (for emacs)    vim:set filetype=fortran:  (for vim)
!  cparam_pencils.inc
!
! This file was automatically generated by $cmdname, so think twice before
! you modify it.
!
! It is included by cparam.f90 and defines the pencil_case data type and
! sets some variables related to this.
!

EOF

# Generate cparam_pencils.inc where the pencil case is defined and initialized:
$data_cparam_pencil .= <<"EOF";
integer, parameter :: npencils=$npencils
type pencil_case
EOF

my $i=1;
my $data_pencil_indices = "";
foreach my $pencil_name (sort @pencil_names) {
  $data_cparam_pencil .= "  real, dimension $pencil_sizes{$pencil_name} :: $pencil_name\n";
  $data_pencil_indices .= "integer, parameter :: i_$pencil_name=$i\n";
  $i++;
}
$data_cparam_pencil .= "endtype pencil_case\n\n".$data_pencil_indices."\n";

unless (open(INPUT,"< cparam.f90")) {
  die "Can't open cparam.f90 for reading"
}
while (defined ($line = <INPUT>)) {
  if ($line =~ /penc_name_len\s*=\s*[0-9]/) {
    ($penc_name_len) = $line =~ m/=([0-9]+)/; 
    last;
  }
}

# Prepare "cparam_pencils.inc"
$data_cparam_pencil .= "character (len=penc_name_len), parameter, dimension(npencils) :: pencil_names = &\n  (/ ";

# Prepare "pencil_init.inc"
my $data_pencil_init = <<"EOF";
!  -*-f90-*-  (for emacs)    vim:set filetype=fortran:  (for vim)
!  pencil_init.inc
!
! This file was automatically generated by $cmdname, so think twice before
! you modify it.
!
! It is included by equ.f90 and defines a subroutine to reset all pencils to
! a reference value for pencil_consistency_check().
!
subroutine initialize_pencils(p,penc0)

  type (pencil_case) :: p
  real :: penc0

EOF

# Generate "cparam_pencils.inc" and "pencil_init.inc"
$i=0;
foreach my $pencil (sort @pencil_names){
  $data_cparam_pencil .= ", " if ($i != 0);
  $data_cparam_pencil .= "'$pencil" . " " x ($penc_name_len-length($pencil)) . "'";
  $data_cparam_pencil .= "  &\n   " if (($i % 5) == 3);
  $i++;
  $data_pencil_init .= "  p%$pencil = penc0\n";
}
$data_cparam_pencil .= " /)\n";
update_file ("cparam_pencils.inc", $data_cparam_pencil);

$data_pencil_init .= "\nendsubroutine initialize_pencils";
update_file ("pencil_init.inc", $data_pencil_init);
#
# cparam_aux.inc
#
# Generate cparam_aux.inc, included at the end of module Cparam. With
# --dynamic-aux it provides the setter for the run-time auxiliary counts,
# which cannot be compiled when they are parameters (then it is a dummy).
#
my $data_cparam_aux = <<"EOF";
!  -*-f90-*-  (for emacs)    vim:set filetype=fortran:  (for vim)
!  cparam_aux.inc
!
! This file was automatically generated by $cmdname, so think twice before
! you modify it.
!
! It is included at the end of cparam.f90.
!
EOF
if (defined $dynamic_aux) {
  $data_cparam_aux .= <<"EOF";
contains
!***********************************************************************
  subroutine set_aux_counts(maux_new,maux_com_new)
!
!  Sets the numbers of (communicated) auxiliaries and the derived sizes.
!  Only to be called by FArrayManager.
!
    integer, intent(in) :: maux_new, maux_com_new
!
    maux=maux_new
    maux_com=maux_com_new
    mfarray=mvar+maux+mglobal+mscratch
    mcom=mvar+maux_com
!
  endsubroutine set_aux_counts
EOF
} else {
  $data_cparam_aux .= <<"EOF";
contains
!***********************************************************************
  subroutine set_aux_counts(maux_new,maux_com_new)
!
!  Dummy: without --dynamic-aux the auxiliary counts are parameters.
!
    integer, intent(in) :: maux_new, maux_com_new
!
  endsubroutine set_aux_counts
EOF
}
update_file ("cparam_aux.inc", $data_cparam_aux);


# ---------------------------------------------------------------------- #
sub add_line_avoid_duplicate {
#
# Add a line but avoid duplicate lines.
#
    my ($data,$line) = @_;

    my $search = $line;

    $search =~ s/=.*$/=/gs;
    $search =~ s/^ +//s;

    if (index ($data, "\n".$search) == -1) {
        # update $data in caller
        $_[0] .= $line."\n";
    }
}

# ---------------------------------------------------------------------- #
sub update_file {
#
# Update a file's content, if necessary.
#
    my ($file,$data) = @_;

    my @old_data;

    if (-e $file) {
        # read existing file's content
        open (OLD, "< ".$file);
        @old_data = <OLD>;
        close (OLD);

        if (join ('', @old_data) eq $data) {
            # file exists and is up-to-date => nothing to be done
            return;
        }
    }

    write_file ($file, $data);
}

# ---------------------------------------------------------------------- #
sub write_file {
#
# Write a file
#
    my ($file,$data) = @_;

    open (my $fh, "> ".$file) or die "Can't open $file for writing!";
    print $fh $data;
    close ($fh);
}

# ---------------------------------------------------------------------- #
sub backup_file {
#
# Return filename for backup of given file
#
    my ($file) = @_;

    return $file . '_previous';
}
# ---------------------------------------------------------------------- #
sub extract_decl {
#
# Extract declaration of contribution to mvar and similar.
#
    my $line = shift;
    my $regexp = shift;
    my $counter_ref = shift;

    if ($line =~ /$regexp/) {
      $$counter_ref += $1;
    }
}

# ---------------------------------------------------------------------- #
sub extract_decl_reset {
#
# Extract declaration of contribution to mvar and similar.
#
    my $line = shift;
    my $regexp = shift;
    my $counter_ref = shift;

    if ($line =~ /$regexp/) {
      $$counter_ref = $1;
    }
}

# ---------------------------------------------------------------------- #
sub usage {
#
# Extract description and usage information from this file's header.
#
    my $thisfile = __FILE__;
    local $/ = '';              # Read paragraphs
    open(FILE, "<$thisfile") or die "Cannot open $thisfile\n";
    while (<FILE>) {
        # Paragraph _must_ contain `Description:' or `Usage:'
        next unless /^\s*\#\s*(Description|Usage):/m;
        # Drop `Author:', etc. (anything before `Description:' or `Usage:')
        s/.*?\n(\s*\#\s*(Description|Usage):\s*\n.*)/$1/s;
        # Don't print comment sign:
        s/^\s*# ?//mg;
        last;                        # ignore body
    }
    $_ or "<No usage information found>\n";
}
# ---------------------------------------------------------------------- #
# End of file mkcparam
