#! /bin/bash
# XXX this does not handle c++ namespacing.
# XXX this becomes *incredibly* slow for some files -
# have to replace the regexp voodoo with a proper parser, i guess.
# ki18n( "str" ) => ki18n( "str") ?!

typefile=typedefs.out

if ! test -f $typefile; then
  find . \( -name '*.c' -o  -name '*.cpp' -o -name '*.h' \) -exec cpp -DHAVE_CONFIG_H -I. -I.. -I../.. {} \; 2>/dev/null | perl -w -e '
use strict;
use re "eval";
my ($btypes, $bt, $ctypes, $np, $wrd);
$bt=$btypes="void
(?:(?:un)?signed\\s+)?(?:short\\s+|long\\s+(?:long\\s+)?\\s+)?int
(?:(?:un)?signed\\s+)?(?:short|long(?:\\s+long)?)
(?:un)?signed
(?:(?:un)?signed\\s+)?char
float
(?:long\\s+(?:long\\s+)?)?double";
$ctypes="(?:struct|union)\\s+\\w+";
$bt =~ s/\n/|/g;
$np=qr{\{(?:(?>[^{}]+)|(??{$np}))*\}}x;
$wrd=qr{(?>\s*)(?>\**)(?>\s*)((?>\b\w+\b))(?{print $^N."\n"})(?>\s*)}x;
read STDIN, $_, 1e9;
s/^\s*typedef\s+(?:$bt\s|(?:struct|union)(?>\s+)(?>\b\w+\b\s+)?(??{$np})|$ctypes\s)$wrd(?:,$wrd)*;//xmg;
s/^\s*class\s+(\w+)(?{print $^N."\n"})//xmg;
print $btypes."\n".$ctypes."\n";
' | sort -u | tr '\n' '|' | sed 's,|$,,' > $typefile
fi

pty=$(<$typefile)
skw="if|switch|for|while|return"
nskw="sizeof|offsetof|defined|as|ki18n|i18n|i18nc|i18np|i18npc|I18N_NOOP|I18N_NOOP2|QString::fromLatin1|QLatin1String|SIGNAL|SLOT"
for i in "$@"; do
  echo "Indenting $i ..."
(
#  cat $i
#  expand -i < $i | unexpand -t 4 --first-only
#  expand -i < $i | unexpand -t 2 --first-only
#  expand -i -t 2 < $i | unexpand -t 8 --first-only
  expand -i -t 8 < $i | unexpand -t 8 --first-only
#  expand -i -t 4 < $i | unexpand -t 2 --first-only
#  expand -i -t 4 < $i | unexpand -t 4 --first-only
) | perl -w -e '
use strict;
use re '"'eval'"';
# remove trailing whitespace, append exactly one space after semicolon
sub stws($) { local $_ = shift; s/ *$//; s/(?<!;);$/; /; return $_; }
my ($np, $fp, $pc);
# complex expression: n times (word or basic expression or parenthesized complex expression)
$pc=qr{(?:(?>[^()]+)|(??{$fp})|(??{$np}))*}xs;
# parenthesized complex expression
$np=qr{\($pc\)}xs;
# basic expression: function( n times complex expression )
$fp=qr{(\b(?:new(?>\s+)|(?!'"$pty|$skw|$nskw|else"'))(?:\w|\.|::)+|\(\ *\*\ *(?:(?>[^()]*)|(??{$np}))*\))(?>\s*)\((?>\ *)($pc)\)}xs;
sub ffn($)
{
  local $_ = shift;
  # fix whitespace around function calls
  # XXX maybe correct parens in $1, too?
  s/$fp/$1."( ".stws(&ffn($2))." )"/eg;
  # fix whitespace around function-like (pseudo-)operators
  s/\b('"$nskw"')\s*\(\s*(?!{~)($pc)\)/$1."(".stws($2).")"/eg;
  # fix whitespace around c-style type casts
  s/(?<!sizeof)\(('"$pty"')\ *(\**)\ *\)\ *(?!{~)/"(".$1.($2?(" ".$2):"").")"/eg;
  return $_;
}
read STDIN, $_, 1e6;
my @subs = ();
my $csub = 0;
# replace strings and comments with {~###~} tokens
s/(\/\*.*?\*\/|\/\/.*?$|"(?:\\.|[^"])*"|^#(?:\\.|[^\n])+(?:\\\n(?:\\.|[^\n])*)*(?=\n))/push(@subs, $1), "{~".($csub++)."~}"/egsm;
# majority of the conversion
$_ = ffn($_);
# fix whitespace around control statements
s/\b('"$skw"')\s*\(\s*(?!{~)($pc)\)/$1." (".stws($2).")"/eg;
# remove spaces between otherwise empty paren pairs
s/\( +\)/()/g;
# convert non-leading tabs to spaces
s/(?<!^)(?<!\t)\t+/ /mgx;
# replace the {~###~} tokens back with strings
for (my $i = 0; $i < @subs; $i++) {
  s/{~$i~}/$subs[$i]/e;
}
s/[ \t]+$//mg;
print $_;
' |
perl -w -e '
use strict;
my $il = "";
my $cmt = 0;
my @pcs = ();
my $cpp = 0;
my @ifst = ();
my $els = 0;
my @elst = ();
while (<>) {
  if ($cpp) {
    $cpp = /\\$/;
  } elsif (/^#/) {
    if (/^#\ *if/) {
      push @ifst, ($il, @pcs);
      push @elst, $els;
      $els = 0;
    } elsif (/^# *elif/) {
      ($il, @pcs) = $ifst[$#ifst];
    } elsif (/^# *else/) {
      ($il, @pcs) = pop @ifst;
      $els = 1;
    } elsif (/^# *endif/) {
      if (!$els) {
        ($il, @pcs) = pop @ifst;
      }
      $els = pop @elst;
    }
    $cpp = /\\$/;
  } else {
    $cmt = 1 if /^([ \t]*)\/\*/;
    if (!$cmt) {
      if (@pcs) {
        s/^[ \t]*/$il.(" " x ($pcs[$#pcs] - length($il)))/e;
      } else {
        /^([ \t]*)/;
        $il = $1;
      }
      /^(?>[ \t]*)(?:\((?>\ *)(?{push @pcs, pos;})|\)(?{pop @pcs;})|\/\*.*?\*\/|'"'"'(?:\\.|[^'"'"'])*'"'"'|"(?:\\.|[^"])*"|.)*$/x;
    }
    $cmt = 0 if /\*\/$/;
  }
  print $_;
}
' > $i.tmp && mv $i.tmp $i
done

exit 0
